���� JFIF �� � ( %"1"%)+...383,7(-.-
![]() Server : Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips PHP/7.4.20 System : Linux st2.domain.com 3.10.0-1127.10.1.el7.x86_64 #1 SMP Wed Jun 3 14:28:03 UTC 2020 x86_64 User : apache ( 48) PHP Version : 7.4.20 Disable Function : NONE Directory : /var/www/html/thietkeweb2/vendor/ip2location/ip2location-php/src/ |
<?php namespace IP2Location; /** * IP2Location database class. */ class Database { /** * Current module's version. * * @var string */ public const VERSION = '9.7.3'; /** * Unsupported field message. * * @var string */ public const FIELD_NOT_SUPPORTED = 'This parameter is unavailable in selected .BIN data file. Please upgrade data file.'; /** * Unknown field message. * * @var string */ public const FIELD_NOT_KNOWN = 'This parameter is inexistent. Please verify.'; /** * Invalid IP address message. * * @var string */ public const INVALID_IP_ADDRESS = 'Invalid IP address.'; /** * 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). * * @var int */ public const COUNTRY_CODE = 1; /** * Country name. * * @var int */ public const COUNTRY_NAME = 2; /** * Region name. * * @var int */ public const REGION_NAME = 3; /** * City name. * * @var int */ public const CITY_NAME = 4; /** * Latitude. * * @var int */ public const LATITUDE = 5; /** * Longitude. * * @var int */ public const LONGITUDE = 6; /** * ISP name. * * @var int */ public const ISP = 7; /** * Domain name. * * @var int */ public const DOMAIN_NAME = 8; /** * Zip code. * * @var int */ public const ZIP_CODE = 9; /** * Time zone. * * @var int */ public const TIME_ZONE = 10; /** * Net speed. * * @var int */ public const NET_SPEED = 11; /** * IDD code. * * @var int */ public const IDD_CODE = 12; /** * Area code. * * @var int */ public const AREA_CODE = 13; /** * Weather station code. * * @var int */ public const WEATHER_STATION_CODE = 14; /** * Weather station name. * * @var int */ public const WEATHER_STATION_NAME = 15; /** * Mobile Country Code. * * @var int */ public const MCC = 16; /** * Mobile Network Code. * * @var int */ public const MNC = 17; /** * Mobile carrier name. * * @var int */ public const MOBILE_CARRIER_NAME = 18; /** * Elevation. * * @var int */ public const ELEVATION = 19; /** * Usage type. * * @var int */ public const USAGE_TYPE = 20; /** * Address type. * * @var int */ public const ADDRESS_TYPE = 21; /** * Category. * * @var int */ public const CATEGORY = 22; /** * District. * * @var int */ public const DISTRICT = 23; /** * ASN. * * @var int */ public const ASN = 24; /** * AS. * * @var int */ public const AS = 25; /** * Country name and code. * * @var int */ public const COUNTRY = 101; /** * Latitude and Longitude. * * @var int */ public const COORDINATES = 102; /** * IDD and area codes. * * @var int */ public const IDD_AREA = 103; /** * Weather station name and code. * * @var int */ public const WEATHER_STATION = 104; /** * MCC, MNC, and mobile carrier name. * * @var int */ public const MCC_MNC_MOBILE_CARRIER_NAME = 105; /** * All fields at once. * * @var int */ public const ALL = 1001; /** * Include the IP address of the looked up IP address. * * @var int */ public const IP_ADDRESS = 1002; /** * Include the IP version of the looked up IP address. * * @var int */ public const IP_VERSION = 1003; /** * Include the IP number of the looked up IP address. * * @var int */ public const IP_NUMBER = 1004; /** * Generic exception code. * * @var int */ public const EXCEPTION = 10000; /** * No shmop extension found. * * @var int */ public const EXCEPTION_NO_SHMOP = 10001; /** * Failed to open shmop memory segment for reading. * * @var int */ public const EXCEPTION_SHMOP_READING_FAILED = 10002; /** * Failed to open shmop memory segment for writing. * * @var int */ public const EXCEPTION_SHMOP_WRITING_FAILED = 10003; /** * Failed to create shmop memory segment. * * @var int */ public const EXCEPTION_SHMOP_CREATE_FAILED = 10004; /** * The specified database file was not found. * * @var int */ public const EXCEPTION_DATABASE_FILE_NOT_FOUND = 10005; /** * Not enough memory to load database file. * * @var int */ public const EXCEPTION_NO_MEMORY = 10006; /** * No candidate database files found. * * @var int */ public const EXCEPTION_NO_CANDIDATES = 10007; /** * Failed to open database file. * * @var int */ public const EXCEPTION_FILE_OPEN_FAILED = 10008; /** * Failed to determine the current path. * * @var int */ public const EXCEPTION_NO_PATH = 10009; /** * Invalid BIN database file. * * @var int */ public const EXCEPTION_INVALID_BIN_DATABASE = 10010; /** * Failed to delete shmop memory segment. * * @var int */ public const EXCEPTION_SHMOP_DELETE_FAILED = 10011; /** * Directly read from the database file. * * @var int */ public const FILE_IO = 100001; /** * Read the whole database into a variable for caching. * * @var int */ public const MEMORY_CACHE = 100002; /** * Use shared memory objects for caching. * * @var int */ public const SHARED_MEMORY = 100003; /** * Share memory segment's permissions (for creation). * * @var int */ public const SHM_PERMS = 0600; /** * Number of bytes to read/write at a time in order to load the shared memory cache (512k). * * @var int */ public const SHM_CHUNK_SIZE = 524288; /** * Column offset mapping. * * 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. * * @var array */ private $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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 88], self::DISTRICT => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 92], self::ASN => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96], self::AS => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100], ]; /** * Column name mapping. * * @var array */ private $names = [ self::COUNTRY_CODE => 'countryCode', self::COUNTRY_NAME => 'countryName', self::REGION_NAME => 'regionName', self::CITY_NAME => 'cityName', self::LATITUDE => 'latitude', self::LONGITUDE => 'longitude', self::ISP => 'isp', self::DOMAIN_NAME => 'domainName', self::ZIP_CODE => 'zipCode', self::TIME_ZONE => 'timeZone', self::NET_SPEED => 'netSpeed', self::IDD_CODE => 'iddCode', self::AREA_CODE => 'areaCode', self::WEATHER_STATION_CODE => 'weatherStationCode', self::WEATHER_STATION_NAME => 'weatherStationName', self::MCC => 'mcc', self::MNC => 'mnc', self::MOBILE_CARRIER_NAME => 'mobileCarrierName', self::ELEVATION => 'elevation', self::USAGE_TYPE => 'usageType', self::ADDRESS_TYPE => 'addressType', self::CATEGORY => 'category', self::DISTRICT => 'district', self::ASN => 'asn', self::AS => 'as', self::IP_ADDRESS => 'ipAddress', self::IP_VERSION => 'ipVersion', self::IP_NUMBER => 'ipNumber', ]; /** * Database names, in order of preference for file lookup. * * @var array */ private $databases = [ // IPv4 databases 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER-MOBILE-ELEVATION-USAGETYPE-ADDRESSTYPE-CATEGORY-DISTRICT-ASN', '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', 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-AREACODE-ELEVATION', 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER-MOBILE', 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ISP-DOMAIN-MOBILE', 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER', 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-NETSPEED-WEATHER', 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE', 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-AREACODE', 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED', 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-TIMEZONE-NETSPEED', 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN', 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE', 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-ISP-DOMAIN', 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE', 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ISP-DOMAIN', 'IP-COUNTRY-REGION-CITY-ISP-DOMAIN', 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ISP', 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE', 'IP-COUNTRY-REGION-CITY-ISP', 'IP-COUNTRY-REGION-CITY', '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-DISTRICT-ASN', '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', 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-AREACODE-ELEVATION', 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER-MOBILE', 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ISP-DOMAIN-MOBILE', 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER', 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-NETSPEED-WEATHER', 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE', 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-AREACODE', 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED', 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-TIMEZONE-NETSPEED', 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN', 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE', 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-ISP-DOMAIN', 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE', 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ISP-DOMAIN', 'IPV6-COUNTRY-REGION-CITY-ISP-DOMAIN', 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ISP', 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE', 'IPV6-COUNTRY-REGION-CITY-ISP', 'IPV6-COUNTRY-REGION-CITY', 'IPV6-COUNTRY-ISP', 'IPV6-COUNTRY', ]; /** * Memory buffer to use for MEMORY_CACHE mode, the keys will be BIN filenames and the values their contents. * * @var array */ private $buffer = []; /** * The machine's float size. * * @var int */ private $floatSize = null; /** * The configured memory limit. * * @var int */ private $memoryLimit = null; /** * Caching mode to use (one of FILE_IO, MEMORY_CACHE, or SHARED_MEMORY). * * @var int */ private $mode; /** * File pointer to use for FILE_IO mode, BIN filename for MEMORY_CACHE mode, or shared memory id to use for SHARED_MEMORY mode. * * @var false|int|resource */ private $resource = false; /** * Database's compilation date. * * @var int */ private $date; /** * Database's type (0--23). * * @var int */ private $type; /** * Database's register width (as an array mapping 4 to IPv4 width, and 6 to IPv6 width). * * @var array */ private $columnWidth = []; /** * Database's pointer offset (as an array mapping 4 to IPv4 offset, and 6 to IPv6 offset). * * @var array */ private $offset = []; /** * Amount of IP address ranges the database contains (as an array mapping 4 to IPv4 count, and 6 to IPv6 count). * * @var array */ private $ipCount = []; /** * Offset withing the database where IP data begins (as an array mapping 4 to IPv4 base, and 6 to IPv6 base). * * @var array */ private $ipBase = []; /** * Base index address. * * @var array */ private $indexBaseAddr = []; /** * The year of the database is released. * * @var string */ private $year; /** * The month of the database is released. * * @var string */ private $month; /** * The day of the database is released. * * @var string */ private $day; /** * 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 */ private $rawPositionsRow; /** * IP2Location web service API key. * * @var string */ private $apiKey; /** * Web service package. * * @var string */ private $package; /** * Either use HTTPS or HTTP. * * @var bool */ private $useSsl; /** * Add ons used by the web service. * * @var array */ private $addOns = []; /** * Default fields to return during lookup. * * @var array|int */ private $defaultFields = self::ALL; /** * Constructor. * * @param string $file Filename of the BIN database to load * @param int $mode Caching mode (FILE_IO, MEMORY_CACHE, or SHARED_MEMORY) * @param mixed $defaultFields * * @throws \Exception */ public function __construct($file = null, $mode = self::FILE_IO, $defaultFields = self::ALL) { // Locate the actual file $realPath = $this->findFile($file); // File size $fileSize = filesize($realPath); // initialize caching backend switch ($mode) { case self::SHARED_MEMORY: // Make sure shmop extension is loaded if (!\extension_loaded('shmop')) { throw new \Exception(__CLASS__ . ": Please make sure your PHP setup has the 'shmop' extension enabled.", self::EXCEPTION_NO_SHMOP); } $memoryLimit = $this->getMemoryLimit(); if ($memoryLimit !== false && $fileSize > $memoryLimit) { throw new \Exception(__CLASS__ . ": Insufficient memory to load file '{$realPath}'.", self::EXCEPTION_NO_MEMORY); } $this->mode = self::SHARED_MEMORY; $shmKey = $this->getShmKey($realPath); $fileSizeChanged = false; // Open shared memory segment $this->resource = @shmop_open($shmKey, 'a', 0, 0); // Segment does not exist or file size changed if ($this->resource === false || $fileSizeChanged = (shmop_size($this->resource) !== filesize($realPath))) { // File size has changed, remove old segment if ($fileSizeChanged && !shmop_delete($this->resource)) { throw new \Exception(__CLASS__ . ": Unable to delete shared memory block '{$shmKey}'.", self::EXCEPTION_SHMOP_DELETE_FAILED); } $fp = fopen($realPath, 'r'); if ($fp === false) { throw new \Exception(__CLASS__ . ": Unable to open file '{$realPath}'.", self::EXCEPTION_FILE_OPEN_FAILED); } // Open the memory segment for exclusive access $shmId = @shmop_open($shmKey, 'n', self::SHM_PERMS, $fileSize); if ($shmId === false) { throw new \Exception(__CLASS__ . ": Unable to create shared memory block '{$shmKey}'.", self::EXCEPTION_SHMOP_CREATE_FAILED); } // Load SHM_CHUNK_SIZE bytes at a time $pointer = 0; while ($pointer < $fileSize) { $buffer = fread($fp, self::SHM_CHUNK_SIZE); shmop_write($shmId, $buffer, $pointer); $pointer += self::SHM_CHUNK_SIZE; } if (PHP_MAJOR_VERSION < 8) { shmop_close($shmId); } fclose($fp); // Open memory segment for readonly access $this->resource = @shmop_open($shmKey, 'a', 0, 0); if ($this->resource === false) { throw new \Exception(__CLASS__ . ": Unable to access shared memory block '{$shmKey}' for reading.", self::EXCEPTION_SHMOP_READING_FAILED); } } break; case self::MEMORY_CACHE: $this->mode = self::MEMORY_CACHE; $this->resource = $realPath; if (!\array_key_exists($realPath, $this->buffer)) { $memoryLimit = $this->getMemoryLimit(); if ($memoryLimit !== false && $fileSize > $memoryLimit) { throw new \Exception(__CLASS__ . ": Insufficient memory to load file '{$realPath}'.", self::EXCEPTION_NO_MEMORY); } $this->buffer[$realPath] = @file_get_contents($realPath); if ($this->buffer[$realPath] === false) { throw new \Exception(__CLASS__ . ": Unable to open file '{$realPath}'.", self::EXCEPTION_FILE_OPEN_FAILED); } } break; case self::FILE_IO: default: $this->mode = self::FILE_IO; $this->resource = @fopen($realPath, 'r'); if ($this->resource === false) { throw new \Exception(__CLASS__ . ": Unable to open file '{$realPath}'.", self::EXCEPTION_FILE_OPEN_FAILED); } break; } // Determine platform's float size if ($this->floatSize === null) { $this->floatSize = \strlen(pack('f', M_PI)); } // Set default return fields $this->defaultFields = $defaultFields; // Read metadata headers from the first 512 bytes $headers = $this->read(0, 512); // Extract metadata from headers $this->type = unpack('C', $headers, 0)[1] - 1; $this->columnWidth[4] = unpack('C', $headers, 1)[1] * 4; $this->columnWidth[6] = $this->columnWidth[4] + 12; $this->offset[4] = -4; $this->offset[6] = 8; $this->year = 2000 + unpack('C', $headers, 2)[1]; $this->month = unpack('C', $headers, 3)[1]; $this->day = unpack('C', $headers, 4)[1]; $this->date = date('Y-m-d', strtotime("{$this->year}-{$this->month}-{$this->day}")); $this->productCode = unpack('C', $headers, 29)[1]; $this->licenseCode = unpack('C', $headers, 30)[1]; $this->databaseSize = unpack('C', $headers, 31)[1]; $this->ipCount[4] = unpack('V', $headers, 5)[1]; $this->ipBase[4] = unpack('V', $headers, 9)[1]; $this->ipCount[6] = unpack('V', $headers, 13)[1]; $this->ipBase[6] = unpack('V', $headers, 17)[1]; $this->indexBaseAddr[4] = unpack('V', $headers, 21)[1]; $this->indexBaseAddr[6] = unpack('V', $headers, 25)[1]; if ($this->productCode == 0) { 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); } } /** * Destructor. */ public function __destruct() { switch ($this->mode) { case self::FILE_IO: // Free the file pointer if ($this->resource !== false) { fclose($this->resource); $this->resource = false; } break; case self::SHARED_MEMORY: // Detach from the memory segment if ($this->resource !== false) { if (PHP_MAJOR_VERSION < 8) { shmop_close($this->resource); } $this->resource = false; } break; } } /** * Tear down a shared memory segment created for the given file. * * @param string $file Filename of the BIN database * * @throws \Exception */ public function shmTeardown($file) { // Make sure shmop extension is loaded if (!\extension_loaded('shmop')) { throw new \Exception(__CLASS__ . ": Please make sure your PHP setup has the 'shmop' extension enabled.", self::EXCEPTION_NO_SHMOP); } // Get actual file path $realPath = realpath($file); // Throw error if file cannot be located if ($realPath === false) { throw new \Exception(__CLASS__ . ": Database file '{$file}' does not seem to exist.", self::EXCEPTION_DATABASE_FILE_NOT_FOUND); } $shmKey = $this->getShmKey($realPath); // Open the memory segment for writing $shmId = @shmop_open($shmKey, 'w', 0, 0); if ($shmId === false) { throw new \Exception(__CLASS__ . ": Unable to access shared memory block '{$shmKey}' for writing.", self::EXCEPTION_SHMOP_WRITING_FAILED); } // Delete the memory segment shmop_delete($shmId); } /** * Get the database's compilation date as a string of the form 'YYYY-MM-DD'. * * @return string */ public function getDate() { return $this->date; } /** * Get the database's type (1 - 25). * * @return int */ public function getType() { return $this->type + 1; } /** * Return fields available in current database. * * @param bool $asNames Whether to return the mapped names instead of numbered constants * * @return array */ public function getFields($asNames = false) { $result = array_keys(array_filter($this->columns, function ($field) { return $field[$this->type] !== 0; })); if ($asNames) { $return = []; foreach ($result as $field) { $return[] = $this->names[$field]; } return $return; } return $result; } /** * Return the version of module. */ public function getModuleVersion() { return self::VERSION; } /** * Return the version of current database. */ public function getDatabaseVersion() { return $this->year . '.' . $this->month . '.' . $this->day; } /** * This function will look the given IP address up in the database and return the result(s) asked for. * * 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 with the returned singular field names as keys and their corresponding * values is returned. * * @param string $ip IP address to look up * @param array|int $fields Field(s) to return * @param bool $asNamed Whether to return an associative array instead * * @return array|bool|mixed */ public function lookup($ip, $fields = null, $asNamed = true) { // Get IP version and number list($ipVersion, $ipNumber) = $this->ipVersionAndNumber($ip); if (!$ipVersion) { return false; } // Perform a binary search $pointer = $this->binSearch($ipVersion, $ipNumber); if (empty($pointer)) { return false; } // Apply default fields if ($fields === null) { $fields = $this->defaultFields; } // Get the entire row based on the pointer value if ($ipVersion === 4) { $this->rawPositionsRow = $this->read($pointer - 1, $this->columnWidth[4] + 4); } elseif ($ipVersion === 6) { $this->rawPositionsRow = $this->read($pointer - 1, $this->columnWidth[6]); } // turn fields into an array in case it wasn't already $ifields = (array) $fields; // add fields if needed if (\in_array(self::ALL, $ifields)) { $ifields[] = self::REGION_NAME; $ifields[] = self::CITY_NAME; $ifields[] = self::ISP; $ifields[] = self::DOMAIN_NAME; $ifields[] = self::ZIP_CODE; $ifields[] = self::TIME_ZONE; $ifields[] = self::NET_SPEED; $ifields[] = self::ELEVATION; $ifields[] = self::USAGE_TYPE; $ifields[] = self::ADDRESS_TYPE; $ifields[] = self::CATEGORY; $ifields[] = self::DISTRICT; $ifields[] = self::ASN; $ifields[] = self::AS; $ifields[] = self::COUNTRY; $ifields[] = self::COORDINATES; $ifields[] = self::IDD_AREA; $ifields[] = self::WEATHER_STATION; $ifields[] = self::MCC_MNC_MOBILE_CARRIER_NAME; $ifields[] = self::IP_ADDRESS; $ifields[] = self::IP_VERSION; $ifields[] = self::IP_NUMBER; } // turn into a uniquely-valued array the fast way // (see: http://php.net/manual/en/function.array-unique.php#77743) $afields = array_keys(array_flip($ifields)); // sorting them in reverse order warrants that by the time we get to // SINGULAR fields, its MULTIPLE counterparts, if at all present, have // already been retrieved rsort($afields); // maintain a list of already retrieved fields to avoid doing it twice $done = [ self::COUNTRY_CODE => false, self::COUNTRY_NAME => false, self::REGION_NAME => false, self::CITY_NAME => false, self::LATITUDE => false, self::LONGITUDE => false, self::ISP => false, self::DOMAIN_NAME => false, self::ZIP_CODE => false, self::TIME_ZONE => false, self::NET_SPEED => false, self::IDD_CODE => false, self::AREA_CODE => false, self::WEATHER_STATION_CODE => false, self::WEATHER_STATION_NAME => false, self::MCC => false, self::MNC => false, self::MOBILE_CARRIER_NAME => false, self::ELEVATION => false, self::USAGE_TYPE => false, self::ADDRESS_TYPE => false, self::CATEGORY => false, self::DISTRICT => false, self::ASN => false, self::AS => false, self::COUNTRY => false, self::COORDINATES => false, self::IDD_AREA => false, self::WEATHER_STATION => false, self::MCC_MNC_MOBILE_CARRIER_NAME => false, self::IP_ADDRESS => false, self::IP_VERSION => false, self::IP_NUMBER => false, ]; $results = []; // 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; 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::ADDRESS_TYPE: if (!$done[self::ADDRESS_TYPE]) { $results[self::ADDRESS_TYPE] = $this->readAddressType($pointer); $done[self::ADDRESS_TYPE] = true; } break; case self::CATEGORY: if (!$done[self::CATEGORY]) { $results[self::CATEGORY] = $this->readCategory($pointer); $done[self::CATEGORY] = true; } break; case self::DISTRICT: if (!$done[self::DISTRICT]) { $results[self::DISTRICT] = $this->readDistrict($pointer); $done[self::DISTRICT] = true; } break; case self::ASN: if (!$done[self::ASN]) { $results[self::ASN] = $this->readAsn($pointer); $done[self::ASN] = true; } break; case self::AS: if (!$done[self::AS]) { $results[self::AS] = $this->readAs($pointer); $done[self::AS] = 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... if (\is_array($fields) || \count($results) > 1) { // return array if ($asNamed) { // apply translations if needed $return = []; foreach ($results as $key => $val) { if (\array_key_exists($key, $this->names)) { $return[$this->names[$key]] = $val; } else { $return[$key] = $val; } } return $return; } return $results; } // return a single value return array_values($results)[0]; } /** * For a given IP address, returns the cidr of his sub-network. * * @param string $ip * * @return array * */ public function getCidr($ip) { // Extract IP version and number list($ipVersion, $ipNumber) = $this->ipVersionAndNumber($ip); // Perform the binary search proper (if the IP address was invalid, binSearch will return false) $records = $this->binSearch($ipVersion, $ipNumber, true); if (!empty($records)) { $result = []; list($ipFrom, $ipTo) = $records; --$ipTo; while ($ipTo >= $ipFrom) { $maxSize = $this->getMaxSize($ipFrom, 32); $x = log($ipTo - $ipFrom + 1) / log(2); $maxDiff = floor(32 - floor($x)); $ip = long2ip($ipFrom); if ($maxSize < $maxDiff) { $maxSize = $maxDiff; } $result[] = $ip . '/' . $maxSize; $ipFrom += pow(2, (32 - $maxSize)); } return $result; } return false; } /** * Get maximum size of a net block. * * @param int $base The base number * @param int $bit The bit number * * @return bool|int */ private function getMaxSize($base, $bit) { while ($bit > 0) { $decimal = hexdec(base_convert((pow(2, 32) - pow(2, (32 - ($bit - 1)))), 10, 16)); if (($base & $decimal) != $base) { break; } --$bit; } return $bit; } /** * Get memory limit from the current PHP settings (return false if no memory limit set). * * @return bool|int */ private function getMemoryLimit() { // Get values if no cache if ($this->memoryLimit === null) { $memoryLimit = ini_get('memory_limit'); // Default memory limit if ((string) $memoryLimit === '') { $memoryLimit = '128M'; } $value = (int) $memoryLimit; // Deal with "no-limit" if ($value < 0) { $value = false; } else { // Deal with shorthand bytes switch (strtoupper(substr($memoryLimit, -1))) { case 'G': $value *= 1024; // no break case 'M': $value *= 1024; // no break case 'K': $value *= 1024; } } $this->memoryLimit = $value; } return $this->memoryLimit; } /** * Return the realpath of the given file or look for the first matching database option. * * @param string $file File to try to find, or null to try the databases in turn on the current file's path * * @throws \Exception * * @return string */ private function findFile($file = null) { if ($file !== null) { // Get actual file path $realPath = realpath($file); // Throw error if file cannot be located if ($realPath === false) { throw new \Exception(__CLASS__ . ": Database file '{$file}' does not seem to exist.", self::EXCEPTION_DATABASE_FILE_NOT_FOUND); } return $realPath; } // Try to get current path $current = realpath(__DIR__); if ($current === false) { throw new \Exception(__CLASS__ . ': Cannot determine current path.', self::EXCEPTION_NO_PATH); } // Try each database in turn foreach ($this->databases as $database) { $realPath = realpath("{$current}/{$database}.BIN"); if ($realPath !== false) { return $realPath; } } // No candidates found throw new \Exception(__CLASS__ . ': No candidate database files found.', self::EXCEPTION_NO_CANDIDATES); } /** * Make the given number positive by wrapping it to 8 bit values. * * @param int $x Number to wrap * * @return int */ private function wrap8($x) { return $x + ($x < 0 ? 256 : 0); } /** * Make the given number positive by wrapping it to 32 bit values. * * @param int $x Number to wrap * * @return int */ private function wrap32($x) { return $x + ($x < 0 ? 4294967296 : 0); } /** * Generate a unique and repeatable shared memory key for each instance to use. * * @param string $filename Filename of the BIN file * * @return int */ private function getShmKey($filename) { return (int) sprintf('%u', $this->wrap32(crc32(__FILE__ . ':' . $filename))); } /** * Determine whether the given IP number of the given version lies between the given bounds. * * This function will return 0 if the given ip number falls within the given bounds * for the given version, -1 if it falls below, and 1 if it falls above. * * @param int $version IP version to use (either 4 or 6) * @param int|string $ip IP number to check (int for IPv4, string for IPv6) * @param int|string $low Lower bound (int for IPv4, string for IPv6) * @param int|string $high Uppoer bound (int for IPv4, string for IPv6) * * @return int */ private function ipBetween($version, $ip, $low, $high) { if ($version === 4) { // Use normal PHP ints if ($low <= $ip) { if ($ip < $high) { return 0; } return 1; } return -1; } // Use BCMath if (bccomp($low, $ip, 0) <= 0) { if (bccomp($ip, $high, 0) <= -1) { return 0; } return 1; } return -1; } /** * Get the IP version and number of the given IP address. * * @param string $ip IP address to extract the version and number for * * @return array */ private function ipVersionAndNumber($ip) { if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { $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 = $this->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), $this->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' && str_replace(':', '', substr($ip, -9)) != '00000000') { return [4, ip2long(long2ip(~hexdec(str_replace(':', '', substr($ip, -9)))))]; } foreach (str_split(bin2hex(inet_pton($ip)), 8) as $word) { $result = bcadd(bcmul($result, '4294967296', 0), $this->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 false return [false, false]; } /** * Return the decimal string representing the binary data given. * * @param string $data Binary data to parse * * @return string */ private 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)), ]; foreach ($parts as &$part) { if ($part[1] < 0) { $part[1] += 4294967296; } } $result = bcadd(bcadd(bcmul($parts[0][1], bcpow(4294967296, 3)), bcmul($parts[1][1], bcpow(4294967296, 2))), bcadd(bcmul($parts[2][1], 4294967296), $parts[3][1])); return $result; } /** * Return the decimal string representing the binary data given. * * @param mixed $ipv6 * * @return string */ private function expand($ipv6) { $hex = unpack('H*hex', inet_pton($ipv6)); return substr(preg_replace('/([A-f0-9]{4})/', '$1:', $hex['hex']), 0, -1); } /** * Low level read function to abstract away the caching mode being used. * * @param int $pos Position from where to start reading * @param int $len Read this many bytes * * @return string */ private function read($pos, $len) { switch ($this->mode) { case self::SHARED_MEMORY: return shmop_read($this->resource, $pos, $len); case self::MEMORY_CACHE: return $data = substr($this->buffer[$this->resource], $pos, $len); default: fseek($this->resource, $pos, SEEK_SET); return fread($this->resource, $len); } } /** * Low level function to fetch a string from the caching backend. * * @param int $pos Position to read from * @param int $additional Additional offset to apply * * @return string */ private function readString($pos, $additional = 0) { // Get the actual pointer to the string's head by extract from the raw row $newPosition = unpack('V', substr($this->rawPositionsRow, $pos, 4))[1] + $additional; // Read as much as the length (first "string" byte) indicates return $this->read($newPosition + 1, $this->readByte($newPosition + 1)); } /** * Low level function to fetch a float from the caching backend. * * @param int $pos Position to read from * * @return float */ private function readFloat($pos) { // Unpack a float's size worth of data return unpack('f', substr($this->rawPositionsRow, $pos, $this->floatSize))[1]; } /** * Low level function to fetch a byte from the caching backend. * * @param int $pos Position to read from * * @return string */ private function readByte($pos) { // Unpack a byte's worth of data return $this->wrap8(unpack('C', $this->read($pos - 1, 1))[1]); } /** * High level function to fetch the country name and code. * * @param bool|int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS * * @return array */ private function readCountryNameAndCode($pointer) { if ($pointer === false) { $countryCode = self::INVALID_IP_ADDRESS; $countryName = self::INVALID_IP_ADDRESS; } elseif ($this->columns[self::COUNTRY_CODE][$this->type] === 0) { $countryCode = self::FIELD_NOT_SUPPORTED; $countryName = self::FIELD_NOT_SUPPORTED; } else { // Read the country code and name (the name shares the country's pointer, // but it must be artificially displaced 3 bytes ahead: 2 for the country code, one // for the country name's length) $countryCode = $this->readString($this->columns[self::COUNTRY_CODE][$this->type]); $countryName = $this->readString($this->columns[self::COUNTRY_NAME][$this->type], 3); } return [$countryName, $countryCode]; } /** * High level function to fetch the region name. * * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS * * @return string */ private function readRegionName($pointer) { if ($pointer === false) { return self::INVALID_IP_ADDRESS; } if ($this->columns[self::REGION_NAME][$this->type] === 0) { return self::FIELD_NOT_SUPPORTED; } return $this->readString($this->columns[self::REGION_NAME][$this->type]); } /** * High level function to fetch the city name. * * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS * * @return string */ private function readCityName($pointer) { if ($pointer === false) { return self::INVALID_IP_ADDRESS; } if ($this->columns[self::CITY_NAME][$this->type] === 0) { return self::FIELD_NOT_SUPPORTED; } return $this->readString($this->columns[self::CITY_NAME][$this->type]); } /** * High level function to fetch the latitude and longitude. * * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS * * @return array */ private function readLatitudeAndLongitude($pointer) { if ($pointer === false) { $latitude = self::INVALID_IP_ADDRESS; $longitude = self::INVALID_IP_ADDRESS; } elseif ($this->columns[self::LATITUDE][$this->type] === 0) { $latitude = self::FIELD_NOT_SUPPORTED; $longitude = self::FIELD_NOT_SUPPORTED; } else { // Read latitude and longitude $latitude = round($this->readFloat($this->columns[self::LATITUDE][$this->type]), 6); $longitude = round($this->readFloat($this->columns[self::LONGITUDE][$this->type]), 6); } return [$latitude, $longitude]; } /** * High level function to fetch the ISP name. * * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS * * @return string */ private function readIsp($pointer) { if ($pointer === false) { return self::INVALID_IP_ADDRESS; } if ($this->columns[self::ISP][$this->type] === 0) { return self::FIELD_NOT_SUPPORTED; } return $this->readString($this->columns[self::ISP][$this->type]); } /** * High level function to fetch the domain name. * * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS * * @return string */ private function readDomainName($pointer) { if ($pointer === false) { return self::INVALID_IP_ADDRESS; } if ($this->columns[self::DOMAIN_NAME][$this->type] === 0) { return self::FIELD_NOT_SUPPORTED; } return $this->readString($this->columns[self::DOMAIN_NAME][$this->type]); } /** * High level function to fetch the zip code. * * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS * * @return string */ private function readZipCode($pointer) { if ($pointer === false) { return self::INVALID_IP_ADDRESS; } if ($this->columns[self::ZIP_CODE][$this->type] === 0) { return self::FIELD_NOT_SUPPORTED; } return $this->readString($this->columns[self::ZIP_CODE][$this->type]); } /** * High level function to fetch the time zone. * * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS * * @return string */ private function readTimeZone($pointer) { if ($pointer === false) { return self::INVALID_IP_ADDRESS; } if ($this->columns[self::TIME_ZONE][$this->type] === 0) { return self::FIELD_NOT_SUPPORTED; } return $this->readString($this->columns[self::TIME_ZONE][$this->type]); } /** * High level function to fetch the net speed. * * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS * * @return string */ private function readNetSpeed($pointer) { if ($pointer === false) { return self::INVALID_IP_ADDRESS; } if ($this->columns[self::NET_SPEED][$this->type] === 0) { return self::FIELD_NOT_SUPPORTED; } return $this->readString($this->columns[self::NET_SPEED][$this->type]); } /** * High level function to fetch the IDD and area codes. * * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS * * @return array */ private function readIddAndAreaCodes($pointer) { if ($pointer === false) { $iddCode = self::INVALID_IP_ADDRESS; $areaCode = self::INVALID_IP_ADDRESS; } elseif ($this->columns[self::IDD_CODE][$this->type] === 0) { $iddCode = self::FIELD_NOT_SUPPORTED; $areaCode = self::FIELD_NOT_SUPPORTED; } else { // Read IDD and area codes $iddCode = $this->readString($this->columns[self::IDD_CODE][$this->type]); $areaCode = $this->readString($this->columns[self::AREA_CODE][$this->type]); } return [$iddCode, $areaCode]; } /** * High level function to fetch the weather station name and code. * * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS * * @return array */ private function readWeatherStationNameAndCode($pointer) { if ($pointer === false) { $weatherStationName = self::INVALID_IP_ADDRESS; $weatherStationCode = self::INVALID_IP_ADDRESS; } elseif ($this->columns[self::WEATHER_STATION_NAME][$this->type] === 0) { $weatherStationName = self::FIELD_NOT_SUPPORTED; $weatherStationCode = self::FIELD_NOT_SUPPORTED; } else { // Read weather station name and code $weatherStationName = $this->readString($this->columns[self::WEATHER_STATION_NAME][$this->type]); $weatherStationCode = $this->readString($this->columns[self::WEATHER_STATION_CODE][$this->type]); } return [$weatherStationName, $weatherStationCode]; } /** * High level function to fetch the MCC, MNC, and mobile carrier name. * * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS * * @return array */ private function readMccMncAndMobileCarrierName($pointer) { if ($pointer === false) { $mcc = self::INVALID_IP_ADDRESS; $mnc = self::INVALID_IP_ADDRESS; $mobileCarrierName = self::INVALID_IP_ADDRESS; } elseif ($this->columns[self::MCC][$this->type] === 0) { $mcc = self::FIELD_NOT_SUPPORTED; $mnc = self::FIELD_NOT_SUPPORTED; $mobileCarrierName = self::FIELD_NOT_SUPPORTED; } else { // Read MCC, MNC, and mobile carrier name $mcc = $this->readString($this->columns[self::MCC][$this->type]); $mnc = $this->readString($this->columns[self::MNC][$this->type]); $mobileCarrierName = $this->readString($this->columns[self::MOBILE_CARRIER_NAME][$this->type]); } return [$mcc, $mnc, $mobileCarrierName]; } /** * High level function to fetch the elevation. * * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS * * @return string */ private function readElevation($pointer) { if ($pointer === false) { return self::INVALID_IP_ADDRESS; } if ($this->columns[self::ELEVATION][$this->type] === 0) { return self::FIELD_NOT_SUPPORTED; } return $this->readString($this->columns[self::ELEVATION][$this->type]); } /** * 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 readUsageType($pointer) { if ($pointer === false) { return self::INVALID_IP_ADDRESS; } if ($this->columns[self::USAGE_TYPE][$this->type] === 0) { return self::FIELD_NOT_SUPPORTED; } return $this->readString($this->columns[self::USAGE_TYPE][$this->type]); } /** * 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) { return self::INVALID_IP_ADDRESS; } if ($this->columns[self::ADDRESS_TYPE][$this->type] === 0) { return self::FIELD_NOT_SUPPORTED; } return $this->readString($this->columns[self::ADDRESS_TYPE][$this->type]); } /** * 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) { return self::INVALID_IP_ADDRESS; } if ($this->columns[self::CATEGORY][$this->type] === 0) { return self::FIELD_NOT_SUPPORTED; } return $this->readString($this->columns[self::CATEGORY][$this->type]); } /** * High level function to fetch the district. * * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS * * @return string */ private function readDistrict($pointer) { if ($pointer === false) { return self::INVALID_IP_ADDRESS; } if ($this->columns[self::DISTRICT][$this->type] === 0) { return self::FIELD_NOT_SUPPORTED; } return $this->readString($this->columns[self::DISTRICT][$this->type]); } /** * High level function to fetch the ASN. * * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS * * @return string */ private function readAsn($pointer) { if ($pointer === false) { return self::INVALID_IP_ADDRESS; } if ($this->columns[self::ASN][$this->type] === 0) { return self::FIELD_NOT_SUPPORTED; } return $this->readString($this->columns[self::ASN][$this->type]); } /** * High level function to fetch the AS. * * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS * * @return string */ private function readAs($pointer) { if ($pointer === false) { return self::INVALID_IP_ADDRESS; } if ($this->columns[self::AS][$this->type] === 0) { return self::FIELD_NOT_SUPPORTED; } return $this->readString($this->columns[self::AS][$this->type]); } /** * Get the boundaries for an IP address. * * @param int $ipVersion IP address version * @param int $position Lookup position * @param int $width The section width * * @return array */ private function getIpBoundary($ipVersion, $position, $width) { // Read 128 bits from the position $section = $this->read($position, 128); switch ($ipVersion) { case 4: return [unpack('V', substr($section, 0, 4))[1], unpack('V', substr($section, $width, 4))[1]]; case 6: return [$this->bcBin2Dec(substr($section, 0, 16)), $this->bcBin2Dec(substr($section, $width, 16))]; } return [false, false]; } /** * Perform a binary search on the given IP number and return a pointer to its record. * * @param int $version IP version to use for searching * @param int $ipNumber IP number to look for * @param mixed $cidr * * @return bool|int */ private function binSearch($version, $ipNumber, $cidr = false) { $base = $this->ipBase[$version]; $offset = $this->offset[$version]; $width = $this->columnWidth[$version]; $high = $this->ipCount[$version]; $low = 0; $indexBaseStart = $this->indexBaseAddr[$version]; if ($indexBaseStart > 1) { $indexPos = 0; switch ($version) { case 4: $number = (int) ($ipNumber / 65536); $indexPos = $indexBaseStart + ($number << 3); break; case 6: $number = (int) (bcdiv($ipNumber, bcpow('2', '112'))); $indexPos = $indexBaseStart + ($number << 3); break; } $section = $this->read($indexPos - 1, 8); $low = unpack('V', substr($section, 0, 4))[1]; $high = unpack('V', substr($section, 4, 4))[1]; } // Narrow down the search while ($low <= $high) { $mid = (int) ($low + (($high - $low) >> 1)); $position = $base + $width * $mid - 1; list($ipStart, $ipEnd) = $this->getIpBoundary($version, $position, $width); // Determine whether to return, repeat on the lower half, or repeat on the upper half switch ($this->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; } } // Record not found return false; } }