(int) ( $stored['refreshed_at'] ?? 0 ), 'checked_at' => (int) ( $stored['checked_at'] ?? 0 ), 'error' => (string) ( $stored['error'] ?? '' ), 'count' => $live > 0 ? $live : count( self::CF_V4 ), 'source' => $live > 0 ? 'live' : 'bundled', ]; } private static function parse_cidr_lines( $text ) { if ( ! is_string( $text ) || $text === '' ) { return []; } $out = []; foreach ( preg_split( '/[\r\n,]+/', $text ) as $line ) { $line = trim( $line ); if ( $line === '' || $line[0] === '#' ) { continue; } $out[] = $line; } return $out; } /** * Match an IP against a CIDR. Supports v4 and v6. */ public static function ip_in_cidr( $ip, $cidr ) { if ( strpos( $cidr, '/' ) === false ) { return $ip === $cidr; } list( $subnet, $bits ) = explode( '/', $cidr, 2 ); $bits = (int) $bits; $ip_packed = @inet_pton( $ip ); $subnet_packed = @inet_pton( $subnet ); if ( $ip_packed === false || $subnet_packed === false ) { return false; } if ( strlen( $ip_packed ) !== strlen( $subnet_packed ) ) { return false; // v4 vs v6 mismatch } $bytes_full = intdiv( $bits, 8 ); $bits_part = $bits % 8; if ( $bytes_full > 0 && substr( $ip_packed, 0, $bytes_full ) !== substr( $subnet_packed, 0, $bytes_full ) ) { return false; } if ( $bits_part === 0 ) { return true; } $mask = chr( 0xff << ( 8 - $bits_part ) & 0xff ); return ( $ip_packed[ $bytes_full ] & $mask ) === ( $subnet_packed[ $bytes_full ] & $mask ); } }