MediaWiki  REL1_24
IPSet.php
Go to the documentation of this file.
00001 <?php
00074 class IPSet {
00076     private $root4 = array( false, false );
00077 
00079     private $root6 = array( false, false );
00080 
00090     public function __construct( array $cfg ) {
00091         foreach ( $cfg as $cidr ) {
00092             $this->addCidr( $cidr );
00093         }
00094 
00095         self::recOptimize( $this->root4 );
00096         self::recCompress( $this->root4, 0, 24 );
00097         self::recOptimize( $this->root6 );
00098         self::recCompress( $this->root6, 0, 120 );
00099     }
00100 
00106     private function addCidr( $cidr ) {
00107         // v4 or v6 check
00108         if ( strpos( $cidr, ':' ) === false ) {
00109             $node =& $this->root4;
00110             $defMask = '32';
00111         } else {
00112             $node =& $this->root6;
00113             $defMask = '128';
00114         }
00115 
00116         // Default to all-1's mask if no netmask in the input
00117         if ( strpos( $cidr, '/' ) === false ) {
00118             $net = $cidr;
00119             $mask = $defMask;
00120         } else {
00121             list( $net, $mask ) = explode( '/', $cidr, 2 );
00122             if ( !ctype_digit( $mask ) || intval( $mask ) > $defMask ) {
00123                 trigger_error( "IPSet: Bad mask '$mask' from '$cidr', ignored", E_USER_WARNING );
00124                 return;
00125             }
00126         }
00127         $mask = intval( $mask ); // explicit integer convert, checked above
00128 
00129         // convert $net to an array of integer bytes, length 4 or 16:
00130         $raw = inet_pton( $net );
00131         if ( $raw === false ) {
00132             return; // inet_pton() sends an E_WARNING for us
00133         }
00134         $rawOrd = array_map( 'ord', str_split( $raw ) );
00135 
00136         // special-case: zero mask overwrites the whole tree with a pair of terminal successes
00137         if ( $mask == 0 ) {
00138             $node = array( true, true );
00139             return;
00140         }
00141 
00142         // iterate the bits of the address while walking the tree structure for inserts
00143         $curBit = 0;
00144         while ( 1 ) {
00145             $maskShift = 7 - ( $curBit & 7 );
00146             $node =& $node[( $rawOrd[$curBit >> 3] & ( 1 << $maskShift ) ) >> $maskShift];
00147             ++$curBit;
00148             if ( $node === true ) {
00149                 // already added a larger supernet, no need to go deeper
00150                 return;
00151             } elseif ( $curBit == $mask ) {
00152                 // this may wipe out deeper subnets from earlier
00153                 $node = true;
00154                 return;
00155             } elseif ( $node === false ) {
00156                 // create new subarray to go deeper
00157                 $node = array( false, false );
00158             }
00159         }
00160     }
00161 
00170     public function match( $ip ) {
00171         $raw = inet_pton( $ip );
00172         if ( $raw === false ) {
00173             return false; // inet_pton() sends an E_WARNING for us
00174         }
00175 
00176         $rawOrd = array_map( 'ord', str_split( $raw ) );
00177         if ( count( $rawOrd ) == 4 ) {
00178             $node =& $this->root4;
00179         } else {
00180             $node =& $this->root6;
00181         }
00182 
00183         $curBit = 0;
00184         while ( 1 ) {
00185             if ( isset( $node['comp'] ) ) {
00186                 // compressed node, matches 1 whole byte on a byte boundary
00187                 if ( $rawOrd[$curBit >> 3] != $node['comp'] ) {
00188                     return false;
00189                 }
00190                 $curBit += 8;
00191                 $node =& $node['next'];
00192             } else {
00193                 // uncompressed node, walk in the correct direction for the current bit-value
00194                 $maskShift = 7 - ( $curBit & 7 );
00195                 $node =& $node[( $rawOrd[$curBit >> 3] & ( 1 << $maskShift ) ) >> $maskShift];
00196                 ++$curBit;
00197             }
00198 
00199             if ( $node === true || $node === false ) {
00200                 return $node;
00201             }
00202         }
00203     }
00204 
00212     private static function recOptimize( &$node ) {
00213         if ( $node[0] !== false && $node[0] !== true && self::recOptimize( $node[0] ) ) {
00214             $node[0] = true;
00215         }
00216         if ( $node[1] !== false && $node[1] !== true && self::recOptimize( $node[1] ) ) {
00217             $node[1] = true;
00218         }
00219         if ( $node[0] === true && $node[1] === true ) {
00220             return true;
00221         }
00222         return false;
00223     }
00224 
00238     private static function recCompress( &$node, $curBit, $maxCompStart ) {
00239         if ( !( $curBit & 7 ) ) { // byte boundary, check for depth-8 single path(s)
00240             $byte = 0;
00241             $cnode =& $node;
00242             $i = 8;
00243             while ( $i-- ) {
00244                 if ( $cnode[0] === false ) {
00245                     $byte |= 1 << $i;
00246                     $cnode =& $cnode[1];
00247                 } elseif ( $cnode[1] === false ) {
00248                     $cnode =& $cnode[0];
00249                 } else {
00250                     // partial-byte branching, give up
00251                     break;
00252                 }
00253             }
00254             if ( $i == -1 ) { // means we did not exit the while() via break
00255                 $node = array(
00256                     'comp' => $byte,
00257                     'next' => &$cnode,
00258                 );
00259                 $curBit += 8;
00260                 if ( $cnode !== true ) {
00261                     self::recCompress( $cnode, $curBit, $maxCompStart );
00262                 }
00263                 return;
00264             }
00265         }
00266 
00267         ++$curBit;
00268         if ( $curBit <= $maxCompStart ) {
00269             if ( $node[0] !== false && $node[0] !== true ) {
00270                 self::recCompress( $node[0], $curBit, $maxCompStart );
00271             }
00272             if ( $node[1] !== false && $node[1] !== true ) {
00273                 self::recCompress( $node[1], $curBit, $maxCompStart );
00274             }
00275         }
00276     }
00277 }