1) return false; $ipPart = $ipParts[$i]; if ($ipPart === '') { if ($i == 0) { if ($ipParts[$i + 1] !== '') return false; $hexCount++; $i++; } elseif ($i == $l - 1) { if ($ipParts[$i - 1] !== '') return false; $hexCount++; break; } $emptyCount++; } elseif (preg_match('/^[0-9a-f]{1,4}$/i', $ipPart)) { $hexCount++; } else { $checkIPv4 = $ipPart; } } if ($emptyCount > 1) return false; if ($checkIPv4 !== false) { if (!validateIPv4($checkIPv4)) return false; $hexCount += 2; } return $emptyCount ? $hexCount < 8 : $hexCount == 8; } // This is a single regexp-approach based on rfc3986 that actually passes all of my testcases, it is reasonably performant function validateIPv6_2($IP) { return preg_match('/^ (?: (?: (?:[a-f0-9]{1,4}:){6} | ::(?:[a-f0-9]{1,4}:){0,4} | (?:[a-f0-9]{1,4}:){1,1}:(?:[a-f0-9]{1,4}:){4} | (?:[a-f0-9]{1,4}:){1,2}:(?:[a-f0-9]{1,4}:){3} | (?:[a-f0-9]{1,4}:){1,3}:(?:[a-f0-9]{1,4}:){2} | (?:[a-f0-9]{1,4}:){1,4}:(?:[a-f0-9]{1,4}:){1} | (?:[a-f0-9]{1,4}:){1,5}: ) (?: [a-f0-9]{1,4}:[a-f0-9]{1,4} | (?:(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]) ) | (?::|(?:[a-f0-9]{1,4}:){1,6}):(?:[a-f0-9]{1,4})? ) $/ix', $IP ); } // This is a bit of an hybrid approach; first it checks if the last part is in IPv4 format // and validates that part first, then checks if the address is in compressed format or not // and uses far more simple expressions to validate the address (also taking into account // the number of colons to validate the compressed format) // There is a nifty trick involved which replaces the IPv4 format-part with '0:0'. There's // a number of ways to do that, some alternatives have been commented here as well // I personally like the first one because it re-uses the validation function for IPv4 addresses // reducing duplication of functionality function validateIPv6_3($IP) { if (strlen($IP) < 3) return $IP == '::'; if (strpos($IP, '.')) { $lastcolon = strrpos($IP, ':'); if (!($lastcolon && validateIPv4(substr($IP, $lastcolon + 1)))) return false; $IP = substr($IP, 0, $lastcolon) . ':0:0'; /* $ipv4 = 0; $IP = preg_replace( '/:(?:(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$/', ':0:0', $IP, 1, $ipv4 ); if (!$ipv4) return false; */ /* if (!preg_match('/:(?:(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$/', $IP)) return false; $IP = substr($IP, 0, strrpos($IP, ':')) . ':0:0'; */ } if (strpos($IP, '::') === false) { return preg_match('/^(?:[a-f0-9]{1,4}:){7}[a-f0-9]{1,4}$/i', $IP); } if (substr_count($IP, ':') < 8) { return preg_match('/^(?::|(?:[a-f0-9]{1,4}:)+):(?:(?:[a-f0-9]{1,4}:)*[a-f0-9]{1,4})?$/i', $IP); } return false; } // This is actually an alternative approach to validateIPv6_3 which branches the checking // regexp based on whether it has an IPv4 part or not // It is slightly faster, but I don't like how large parts of the expressions are duplicated function validateIPv6_4($IP) { if (strlen($IP) < 3) return $IP == '::'; if (strpos($IP, '.')) { if (strpos($IP, '::') === false) { return preg_match('/^(?:[a-f0-9]{1,4}:){6}(?:(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$/i', $IP); } if (substr_count($IP, ':') < 7) { return preg_match('/^(?::|(?:[a-f0-9]{1,4}:)+):(?:(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$/i', $IP); } } else { if (strpos($IP, '::') === false) { return preg_match('/^(?:[a-f0-9]{1,4}:){7}[a-f0-9]{1,4}$/i', $IP); } if (substr_count($IP, ':') < 8) { return preg_match('/^(?::|(?:[a-f0-9]{1,4}:)+):(?:(?:[a-f0-9]{1,4}:)*[a-f0-9]{1,4})?$/i', $IP); } } return false; } // This is the expression that is being used(?) by CakePHP (https://trac.cakephp.org/ticket/5872) // - it has the most failures in my testcases :P function validateIPv6_5($IP) { return preg_match('/^ (?: (?:[a-f0-9]{1,4}:){7}[a-f0-9]{1,4} | [a-f0-9]{0,4}:: | :(?::[a-f0-9]{1,4}){1,6} | (?:[a-f0-9]{1,4}:){1,6}: | (?:[a-f0-9]{1,4}:)(?::[a-f0-9]{1,4}){1,6} ) | (?:[a-f0-9]{1,4}:){2}(?::[a-f0-9]{1,4}){1,5} | (?:[a-f0-9]{1,4}:){3}(?::[a-f0-9]{1,4}){1,4} | (?:[a-f0-9]{1,4}:){4}(?::[a-f0-9]{1,4}){1,3} | (?:[a-f0-9]{1,4}:){5}(?::[a-f0-9]{1,4}){1,2} | (?:[a-f0-9]{1,4}:){6}(?::[a-f0-9]{1,4}) | (?:0:){5}ffff:(?:\d{1,3}\.){3}\d{1,3} | (?:0:){6}(?:\d{1,3}\.){3}\d{1,3} | ::(?:ffff:)?(?:\d{1,3}\.){3}\d{1,3} $/ix', $IP); } // This is a regexp based on rfc2732. It is reasonably fast but incorrect on many points // (also why rfc2732 has been superceeded by rfc3986) function validateIPv6_6($IP) { return preg_match('/^ (?: [a-f0-9]{1,4}(?::[a-f0-9]{1,4})* | [a-f0-9]{1,4}(?::[a-f0-9]{1,4})*::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4})*)? | ::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4})*)? ) (?::\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})? $/ix', $IP); } // This is probably the fastest way to do IPv6 validation in PHP, however it is not // 100% rfc-compliant so it fails on a couple of testcases function validateIPv6_7($IP) { return filter_var($IP, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6); } // testcases; a value of true means it is a valid IPv6 address, false means it is invalid $unitTests = array( '2001:0db8:0000:0000:0000:0000:1428:57ab' => true, '2001:0DB8:0000:0000:0000:0000:1428:57AB' => true, '2001:00db8:0000:0000:0000:0000:1428:57ab' => false, '2001:0db8:xxxx:0000:0000:0000:1428:57ab' => false, '2001:db8::1428:57ab' => true, '2001:db8::1428::57ab' => false, '2001:dx0::1234' => false, '2001:db0::12345' => false, '' => false, ':' => false, '::' => true, ':::' => false, '::::' => false, '::1' => true, ':::1' => false, '::1.2.3.4' => true, '::256.0.0.1' => false, '::01.02.03.04' => false, 'a:b:c::1.2.3.4' => true, 'a:b:c:d::1.2.3.4' => true, 'a:b:c:d:e::1.2.3.4' => true, 'a:b:c:d:e:f:1.2.3.4' => true, 'a:b:c:d:e:f:1.256.3.4' => false, 'a:b:c:d:e:f::1.2.3.4' => false, 'a:b:c:d:e:f:0:1:2' => false, 'a:b:c:d:e:f:0:1' => true, 'a::b:c:d:e:f:0:1' => false, 'a::c:d:e:f:0:1' => true, 'a::d:e:f:0:1' => true, 'a::e:f:0:1' => true, 'a::f:0:1' => true, 'a::0:1' => true, 'a::1' => true, 'a::' => true, '::0:1:a:b:c:d:e:f' => false, '::0:a:b:c:d:e:f' => false, '::a:b:c:d:e:f' => true, '::b:c:d:e:f' => true, '::c:d:e:f' => true, '::d:e:f' => true, '::e:f' => true, '::f' => true, '0:1:a:b:c:d:e:f::' => false, '0:a:b:c:d:e:f::' => false, 'a:b:c:d:e:f::' => true, 'b:c:d:e:f::' => true, 'c:d:e:f::' => true, 'd:e:f::' => true, 'e:f::' => true, 'f::' => true, 'a:b:::e:f' => false, '::a:' => false, '::a::' => false, ':a::b' => false, 'a::b:' => false, '::a:b::c' => false, 'abcde::f' => false, '10.0.0.1' => false, ':10.0.0.1' => false, '0:0:0:255.255.255.255' => false, '1fff::a88:85a3::172.31.128.1' => false, 'a:b:c:d:e:f:0::1' => false, 'a:b:c:d:e:f:0::' => false, 'a:b:c:d:e:f::0' => true, 'a:b:c:d:e:f::' => true, 'total gibberish' => false ); $benchmarks = array(); $iterations = 100; // only test the first 4 functions which pass all the unittests $numfunctions = 4; foreach ($unitTests as $ip => $assertion) { $benchmarks[$ip] = array(); for ($i = 1; $i <= $numfunctions; $i++) { $j = $iterations; $f = 'validateIPv6_' . $i; $s = microtime(true); while ($j--) { $f($ip); } $e = microtime(true); $benchmarks[$ip][$i] = $e - $s; } } // print table echo ''; for ($i = 0; $i <= $numfunctions; $i++) { echo ''; } echo ''; $totals = array( 'valid' => array(), 'invalid' => array(), 'grand' => array() ); foreach ($unitTests as $ip => $assertion) { echo ''; for ($i = 1; $i <= $numfunctions; $i++) { $f = 'validateIPv6_' . $i; $result = $f($ip); $min = min($benchmarks[$ip]); $correct = (bool)$result == $assertion; $totals['grand'][$i] += $benchmarks[$ip][$i]; if ($assertion) $totals['valid'][$i] += $benchmarks[$ip][$i]; else $totals['invalid'][$i] += $benchmarks[$ip][$i]; echo ''; } echo ''; } foreach ($totals as $label => $results) { echo ''; for ($i = 1; $i <= $numfunctions; $i++) { $min = min($results); echo ''; } echo ''; } echo '
' . ($i ? 'validateIPv6_' . $i : ' ') . '
' . ($ip ? $ip : ' ') . '' . number_format($benchmarks[$ip][$i], 3, '.', '') . '
' . $label . '' . number_format($results[$i], 3, '.', '') . '
'; ?>