Added support for IPv6 IP ranges... ehh, not easy.
authorDan
Fri, 19 Dec 2008 21:27:51 -0500
changeset 766 d6690840e331
parent 765 51f64b1c9413
child 767 cba10e1031eb
Added support for IPv6 IP ranges... ehh, not easy.
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 <http://examples.oreilly.com/regex/>.
  * @param string E-mail address
  * @return bool