<?php

    /*
     
     This code creates an index starting from a special format
     of the ip-to-country.com database, and provides functions for
     searching in this index/db. The format of the database is:
     
      n records representing IP range and country relations
      the format of a record is:
      
       - starting IP number [10 bytes, padded with zeros from left]
       - ending IP number [10 bytes, padded with zeros from left]
       - ISO country code [3 letters]
       - newline [1 byte: \n]
       
     IP numbers can be created with i2c_ip2num(). The database is
     indexed for fast searching. The size of the index is determined
     by the IDX_GRANULARITY value. The index is a CVS file, with the
     line format:
     
       - startring number of IP address [with IDX_GRANULARITY steps]
       - comma
       - record number where the db needs to be searched from
       - newline
       
     This code was written by Gabor Hojtsy [goba@php.net] for the
     PHP.net website and mirror sites in 2003. The code for i2c_ip2num
     comes from ip-to-country.com
       
    */
    
    // =======================================================================    
    
    // Step value used to create the index
    define("IDX_GRANULARITY", 10000000);
    define("IDX_LENGTH", strlen(IDX_GRANULARITY));
    
    // Filenames
    define("IPDB", "ip-to-country.db");
    define("IPIDX", "ip-to-country.idx");

    // IP addresses to test (comment out as needed)
    $ip = "217.20.134.241";  // [index.hu  - HUN]
    $ip = "64.246.30.37";    // [php.net   - USA]
    $ip = "64.58.79.230";    // [yahoo.com - USA]
    $ip = "212.227.242.242"; // [php.de - DEU]
    $ip = "66.93.78.119";    // [lerdorf.com - USA]

    // =======================================================================    
        
    // Index the ip database
    if (!i2c_indexer()) { die("ERROR: Unable to create index"); }
    
    // Start measuring time
    $time_start = getmicrotime();
    
    // Convert the IP address to a number
    $ipn = i2c_ip2num($ip);
    
    // Find the index where we should start searching
    $idx = i2c_findindex($ipn);

    // Find the country from the index
    $country = i2c_findcountry($ipn, $idx);

    // Measure speed
    $time_end = getmicrotime();
    $time = $time_end - $time_start;
    echo "$country found in $time seconds\n";
    
    // Convert a dotted IP address to a number.
    // (float) is important, as otherwise we would
    // get a signed value, which is not appropriate
    function i2c_ip2num($dotted)
    {
        $dotted = preg_split("/[.]+/", $dotted);
        return (float) ($dotted[0] * 16777216) +
                       ($dotted[1] * 65536) +
                       ($dotted[2] * 256) +
                       ($dotted[3]);
    }
    
    // Find the country searching from record $idx
    // $ip should be an IP number and not an IP address
    function i2c_findcountry($ip, $idx = 0)
    {
        // Default range and country
        $range_start = 0; $range_end = 0;
        $country = "NA";

        // Open DB for reading
        $ipdb = fopen(IPDB, "r");
        
        // Return with "NA" in case of we cannot open the db
        if (!$ipdb) { return $country; }
        
        // Jump to record $idx
        fseek($ipdb, $idx*24);

        // Read records until we hit the end of the file,
        // or we find the range where this IP is
        while (!feof($ipdb) && !($range_start <= $ip && $range_end >= $ip)) {
            
            // Try to read record
            $record = fread($ipdb, 24);
            
            // Unable to read the record => error
            if (strlen($record) != 24) { $country = "NA"; break; }
            
            // Split the record to it's parts
            $range_start = (float) substr($record, 0, 10);
            $range_end   = (float) substr($record, 10, 10);
            $country     = substr($record, 20, 3);
        }
        
        // Close datafile
        fclose($ipdb);

        // Return with the country found
        return $country;
    }
    
    // Index the ip-to-country database with the given
    // granularity, and save the index in a file
    function i2c_indexer()
    {
        // Last indexed number and last record number
        $lastidx = 0; $recnum = 0;
         
        // We store the IDX in a PHP array temporarily
        $idx_list = array("0,0");
        
        // Open database for reading
        $ipdb = fopen(IPDB, "r");
        
        // Return with error in case of we cannot open the db
        if (!$ipdb) { return FALSE; }

        // While we can read the file
        while (!feof($ipdb)) {
            
            // Get one record
            $record = fread($ipdb, 24);
            
            // Unable to read a record and not at end => error
            if (strlen($record) != 24 && !feof($ipdb)) { return FALSE; }
            
            // This is a new record
            $recnum++;
            
            // Get the start of the range for this record
            $range_start = (float) substr($record, 0, 10);
            
            // If this range starts a new step with our granularity,
            // add a new element to the index array
            if (intval($range_start / IDX_GRANULARITY) > $lastidx) {
                $lastidx = intval($range_start / IDX_GRANULARITY);
                $idx_list[] = "$lastidx,$recnum";
            }
        }
        
        // Close the database file
        fclose($ipdb);
        
        // Write out index to file
        $idx = fopen(IPIDX, "w");
        if (!$idx) { return FALSE; }
        fwrite($idx, join("\n", $idx_list));
        fclose($idx);
        
        // Success
        return TRUE;
    }
    
    // Find nearest index entry for IP number 
    function i2c_findindex($ip)
    {
        // Indexed part and record number to jump to
        $idxpart = 0; $recnum = 0;
        
        // Get the part of the IP which we can
        // search for in the index
        $ip_chunk = intval($ip / IDX_GRANULARITY);
        
        // Open the index file for reading
        $dbidx = fopen(IPIDX, "r");
        if (!$dbidx) { return 0; }

        // Loop till we can read the file
        while (!feof($dbidx)) {

            // Get CSV data from index file
            $data = fgetcsv($dbidx, 100);
            
            // Compare current index part with our IP
            if ($ip_chunk >= $idxpart && $ip_chunk <= (int) $data[0]) {
                return $recnum;
            }
            
            // Store for next compare
            $idxpart = (int) $data[0];
            $recnum  = (int) $data[1];
        }

        // Return record number found
        return $recnum;
    }

    // Utility function used for measuring
    function getmicrotime()
    { 
        list($usec, $sec) = explode(" ", microtime()); 
        return ((float) $usec + (float) $sec); 
    } 

?>
