MediaWiki
REL1_24
|
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 }