diff --git a/README.md b/README.md index e9a30c9..94b6506 100644 --- a/README.md +++ b/README.md @@ -50,10 +50,10 @@ Below is the description of the functions available in the **Database** class. |---|---| |Constructor|Expect 2 input parameters:
'; echo 'IP Number : ' . $records['ipNumber'] . "\n"; echo 'IP Version : ' . $records['ipVersion'] . "\n"; @@ -43,14 +45,18 @@ echo 'ZIP Code : ' . $records['zipCode'] . "\n"; echo 'Domain Name : ' . $records['domainName'] . "\n"; echo 'ISP Name : ' . $records['isp'] . "\n"; +echo 'Address Type : ' . $records['addressType'] . "\n"; +echo 'Category : ' . $records['category'] . "\n"; echo ''; +die; + echo '
CIDR: ' . implode(', ', $db->getCidr('8.8.8.8')) . ''; // Web Service -$ws = new \IP2Location\WebService('demo', 'WS24', true); +$ws = new \IP2Location\WebService('demo', 'WS25', true); $records = $ws->lookup('8.8.8.8', [ 'continent', 'country', 'region', 'city', 'geotargeting', 'country_groupings', 'time_zone_info', ], 'en'); diff --git a/src/Database.php b/src/Database.php index bee45ef..0de00d2 100644 --- a/src/Database.php +++ b/src/Database.php @@ -12,7 +12,7 @@ class Database * * @var string */ - public const VERSION = '9.1.0'; + public const VERSION = '9.2.0'; ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Error field constants /////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -23,7 +23,7 @@ class Database * * @var string */ - public const FIELD_NOT_SUPPORTED = 'This parameter is unavailable in selected .BIN data file. Please upgrade.'; + public const FIELD_NOT_SUPPORTED = 'This parameter is unavailable in selected .BIN data file. Please upgrade data file.'; /** * Unknown field message. @@ -43,6 +43,20 @@ class Database // Field selection constants /////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /** + * Maximum IPv4 number. + * + * @var int + */ + public const MAX_IPV4_RANGE = 4294967295; + + /** + * MAximum IPv6 number. + * + * @var int + */ + public const MAX_IPV6_RANGE = 340282366920938463463374607431768211455; + /** * Country code (ISO 3166-1 Alpha 2). * @@ -183,6 +197,20 @@ class Database */ public const USAGE_TYPE = 20; + /** + * Address type. + * + * @var int + */ + public const ADDRESS_TYPE = 21; + + /** + * Category. + * + * @var int + */ + public const CATEGORY = 22; + /** * Country name and code. * @@ -290,7 +318,7 @@ class Database * * @var int */ - public const EXCEPTION_DBFILE_NOT_FOUND = 10005; + public const EXCEPTION_DATABASE_FILE_NOT_FOUND = 10005; /** * Not enough memory to load database file. @@ -300,7 +328,7 @@ class Database public const EXCEPTION_NO_MEMORY = 10006; /** - * No candidate databse files found. + * No candidate database files found. * * @var int */ @@ -320,12 +348,19 @@ class Database */ public const EXCEPTION_NO_PATH = 10009; + /** + * Invalid BIN database file. + * + * @var int + */ + public const EXCEPTION_INVALID_BIN_DATABASE = 10010; + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Caching method constants //////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** - * Directly read from the databse file. + * Directly read from the database file. * * @var int */ @@ -370,7 +405,7 @@ class Database /** * Column offset mapping. * - * Each entry contains an array mapping databse version (0--23) to offset within a record. + * Each entry contains an array mapping database version (0--23) to offset within a record. * A value of 0 means the column is not present in the given database version. * * @static @@ -378,26 +413,28 @@ class Database * @var array */ private static $columns = [ - self::COUNTRY_CODE => [8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8], - self::COUNTRY_NAME => [8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8], - self::REGION_NAME => [0, 0, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12], - self::CITY_NAME => [0, 0, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16], - self::LATITUDE => [0, 0, 0, 0, 20, 20, 0, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20], - self::LONGITUDE => [0, 0, 0, 0, 24, 24, 0, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24], - self::ISP => [0, 12, 0, 20, 0, 28, 20, 28, 0, 32, 0, 36, 0, 36, 0, 36, 0, 36, 28, 36, 0, 36, 28, 36], - self::DOMAIN_NAME => [0, 0, 0, 0, 0, 0, 24, 32, 0, 36, 0, 40, 0, 40, 0, 40, 0, 40, 32, 40, 0, 40, 32, 40], - self::ZIP_CODE => [0, 0, 0, 0, 0, 0, 0, 0, 28, 28, 28, 28, 0, 28, 28, 28, 0, 28, 0, 28, 28, 28, 0, 28], - self::TIME_ZONE => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 32, 28, 32, 32, 32, 28, 32, 0, 32, 32, 32, 0, 32], - self::NET_SPEED => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 44, 0, 44, 32, 44, 0, 44, 0, 44, 0, 44], - self::IDD_CODE => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 48, 0, 48, 0, 48, 36, 48, 0, 48], - self::AREA_CODE => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 52, 0, 52, 0, 52, 40, 52, 0, 52], - self::WEATHER_STATION_CODE => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 56, 0, 56, 0, 56, 0, 56], - self::WEATHER_STATION_NAME => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 60, 0, 60, 0, 60, 0, 60], - self::MCC => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 64, 0, 64, 36, 64], - self::MNC => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 68, 0, 68, 40, 68], - self::MOBILE_CARRIER_NAME => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 72, 0, 72, 44, 72], - self::ELEVATION => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 76, 0, 76], - self::USAGE_TYPE => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 80], + self::COUNTRY_CODE => [8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8], + self::COUNTRY_NAME => [8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8], + self::REGION_NAME => [0, 0, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12], + self::CITY_NAME => [0, 0, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16], + self::LATITUDE => [0, 0, 0, 0, 20, 20, 0, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20], + self::LONGITUDE => [0, 0, 0, 0, 24, 24, 0, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24], + self::ZIP_CODE => [0, 0, 0, 0, 0, 0, 0, 0, 28, 28, 28, 28, 0, 28, 28, 28, 0, 28, 0, 28, 28, 28, 0, 28, 28], + self::TIME_ZONE => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 32, 28, 32, 32, 32, 28, 32, 0, 32, 32, 32, 0, 32, 32], + self::ISP => [0, 12, 0, 20, 0, 28, 20, 28, 0, 32, 0, 36, 0, 36, 0, 36, 0, 36, 28, 36, 0, 36, 28, 36, 36], + self::DOMAIN_NAME => [0, 0, 0, 0, 0, 0, 24, 32, 0, 36, 0, 40, 0, 40, 0, 40, 0, 40, 32, 40, 0, 40, 32, 40, 40], + self::NET_SPEED => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 44, 0, 44, 32, 44, 0, 44, 0, 44, 0, 44, 44], + self::IDD_CODE => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 48, 0, 48, 0, 48, 36, 48, 0, 48, 48], + self::AREA_CODE => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 52, 0, 52, 0, 52, 40, 52, 0, 52, 52], + self::WEATHER_STATION_CODE => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 56, 0, 56, 0, 56, 0, 56, 56], + self::WEATHER_STATION_NAME => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 60, 0, 60, 0, 60, 0, 60, 60], + self::MCC => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 64, 0, 64, 36, 64, 64], + self::MNC => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 68, 0, 68, 40, 68, 68], + self::MOBILE_CARRIER_NAME => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 72, 0, 72, 44, 72, 72], + self::ELEVATION => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 76, 0, 76, 76], + self::USAGE_TYPE => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 80, 80], + self::ADDRESS_TYPE => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84], + self::CATEGORY => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 88], ]; /** @@ -428,6 +465,8 @@ class Database self::MOBILE_CARRIER_NAME => 'mobileCarrierName', self::ELEVATION => 'elevation', self::USAGE_TYPE => 'usageType', + self::ADDRESS_TYPE => 'addressType', + self::CATEGORY => 'category', self::IP_ADDRESS => 'ipAddress', self::IP_VERSION => 'ipVersion', self::IP_NUMBER => 'ipNumber', @@ -440,6 +479,7 @@ class Database */ private static $databases = [ // IPv4 databases + 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER-MOBILE-ELEVATION-USAGETYPE-ADDRESSTYPE-CATEGORY', 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER-MOBILE-ELEVATION-USAGETYPE', 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ISP-DOMAIN-MOBILE-USAGETYPE', 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER-MOBILE-ELEVATION', @@ -465,6 +505,7 @@ class Database 'IP-COUNTRY-ISP', 'IP-COUNTRY', // IPv6 databases + 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER-MOBILE-ELEVATION-USAGETYPE-ADDRESSTYPE-CATEGORY', 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER-MOBILE-ELEVATION-USAGETYPE', 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ISP-DOMAIN-MOBILE-USAGETYPE', 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER-MOBILE-ELEVATION', @@ -611,7 +652,28 @@ class Database private $day; /** - * The raw row of columns's positions. + * Product code. + * + * @var string + */ + private $productCode; + + /** + * License code. + * + * @var string + */ + private $licenseCode; + + /** + * Database size. + * + * @var int + */ + private $databaseSize; + + /** + * The raw row of column positions. * * @var string */ @@ -754,7 +816,7 @@ public function __construct($file = null, $mode = self::FILE_IO, $defaultFields // determine the platform's float size // // NB: this should be a constant instead, and some unpack / typebanging magic - // should be used to accomodate different float sizes, but, as the libreary + // should be used to accomodate different float sizes, but, as the library // is written, this is the sanest thing to do anyway // if (self::$floatSize === null) { @@ -775,13 +837,24 @@ public function __construct($file = null, $mode = self::FILE_IO, $defaultFields $this->month = $this->readByte(4); $this->day = $this->readByte(5); $this->date = date('Y-m-d', strtotime("{$this->year}-{$this->month}-{$this->day}")); + $this->productCode = $this->readByte(30); + $this->licenseCode = $this->readByte(31); + $this->databaseSize = $this->readByte(32); $this->ipCount[4] = $this->readWord(6); $this->ipBase[4] = $this->readWord(10); //hjlim readword $this->ipCount[6] = $this->readWord(14); $this->ipBase[6] = $this->readWord(18); $this->indexBaseAddr[4] = $this->readWord(22); //hjlim - $this->indexBaseAddr[6] = $this->readWord(26); //hjlim + $this->indexBaseAddr[6] = $this->readWord(26); //hjlim + + if ($this->productCode == 1) { + } else { + if ($this->year <= 20 && $this->productCode == 0) { + } else { + throw new \Exception(__CLASS__ . ': Incorrect IP2Location BIN file format. Please make sure that you are using the latest IP2Location BIN file.', self::EXCEPTION_INVALID_BIN_DATABASE); + } + } } /** @@ -812,7 +885,7 @@ public function __destruct() * * @static * - * @param string $file Filename of the BIN database whise segment must be deleted + * @param string $file Filename of the BIN database * * @throws \Exception */ @@ -828,7 +901,7 @@ public static function shmTeardown($file) // If the file cannot be found, except away if ($rfile === false) { - throw new \Exception(__CLASS__ . ": Database file '{$file}' does not seem to exist.", self::EXCEPTION_DBFILE_NOT_FOUND); + throw new \Exception(__CLASS__ . ": Database file '{$file}' does not seem to exist.", self::EXCEPTION_DATABASE_FILE_NOT_FOUND); } $shmKey = self::getShmKey($rfile); @@ -841,7 +914,6 @@ public static function shmTeardown($file) // Delete and close the descriptor shmop_delete($shmId); - shmop_close($shmId); } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -871,7 +943,7 @@ public function getType() /** * Return this database's available fields. * - * @param bool $asNames Whether to return the mapped names intead of numbered constants + * @param bool $asNames Whether to return the mapped names instead of numbered constants * * @return array */ @@ -913,7 +985,7 @@ public function getDatabaseVersion() * * If a single, SINGULAR, field is specified, only its mapped value is returned. * If many fields are given (as an array) or a MULTIPLE field is specified, an - * array whith the returned singular field names as keys and their corresponding + * array with the returned singular field names as keys and their corresponding * values is returned. * * @param string $ip IP address to look up @@ -926,8 +998,10 @@ public function lookup($ip, $fields = null, $asNamed = true) { // extract IP version and number list($ipVersion, $ipNumber) = self::ipVersionAndNumber($ip); + // perform the binary search proper (if the IP address was invalid, binSearch will return false) $pointer = $this->binSearch($ipVersion, $ipNumber); + if (empty($pointer)) { return false; } @@ -959,6 +1033,8 @@ public function lookup($ip, $fields = null, $asNamed = true) $ifields[] = self::NET_SPEED; $ifields[] = self::ELEVATION; $ifields[] = self::USAGE_TYPE; + $ifields[] = self::ADDRESS_TYPE; + $ifields[] = self::CATEGORY; $ifields[] = self::COUNTRY; $ifields[] = self::COORDINATES; @@ -1000,6 +1076,8 @@ public function lookup($ip, $fields = null, $asNamed = true) self::MOBILE_CARRIER_NAME => false, self::ELEVATION => false, self::USAGE_TYPE => false, + self::ADDRESS_TYPE => false, + self::CATEGORY => false, self::COUNTRY => false, self::COORDINATES => false, @@ -1017,194 +1095,208 @@ public function lookup($ip, $fields = null, $asNamed = true) // treat each field in turn foreach ($afields as $afield) { switch ($afield) { - // purposefully ignore self::ALL, we already dealt with it - case self::ALL: break; - - case self::COUNTRY: - if (!$done[self::COUNTRY]) { - list($results[self::COUNTRY_NAME], $results[self::COUNTRY_CODE]) = $this->readCountryNameAndCode($pointer); - $done[self::COUNTRY] = true; - $done[self::COUNTRY_CODE] = true; - $done[self::COUNTRY_NAME] = true; - } - break; - case self::COORDINATES: - if (!$done[self::COORDINATES]) { - list($results[self::LATITUDE], $results[self::LONGITUDE]) = $this->readLatitudeAndLongitude($pointer); - $done[self::COORDINATES] = true; - $done[self::LATITUDE] = true; - $done[self::LONGITUDE] = true; - } - break; - case self::IDD_AREA: - if (!$done[self::IDD_AREA]) { - list($results[self::IDD_CODE], $results[self::AREA_CODE]) = $this->readIddAndAreaCodes($pointer); - $done[self::IDD_AREA] = true; - $done[self::IDD_CODE] = true; - $done[self::AREA_CODE] = true; - } - break; - case self::WEATHER_STATION: - if (!$done[self::WEATHER_STATION]) { - list($results[self::WEATHER_STATION_NAME], $results[self::WEATHER_STATION_CODE]) = $this->readWeatherStationNameAndCode($pointer); - $done[self::WEATHER_STATION] = true; - $done[self::WEATHER_STATION_NAME] = true; - $done[self::WEATHER_STATION_CODE] = true; - } - break; - case self::MCC_MNC_MOBILE_CARRIER_NAME: - if (!$done[self::MCC_MNC_MOBILE_CARRIER_NAME]) { - list($results[self::MCC], $results[self::MNC], $results[self::MOBILE_CARRIER_NAME]) = $this->readMccMncAndMobileCarrierName($pointer); - $done[self::MCC_MNC_MOBILE_CARRIER_NAME] = true; - $done[self::MCC] = true; - $done[self::MNC] = true; - $done[self::MOBILE_CARRIER_NAME] = true; - } - break; + // purposefully ignore self::ALL, we already dealt with it + case self::ALL: break; + + case self::COUNTRY: + if (!$done[self::COUNTRY]) { + list($results[self::COUNTRY_NAME], $results[self::COUNTRY_CODE]) = $this->readCountryNameAndCode($pointer); + $done[self::COUNTRY] = true; + $done[self::COUNTRY_CODE] = true; + $done[self::COUNTRY_NAME] = true; + } + break; + case self::COORDINATES: + if (!$done[self::COORDINATES]) { + list($results[self::LATITUDE], $results[self::LONGITUDE]) = $this->readLatitudeAndLongitude($pointer); + $done[self::COORDINATES] = true; + $done[self::LATITUDE] = true; + $done[self::LONGITUDE] = true; + } + break; + case self::IDD_AREA: + if (!$done[self::IDD_AREA]) { + list($results[self::IDD_CODE], $results[self::AREA_CODE]) = $this->readIddAndAreaCodes($pointer); + $done[self::IDD_AREA] = true; + $done[self::IDD_CODE] = true; + $done[self::AREA_CODE] = true; + } + break; + case self::WEATHER_STATION: + if (!$done[self::WEATHER_STATION]) { + list($results[self::WEATHER_STATION_NAME], $results[self::WEATHER_STATION_CODE]) = $this->readWeatherStationNameAndCode($pointer); + $done[self::WEATHER_STATION] = true; + $done[self::WEATHER_STATION_NAME] = true; + $done[self::WEATHER_STATION_CODE] = true; + } + break; + case self::MCC_MNC_MOBILE_CARRIER_NAME: + if (!$done[self::MCC_MNC_MOBILE_CARRIER_NAME]) { + list($results[self::MCC], $results[self::MNC], $results[self::MOBILE_CARRIER_NAME]) = $this->readMccMncAndMobileCarrierName($pointer); + $done[self::MCC_MNC_MOBILE_CARRIER_NAME] = true; + $done[self::MCC] = true; + $done[self::MNC] = true; + $done[self::MOBILE_CARRIER_NAME] = true; + } + break; - case self::COUNTRY_CODE: - if (!$done[self::COUNTRY_CODE]) { - $results[self::COUNTRY_CODE] = $this->readCountryNameAndCode($pointer)[1]; - $done[self::COUNTRY_CODE] = true; - } - break; - case self::COUNTRY_NAME: - if (!$done[self::COUNTRY_NAME]) { - $results[self::COUNTRY_NAME] = $this->readCountryNameAndCode($pointer)[0]; - $done[self::COUNTRY_NAME] = true; - } - break; - case self::REGION_NAME: - if (!$done[self::REGION_NAME]) { - $results[self::REGION_NAME] = $this->readRegionName($pointer); - $done[self::REGION_NAME] = true; - } - break; - case self::CITY_NAME: - if (!$done[self::CITY_NAME]) { - $results[self::CITY_NAME] = $this->readCityName($pointer); - $done[self::CITY_NAME] = true; - } - break; - case self::LATITUDE: - if (!$done[self::LATITUDE]) { - $results[self::LATITUDE] = $this->readLatitudeAndLongitude($pointer)[0]; - $done[self::LATITUDE] = true; - } - break; - case self::LONGITUDE: - if (!$done[self::LONGITUDE]) { - $results[self::LONGITUDE] = $this->readLatitudeAndLongitude($pointer)[1]; - $done[self::LONGITUDE] = true; - } - break; - case self::ISP: - if (!$done[self::ISP]) { - $results[self::ISP] = $this->readIsp($pointer); - $done[self::ISP] = true; - } - break; - case self::DOMAIN_NAME: - if (!$done[self::DOMAIN_NAME]) { - $results[self::DOMAIN_NAME] = $this->readDomainName($pointer); - $done[self::DOMAIN_NAME] = true; - } - break; - case self::ZIP_CODE: - if (!$done[self::ZIP_CODE]) { - $results[self::ZIP_CODE] = $this->readZipCode($pointer); - $done[self::ZIP_CODE] = true; - } - break; - case self::TIME_ZONE: - if (!$done[self::TIME_ZONE]) { - $results[self::TIME_ZONE] = $this->readTimeZone($pointer); - $done[self::TIME_ZONE] = true; - } - break; - case self::NET_SPEED: - if (!$done[self::NET_SPEED]) { - $results[self::NET_SPEED] = $this->readNetSpeed($pointer); - $done[self::NET_SPEED] = true; - } - break; - case self::IDD_CODE: - if (!$done[self::IDD_CODE]) { - $results[self::IDD_CODE] = $this->readIddAndAreaCodes($pointer)[0]; - $done[self::IDD_CODE] = true; - } - break; - case self::AREA_CODE: - if (!$done[self::AREA_CODE]) { - $results[self::AREA_CODE] = $this->readIddAndAreaCodes($pointer)[1]; - $done[self::AREA_CODE] = true; - } - break; - case self::WEATHER_STATION_CODE: - if (!$done[self::WEATHER_STATION_CODE]) { - $results[self::WEATHER_STATION_CODE] = $this->readWeatherStationNameAndCode($pointer)[1]; - $done[self::WEATHER_STATION_CODE] = true; - } - break; - case self::WEATHER_STATION_NAME: - if (!$done[self::WEATHER_STATION_NAME]) { - $results[self::WEATHER_STATION_NAME] = $this->readWeatherStationNameAndCode($pointer)[0]; - $done[self::WEATHER_STATION_NAME] = true; - } - break; - case self::MCC: - if (!$done[self::MCC]) { - $results[self::MCC] = $this->readMccMncAndMobileCarrierName($pointer)[0]; - $done[self::MCC] = true; - } - break; - case self::MNC: - if (!$done[self::MNC]) { - $results[self::MNC] = $this->readMccMncAndMobileCarrierName($pointer)[1]; - $done[self::MNC] = true; - } - break; - case self::MOBILE_CARRIER_NAME: - if (!$done[self::MOBILE_CARRIER_NAME]) { - $results[self::MOBILE_CARRIER_NAME] = $this->readMccMncAndMobileCarrierName($pointer)[2]; - $done[self::MOBILE_CARRIER_NAME] = true; - } - break; - case self::ELEVATION: - if (!$done[self::ELEVATION]) { - $results[self::ELEVATION] = $this->readElevation($pointer); - $done[self::ELEVATION] = true; - } - break; - case self::USAGE_TYPE: - if (!$done[self::USAGE_TYPE]) { - $results[self::USAGE_TYPE] = $this->readUsageType($pointer); - $done[self::USAGE_TYPE] = true; - } - break; + case self::COUNTRY_CODE: + if (!$done[self::COUNTRY_CODE]) { + $results[self::COUNTRY_CODE] = $this->readCountryNameAndCode($pointer)[1]; + $done[self::COUNTRY_CODE] = true; + } + break; + case self::COUNTRY_NAME: + if (!$done[self::COUNTRY_NAME]) { + $results[self::COUNTRY_NAME] = $this->readCountryNameAndCode($pointer)[0]; + $done[self::COUNTRY_NAME] = true; + } + break; + case self::REGION_NAME: + if (!$done[self::REGION_NAME]) { + $results[self::REGION_NAME] = $this->readRegionName($pointer); + $done[self::REGION_NAME] = true; + } + break; + case self::CITY_NAME: + if (!$done[self::CITY_NAME]) { + $results[self::CITY_NAME] = $this->readCityName($pointer); + $done[self::CITY_NAME] = true; + } + break; + case self::LATITUDE: + if (!$done[self::LATITUDE]) { + $results[self::LATITUDE] = $this->readLatitudeAndLongitude($pointer)[0]; + $done[self::LATITUDE] = true; + } + break; + case self::LONGITUDE: + if (!$done[self::LONGITUDE]) { + $results[self::LONGITUDE] = $this->readLatitudeAndLongitude($pointer)[1]; + $done[self::LONGITUDE] = true; + } + break; + case self::ISP: + if (!$done[self::ISP]) { + $results[self::ISP] = $this->readIsp($pointer); + $done[self::ISP] = true; + } + break; + case self::DOMAIN_NAME: + if (!$done[self::DOMAIN_NAME]) { + $results[self::DOMAIN_NAME] = $this->readDomainName($pointer); + $done[self::DOMAIN_NAME] = true; + } + break; + case self::ZIP_CODE: + if (!$done[self::ZIP_CODE]) { + $results[self::ZIP_CODE] = $this->readZipCode($pointer); + $done[self::ZIP_CODE] = true; + } + break; + case self::TIME_ZONE: + if (!$done[self::TIME_ZONE]) { + $results[self::TIME_ZONE] = $this->readTimeZone($pointer); + $done[self::TIME_ZONE] = true; + } + break; + case self::NET_SPEED: + if (!$done[self::NET_SPEED]) { + $results[self::NET_SPEED] = $this->readNetSpeed($pointer); + $done[self::NET_SPEED] = true; + } + break; + case self::IDD_CODE: + if (!$done[self::IDD_CODE]) { + $results[self::IDD_CODE] = $this->readIddAndAreaCodes($pointer)[0]; + $done[self::IDD_CODE] = true; + } + break; + case self::AREA_CODE: + if (!$done[self::AREA_CODE]) { + $results[self::AREA_CODE] = $this->readIddAndAreaCodes($pointer)[1]; + $done[self::AREA_CODE] = true; + } + break; + case self::WEATHER_STATION_CODE: + if (!$done[self::WEATHER_STATION_CODE]) { + $results[self::WEATHER_STATION_CODE] = $this->readWeatherStationNameAndCode($pointer)[1]; + $done[self::WEATHER_STATION_CODE] = true; + } + break; + case self::WEATHER_STATION_NAME: + if (!$done[self::WEATHER_STATION_NAME]) { + $results[self::WEATHER_STATION_NAME] = $this->readWeatherStationNameAndCode($pointer)[0]; + $done[self::WEATHER_STATION_NAME] = true; + } + break; + case self::MCC: + if (!$done[self::MCC]) { + $results[self::MCC] = $this->readMccMncAndMobileCarrierName($pointer)[0]; + $done[self::MCC] = true; + } + break; + case self::MNC: + if (!$done[self::MNC]) { + $results[self::MNC] = $this->readMccMncAndMobileCarrierName($pointer)[1]; + $done[self::MNC] = true; + } + break; + case self::MOBILE_CARRIER_NAME: + if (!$done[self::MOBILE_CARRIER_NAME]) { + $results[self::MOBILE_CARRIER_NAME] = $this->readMccMncAndMobileCarrierName($pointer)[2]; + $done[self::MOBILE_CARRIER_NAME] = true; + } + break; + case self::ELEVATION: + if (!$done[self::ELEVATION]) { + $results[self::ELEVATION] = $this->readElevation($pointer); + $done[self::ELEVATION] = true; + } + break; + case self::USAGE_TYPE: + if (!$done[self::USAGE_TYPE]) { + $results[self::USAGE_TYPE] = $this->readUsageType($pointer); + $done[self::USAGE_TYPE] = true; + } + break; - case self::IP_ADDRESS: - if (!$done[self::IP_ADDRESS]) { - $results[self::IP_ADDRESS] = $ip; - $done[self::IP_ADDRESS] = true; - } - break; - case self::IP_VERSION: - if (!$done[self::IP_VERSION]) { - $results[self::IP_VERSION] = $ipVersion; - $done[self::IP_VERSION] = true; - } - break; - case self::IP_NUMBER: - if (!$done[self::IP_NUMBER]) { - $results[self::IP_NUMBER] = $ipNumber; - $done[self::IP_NUMBER] = true; - } - break; + case self::ADDRESS_TYPE: + if (!$done[self::ADDRESS_TYPE]) { + $results[self::ADDRESS_TYPE] = $this->readAddressType($pointer); + $done[self::ADDRESS_TYPE] = true; + } + break; - default: - $results[$afield] = self::FIELD_NOT_KNOWN; - } + case self::CATEGORY: + if (!$done[self::CATEGORY]) { + $results[self::CATEGORY] = $this->readCategory($pointer); + $done[self::CATEGORY] = true; + } + break; + + case self::IP_ADDRESS: + if (!$done[self::IP_ADDRESS]) { + $results[self::IP_ADDRESS] = $ip; + $done[self::IP_ADDRESS] = true; + } + break; + case self::IP_VERSION: + if (!$done[self::IP_VERSION]) { + $results[self::IP_VERSION] = $ipVersion; + $done[self::IP_VERSION] = true; + } + break; + case self::IP_NUMBER: + if (!$done[self::IP_NUMBER]) { + $results[self::IP_NUMBER] = $ipNumber; + $done[self::IP_NUMBER] = true; + } + break; + + default: + $results[$afield] = self::FIELD_NOT_KNOWN; + } } // If we were asked for an array, or we have multiple results to return... @@ -1388,7 +1480,7 @@ private static function findFile($file = null) // If the file cannot be found, except away if ($rfile === false) { - throw new \Exception(__CLASS__ . ": Database file '{$file}' does not seem to exist.", self::EXCEPTION_DBFILE_NOT_FOUND); + throw new \Exception(__CLASS__ . ": Database file '{$file}' does not seem to exist.", self::EXCEPTION_DATABASE_FILE_NOT_FOUND); } return $rfile; @@ -1513,17 +1605,40 @@ private static function ipBetween($version, $ip, $low, $high) private static function ipVersionAndNumber($ip) { if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { - return [4, sprintf('%u', ip2long($ip))]; + $number = sprintf('%u', ip2long($ip)); + + return [4, ($number == self::MAX_IPV4_RANGE) ? ($number - 1) : $number]; } elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { $result = 0; + $ip = self::expand($ip); + + // 6to4 Address - 2002::/16 + if (substr($ip, 0, 4) == '2002') { + foreach (str_split(bin2hex(inet_pton($ip)), 8) as $word) { + $result = bcadd(bcmul($result, '4294967296', 0), self::wrap32(hexdec($word)), 0); + } + + return [4, bcmod(bcdiv($result, bcpow(2, 80)), '4294967296')]; + } + + // Teredo Address - 2001:0::/32 + if (substr($ip, 0, 9) == '2001:0000') { + return [4, hexdec(substr($ip, 10, 4) . substr($ip, 15, 4))]; + } foreach (str_split(bin2hex(inet_pton($ip)), 8) as $word) { $result = bcadd(bcmul($result, '4294967296', 0), self::wrap32(hexdec($word)), 0); } + // IPv4 address in IPv6 + if (bccomp($result, '281470681743360') >= 0 && bccomp($result, '281474976710655') <= 0) { + return [4, bcsub($result, '281470681743360')]; + } + return [6, $result]; } - // Invalid IP address, return falses + + // Invalid IP address, return false return [false, false]; } @@ -1538,12 +1653,16 @@ private static function ipVersionAndNumber($ip) */ private static function bcBin2Dec($data) { + if (!$data) { + return; + } + $parts = [ - unpack('V', substr($data, 12, 4)), - unpack('V', substr($data, 8, 4)), - unpack('V', substr($data, 4, 4)), - unpack('V', substr($data, 0, 4)), - ]; + unpack('V', substr($data, 12, 4)), + unpack('V', substr($data, 8, 4)), + unpack('V', substr($data, 4, 4)), + unpack('V', substr($data, 0, 4)), + ]; foreach ($parts as &$part) { if ($part[1] < 0) { @@ -1556,6 +1675,22 @@ private static function bcBin2Dec($data) return $result; } + /** + * Return the decimal string representing the binary data given. + * + * @static + * + * @param mixed $ipv6 + * + * @return string + */ + private static function expand($ipv6) + { + $hex = unpack('H*hex', inet_pton($ipv6)); + + return substr(preg_replace('/([A-f0-9]{4})/', '$1:', $hex['hex']), 0, -1); + } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Caching backend abstraction ///////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1599,10 +1734,10 @@ private function read($pos, $len) private function readString($pos, $additional = 0) { // Get the actual pointer to the string's head by extract from the raw row - $spos = unpack('V', substr($this->rawPositionsRow, $pos, 4))[1] + $additional; + $newPosition = unpack('V', substr($this->rawPositionsRow, $pos, 4))[1] + $additional; // Read as much as the length (first "string" byte) indicates - return $this->read($spos + 1, $this->readByte($spos + 1)); + return $this->read($newPosition + 1, $this->readByte($newPosition + 1)); } /** @@ -1675,7 +1810,7 @@ private function readCountryNameAndCode($pointer) $countryCode = self::INVALID_IP_ADDRESS; $countryName = self::INVALID_IP_ADDRESS; } elseif (self::$columns[self::COUNTRY_CODE][$this->type] === 0) { - // If the field is not suported, return accordingly + // If the field is not supported, return accordingly $countryCode = self::FIELD_NOT_SUPPORTED; $countryName = self::FIELD_NOT_SUPPORTED; } else { @@ -1702,7 +1837,7 @@ private function readRegionName($pointer) // Deal with invalid IPs $regionName = self::INVALID_IP_ADDRESS; } elseif (self::$columns[self::REGION_NAME][$this->type] === 0) { - // If the field is not suported, return accordingly + // If the field is not supported, return accordingly $regionName = self::FIELD_NOT_SUPPORTED; } else { // Read the region name @@ -1725,7 +1860,7 @@ private function readCityName($pointer) // Deal with invalid IPs $cityName = self::INVALID_IP_ADDRESS; } elseif (self::$columns[self::CITY_NAME][$this->type] === 0) { - // If the field is not suported, return accordingly + // If the field is not supported, return accordingly $cityName = self::FIELD_NOT_SUPPORTED; } else { // Read the city name @@ -1749,7 +1884,7 @@ private function readLatitudeAndLongitude($pointer) $latitude = self::INVALID_IP_ADDRESS; $longitude = self::INVALID_IP_ADDRESS; } elseif (self::$columns[self::LATITUDE][$this->type] === 0) { - // If the field is not suported, return accordingly + // If the field is not supported, return accordingly $latitude = self::FIELD_NOT_SUPPORTED; $longitude = self::FIELD_NOT_SUPPORTED; } else { @@ -1774,7 +1909,7 @@ private function readIsp($pointer) // Deal with invalid IPs $isp = self::INVALID_IP_ADDRESS; } elseif (self::$columns[self::ISP][$this->type] === 0) { - // If the field is not suported, return accordingly + // If the field is not supported, return accordingly $isp = self::FIELD_NOT_SUPPORTED; } else { // Read isp name @@ -1797,7 +1932,7 @@ private function readDomainName($pointer) // Deal with invalid IPs $domainName = self::INVALID_IP_ADDRESS; } elseif (self::$columns[self::DOMAIN_NAME][$this->type] === 0) { - // If the field is not suported, return accordingly + // If the field is not supported, return accordingly $domainName = self::FIELD_NOT_SUPPORTED; } else { // Read the domain name @@ -1820,7 +1955,7 @@ private function readZipCode($pointer) // Deal with invalid IPs $zipCode = self::INVALID_IP_ADDRESS; } elseif (self::$columns[self::ZIP_CODE][$this->type] === 0) { - // If the field is not suported, return accordingly + // If the field is not supported, return accordingly $zipCode = self::FIELD_NOT_SUPPORTED; } else { // Read the zip code @@ -1843,7 +1978,7 @@ private function readTimeZone($pointer) // Deal with invalid IPs $timeZone = self::INVALID_IP_ADDRESS; } elseif (self::$columns[self::TIME_ZONE][$this->type] === 0) { - // If the field is not suported, return accordingly + // If the field is not supported, return accordingly $timeZone = self::FIELD_NOT_SUPPORTED; } else { // Read the time zone @@ -1866,7 +2001,7 @@ private function readNetSpeed($pointer) // Deal with invalid IPs $netSpeed = self::INVALID_IP_ADDRESS; } elseif (self::$columns[self::NET_SPEED][$this->type] === 0) { - // If the field is not suported, return accordingly + // If the field is not supported, return accordingly $netSpeed = self::FIELD_NOT_SUPPORTED; } else { // Read the net speed @@ -1890,7 +2025,7 @@ private function readIddAndAreaCodes($pointer) $iddCode = self::INVALID_IP_ADDRESS; $areaCode = self::INVALID_IP_ADDRESS; } elseif (self::$columns[self::IDD_CODE][$this->type] === 0) { - // If the field is not suported, return accordingly + // If the field is not supported, return accordingly $iddCode = self::FIELD_NOT_SUPPORTED; $areaCode = self::FIELD_NOT_SUPPORTED; } else { @@ -1916,7 +2051,7 @@ private function readWeatherStationNameAndCode($pointer) $weatherStationName = self::INVALID_IP_ADDRESS; $weatherStationCode = self::INVALID_IP_ADDRESS; } elseif (self::$columns[self::WEATHER_STATION_NAME][$this->type] === 0) { - // If the field is not suported, return accordingly + // If the field is not supported, return accordingly $weatherStationName = self::FIELD_NOT_SUPPORTED; $weatherStationCode = self::FIELD_NOT_SUPPORTED; } else { @@ -1943,7 +2078,7 @@ private function readMccMncAndMobileCarrierName($pointer) $mnc = self::INVALID_IP_ADDRESS; $mobileCarrierName = self::INVALID_IP_ADDRESS; } elseif (self::$columns[self::MCC][$this->type] === 0) { - // If the field is not suported, return accordingly + // If the field is not supported, return accordingly $mcc = self::FIELD_NOT_SUPPORTED; $mnc = self::FIELD_NOT_SUPPORTED; $mobileCarrierName = self::FIELD_NOT_SUPPORTED; @@ -1970,7 +2105,7 @@ private function readElevation($pointer) // Deal with invalid IPs $elevation = self::INVALID_IP_ADDRESS; } elseif (self::$columns[self::ELEVATION][$this->type] === 0) { - // If the field is not suported, return accordingly + // If the field is not supported, return accordingly $elevation = self::FIELD_NOT_SUPPORTED; } else { // Read the elevation @@ -1993,7 +2128,7 @@ private function readUsageType($pointer) // Deal with invalid IPs $usageType = self::INVALID_IP_ADDRESS; } elseif (self::$columns[self::USAGE_TYPE][$this->type] === 0) { - // If the field is not suported, return accordingly + // If the field is not supported, return accordingly $usageType = self::FIELD_NOT_SUPPORTED; } else { $usageType = $this->readString(self::$columns[self::USAGE_TYPE][$this->type]); @@ -2002,6 +2137,50 @@ private function readUsageType($pointer) return $usageType; } + /** + * High level function to fetch the address type. + * + * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS + * + * @return string + */ + private function readAddressType($pointer) + { + if ($pointer === false) { + // Deal with invalid IPs + $addressType = self::INVALID_IP_ADDRESS; + } elseif (self::$columns[self::ADDRESS_TYPE][$this->type] === 0) { + // If the field is not supported, return accordingly + $addressType = self::FIELD_NOT_SUPPORTED; + } else { + $addressType = $this->readString(self::$columns[self::ADDRESS_TYPE][$this->type]); + } + + return $addressType; + } + + /** + * High level function to fetch the usage type. + * + * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS + * + * @return string + */ + private function readCategory($pointer) + { + if ($pointer === false) { + // Deal with invalid IPs + $category = self::INVALID_IP_ADDRESS; + } elseif (self::$columns[self::CATEGORY][$this->type] === 0) { + // If the field is not supported, return accordingly + $category = self::FIELD_NOT_SUPPORTED; + } else { + $category = $this->readString(self::$columns[self::CATEGORY][$this->type]); + } + + return $category; + } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Binary search and support functions ///////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -2054,22 +2233,23 @@ private function binSearch($version, $ipNumber, $cidr = false) $indexBaseStart = $this->indexBaseAddr[$version]; if ($indexBaseStart > 0) { $indexPos = 0; + switch ($version) { - case 4: - $ipNum1_2 = (int) ($ipNumber / 65536); - $indexPos = $indexBaseStart + ($ipNum1_2 << 3); + case 4: + $ipNum1_2 = (int) ($ipNumber / 65536); + $indexPos = $indexBaseStart + ($ipNum1_2 << 3); - break; + break; - case 6: - $ipNum1 = (int) (bcdiv($ipNumber, bcpow('2', '112'))); - $indexPos = $indexBaseStart + ($ipNum1 << 3); + case 6: + $ipNum1 = (int) (bcdiv($ipNumber, bcpow('2', '112'))); + $indexPos = $indexBaseStart + ($ipNum1 << 3); - break; + break; - default: - return false; - } + default: + return false; + } $low = $this->readWord($indexPos); $high = $this->readWord($indexPos + 4); @@ -2080,20 +2260,22 @@ private function binSearch($version, $ipNumber, $cidr = false) $mid = (int) ($low + (($high - $low) >> 1)); // Read IP ranges to get boundaries - $ip_from = $this->readIp($version, $base + $width * $mid); - $ip_to = $this->readIp($version, $base + $width * ($mid + 1)); + $ipStart = $this->readIp($version, $base + $width * $mid); + $ipEnd = $this->readIp($version, $base + $width * ($mid + 1)); // determine whether to return, repeat on the lower half, or repeat on the upper half - switch (self::ipBetween($version, $ipNumber, $ip_from, $ip_to)) { - case 0: - return ($cidr) ? [$ip_from, $ip_to] : $base + $offset + $mid * $width; - case -1: - $high = $mid - 1; - break; - case 1: - $low = $mid + 1; - break; - } + switch (self::ipBetween($version, $ipNumber, $ipStart, $ipEnd)) { + case 0: + return ($cidr) ? [$ipStart, $ipEnd] : $base + $offset + $mid * $width; + + case -1: + $high = $mid - 1; + break; + + case 1: + $low = $mid + 1; + break; + } } // nothing found