| <?php |
| |
| /* |
| * Copyright (C) 2005-2020 IP2Location.com |
| * All Rights Reserved |
| * |
| * This library is free software: you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation, either |
| * version 3 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; If not, see <http://www.gnu.org/licenses/>. |
| * |
| */ |
| |
| namespace IP2Location; |
| |
| /** |
| * IP2Location database class. |
| */ |
| class Database |
| { |
| /** |
| * Current module's version. |
| * |
| * @var string |
| */ |
| public const VERSION = '8.2.2'; |
| |
| ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| // Error field constants /////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * Unsupported field message. |
| * |
| * @var string |
| */ |
| public const FIELD_NOT_SUPPORTED = 'This parameter is unavailable in selected .BIN data file. Please upgrade.'; |
| |
| /** |
| * 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.'; |
| |
| ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| // Field selection constants /////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * 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; |
| |
| /** |
| * 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; |
| |
| ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| // Exception code constants //////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * 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_DBFILE_NOT_FOUND = 10005; |
| |
| /** |
| * Not enough memory to load database file. |
| * |
| * @var int |
| */ |
| public const EXCEPTION_NO_MEMORY = 10006; |
| |
| /** |
| * No candidate databse 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; |
| |
| ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| // Caching method constants //////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * Directly read from the databse 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; |
| |
| ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| // Shared memory constants ///////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * 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; |
| |
| ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| // Static data ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * Column offset mapping. |
| * |
| * Each entry contains an array mapping databse version (0--23) to offset within a record. |
| * A value of 0 means the column is not present in the given database version. |
| * |
| * @static |
| * |
| * @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], |
| ]; |
| |
| /** |
| * Column name mapping. |
| * |
| * @static |
| * |
| * @var array |
| */ |
| private static $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::IP_ADDRESS => 'ipAddress', |
| self::IP_VERSION => 'ipVersion', |
| self::IP_NUMBER => 'ipNumber', |
| ]; |
| |
| /** |
| * Database names, in order of preference for file lookup. |
| * |
| * @var array |
| */ |
| private static $databases = [ |
| // IPv4 databases |
| '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', |
| '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', |
| ]; |
| |
| /** |
| * Static memory buffer to use for MEMORY_CACHE mode, the keys will be BIN filenames and the values their contents. |
| * |
| * @static |
| * |
| * @var array |
| */ |
| private static $buffer = []; |
| |
| /** |
| * The machine's float size. |
| * |
| * @static |
| * |
| * @var int |
| */ |
| private static $floatSize = null; |
| |
| /** |
| * The configured memory limit. |
| * |
| * @static |
| * |
| * @var int |
| */ |
| private static $memoryLimit = null; |
| |
| ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| // Caching backend controls //////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * 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 metadata /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * 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; |
| |
| /** |
| * The raw row of columns's 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 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * Default fields to return during lookup. |
| * |
| * @var array|int |
| */ |
| private $defaultFields = self::ALL; |
| |
| ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| // Administrative public interface ///////////////////////////////////////////////////////////////////////////////////////////////////// |
| ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * Constructor. |
| * |
| * @param string $file Filename of the BIN database to load |
| * @param int $mode Caching mode (one of FILE_IO, MEMORY_CACHE, or SHARED_MEMORY) |
| * @param mixed $defaultFields |
| * |
| * @throws \Exception |
| */ |
| public function __construct($file = null, $mode = self::FILE_IO, $defaultFields = self::ALL) |
| { |
| // find the referred file and its size |
| $rfile = self::findFile($file); |
| $size = filesize($rfile); |
| |
| // initialize caching backend |
| switch ($mode) { |
| case self::SHARED_MEMORY: |
| // verify the 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); |
| } |
| |
| $limit = self::getMemoryLimit(); |
| if ($limit !== false && $size > $limit) { |
| throw new \Exception(__CLASS__ . ": Insufficient memory to load file '{$rfile}'.", self::EXCEPTION_NO_MEMORY); |
| } |
| |
| $this->mode = self::SHARED_MEMORY; |
| $shmKey = self::getShmKey($rfile); |
| |
| // try to open the shared memory segment |
| $this->resource = @shmop_open($shmKey, 'a', 0, 0); |
| if ($this->resource === false) { |
| // the segment did not exist, create it and load the database into it |
| $fp = fopen($rfile, 'r'); |
| if ($fp === false) { |
| throw new \Exception(__CLASS__ . ": Unable to open file '{$rfile}'.", self::EXCEPTION_FILE_OPEN_FAILED); |
| } |
| |
| // try to open the memory segment for exclusive access |
| $shmId = @shmop_open($shmKey, 'n', self::SHM_PERMS, $size); |
| 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 < $size) { |
| $buf = fread($fp, self::SHM_CHUNK_SIZE); |
| shmop_write($shmId, $buf, $pointer); |
| $pointer += self::SHM_CHUNK_SIZE; |
| } |
| shmop_close($shmId); |
| fclose($fp); |
| |
| // now open the 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::FILE_IO: |
| $this->mode = self::FILE_IO; |
| $this->resource = @fopen($rfile, 'r'); |
| if ($this->resource === false) { |
| throw new \Exception(__CLASS__ . ": Unable to open file '{$rfile}'.", self::EXCEPTION_FILE_OPEN_FAILED); |
| } |
| break; |
| |
| case self::MEMORY_CACHE: |
| $this->mode = self::MEMORY_CACHE; |
| $this->resource = $rfile; |
| if (!\array_key_exists($rfile, self::$buffer)) { |
| $limit = self::getMemoryLimit(); |
| if ($limit !== false && $size > $limit) { |
| throw new \Exception(__CLASS__ . ": Insufficient memory to load file '{$rfile}'.", self::EXCEPTION_NO_MEMORY); |
| } |
| |
| self::$buffer[$rfile] = @file_get_contents($rfile); |
| if (self::$buffer[$rfile] === false) { |
| throw new \Exception(__CLASS__ . ": Unable to open file '{$rfile}'.", self::EXCEPTION_FILE_OPEN_FAILED); |
| } |
| } |
| break; |
| |
| default: |
| } |
| |
| // 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 |
| // is written, this is the sanest thing to do anyway |
| // |
| if (self::$floatSize === null) { |
| self::$floatSize = \strlen(pack('f', M_PI)); |
| } |
| |
| // set default fields to retrieve |
| $this->defaultFields = $defaultFields; |
| |
| // extract database metadata |
| $this->type = $this->readByte(1) - 1; |
| $this->columnWidth[4] = $this->readByte(2) * 4; |
| $this->columnWidth[6] = $this->columnWidth[4] + 12; |
| $this->offset[4] = -4; |
| $this->offset[6] = 8; |
| |
| $this->year = 2000 + $this->readByte(3); |
| $this->month = $this->readByte(4); |
| $this->day = $this->readByte(5); |
| $this->date = date('Y-m-d', strtotime("{$this->year}-{$this->month}-{$this->day}")); |
| |
| $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 |
| } |
| |
| /** |
| * 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) { |
| shmop_close($this->resource); |
| $this->resource = false; |
| } |
| break; |
| } |
| } |
| |
| /** |
| * Tear down a shared memory segment created for the given file. |
| * |
| * @static |
| * |
| * @param string $file Filename of the BIN database whise segment must be deleted |
| * |
| * @throws \Exception |
| */ |
| public static function shmTeardown($file) |
| { |
| // verify the 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 |
| $rfile = realpath($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); |
| } |
| |
| $shmKey = self::getShmKey($rfile); |
| |
| // Try to 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 and close the descriptor |
| shmop_delete($shmId); |
| shmop_close($shmId); |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| // Public interface //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * 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--24). |
| * |
| * @return int |
| */ |
| public function getType() |
| { |
| return $this->type + 1; |
| } |
| |
| /** |
| * Return this database's available fields. |
| * |
| * @param bool $asNames Whether to return the mapped names intead of numbered constants |
| * |
| * @return array |
| */ |
| public function getFields($asNames = false) |
| { |
| $result = array_keys(array_filter(self::$columns, function ($field) { |
| return $field[$this->type] !== 0; |
| })); |
| if ($asNames) { |
| $return = []; |
| foreach ($result as $field) { |
| $return[] = self::$names[$field]; |
| } |
| |
| return $return; |
| } |
| |
| return $result; |
| } |
| |
| /** |
| * Return the version of module. |
| */ |
| public function getModuleVersion() |
| { |
| return self::VERSION; |
| } |
| |
| /** |
| * Return the version of module. |
| */ |
| 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 whith 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) |
| { |
| // 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; |
| } |
| |
| // apply defaults if needed |
| if ($fields === null) { |
| $fields = $this->defaultFields; |
| } |
| |
| // Get the entire row based on the pointer value |
| // The length of the row differs based on the IP version |
| 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::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::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 are empty to begin with |
| $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::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, static::$names)) { |
| $return[static::$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) = self::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 -= 1; |
| |
| while ($ipTo >= $ipFrom) { |
| $maxSize = self::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; |
| } |
| |
| /** |
| * For a given IP address, returns the cidr of his sub-network. (This function will be deprecated in next version). |
| * |
| * For example, calling get_cidr('91.200.12.233') returns '91.200.0.0/13'. |
| * Useful to setup "Deny From 91.200.0.0/13" in .htaccess file for Apache2 |
| * server against spam. |
| * |
| * @param mixed $ip |
| * */ |
| public function get_cidr($ip) |
| { |
| // 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) |
| |
| $resp = $this->binSearch($ipVersion, $ipNumber, true); |
| if (!empty($resp)) { |
| list($ipFrom, $ipTo) = $resp; |
| $i = 32; |
| $mask = 1; |
| while (($ipTo & $mask) == 0) { |
| $mask *= 2; |
| --$i; |
| } |
| $ip = long2ip($ipFrom); |
| |
| return "$ip/$i"; |
| } |
| |
| return false; |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| // Static tools //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * Rerturn maxinum size of a net block. |
| * |
| * @param int $base the base number |
| * @param int $bit the bit number |
| * @static |
| * |
| * @return bool|int |
| */ |
| private static 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). |
| * |
| * @static |
| * |
| * @return bool|int |
| */ |
| private static function getMemoryLimit() |
| { |
| // Get values if no cache |
| if (self::$memoryLimit === null) { |
| $limit = ini_get('memory_limit'); |
| |
| // Feal with defaults |
| if ((string) $limit === '') { |
| $limit = '128M'; |
| } |
| |
| $value = (int) $limit; |
| |
| // Deal with "no-limit" |
| if ($value < 0) { |
| $value = false; |
| } else { |
| // Deal with shorthand bytes |
| switch (strtoupper(substr($limit, -1))) { |
| case 'G': $value *= 1024; |
| // no break |
| case 'M': $value *= 1024; |
| // no break |
| case 'K': $value *= 1024; |
| } |
| } |
| self::$memoryLimit = $value; |
| } |
| |
| return self::$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 static function findFile($file = null) |
| { |
| if ($file !== null) { |
| // Get actual file path |
| $rfile = realpath($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); |
| } |
| |
| return $rfile; |
| } |
| // 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 (self::$databases as $database) { |
| $rfile = realpath("{$current}/{$database}.BIN"); |
| if ($rfile !== false) { |
| return $rfile; |
| } |
| } |
| // 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. |
| * |
| * @static |
| * |
| * @param int $x Number to wrap |
| * |
| * @return int |
| */ |
| private static function wrap8($x) |
| { |
| return $x + ($x < 0 ? 256 : 0); |
| } |
| |
| /** |
| * Make the given number positive by wrapping it to 32 bit values. |
| * |
| * @static |
| * |
| * @param int $x Number to wrap |
| * |
| * @return int |
| */ |
| private static function wrap32($x) |
| { |
| return $x + ($x < 0 ? 4294967296 : 0); |
| } |
| |
| /** |
| * Generate a unique and repeatable shared memory key for each instance to use. |
| * |
| * @static |
| * |
| * @param string $filename Filename of the BIN file |
| * |
| * @return int |
| */ |
| private static function getShmKey($filename) |
| { |
| // This will create a shared memory key that deterministically depends only on |
| // the current file's path and the BIN file's path |
| return (int) sprintf('%u', self::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. |
| * |
| * @static |
| * |
| * @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 static 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. |
| * |
| * This method will return an array, whose components will be: |
| * - first: 4 if the given IP address is an IPv4 one, 6 if it's an IPv6 one, |
| * or false if it's neither. |
| * - second: the IP address' number if its version is 4, the number string if |
| * its version is 6, false otherwise. |
| * |
| * @static |
| * |
| * @param string $ip IP address to extract the version and number for |
| * |
| * @return array |
| */ |
| private static function ipVersionAndNumber($ip) |
| { |
| if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { |
| return [4, sprintf('%u', ip2long($ip))]; |
| } elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { |
| $result = 0; |
| |
| foreach (str_split(bin2hex(inet_pton($ip)), 8) as $word) { |
| $result = bcadd(bcmul($result, '4294967296', 0), self::wrap32(hexdec($word)), 0); |
| } |
| |
| return [6, $result]; |
| } |
| // Invalid IP address, return falses |
| return [false, false]; |
| } |
| |
| /** |
| * Return the decimal string representing the binary data given. |
| * |
| * @static |
| * |
| * @param string $data Binary data to parse |
| * |
| * @return string |
| */ |
| private static function bcBin2Dec($data) |
| { |
| $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; |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| // Caching backend abstraction ///////////////////////////////////////////////////////////////////////////////////////////////////////// |
| ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * 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(self::$buffer[$this->resource], $pos, $len); |
| |
| default: |
| fseek($this->resource, $pos, SEEK_SET); |
| |
| return fread($this->resource, $len); |
| } |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| // Low-level read functions //////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * 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 |
| $spos = 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)); |
| } |
| |
| /** |
| * 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, self::$floatSize))[1]; |
| } |
| |
| /** |
| * Low level function to fetch a quadword (128 bits) from the caching backend. |
| * |
| * @param int $pos Position to read from |
| * |
| * @return string |
| */ |
| private function readQuad($pos) |
| { |
| // Use BCMath ints to get a quad's (128-bit) value |
| return self::bcBin2Dec($this->read($pos - 1, 16)); |
| } |
| |
| /** |
| * Low level function to fetch a word (32 bits) from the caching backend. |
| * |
| * @param int $pos Position to read from |
| * |
| * @return int |
| */ |
| private function readWord($pos) |
| { |
| // Unpack a long's worth of data |
| return self::wrap32(unpack('V', $this->read($pos - 1, 4))[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 self::wrap8(unpack('C', $this->read($pos - 1, 1))[1]); |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| // High-level read functions /////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * 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) { |
| // Deal with invalid IPs |
| $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 |
| $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(self::$columns[self::COUNTRY_CODE][$this->type]); |
| $countryName = $this->readString(self::$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) { |
| // 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 |
| $regionName = self::FIELD_NOT_SUPPORTED; |
| } else { |
| // Read the region name |
| $regionName = $this->readString(self::$columns[self::REGION_NAME][$this->type]); |
| } |
| |
| return $regionName; |
| } |
| |
| /** |
| * 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) { |
| // 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 |
| $cityName = self::FIELD_NOT_SUPPORTED; |
| } else { |
| // Read the city name |
| $cityName = $this->readString(self::$columns[self::CITY_NAME][$this->type]); |
| } |
| |
| return $cityName; |
| } |
| |
| /** |
| * 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) { |
| // Deal with invalid IPs |
| $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 |
| $latitude = self::FIELD_NOT_SUPPORTED; |
| $longitude = self::FIELD_NOT_SUPPORTED; |
| } else { |
| // Read latitude and longitude |
| $latitude = round($this->readFloat(self::$columns[self::LATITUDE][$this->type]), 6); |
| $longitude = round($this->readFloat(self::$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) { |
| // 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 |
| $isp = self::FIELD_NOT_SUPPORTED; |
| } else { |
| // Read isp name |
| $isp = $this->readString(self::$columns[self::ISP][$this->type]); |
| } |
| |
| return $isp; |
| } |
| |
| /** |
| * 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) { |
| // 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 |
| $domainName = self::FIELD_NOT_SUPPORTED; |
| } else { |
| // Read the domain name |
| $domainName = $this->readString(self::$columns[self::DOMAIN_NAME][$this->type]); |
| } |
| |
| return $domainName; |
| } |
| |
| /** |
| * 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) { |
| // 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 |
| $zipCode = self::FIELD_NOT_SUPPORTED; |
| } else { |
| // Read the zip code |
| $zipCode = $this->readString(self::$columns[self::ZIP_CODE][$this->type]); |
| } |
| |
| return $zipCode; |
| } |
| |
| /** |
| * 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) { |
| // 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 |
| $timeZone = self::FIELD_NOT_SUPPORTED; |
| } else { |
| // Read the time zone |
| $timeZone = $this->readString(self::$columns[self::TIME_ZONE][$this->type]); |
| } |
| |
| return $timeZone; |
| } |
| |
| /** |
| * 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) { |
| // 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 |
| $netSpeed = self::FIELD_NOT_SUPPORTED; |
| } else { |
| // Read the net speed |
| $netSpeed = $this->readString(self::$columns[self::NET_SPEED][$this->type]); |
| } |
| |
| return $netSpeed; |
| } |
| |
| /** |
| * 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) { |
| // Deal with invalid IPs |
| $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 |
| $iddCode = self::FIELD_NOT_SUPPORTED; |
| $areaCode = self::FIELD_NOT_SUPPORTED; |
| } else { |
| // Read IDD and area codes |
| $iddCode = $this->readString(self::$columns[self::IDD_CODE][$this->type]); |
| $areaCode = $this->readString(self::$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) { |
| // Deal with invalid IPs |
| $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 |
| $weatherStationName = self::FIELD_NOT_SUPPORTED; |
| $weatherStationCode = self::FIELD_NOT_SUPPORTED; |
| } else { |
| // Read weather station name and code |
| $weatherStationName = $this->readString(self::$columns[self::WEATHER_STATION_NAME][$this->type]); |
| $weatherStationCode = $this->readString(self::$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) { |
| // Deal with invalid IPs |
| $mcc = self::INVALID_IP_ADDRESS; |
| $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 |
| $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(self::$columns[self::MCC][$this->type]); |
| $mnc = $this->readString(self::$columns[self::MNC][$this->type]); |
| $mobileCarrierName = $this->readString(self::$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) { |
| // 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 |
| $elevation = self::FIELD_NOT_SUPPORTED; |
| } else { |
| // Read the elevation |
| $elevation = $this->readString(self::$columns[self::ELEVATION][$this->type]); |
| } |
| |
| return $elevation; |
| } |
| |
| /** |
| * 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) { |
| // 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 |
| $usageType = self::FIELD_NOT_SUPPORTED; |
| } else { |
| $usageType = $this->readString(self::$columns[self::USAGE_TYPE][$this->type]); |
| } |
| |
| return $usageType; |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| // Binary search and support functions ///////////////////////////////////////////////////////////////////////////////////////////////// |
| ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * High level fucntion to read an IP address of the given version. |
| * |
| * @param int $version IP version to read (either 4 or 6, returns false on anything else) |
| * @param int $pos Position to read from |
| * |
| * @return bool|int|string |
| */ |
| private function readIp($version, $pos) |
| { |
| if ($version === 4) { |
| // Read a standard PHP int |
| return self::wrap32($this->readWord($pos)); |
| } elseif ($version === 6) { |
| // Read as BCMath int (quad) |
| return $this->readQuad($pos); |
| } |
| // unrecognized |
| return 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) |
| { |
| if ($version === false) { |
| // unrecognized version |
| return false; |
| } |
| |
| // initialize fields |
| $base = $this->ipBase[$version]; |
| $offset = $this->offset[$version]; |
| $width = $this->columnWidth[$version]; |
| $high = $this->ipCount[$version]; |
| $low = 0; |
| |
| //hjlim |
| $indexBaseStart = $this->indexBaseAddr[$version]; |
| if ($indexBaseStart > 0) { |
| $indexPos = 0; |
| switch ($version) { |
| case 4: |
| $ipNum1_2 = (int) ($ipNumber / 65536); |
| $indexPos = $indexBaseStart + ($ipNum1_2 << 3); |
| |
| break; |
| |
| case 6: |
| $ipNum1 = (int) (bcdiv($ipNumber, bcpow('2', '112'))); |
| $indexPos = $indexBaseStart + ($ipNum1 << 3); |
| |
| break; |
| |
| default: |
| return false; |
| } |
| |
| $low = $this->readWord($indexPos); |
| $high = $this->readWord($indexPos + 4); |
| } |
| |
| // as long as we can narrow down the search... |
| while ($low <= $high) { |
| $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)); |
| |
| // 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; |
| } |
| } |
| |
| // nothing found |
| return false; |
| } |
| } |
| |
| /** |
| * IP2Location web service class. |
| */ |
| class WebService |
| { |
| /** |
| * No cURL extension found. |
| * |
| * @var int |
| */ |
| public const EXCEPTION_NO_CURL = 10001; |
| |
| /** |
| * Invalid API key format. |
| * |
| * @var int |
| */ |
| public const EXCEPTION_INVALID_API_KEY = 10002; |
| |
| /** |
| * Web service error. |
| * |
| * @var int |
| */ |
| public const EXCEPTION_WEB_SERVICE_ERROR = 10003; |
| |
| /** |
| * Constructor. |
| * |
| * @param string $apiKey API key of your IP2Location web service |
| * @param string $package Supported IP2Location package from WS1 to WS24 |
| * @param bool $useSsl Enable or disabled HTTPS connection. HTTP is faster but less secure. |
| * |
| * @throws \Exception |
| */ |
| public function __construct($apiKey, $package = 'WS1', $useSsl = false) |
| { |
| if (!\extension_loaded('curl')) { |
| throw new \Exception(__CLASS__ . ": Please make sure your PHP setup has the 'curl' extension enabled.", self::EXCEPTION_NO_CURL); |
| } |
| |
| if (!preg_match('/^[0-9A-Z]{10}$/', $apiKey) && $apiKey != 'demo') { |
| throw new \Exception(__CLASS__ . ': Please provide a valid IP2Location web service API key.', self::EXCEPTION_INVALID_API_KEY); |
| } |
| |
| if (!preg_match('/^WS[0-9]+$/', $package)) { |
| $package = 'WS1'; |
| } |
| |
| $this->apiKey = $apiKey; |
| $this->package = $package; |
| $this->useSsl = $useSsl; |
| } |
| |
| /** |
| * This function will look the given IP address up in IP2Location web service. |
| * |
| * @param string $ip IP address to look up |
| * @param array $addOns Extra fields to return. Please refer to https://www.ip2location.com/web-service/ip2location |
| * @param string $language the translation for continent, country, region and city name for the addon package |
| * |
| * @throws \Exception |
| * |
| * @return array|false |
| */ |
| public function lookup($ip, $addOns = [], $language = 'en') |
| { |
| $response = $this->httpRequest('http://api.ip2location.com/v2/?' . http_build_query([ |
| 'key' => $this->apiKey, |
| 'ip' => $ip, |
| 'package' => $this->package, |
| 'addon' => implode(',', $addOns), |
| 'lang' => $language, |
| ])); |
| |
| if (($data = json_decode($response, true)) === null) { |
| return false; |
| } |
| |
| if (isset($data['response'])) { |
| throw new \Exception(__CLASS__ . ': ' . $data['response'], self::EXCEPTION_WEB_SERVICE_ERROR); |
| } |
| |
| return $data; |
| } |
| |
| /** |
| * Get the remaing credit in your IP2Location web service account. |
| * |
| * @return int |
| */ |
| public function getCredit() |
| { |
| $response = $this->httpRequest('http://api.ip2location.com/v2/?' . http_build_query([ |
| 'key' => $this->apiKey, |
| 'check' => true, |
| ])); |
| |
| if (($data = json_decode($response, true)) === null) { |
| return 0; |
| } |
| |
| if (!isset($data['response'])) { |
| return 0; |
| } |
| |
| return $data['response']; |
| } |
| |
| /** |
| * Open a remote web address. |
| * |
| * @param string $url Website URL |
| * |
| * @return bool|string |
| */ |
| private function httpRequest($url) |
| { |
| $ch = curl_init(); |
| curl_setopt($ch, CURLOPT_URL, $url); |
| curl_setopt($ch, CURLOPT_FAILONERROR, 1); |
| curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); |
| curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); |
| curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); |
| curl_setopt($ch, CURLOPT_TIMEOUT, 30); |
| |
| $response = curl_exec($ch); |
| |
| if (!curl_errno($ch)) { |
| curl_close($ch); |
| |
| return $response; |
| } |
| |
| curl_close($ch); |
| |
| return false; |
| } |
| } |