[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * +----------------------------------------------------------------------+ 4 * | PHP version 5 | 5 * +----------------------------------------------------------------------+ 6 * | Copyright (C) 2004 MaxMind LLC | 7 * +----------------------------------------------------------------------+ 8 * | This library is free software; you can redistribute it and/or | 9 * | modify it under the terms of the GNU Lesser General Public | 10 * | License as published by the Free Software Foundation; either | 11 * | version 2.1 of the License, or (at your option) any later version. | 12 * | | 13 * | This library is distributed in the hope that it will be useful, | 14 * | but WITHOUT ANY WARRANTY; without even the implied warranty of | 15 * | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | 16 * | Lesser General Public License for more details. | 17 * | | 18 * | You should have received a copy of the GNU Lesser General Public | 19 * | License along with this library; if not, write to the Free Software | 20 * | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 | 21 * | USA, or view it online at http://www.gnu.org/licenses/lgpl.txt. | 22 * +----------------------------------------------------------------------+ 23 * | Authors: Jim Winstead <jimw@apache.org> (original Maxmind version) | 24 * | Hans Lellelid <hans@xmpl.org> | 25 * +----------------------------------------------------------------------+ 26 * 27 * @category Net 28 * @package Net_GeoIP 29 * @author Jim Winstead <jimw@apache.org> (original Maxmind PHP API) 30 * @author Hans Lellelid <hans@xmpl.org> 31 * @license LGPL http://www.gnu.org/licenses/lgpl.txt 32 * @link http://pear.php.net/package/Net_GeoIp 33 * $Id$ 34 */ 35 36 require_once 'PEAR/Exception.php'; 37 38 /** 39 * GeoIP class provides an API for performing geo-location lookups based on IP 40 * address. 41 * 42 * To use this class you must have a [binary version] GeoIP database. There is 43 * a free GeoIP country database which can be obtained from Maxmind: 44 * {@link http://www.maxmind.com/app/geoip_country} 45 * 46 * 47 * <b>SIMPLE USE</b> 48 * 49 * 50 * Create an instance: 51 * 52 * <code> 53 * $geoip = Net_GeoIP::getInstance('/path/to/geoipdb.dat', Net_GeoIP::SHARED_MEMORY); 54 * </code> 55 * 56 * Depending on which database you are using (free, or one of paid versions) 57 * you must use appropriate lookup method: 58 * 59 * <code> 60 * // for free country db: 61 * $country_name = $geoip->lookupCountryName($_SERVER['REMOTE_ADDR']); 62 * $country_code = $geoip->lookupCountryCode($_SERVER['REMOTE_ADDR']); 63 * 64 * // for [non-free] region db: 65 * list($ctry_code, $region) = $geoip->lookupRegion($_SERVER['REMOTE_ADDR']); 66 * 67 * // for [non-free] city db: 68 * $location = $geoip->lookupLocation($_SERVER['REMOTE_ADDR']); 69 * print "city: " . $location->city . ", " . $location->region; 70 * print "lat: " . $location->latitude . ", long: " . $location->longitude; 71 * 72 * // for organization or ISP db: 73 * $org_or_isp_name = $geoip->lookupOrg($_SERVER['REMOTE_ADDR']); 74 * </code> 75 * 76 * 77 * <b>MULTIPLE INSTANCES</b> 78 * 79 * 80 * You can have several instances of this class, one for each database file 81 * you are using. You should use the static getInstance() singleton method 82 * to save on overhead of setting up database segments. Note that only one 83 * instance is stored per filename, and any flags will be ignored if an 84 * instance already exists for the specifiedfilename. 85 * 86 * <b>Special note on using SHARED_MEMORY flag</b> 87 * 88 * If you are using SHARED_MEMORY (shmop) you can only use SHARED_MEMORY for 89 * one (1) instance (i.e. for one database). Any subsequent attempts to 90 * instantiate using SHARED_MEMORY will read the same shared memory block 91 * already initialized, and therefore will cause problems since the expected 92 * database format won't match the database in the shared memory block. 93 * 94 * Note that there is no easy way to flag "nice errors" to prevent attempts 95 * to create new instances using SHARED_MEMORY flag and it is also not posible 96 * (in a safe way) to allow new instances to overwrite the shared memory block. 97 * 98 * In short, is you are using multiple databses, use the SHARED_MEMORY flag 99 * with care. 100 * 101 * 102 * <b>LOOKUPS ON HOSTNAMES</b> 103 * 104 * 105 * Note that this PHP API does NOT support lookups on hostnames. This is so 106 * that the public API can be kept simple and so that the lookup functions 107 * don't need to try name lookups if IP lookup fails (which would be the only 108 * way to keep the API simple and support name-based lookups). 109 * 110 * If you do not know the IP address, you can convert an name to IP very 111 * simply using PHP native functions or other libraries: 112 * 113 * <code> 114 * $geoip->lookupCountryName(gethostbyname('www.sunset.se')); 115 * </code> 116 * 117 * Or, if you don't know whether an address is a name or ip address, use 118 * application-level logic: 119 * 120 * <code> 121 * if (ip2long($ip_or_name) === false) { 122 * $ip = gethostbyname($ip_or_name); 123 * } else { 124 * $ip = $ip_or_name; 125 * } 126 * $ctry = $geoip->lookupCountryName($ip); 127 * </code> 128 * 129 * @category Net 130 * @package Net_GeoIP 131 * @author Jim Winstead <jimw@apache.org> (original Maxmind PHP API) 132 * @author Hans Lellelid <hans@xmpl.org> 133 * @license LGPL http://www.gnu.org/licenses/lgpl.txt 134 * @link http://pear.php.net/package/Net_GeoIp 135 */ 136 class Net_GeoIP 137 { 138 /** 139 * Exception error code used for invalid IP address. 140 */ 141 const ERR_INVALID_IP = 218624992; // crc32('Net_GeoIP::ERR_INVALID_IP') 142 143 /** 144 * Exception error code when there is a DB-format-related error. 145 */ 146 const ERR_DB_FORMAT = 866184008; // crc32('Net_GeoIP::ERR_DB_FORMAT') 147 148 public static $COUNTRY_CODES = array( 149 "", "AP", "EU", "AD", "AE", "AF", "AG", "AI", "AL", "AM", "AN", "AO", "AQ", 150 "AR", "AS", "AT", "AU", "AW", "AZ", "BA", "BB", "BD", "BE", "BF", "BG", "BH", 151 "BI", "BJ", "BM", "BN", "BO", "BR", "BS", "BT", "BV", "BW", "BY", "BZ", "CA", 152 "CC", "CD", "CF", "CG", "CH", "CI", "CK", "CL", "CM", "CN", "CO", "CR", "CU", 153 "CV", "CX", "CY", "CZ", "DE", "DJ", "DK", "DM", "DO", "DZ", "EC", "EE", "EG", 154 "EH", "ER", "ES", "ET", "FI", "FJ", "FK", "FM", "FO", "FR", "FX", "GA", "GB", 155 "GD", "GE", "GF", "GH", "GI", "GL", "GM", "GN", "GP", "GQ", "GR", "GS", "GT", 156 "GU", "GW", "GY", "HK", "HM", "HN", "HR", "HT", "HU", "ID", "IE", "IL", "IN", 157 "IO", "IQ", "IR", "IS", "IT", "JM", "JO", "JP", "KE", "KG", "KH", "KI", "KM", 158 "KN", "KP", "KR", "KW", "KY", "KZ", "LA", "LB", "LC", "LI", "LK", "LR", "LS", 159 "LT", "LU", "LV", "LY", "MA", "MC", "MD", "MG", "MH", "MK", "ML", "MM", "MN", 160 "MO", "MP", "MQ", "MR", "MS", "MT", "MU", "MV", "MW", "MX", "MY", "MZ", "NA", 161 "NC", "NE", "NF", "NG", "NI", "NL", "NO", "NP", "NR", "NU", "NZ", "OM", "PA", 162 "PE", "PF", "PG", "PH", "PK", "PL", "PM", "PN", "PR", "PS", "PT", "PW", "PY", 163 "QA", "RE", "RO", "RU", "RW", "SA", "SB", "SC", "SD", "SE", "SG", "SH", "SI", 164 "SJ", "SK", "SL", "SM", "SN", "SO", "SR", "ST", "SV", "SY", "SZ", "TC", "TD", 165 "TF", "TG", "TH", "TJ", "TK", "TM", "TN", "TO", "TL", "TR", "TT", "TV", "TW", 166 "TZ", "UA", "UG", "UM", "US", "UY", "UZ", "VA", "VC", "VE", "VG", "VI", "VN", 167 "VU", "WF", "WS", "YE", "YT", "RS", "ZA", "ZM", "ME", "ZW", "A1", "A2", "O1", 168 "AX", "GG", "IM", "JE", "BL", "MF" 169 ); 170 171 public static $COUNTRY_CODES3 = array( 172 "","AP","EU","AND","ARE","AFG","ATG","AIA","ALB","ARM","ANT","AGO","AQ","ARG", 173 "ASM","AUT","AUS","ABW","AZE","BIH","BRB","BGD","BEL","BFA","BGR","BHR","BDI", 174 "BEN","BMU","BRN","BOL","BRA","BHS","BTN","BV","BWA","BLR","BLZ","CAN","CC", 175 "COD","CAF","COG","CHE","CIV","COK","CHL","CMR","CHN","COL","CRI","CUB","CPV", 176 "CX","CYP","CZE","DEU","DJI","DNK","DMA","DOM","DZA","ECU","EST","EGY","ESH", 177 "ERI","ESP","ETH","FIN","FJI","FLK","FSM","FRO","FRA","FX","GAB","GBR","GRD", 178 "GEO","GUF","GHA","GIB","GRL","GMB","GIN","GLP","GNQ","GRC","GS","GTM","GUM", 179 "GNB","GUY","HKG","HM","HND","HRV","HTI","HUN","IDN","IRL","ISR","IND","IO", 180 "IRQ","IRN","ISL","ITA","JAM","JOR","JPN","KEN","KGZ","KHM","KIR","COM","KNA", 181 "PRK","KOR","KWT","CYM","KAZ","LAO","LBN","LCA","LIE","LKA","LBR","LSO","LTU", 182 "LUX","LVA","LBY","MAR","MCO","MDA","MDG","MHL","MKD","MLI","MMR","MNG","MAC", 183 "MNP","MTQ","MRT","MSR","MLT","MUS","MDV","MWI","MEX","MYS","MOZ","NAM","NCL", 184 "NER","NFK","NGA","NIC","NLD","NOR","NPL","NRU","NIU","NZL","OMN","PAN","PER", 185 "PYF","PNG","PHL","PAK","POL","SPM","PCN","PRI","PSE","PRT","PLW","PRY","QAT", 186 "REU","ROU","RUS","RWA","SAU","SLB","SYC","SDN","SWE","SGP","SHN","SVN","SJM", 187 "SVK","SLE","SMR","SEN","SOM","SUR","STP","SLV","SYR","SWZ","TCA","TCD","TF", 188 "TGO","THA","TJK","TKL","TLS","TKM","TUN","TON","TUR","TTO","TUV","TWN","TZA", 189 "UKR","UGA","UM","USA","URY","UZB","VAT","VCT","VEN","VGB","VIR","VNM","VUT", 190 "WLF","WSM","YEM","YT","SRB","ZAF","ZMB","MNE","ZWE","A1","A2","O1", 191 "ALA","GGY","IMN","JEY","BLM","MAF" 192 ); 193 194 public static $COUNTRY_NAMES = array( 195 "", "Asia/Pacific Region", "Europe", "Andorra", "United Arab Emirates", 196 "Afghanistan", "Antigua and Barbuda", "Anguilla", "Albania", "Armenia", 197 "Netherlands Antilles", "Angola", "Antarctica", "Argentina", "American Samoa", 198 "Austria", "Australia", "Aruba", "Azerbaijan", "Bosnia and Herzegovina", 199 "Barbados", "Bangladesh", "Belgium", "Burkina Faso", "Bulgaria", "Bahrain", 200 "Burundi", "Benin", "Bermuda", "Brunei Darussalam", "Bolivia", "Brazil", 201 "Bahamas", "Bhutan", "Bouvet Island", "Botswana", "Belarus", "Belize", 202 "Canada", "Cocos (Keeling) Islands", "Congo, The Democratic Republic of the", 203 "Central African Republic", "Congo", "Switzerland", "Cote D'Ivoire", "Cook Islands", 204 "Chile", "Cameroon", "China", "Colombia", "Costa Rica", "Cuba", "Cape Verde", 205 "Christmas Island", "Cyprus", "Czech Republic", "Germany", "Djibouti", 206 "Denmark", "Dominica", "Dominican Republic", "Algeria", "Ecuador", "Estonia", 207 "Egypt", "Western Sahara", "Eritrea", "Spain", "Ethiopia", "Finland", "Fiji", 208 "Falkland Islands (Malvinas)", "Micronesia, Federated States of", "Faroe Islands", 209 "France", "France, Metropolitan", "Gabon", "United Kingdom", 210 "Grenada", "Georgia", "French Guiana", "Ghana", "Gibraltar", "Greenland", 211 "Gambia", "Guinea", "Guadeloupe", "Equatorial Guinea", "Greece", "South Georgia and the South Sandwich Islands", 212 "Guatemala", "Guam", "Guinea-Bissau", 213 "Guyana", "Hong Kong", "Heard Island and McDonald Islands", "Honduras", 214 "Croatia", "Haiti", "Hungary", "Indonesia", "Ireland", "Israel", "India", 215 "British Indian Ocean Territory", "Iraq", "Iran, Islamic Republic of", 216 "Iceland", "Italy", "Jamaica", "Jordan", "Japan", "Kenya", "Kyrgyzstan", 217 "Cambodia", "Kiribati", "Comoros", "Saint Kitts and Nevis", "Korea, Democratic People's Republic of", 218 "Korea, Republic of", "Kuwait", "Cayman Islands", 219 "Kazakstan", "Lao People's Democratic Republic", "Lebanon", "Saint Lucia", 220 "Liechtenstein", "Sri Lanka", "Liberia", "Lesotho", "Lithuania", "Luxembourg", 221 "Latvia", "Libyan Arab Jamahiriya", "Morocco", "Monaco", "Moldova, Republic of", 222 "Madagascar", "Marshall Islands", "Macedonia", 223 "Mali", "Myanmar", "Mongolia", "Macau", "Northern Mariana Islands", 224 "Martinique", "Mauritania", "Montserrat", "Malta", "Mauritius", "Maldives", 225 "Malawi", "Mexico", "Malaysia", "Mozambique", "Namibia", "New Caledonia", 226 "Niger", "Norfolk Island", "Nigeria", "Nicaragua", "Netherlands", "Norway", 227 "Nepal", "Nauru", "Niue", "New Zealand", "Oman", "Panama", "Peru", "French Polynesia", 228 "Papua New Guinea", "Philippines", "Pakistan", "Poland", "Saint Pierre and Miquelon", 229 "Pitcairn Islands", "Puerto Rico", "Palestinian Territory", 230 "Portugal", "Palau", "Paraguay", "Qatar", "Reunion", "Romania", 231 "Russian Federation", "Rwanda", "Saudi Arabia", "Solomon Islands", 232 "Seychelles", "Sudan", "Sweden", "Singapore", "Saint Helena", "Slovenia", 233 "Svalbard and Jan Mayen", "Slovakia", "Sierra Leone", "San Marino", "Senegal", 234 "Somalia", "Suriname", "Sao Tome and Principe", "El Salvador", "Syrian Arab Republic", 235 "Swaziland", "Turks and Caicos Islands", "Chad", "French Southern Territories", 236 "Togo", "Thailand", "Tajikistan", "Tokelau", "Turkmenistan", 237 "Tunisia", "Tonga", "Timor-Leste", "Turkey", "Trinidad and Tobago", "Tuvalu", 238 "Taiwan", "Tanzania, United Republic of", "Ukraine", 239 "Uganda", "United States Minor Outlying Islands", "United States", "Uruguay", 240 "Uzbekistan", "Holy See (Vatican City State)", "Saint Vincent and the Grenadines", 241 "Venezuela", "Virgin Islands, British", "Virgin Islands, U.S.", 242 "Vietnam", "Vanuatu", "Wallis and Futuna", "Samoa", "Yemen", "Mayotte", 243 "Serbia", "South Africa", "Zambia", "Montenegro", "Zimbabwe", 244 "Anonymous Proxy","Satellite Provider","Other", 245 "Aland Islands","Guernsey","Isle of Man","Jersey","Saint Barthelemy","Saint Martin" 246 ); 247 248 // storage / caching flags 249 const STANDARD = 0; 250 const MEMORY_CACHE = 1; 251 const SHARED_MEMORY = 2; 252 253 // Database structure constants 254 const COUNTRY_BEGIN = 16776960; 255 const STATE_BEGIN_REV0 = 16700000; 256 const STATE_BEGIN_REV1 = 16000000; 257 258 const STRUCTURE_INFO_MAX_SIZE = 20; 259 const DATABASE_INFO_MAX_SIZE = 100; 260 const COUNTRY_EDITION = 106; 261 const REGION_EDITION_REV0 = 112; 262 const REGION_EDITION_REV1 = 3; 263 const CITY_EDITION_REV0 = 111; 264 const CITY_EDITION_REV1 = 2; 265 const ORG_EDITION = 110; 266 const SEGMENT_RECORD_LENGTH = 3; 267 const STANDARD_RECORD_LENGTH = 3; 268 const ORG_RECORD_LENGTH = 4; 269 const MAX_RECORD_LENGTH = 4; 270 const MAX_ORG_RECORD_LENGTH = 300; 271 const FULL_RECORD_LENGTH = 50; 272 273 const US_OFFSET = 1; 274 const CANADA_OFFSET = 677; 275 const WORLD_OFFSET = 1353; 276 const FIPS_RANGE = 360; 277 278 // SHMOP memory address 279 const SHM_KEY = 0x4f415401; 280 281 /** 282 * @var int 283 */ 284 private $flags = 0; 285 286 /** 287 * @var resource 288 */ 289 private $filehandle; 290 291 /** 292 * @var string 293 */ 294 private $memoryBuffer; 295 296 /** 297 * @var int 298 */ 299 private $databaseType; 300 301 /** 302 * @var int 303 */ 304 private $databaseSegments; 305 306 /** 307 * @var int 308 */ 309 private $recordLength; 310 311 /** 312 * The memory addr "id" for use with SHMOP. 313 * @var int 314 */ 315 private $shmid; 316 317 /** 318 * Support for singleton pattern. 319 * @var array 320 */ 321 private static $instances = array(); 322 323 /** 324 * Construct a Net_GeoIP instance. 325 * You should use the getInstance() method if you plan to use multiple databases or 326 * the same database from several different places in your script. 327 * 328 * @param string $filename Path to binary geoip database. 329 * @param int $flags Flags 330 * 331 * @see getInstance() 332 */ 333 public function __construct($filename = null, $flags = null) 334 { 335 if ($filename !== null) { 336 $this->open($filename, $flags); 337 } 338 // store the instance, so that it will be returned by a call to 339 // getInstance() (with the same db filename). 340 self::$instances[$filename] = $this; 341 } 342 343 /** 344 * Calls the close() function to free any resources. 345 * @see close() 346 * 347 * COMMENTED OUT TO ADDRESS BUG IN PHP 5.0.4, 5.0.5dev. THIS RESOURCE 348 * SHOULD AUTOMATICALLY BE FREED AT SCRIPT CLOSE, SO A DESTRUCTOR 349 * IS A GOOD IDEA BUT NOT NECESSARILY A NECESSITY. 350 public function __destruct() 351 { 352 $this->close(); 353 } 354 */ 355 356 /** 357 * Singleton method, use this to get an instance and avoid re-parsing the db. 358 * 359 * Unique instances are instantiated based on the filename of the db. The flags 360 * are ignored -- in that requests to for instance with same filename but different 361 * flags will return the already-instantiated instance. For example: 362 * <code> 363 * // create new instance with memory_cache enabled 364 * $geoip = Net_GeoIP::getInstance('C:\mydb.dat', Net_GeoIP::MEMORY_CACHE); 365 * .... 366 * 367 * // later in code, request instance with no flags specified. 368 * $geoip = Net_GeoIP::getInstance('C:\mydb.dat'); 369 * 370 * // Normally this means no MEMORY_CACHE but since an instance 371 * // with memory cache enabled has already been created for 'C:\mydb.dat', the 372 * // existing instance (with memory cache) will be returned. 373 * </code> 374 * 375 * NOTE: You can only use SHARED_MEMORY flag for one instance! Any subsquent instances 376 * that attempt to use the SHARED_MEMORY will use the *same* shared memory, which will break 377 * your script. 378 * 379 * @param string $filename Filename 380 * @param int $flags Flags that control class behavior. 381 * + Net_GeoIp::SHARED_MEMORY 382 * Use SHMOP to share a db among multiple PHP instances. 383 * NOTE: ONLY ONE GEOIP INSTANCE CAN USE SHARED MEMORY!!! 384 * + Net_GeoIp::MEMORY_CACHE 385 * Store the full contents of the database in memory for current script. 386 * This is useful if you access the database several times in a script. 387 * + Net_GeoIp::STANDARD 388 * [default] standard no-cache version. 389 * 390 * @return Net_GeoIP 391 */ 392 public static function getInstance($filename = null, $flags = null) 393 { 394 if (!isset(self::$instances[$filename])) { 395 self::$instances[$filename] = new Net_GeoIP($filename, $flags); 396 } 397 return self::$instances[$filename]; 398 } 399 400 /** 401 * Opens geoip database at filename and with specified flags. 402 * 403 * @param string $filename File to open 404 * @param int $flags Flags 405 * 406 * @return void 407 * 408 * @throws PEAR_Exception if unable to open specified file or shared memory. 409 */ 410 public function open($filename, $flags = null) 411 { 412 if ($flags !== null) { 413 $this->flags = $flags; 414 } 415 if ($this->flags & self::SHARED_MEMORY) { 416 $this->shmid = @shmop_open(self::SHM_KEY, "a", 0, 0); 417 if ($this->shmid === false) { 418 $this->loadSharedMemory($filename); 419 $this->shmid = @shmop_open(self::SHM_KEY, "a", 0, 0); 420 if ($this->shmid === false) { // should never be false as loadSharedMemory() will throw Exc if cannot create 421 throw new PEAR_Exception("Unable to open shared memory at key: " . dechex(self::SHM_KEY)); 422 } 423 } 424 } else { 425 $this->filehandle = fopen($filename, "rb"); 426 if (!$this->filehandle) { 427 throw new PEAR_Exception("Unable to open file: $filename"); 428 } 429 if ($this->flags & self::MEMORY_CACHE) { 430 $s_array = fstat($this->filehandle); 431 $this->memoryBuffer = fread($this->filehandle, $s_array['size']); 432 } 433 } 434 $this->setupSegments(); 435 } 436 437 /** 438 * Loads the database file into shared memory. 439 * 440 * @param string $filename Path to database file to read into shared memory. 441 * 442 * @return void 443 * 444 * @throws PEAR_Exception - if unable to read the db file. 445 */ 446 protected function loadSharedMemory($filename) 447 { 448 $fp = fopen($filename, "rb"); 449 if (!$fp) { 450 throw new PEAR_Exception("Unable to open file: $filename"); 451 } 452 $s_array = fstat($fp); 453 $size = $s_array['size']; 454 455 if ($shmid = @shmop_open(self::SHM_KEY, "w", 0, 0)) { 456 shmop_delete($shmid); 457 shmop_close($shmid); 458 } 459 460 if ($shmid = @shmop_open(self::SHM_KEY, "c", 0644, $size)) { 461 $offset = 0; 462 while ($offset < $size) { 463 $buf = fread($fp, 524288); 464 shmop_write($shmid, $buf, $offset); 465 $offset += 524288; 466 } 467 shmop_close($shmid); 468 } 469 470 fclose($fp); 471 } 472 473 /** 474 * Parses the database file to determine what kind of database is being used and setup 475 * segment sizes and start points that will be used by the seek*() methods later. 476 * 477 * @return void 478 */ 479 protected function setupSegments() 480 { 481 482 $this->databaseType = self::COUNTRY_EDITION; 483 $this->recordLength = self::STANDARD_RECORD_LENGTH; 484 485 if ($this->flags & self::SHARED_MEMORY) { 486 487 $offset = shmop_size($this->shmid) - 3; 488 for ($i = 0; $i < self::STRUCTURE_INFO_MAX_SIZE; $i++) { 489 $delim = shmop_read($this->shmid, $offset, 3); 490 $offset += 3; 491 if ($delim == (chr(255).chr(255).chr(255))) { 492 $this->databaseType = ord(shmop_read($this->shmid, $offset, 1)); 493 $offset++; 494 if ($this->databaseType === self::REGION_EDITION_REV0) { 495 $this->databaseSegments = self::STATE_BEGIN_REV0; 496 } elseif ($this->databaseType === self::REGION_EDITION_REV1) { 497 $this->databaseSegments = self::STATE_BEGIN_REV1; 498 } elseif (($this->databaseType === self::CITY_EDITION_REV0) 499 || ($this->databaseType === self::CITY_EDITION_REV1) 500 || ($this->databaseType === self::ORG_EDITION)) { 501 $this->databaseSegments = 0; 502 $buf = shmop_read($this->shmid, $offset, self::SEGMENT_RECORD_LENGTH); 503 for ($j = 0; $j < self::SEGMENT_RECORD_LENGTH; $j++) { 504 $this->databaseSegments += (ord($buf[$j]) << ($j * 8)); 505 } 506 if ($this->databaseType === self::ORG_EDITION) { 507 $this->recordLength = self::ORG_RECORD_LENGTH; 508 } 509 } 510 break; 511 } else { 512 $offset -= 4; 513 } 514 } 515 if ($this->databaseType == self::COUNTRY_EDITION) { 516 $this->databaseSegments = self::COUNTRY_BEGIN; 517 } 518 519 } else { 520 521 $filepos = ftell($this->filehandle); 522 fseek($this->filehandle, -3, SEEK_END); 523 for ($i = 0; $i < self::STRUCTURE_INFO_MAX_SIZE; $i++) { 524 $delim = fread($this->filehandle, 3); 525 if ($delim == (chr(255).chr(255).chr(255))) { 526 $this->databaseType = ord(fread($this->filehandle, 1)); 527 if ($this->databaseType === self::REGION_EDITION_REV0) { 528 $this->databaseSegments = self::STATE_BEGIN_REV0; 529 } elseif ($this->databaseType === self::REGION_EDITION_REV1) { 530 $this->databaseSegments = self::STATE_BEGIN_REV1; 531 } elseif ($this->databaseType === self::CITY_EDITION_REV0 532 || $this->databaseType === self::CITY_EDITION_REV1 533 || $this->databaseType === self::ORG_EDITION) { 534 $this->databaseSegments = 0; 535 $buf = fread($this->filehandle, self::SEGMENT_RECORD_LENGTH); 536 for ($j = 0; $j < self::SEGMENT_RECORD_LENGTH; $j++) { 537 $this->databaseSegments += (ord($buf[$j]) << ($j * 8)); 538 } 539 if ($this->databaseType === self::ORG_EDITION) { 540 $this->recordLength = self::ORG_RECORD_LENGTH; 541 } 542 } 543 break; 544 } else { 545 fseek($this->filehandle, -4, SEEK_CUR); 546 } 547 } 548 if ($this->databaseType === self::COUNTRY_EDITION) { 549 $this->databaseSegments = self::COUNTRY_BEGIN; 550 } 551 fseek($this->filehandle, $filepos, SEEK_SET); 552 553 } 554 } 555 556 /** 557 * Closes the geoip database. 558 * 559 * @return int Status of close command. 560 */ 561 public function close() 562 { 563 if ($this->flags & self::SHARED_MEMORY) { 564 return shmop_close($this->shmid); 565 } else { 566 // right now even if file was cached in RAM the file was not closed 567 // so it's safe to expect no error w/ fclose() 568 return fclose($this->filehandle); 569 } 570 } 571 572 /** 573 * Get the country index. 574 * 575 * This method is called by the lookupCountryCode() and lookupCountryName() 576 * methods. It lookups up the index ('id') for the country which is the key 577 * for the code and name. 578 * 579 * @param string $addr IP address (hostname not allowed) 580 * 581 * @throws PEAR_Exception - if IP address is invalid. 582 * - if database type is incorrect 583 * 584 * @return string ID for the country 585 */ 586 protected function lookupCountryId($addr) 587 { 588 $ipnum = ip2long($addr); 589 if ($ipnum === false) { 590 throw new PEAR_Exception("Invalid IP address: " . var_export($addr, true), self::ERR_INVALID_IP); 591 } 592 if ($this->databaseType !== self::COUNTRY_EDITION) { 593 throw new PEAR_Exception("Invalid database type; lookupCountry*() methods expect Country database."); 594 } 595 return $this->seekCountry($ipnum) - self::COUNTRY_BEGIN; 596 } 597 598 /** 599 * Returns 2-letter country code (e.g. 'CA') for specified IP address. 600 * Use this method if you have a Country database. 601 * 602 * @param string $addr IP address (hostname not allowed). 603 * 604 * @return string 2-letter country code 605 * 606 * @throws PEAR_Exception (see lookupCountryId()) 607 * @see lookupCountryId() 608 */ 609 public function lookupCountryCode($addr) 610 { 611 return self::$COUNTRY_CODES[$this->lookupCountryId($addr)]; 612 } 613 614 /** 615 * Returns full country name for specified IP address. 616 * Use this method if you have a Country database. 617 * 618 * @param string $addr IP address (hostname not allowed). 619 * 620 * @return string Country name 621 * @throws PEAR_Exception (see lookupCountryId()) 622 * @see lookupCountryId() 623 */ 624 public function lookupCountryName($addr) 625 { 626 return self::$COUNTRY_NAMES[$this->lookupCountryId($addr)]; 627 } 628 629 /** 630 * Using the record length and appropriate start points, seek to the country that corresponds 631 * to the converted IP address integer. 632 * 633 * @param int $ipnum Result of ip2long() conversion. 634 * 635 * @return int Offset of start of record. 636 * @throws PEAR_Exception - if fseek() fails on the file or no results after traversing the database (indicating corrupt db). 637 */ 638 protected function seekCountry($ipnum) 639 { 640 $offset = 0; 641 for ($depth = 31; $depth >= 0; --$depth) { 642 if ($this->flags & self::MEMORY_CACHE) { 643 $buf = substr($this->memoryBuffer, 2 * $this->recordLength * $offset, 2 * $this->recordLength); 644 } elseif ($this->flags & self::SHARED_MEMORY) { 645 $buf = shmop_read($this->shmid, 2 * $this->recordLength * $offset, 2 * $this->recordLength); 646 } else { 647 if (fseek($this->filehandle, 2 * $this->recordLength * $offset, SEEK_SET) !== 0) { 648 throw new PEAR_Exception("fseek failed"); 649 } 650 $buf = fread($this->filehandle, 2 * $this->recordLength); 651 } 652 $x = array(0,0); 653 for ($i = 0; $i < 2; ++$i) { 654 for ($j = 0; $j < $this->recordLength; ++$j) { 655 $x[$i] += ord($buf[$this->recordLength * $i + $j]) << ($j * 8); 656 } 657 } 658 if ($ipnum & (1 << $depth)) { 659 if ($x[1] >= $this->databaseSegments) { 660 return $x[1]; 661 } 662 $offset = $x[1]; 663 } else { 664 if ($x[0] >= $this->databaseSegments) { 665 return $x[0]; 666 } 667 $offset = $x[0]; 668 } 669 } 670 throw new PEAR_Exception("Error traversing database - perhaps it is corrupt?"); 671 } 672 673 /** 674 * Lookup the organization (or ISP) for given IP address. 675 * Use this method if you have an Organization/ISP database. 676 * 677 * @param string $addr IP address (hostname not allowed). 678 * 679 * @throws PEAR_Exception - if IP address is invalid. 680 * - if database is of wrong type 681 * 682 * @return string The organization 683 */ 684 public function lookupOrg($addr) 685 { 686 $ipnum = ip2long($addr); 687 if ($ipnum === false) { 688 throw new PEAR_Exception("Invalid IP address: " . var_export($addr, true), self::ERR_INVALID_IP); 689 } 690 if ($this->databaseType !== self::ORG_EDITION) { 691 throw new PEAR_Exception("Invalid database type; lookupOrg() method expects Org/ISP database.", self::ERR_DB_FORMAT); 692 } 693 return $this->getOrg($ipnum); 694 } 695 696 /** 697 * Lookup the region for given IP address. 698 * Use this method if you have a Region database. 699 * 700 * @param string $addr IP address (hostname not allowed). 701 * 702 * @return array Array containing country code and region: array($country_code, $region) 703 * 704 * @throws PEAR_Exception - if IP address is invalid. 705 */ 706 public function lookupRegion($addr) 707 { 708 $ipnum = ip2long($addr); 709 if ($ipnum === false) { 710 throw new PEAR_Exception("Invalid IP address: " . var_export($addr, true), self::ERR_INVALID_IP); 711 } 712 if ($this->databaseType !== self::REGION_EDITION_REV0 && $this->databaseType !== self::REGION_EDITION_REV1) { 713 throw new PEAR_Exception("Invalid database type; lookupRegion() method expects Region database.", self::ERR_DB_FORMAT); 714 } 715 return $this->getRegion($ipnum); 716 } 717 718 /** 719 * Lookup the location record for given IP address. 720 * Use this method if you have a City database. 721 * 722 * @param string $addr IP address (hostname not allowed). 723 * 724 * @return Net_GeoIP_Location The full location record. 725 * 726 * @throws PEAR_Exception - if IP address is invalid. 727 */ 728 public function lookupLocation($addr) 729 { 730 include_once 'Net/GeoIP/Location.php'; 731 $ipnum = ip2long($addr); 732 if ($ipnum === false) { 733 throw new PEAR_Exception("Invalid IP address: " . var_export($addr, true), self::ERR_INVALID_IP); 734 } 735 if ($this->databaseType !== self::CITY_EDITION_REV0 && $this->databaseType !== self::CITY_EDITION_REV1) { 736 throw new PEAR_Exception("Invalid database type; lookupLocation() method expects City database."); 737 } 738 return $this->getRecord($ipnum); 739 } 740 741 /** 742 * Seek and return organization (or ISP) name for converted IP addr. 743 * 744 * @param int $ipnum Converted IP address. 745 * 746 * @return string The organization 747 */ 748 protected function getOrg($ipnum) 749 { 750 $seek_org = $this->seekCountry($ipnum); 751 if ($seek_org == $this->databaseSegments) { 752 return null; 753 } 754 $record_pointer = $seek_org + (2 * $this->recordLength - 1) * $this->databaseSegments; 755 if ($this->flags & self::SHARED_MEMORY) { 756 $org_buf = shmop_read($this->shmid, $record_pointer, self::MAX_ORG_RECORD_LENGTH); 757 } else { 758 fseek($this->filehandle, $record_pointer, SEEK_SET); 759 $org_buf = fread($this->filehandle, self::MAX_ORG_RECORD_LENGTH); 760 } 761 $org_buf = substr($org_buf, 0, strpos($org_buf, 0)); 762 return $org_buf; 763 } 764 765 /** 766 * Seek and return the region info (array containing country code and region name) for converted IP addr. 767 * 768 * @param int $ipnum Converted IP address. 769 * 770 * @return array Array containing country code and region: array($country_code, $region) 771 */ 772 protected function getRegion($ipnum) 773 { 774 if ($this->databaseType == self::REGION_EDITION_REV0) { 775 $seek_region = $this->seekCountry($ipnum) - self::STATE_BEGIN_REV0; 776 if ($seek_region >= 1000) { 777 $country_code = "US"; 778 $region = chr(($seek_region - 1000)/26 + 65) . chr(($seek_region - 1000)%26 + 65); 779 } else { 780 $country_code = self::$COUNTRY_CODES[$seek_region]; 781 $region = ""; 782 } 783 return array($country_code, $region); 784 } elseif ($this->databaseType == self::REGION_EDITION_REV1) { 785 $seek_region = $this->seekCountry($ipnum) - self::STATE_BEGIN_REV1; 786 //print $seek_region; 787 if ($seek_region < self::US_OFFSET) { 788 $country_code = ""; 789 $region = ""; 790 } elseif ($seek_region < self::CANADA_OFFSET) { 791 $country_code = "US"; 792 $region = chr(($seek_region - self::US_OFFSET)/26 + 65) . chr(($seek_region - self::US_OFFSET)%26 + 65); 793 } elseif ($seek_region < self::WORLD_OFFSET) { 794 $country_code = "CA"; 795 $region = chr(($seek_region - self::CANADA_OFFSET)/26 + 65) . chr(($seek_region - self::CANADA_OFFSET)%26 + 65); 796 } else { 797 $country_code = self::$COUNTRY_CODES[($seek_region - self::WORLD_OFFSET) / self::FIPS_RANGE]; 798 $region = ""; 799 } 800 return array ($country_code,$region); 801 } 802 } 803 804 /** 805 * Seek and populate Net_GeoIP_Location object for converted IP addr. 806 * Note: this 807 * 808 * @param int $ipnum Converted IP address. 809 * 810 * @return Net_GeoIP_Location 811 */ 812 protected function getRecord($ipnum) 813 { 814 $seek_country = $this->seekCountry($ipnum); 815 if ($seek_country == $this->databaseSegments) { 816 return null; 817 } 818 819 $record_pointer = $seek_country + (2 * $this->recordLength - 1) * $this->databaseSegments; 820 821 if ($this->flags & self::SHARED_MEMORY) { 822 $record_buf = shmop_read($this->shmid, $record_pointer, self::FULL_RECORD_LENGTH); 823 } else { 824 fseek($this->filehandle, $record_pointer, SEEK_SET); 825 $record_buf = fread($this->filehandle, self::FULL_RECORD_LENGTH); 826 } 827 828 $record = new Net_GeoIP_Location(); 829 830 $record_buf_pos = 0; 831 $char = ord(substr($record_buf, $record_buf_pos, 1)); 832 833 $record->countryCode = self::$COUNTRY_CODES[$char]; 834 $record->countryCode3 = self::$COUNTRY_CODES3[$char]; 835 $record->countryName = self::$COUNTRY_NAMES[$char]; 836 $record_buf_pos++; 837 $str_length = 0; 838 839 //get region 840 $char = ord(substr($record_buf, $record_buf_pos+$str_length, 1)); 841 while ($char != 0) { 842 $str_length++; 843 $char = ord(substr($record_buf, $record_buf_pos+$str_length, 1)); 844 } 845 if ($str_length > 0) { 846 $record->region = substr($record_buf, $record_buf_pos, $str_length); 847 } 848 $record_buf_pos += $str_length + 1; 849 $str_length = 0; 850 851 //get city 852 $char = ord(substr($record_buf, $record_buf_pos+$str_length, 1)); 853 while ($char != 0) { 854 $str_length++; 855 $char = ord(substr($record_buf, $record_buf_pos+$str_length, 1)); 856 } 857 if ($str_length > 0) { 858 $record->city = substr($record_buf, $record_buf_pos, $str_length); 859 } 860 $record_buf_pos += $str_length + 1; 861 $str_length = 0; 862 863 //get postal code 864 $char = ord(substr($record_buf, $record_buf_pos+$str_length, 1)); 865 while ($char != 0) { 866 $str_length++; 867 $char = ord(substr($record_buf, $record_buf_pos+$str_length, 1)); 868 } 869 if ($str_length > 0) { 870 $record->postalCode = substr($record_buf, $record_buf_pos, $str_length); 871 } 872 $record_buf_pos += $str_length + 1; 873 $str_length = 0; 874 $latitude = 0; 875 $longitude = 0; 876 for ($j = 0;$j < 3; ++$j) { 877 $char = ord(substr($record_buf, $record_buf_pos++, 1)); 878 $latitude += ($char << ($j * 8)); 879 } 880 $record->latitude = ($latitude/10000) - 180; 881 882 for ($j = 0;$j < 3; ++$j) { 883 $char = ord(substr($record_buf, $record_buf_pos++, 1)); 884 $longitude += ($char << ($j * 8)); 885 } 886 $record->longitude = ($longitude/10000) - 180; 887 888 if ($this->databaseType === self::CITY_EDITION_REV1) { 889 $dmaarea_combo = 0; 890 if ($record->countryCode == "US") { 891 for ($j = 0;$j < 3;++$j) { 892 $char = ord(substr($record_buf, $record_buf_pos++, 1)); 893 $dmaarea_combo += ($char << ($j * 8)); 894 } 895 $record->dmaCode = floor($dmaarea_combo/1000); 896 $record->areaCode = $dmaarea_combo%1000; 897 } 898 } 899 900 return $record; 901 } 902 903 } 904
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |