1<?php 2 3/* 4 5 This script uses the local copy of the ip-to-country.com 6 database available on all php.net mirror sites to find 7 out what country the user is in. This data is stored in 8 a cookie for future usage, and another check is only done, 9 if the cookie is expired. 10 11 We have an index to speed up the search. The first line of 12 the index file contains the step value used to create the 13 index. The IP should be divided by this number, when an 14 appropriate index entry is searched for. Then CSV formatted 15 lines are placed in the file with the division results and 16 starting indexes (pointing to the data file). 17 18 The data file has a fixed record size format to ease 19 fseek()ing. The format of the database is: 20 21 n records representing IP range and country relations 22 the format of a record is: 23 24 - starting IP number [10 bytes, padded with zeros from left] 25 - ending IP number [10 bytes, padded with zeros from left] 26 - ISO country code [3 bytes / letters] 27 - newline [1 byte: \n] 28 29 IP numbers can be created with i2c_ip2num(). 30 31 Some things to consider: 32 33 - Now 'NA' is stored in the cookie (no country detected). 34 This is partly for safety reasons, not to bog down the 35 server with country detection in case the data files are 36 not there, or they are bogus, or the user has a non listed 37 IP. 38*/ 39 40// Start the detection 41i2c_go(); 42 43// Try to find out the country of the user and 44// set a cookie in the browser (in case there is no 45// cookie already storing this information) 46function i2c_go() 47{ 48 global $COUNTRY; 49 50 // Get the real IP of the user 51 $ipnum = i2c_realip(); 52 53 // User already has a country detected with this IP stored 54 if (!empty($_COOKIE['COUNTRY']) && strpos($_COOKIE['COUNTRY'], ',') !== false) { 55 list($COUNTRY, $storedip) = explode(",", $_COOKIE['COUNTRY']); 56 if ($storedip == $ipnum) { return true; } 57 } 58 59 // Convert the IP number to some useable form for searching 60 $ipn = (float) sprintf("%u", ip2long($ipnum)); 61 62 // Find the index to start search from 63 $idx = i2c_search_in_index($ipn); 64 65 // If we were unable to find any helpful entry 66 // in the index do not search, as it would take 67 // a very long time. It is an error, if we have 68 // not found anything in the index 69 if ($idx !== false) { 70 $country = i2c_search_in_db($ipn, $idx); 71 } else { 72 $country = 'NA'; 73 } 74 75 // Set global variable for further usage 76 $COUNTRY = $country; 77 78 // Set the country in a cookie for a week 79 return mirror_setcookie("COUNTRY", "$country,$ipnum", 60 * 60 * 24 * 7); 80 81} 82 83// Find nearest index entry for IP number 84function i2c_search_in_index($ip) 85{ 86 // Indexed part and record number to jump to 87 $idxpart = 0; $recnum = 0; 88 89 // Open the index file for reading 90 $dbidx = fopen( 91 __DIR__ . "/../backend/ip-to-country.idx", 92 "r", 93 ); 94 if (!$dbidx) { return false; } 95 96 // Read in granularity from index file and 97 // convert current IP to something useful 98 $granularity = (int) fgets($dbidx, 64); 99 if (!$granularity) { 100 // The file is empty (demo file) 101 return false; 102 } 103 $ip_chunk = (int) ($ip / $granularity); 104 105 // Loop till we can read the file 106 while (!feof($dbidx)) { 107 108 // Get CSV data from index file 109 $data = fgetcsv($dbidx, 100); 110 111 // Compare current index part with our IP 112 if ($ip_chunk >= $idxpart && $ip_chunk < (int) $data[0]) { 113 return [$recnum, (int) $data[1]]; 114 } 115 116 // Store for next compare 117 $idxpart = (int) $data[0]; 118 $recnum = (int) $data[1]; 119 } 120 121 // Return record number found 122 return [$recnum, -1]; 123} 124 125// Find the country searching from record $idx 126// $ip should be an IP number and not an IP address 127function i2c_search_in_db($ip, $idx) 128{ 129 // Default range and country 130 $range_start = 0; $range_end = 0; 131 $country = "NA"; 132 133 // Open DB for reading 134 $ipdb = fopen( 135 $_SERVER['DOCUMENT_ROOT'] . "/backend/ip-to-country.db", 136 "r", 137 ); 138 139 // Return with "NA" in case of we cannot open the db 140 if (!$ipdb) { return $country; } 141 142 // Jump to record $idx 143 fseek($ipdb, ($idx[0] ? (($idx[0] - 1) * 24) : 0)); 144 145 // Read records until we hit the end of the file, 146 // or we find the range where this IP is, or we 147 // reach the next indexed part [where the IP should 148 // not be found, so there is no point in searching further] 149 while (!feof($ipdb) && !($range_start <= $ip && $range_end >= $ip)) { 150 151 // We had run out of the indexed region, 152 // where we expected to find the IP 153 if ($idx[1] != -1 && $idx[0] > $idx[1]) { 154 $country = "NA"; break; 155 } 156 157 // Try to read record 158 $record = fread($ipdb, 24); 159 160 // Unable to read the record => error 161 if (strlen($record) != 24) { $country = "NA"; break; } 162 163 // Split the record to it's parts 164 $range_start = (float) substr($record, 0, 10); 165 $range_end = (float) substr($record, 10, 10); 166 $country = substr($record, 20, 3); 167 168 // Getting closer to the end of the indexed region 169 $idx[0] += 1; 170 } 171 172 // Close datafile 173 fclose($ipdb); 174 175 // Return with the country found 176 return $country; 177} 178 179// Check if the current country is valid 180function i2c_valid_country() 181{ 182 global $COUNTRY, $COUNTRIES; 183 return (!empty($COUNTRY) && $COUNTRY != "NA" && isset($COUNTRIES[$COUNTRY])); 184} 185 186// Returns the real IP address of the user 187function i2c_realip() 188{ 189 // No IP found (will be overwritten by for 190 // if any IP is found behind a firewall) 191 $ip = false; 192 193 // If HTTP_CLIENT_IP is set, then give it priority 194 if (!empty($_SERVER["HTTP_CLIENT_IP"])) { 195 $ip = $_SERVER["HTTP_CLIENT_IP"]; 196 } 197 198 // User is behind a proxy and check that we discard RFC1918 IP addresses 199 // if they are behind a proxy then only figure out which IP belongs to the 200 // user. Might not need any more hackin if there is a squid reverse proxy 201 // infront of apache. 202 if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { 203 204 // Put the IP's into an array which we shall work with shortly. 205 $ips = explode(", ", $_SERVER['HTTP_X_FORWARDED_FOR']); 206 if ($ip) { array_unshift($ips, $ip); $ip = false; } 207 208 for ($i = 0; $i < count($ips); $i++) { 209 // Skip RFC 1918 IP's 10.0.0.0/8, 172.16.0.0/12 and 210 // 192.168.0.0/16 211 // Also skip RFC 6598 IP's 212 if (!preg_match('/^(?:10|100\.(?:6[4-9]|[7-9]\d|1[01]\d|12[0-7])|172\.(?:1[6-9]|2\d|3[01])|192\.168)\./', $ips[$i]) && ip2long($ips[$i])) { 213 $ip = $ips[$i]; 214 break; 215 } 216 } 217 } 218 219 // Return with the found IP or the remote address 220 return $ip ?: $_SERVER['REMOTE_ADDR']; 221} 222