# HG changeset patch # User Dan # Date 1229740071 18000 # Node ID d6690840e331041bd395b59b2d0979e6d32d80aa # Parent 51f64b1c9413f303a64b45f7939fb27ff19476a2 Added support for IPv6 IP ranges... ehh, not easy. diff -r 51f64b1c9413 -r d6690840e331 includes/functions.php --- a/includes/functions.php Fri Dec 19 21:27:05 2008 -0500 +++ b/includes/functions.php Fri Dec 19 21:27:51 2008 -0500 @@ -3442,6 +3442,24 @@ function parse_ip_range_regex($range) { + if ( strstr($range, ':') ) + { + return parse_ipv6_range_regex($range); + } + else + { + return parse_ipv4_range_regex($range); + } +} + +/** + * Parses a valid IPv4 address range into a regular expression. + * @param string IP range string + * @return string + */ + +function parse_ipv4_range_regex($range) +{ // Regular expression to test the range string for validity $regex = '/^(([0-9]+(-[0-9]+)?)(\|([0-9]+(-[0-9]+)?))*)\.' . '(([0-9]+(-[0-9]+)?)(\|([0-9]+(-[0-9]+)?))*)\.' @@ -3501,6 +3519,255 @@ } /** + * Parses a valid IPv6 address range into a regular expression. + * @param string IP range string + * @return string + */ + +function parse_ipv6_range_regex($range) +{ + $range = strtolower(trim($range)); + $valid = '/^'; + $valid .= '(?:[0-9a-f]{0,4}|[0-9a-f]{1,4}-[0-9a-f]{1,4}):'; + $valid .= '(?:[0-9a-f]{0,4}|[0-9a-f]{1,4}-[0-9a-f]{1,4}):'; + $valid .= '(?:[0-9a-f]{0,4}|[0-9a-f]{1,4}-[0-9a-f]{1,4}:|:)?'; + $valid .= '(?:[0-9a-f]{0,4}|[0-9a-f]{1,4}-[0-9a-f]{1,4}:|:)?'; + $valid .= '(?:[0-9a-f]{0,4}|[0-9a-f]{1,4}-[0-9a-f]{1,4}:|:)?'; + $valid .= '(?:[0-9a-f]{0,4}|[0-9a-f]{1,4}-[0-9a-f]{1,4}:|:)?'; + $valid .= '(?:[0-9a-f]{0,4}|[0-9a-f]{1,4}-[0-9a-f]{1,4}:|:)?'; + $valid .= '(?:[0-9a-f]{0,4}|[0-9a-f]{1,4}-[0-9a-f]{1,4})$/'; + if ( !preg_match($valid, $range) ) + return false; + + // expand address range. + // this takes short ranges like: + // 2001:470-471:054-b02b::5-bb + // up to: + // 2001:0470-0471:0054-b02b:0000:0000:0000:0005-00bb + $range = explode(':', $range); + $expanded = ''; + $size = count($range); + foreach ( $range as $byteset ) + { + if ( empty($byteset) ) + { + // :: + while ( $size < 9 ) + { + $expanded .= '0000:'; + $size++; + } + } + else + { + if ( strstr($byteset, '-') ) + { + // this is a range + $sides = explode('-', $byteset); + foreach ( $sides as &$bytepair ) + { + while ( strlen($bytepair) < 4 ) + { + $bytepair = "0$bytepair"; + } + } + $byteset = implode('-', $sides); + } + else + { + while ( strlen($byteset) < 4 ) + { + $byteset = "0$byteset"; + } + } + $expanded .= "$byteset:"; + } + } + $expanded = explode(':', rtrim($expanded, ':')); + + // ready to dive in and start generating range regexes. + // this has to be pretty optimized... we want to end up with regexes like: + // range: 54-b12b + /* + /005[4-9a-f]| + 00[6-9a-f][0-9a-f]| + 0[1-9a-f][0-9a-f][0-9a-f]| + [1-9a][0-9a-f][0-9a-f][0-9a-f]| + b[0-0][0-1][0-9a-f]| + b0[0-1][0-9a-f]| + b02[0-9a-b]/x + */ + foreach ( $expanded as &$word ) + { + if ( strstr($word, '-') ) + { + // oh... damn. + $word = '(?:' . generate_hex_numeral_range($word) . ')'; + } + } + + // return print_r($expanded, true); + return '^' . implode(':', $expanded) . '$'; +} + +/** + * Take a hex numeral range and parse it in to a PCRE. + * @param string + * @return string + * @access private + */ + +function generate_hex_numeral_range($word) +{ + list($low, $high) = explode('-', $word); + + if ( hexdec($low) > hexdec($high) ) + { + $_ = $low; + $low = $high; + $high = $_; + unset($_); + } + + while ( strlen($low) < strlen($high) ) + { + $low = "0$low"; + } + + // trim off everything that's the same + $trimmed = ''; + $len = strlen($low); + for ( $i = 0; $i < $len; $i++ ) + { + if ( $low{0} === $high{0} ) + { + $trimmed .= $low{0}; + $low = substr($low, 1); + $high = substr($high, 1); + } + else + { + break; + } + } + + $len = strlen($high); + if ( $len == 1 ) + { + // this does happen sometimes, so we can save a bit of CPU power here. + return $trimmed . __hexdigitrange($low, $high); + } + + $return = ''; + // lower half + for ( $i = $len - 1; $i > 0; $i-- ) + { + if ( $low{$i} == 'f' ) + continue; + $return .= $trimmed; + for ( $j = 0; $j < $len; $j++ ) + { + if ( $j < $i ) + { + $return .= $low{$j}; + } + else if ( $j == $i && ( $i == $len - 1 || $low{$j} == 'f' ) ) + { + $return .= __hexdigitrange($low{$j}, 'f'); + } + else if ( $j == $i && $i != $len - 1 ) + { + $return .= __hexdigitrange(dechex(hexdec($low{$j}) + 1), 'f'); + } + else + { + $return .= __hexdigitrange('0', 'f'); + } + } + $return .= '|'; + } + // middle block + if ( hexdec($low{0}) + 1 < hexdec($high{0}) ) + { + if ( hexdec($low{0}) + 1 < hexdec($high{0}) - 1 ) + $return .= $trimmed . __hexdigitrange(dechex(hexdec($low{0}) + 1), dechex(hexdec($high{0}) - 1)); + else + $return .= $trimmed . __hexdigitrange($low{0}, $high{0}); + if ( $len - 1 > 0 ) + $return .= '[0-9a-f]{' . ( $len - 1 ) . '}|'; + } + // higher half + for ( $i = 1; $i < $len; $i++ ) + { + if ( $high{$i} == '0' ) + continue; + $return .= $trimmed; + for ( $j = 0; $j < $len; $j++ ) + { + if ( $j < $i ) + { + $return .= $high{$j}; + } + else if ( $j == $i && ( $i == $len - 1 || $high{$j} == '0' ) ) + { + $return .= __hexdigitrange('0', $high{$j}); + } + else if ( $j == $i && $i != $len - 1 ) + { + $return .= __hexdigitrange('0', dechex(hexdec($high{$j}) - 1)); + } + else if ( $j > $i ) + { + $return .= __hexdigitrange('0', 'f'); + } + else + { + die("I don't know what to do! i $i j $j"); + } + } + $return .= '|'; + } + + return rtrim($return, '|'); +} + +function __hexdigitrange($low, $high) +{ + if ( $low == $high ) + return $low; + if ( empty($low) ) + $low = '0'; + + $low_type = ( preg_match('/[0-9]/', $low) ) ? 'num' : 'alph'; + $high_type = ( preg_match('/[0-9]/', $high) ) ? 'num' : 'alph'; + if ( ( $low_type == 'num' && $high_type == 'num') || ( $low_type == 'alph' && $high_type == 'alph' ) ) + { + return "[$low-$high]"; + } + else if ( $low_type == 'num' && $high_type == 'alph' ) + { + $ret = '['; + + if ( $low == '9' ) + $ret .= '9'; + else + $ret .= "$low-9"; + if ( $high == 'a' ) + $ret .= 'a'; + else + $ret .= "a-$high"; + + $ret .= "]"; + return $ret; + } + else if ( $low_type == 'alph' && $high_type == 'num' ) + { + // ???? this should never happen + return __hexdigitrange($high, $low); + } +} + +/** * Validates an e-mail address. Uses a compacted version of the regular expression generated by the scripts at . * @param string E-mail address * @return bool