| @@ -0,0 +1,5 @@ | |||
| { | |||
| "require": { | |||
| "geoip2/geoip2": "~2.10" | |||
| } | |||
| } | |||
| @@ -0,0 +1,233 @@ | |||
| { | |||
| "_readme": [ | |||
| "This file locks the dependencies of your project to a known state", | |||
| "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", | |||
| "This file is @generated automatically" | |||
| ], | |||
| "content-hash": "b199b37bd9cfaa4153d1468d96aa7903", | |||
| "packages": [ | |||
| { | |||
| "name": "composer/ca-bundle", | |||
| "version": "1.2.6", | |||
| "source": { | |||
| "type": "git", | |||
| "url": "https://github.com/composer/ca-bundle.git", | |||
| "reference": "47fe531de31fca4a1b997f87308e7d7804348f7e" | |||
| }, | |||
| "dist": { | |||
| "type": "zip", | |||
| "url": "https://api.github.com/repos/composer/ca-bundle/zipball/47fe531de31fca4a1b997f87308e7d7804348f7e", | |||
| "reference": "47fe531de31fca4a1b997f87308e7d7804348f7e", | |||
| "shasum": "" | |||
| }, | |||
| "require": { | |||
| "ext-openssl": "*", | |||
| "ext-pcre": "*", | |||
| "php": "^5.3.2 || ^7.0 || ^8.0" | |||
| }, | |||
| "require-dev": { | |||
| "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8", | |||
| "psr/log": "^1.0", | |||
| "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0" | |||
| }, | |||
| "type": "library", | |||
| "extra": { | |||
| "branch-alias": { | |||
| "dev-master": "1.x-dev" | |||
| } | |||
| }, | |||
| "autoload": { | |||
| "psr-4": { | |||
| "Composer\\CaBundle\\": "src" | |||
| } | |||
| }, | |||
| "notification-url": "https://packagist.org/downloads/", | |||
| "license": [ | |||
| "MIT" | |||
| ], | |||
| "authors": [ | |||
| { | |||
| "name": "Jordi Boggiano", | |||
| "email": "j.boggiano@seld.be", | |||
| "homepage": "http://seld.be" | |||
| } | |||
| ], | |||
| "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", | |||
| "keywords": [ | |||
| "cabundle", | |||
| "cacert", | |||
| "certificate", | |||
| "ssl", | |||
| "tls" | |||
| ], | |||
| "time": "2020-01-13T10:02:55+00:00" | |||
| }, | |||
| { | |||
| "name": "geoip2/geoip2", | |||
| "version": "v2.10.0", | |||
| "source": { | |||
| "type": "git", | |||
| "url": "https://github.com/maxmind/GeoIP2-php.git", | |||
| "reference": "419557cd21d9fe039721a83490701a58c8ce784a" | |||
| }, | |||
| "dist": { | |||
| "type": "zip", | |||
| "url": "https://api.github.com/repos/maxmind/GeoIP2-php/zipball/419557cd21d9fe039721a83490701a58c8ce784a", | |||
| "reference": "419557cd21d9fe039721a83490701a58c8ce784a", | |||
| "shasum": "" | |||
| }, | |||
| "require": { | |||
| "ext-json": "*", | |||
| "maxmind-db/reader": "~1.5", | |||
| "maxmind/web-service-common": "~0.6", | |||
| "php": ">=5.6" | |||
| }, | |||
| "require-dev": { | |||
| "friendsofphp/php-cs-fixer": "2.*", | |||
| "phpunit/phpunit": "5.*", | |||
| "squizlabs/php_codesniffer": "3.*" | |||
| }, | |||
| "type": "library", | |||
| "autoload": { | |||
| "psr-4": { | |||
| "GeoIp2\\": "src" | |||
| } | |||
| }, | |||
| "notification-url": "https://packagist.org/downloads/", | |||
| "license": [ | |||
| "Apache-2.0" | |||
| ], | |||
| "authors": [ | |||
| { | |||
| "name": "Gregory J. Oschwald", | |||
| "email": "goschwald@maxmind.com", | |||
| "homepage": "https://www.maxmind.com/" | |||
| } | |||
| ], | |||
| "description": "MaxMind GeoIP2 PHP API", | |||
| "homepage": "https://github.com/maxmind/GeoIP2-php", | |||
| "keywords": [ | |||
| "IP", | |||
| "geoip", | |||
| "geoip2", | |||
| "geolocation", | |||
| "maxmind" | |||
| ], | |||
| "time": "2019-12-12T18:48:39+00:00" | |||
| }, | |||
| { | |||
| "name": "maxmind-db/reader", | |||
| "version": "v1.6.0", | |||
| "source": { | |||
| "type": "git", | |||
| "url": "https://github.com/maxmind/MaxMind-DB-Reader-php.git", | |||
| "reference": "febd4920bf17c1da84cef58e56a8227dfb37fbe4" | |||
| }, | |||
| "dist": { | |||
| "type": "zip", | |||
| "url": "https://api.github.com/repos/maxmind/MaxMind-DB-Reader-php/zipball/febd4920bf17c1da84cef58e56a8227dfb37fbe4", | |||
| "reference": "febd4920bf17c1da84cef58e56a8227dfb37fbe4", | |||
| "shasum": "" | |||
| }, | |||
| "require": { | |||
| "php": ">=5.6" | |||
| }, | |||
| "conflict": { | |||
| "ext-maxminddb": "<1.6.0,>=2.0.0" | |||
| }, | |||
| "require-dev": { | |||
| "friendsofphp/php-cs-fixer": "2.*", | |||
| "php-coveralls/php-coveralls": "^2.1", | |||
| "phpunit/phpcov": "^3.0", | |||
| "phpunit/phpunit": "5.*", | |||
| "squizlabs/php_codesniffer": "3.*" | |||
| }, | |||
| "suggest": { | |||
| "ext-bcmath": "bcmath or gmp is required for decoding larger integers with the pure PHP decoder", | |||
| "ext-gmp": "bcmath or gmp is required for decoding larger integers with the pure PHP decoder", | |||
| "ext-maxminddb": "A C-based database decoder that provides significantly faster lookups" | |||
| }, | |||
| "type": "library", | |||
| "autoload": { | |||
| "psr-4": { | |||
| "MaxMind\\Db\\": "src/MaxMind/Db" | |||
| } | |||
| }, | |||
| "notification-url": "https://packagist.org/downloads/", | |||
| "license": [ | |||
| "Apache-2.0" | |||
| ], | |||
| "authors": [ | |||
| { | |||
| "name": "Gregory J. Oschwald", | |||
| "email": "goschwald@maxmind.com", | |||
| "homepage": "https://www.maxmind.com/" | |||
| } | |||
| ], | |||
| "description": "MaxMind DB Reader API", | |||
| "homepage": "https://github.com/maxmind/MaxMind-DB-Reader-php", | |||
| "keywords": [ | |||
| "database", | |||
| "geoip", | |||
| "geoip2", | |||
| "geolocation", | |||
| "maxmind" | |||
| ], | |||
| "time": "2019-12-19T22:59:03+00:00" | |||
| }, | |||
| { | |||
| "name": "maxmind/web-service-common", | |||
| "version": "v0.6.0", | |||
| "source": { | |||
| "type": "git", | |||
| "url": "https://github.com/maxmind/web-service-common-php.git", | |||
| "reference": "40c928bb0194c45088b369a17f9baef9c3fc7460" | |||
| }, | |||
| "dist": { | |||
| "type": "zip", | |||
| "url": "https://api.github.com/repos/maxmind/web-service-common-php/zipball/40c928bb0194c45088b369a17f9baef9c3fc7460", | |||
| "reference": "40c928bb0194c45088b369a17f9baef9c3fc7460", | |||
| "shasum": "" | |||
| }, | |||
| "require": { | |||
| "composer/ca-bundle": "^1.0.3", | |||
| "ext-curl": "*", | |||
| "ext-json": "*", | |||
| "php": ">=5.6" | |||
| }, | |||
| "require-dev": { | |||
| "friendsofphp/php-cs-fixer": "2.*", | |||
| "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0", | |||
| "squizlabs/php_codesniffer": "3.*" | |||
| }, | |||
| "type": "library", | |||
| "autoload": { | |||
| "psr-4": { | |||
| "MaxMind\\Exception\\": "src/Exception", | |||
| "MaxMind\\WebService\\": "src/WebService" | |||
| } | |||
| }, | |||
| "notification-url": "https://packagist.org/downloads/", | |||
| "license": [ | |||
| "Apache-2.0" | |||
| ], | |||
| "authors": [ | |||
| { | |||
| "name": "Gregory Oschwald", | |||
| "email": "goschwald@maxmind.com" | |||
| } | |||
| ], | |||
| "description": "Internal MaxMind Web Service API", | |||
| "homepage": "https://github.com/maxmind/web-service-common-php", | |||
| "time": "2019-12-12T15:56:05+00:00" | |||
| } | |||
| ], | |||
| "packages-dev": [], | |||
| "aliases": [], | |||
| "minimum-stability": "stable", | |||
| "stability-flags": [], | |||
| "prefer-stable": false, | |||
| "prefer-lowest": false, | |||
| "platform": [], | |||
| "platform-dev": [] | |||
| } | |||
| @@ -0,0 +1,62 @@ | |||
| <?php | |||
| require_once 'vendor/autoload.php'; | |||
| use GeoIp2\Database\Reader; | |||
| // This creates the Reader object, which should be reused across | |||
| // lookups. | |||
| $reader = new Reader(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'GeoLite2-City.mmdb'); | |||
| // Replace "city" with the appropriate method for your database, e.g., | |||
| // "country". | |||
| //$record = $reader->city('128.101.101.101'); | |||
| // $record = $reader->city('115.64.88.12'); | |||
| // $record = $reader->city('175.22.14.240'); | |||
| // print($record->country->isoCode . "\n"); // 'US' | |||
| // print($record->country->name . "\n"); // 'United States' | |||
| // print($record->country->names['zh-CN'] . "\n"); // '美国' | |||
| // print($record->mostSpecificSubdivision->name . "\n"); // 'Minnesota' | |||
| // print($record->mostSpecificSubdivision->isoCode . "\n"); // 'MN' | |||
| // print($record->city->name . "\n"); // 'Minneapolis' | |||
| // print($record->postal->code . "\n"); // '55455' | |||
| // print($record->location->latitude . "\n"); // 44.9733 | |||
| // print($record->location->longitude . "\n"); // -93.2323 | |||
| //print($record->traits->network . "\n"); // '128.101.101.101/32' | |||
| function get_client_ip() { | |||
| $ipaddress = ''; | |||
| if (getenv('HTTP_CLIENT_IP')) | |||
| $ipaddress = getenv('HTTP_CLIENT_IP'); | |||
| else if(getenv('HTTP_X_FORWARDED_FOR')) | |||
| $ipaddress = getenv('HTTP_X_FORWARDED_FOR'); | |||
| else if(getenv('HTTP_X_FORWARDED')) | |||
| $ipaddress = getenv('HTTP_X_FORWARDED'); | |||
| else if(getenv('HTTP_FORWARDED_FOR')) | |||
| $ipaddress = getenv('HTTP_FORWARDED_FOR'); | |||
| else if(getenv('HTTP_FORWARDED')) | |||
| $ipaddress = getenv('HTTP_FORWARDED'); | |||
| else if(getenv('REMOTE_ADDR')) | |||
| $ipaddress = getenv('REMOTE_ADDR'); | |||
| else | |||
| $ipaddress = 'UNKNOWN'; | |||
| return $ipaddress; | |||
| } | |||
| $ip = get_client_ip(); | |||
| if ($ip == 'UNKNOWN') { | |||
| echo " webtradepay "; | |||
| header("Location: https://webtradepay.com.au"); | |||
| }else{ //determin $ip address | |||
| $record = $reader->city($ip); | |||
| if ( trim($record->country->isoCode) == 'AU' ){ | |||
| echo "https://www.supertraderfx.com.au"; | |||
| }else{ | |||
| echo "https://www.supertraderfx.com"; | |||
| } | |||
| } | |||
| @@ -0,0 +1,7 @@ | |||
| <?php | |||
| // autoload.php @generated by Composer | |||
| require_once __DIR__ . '/composer/autoload_real.php'; | |||
| return ComposerAutoloaderIniteabaa05010c1f1c15dd62473ac209085::getLoader(); | |||
| @@ -0,0 +1,445 @@ | |||
| <?php | |||
| /* | |||
| * This file is part of Composer. | |||
| * | |||
| * (c) Nils Adermann <naderman@naderman.de> | |||
| * Jordi Boggiano <j.boggiano@seld.be> | |||
| * | |||
| * For the full copyright and license information, please view the LICENSE | |||
| * file that was distributed with this source code. | |||
| */ | |||
| namespace Composer\Autoload; | |||
| /** | |||
| * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. | |||
| * | |||
| * $loader = new \Composer\Autoload\ClassLoader(); | |||
| * | |||
| * // register classes with namespaces | |||
| * $loader->add('Symfony\Component', __DIR__.'/component'); | |||
| * $loader->add('Symfony', __DIR__.'/framework'); | |||
| * | |||
| * // activate the autoloader | |||
| * $loader->register(); | |||
| * | |||
| * // to enable searching the include path (eg. for PEAR packages) | |||
| * $loader->setUseIncludePath(true); | |||
| * | |||
| * In this example, if you try to use a class in the Symfony\Component | |||
| * namespace or one of its children (Symfony\Component\Console for instance), | |||
| * the autoloader will first look for the class under the component/ | |||
| * directory, and it will then fallback to the framework/ directory if not | |||
| * found before giving up. | |||
| * | |||
| * This class is loosely based on the Symfony UniversalClassLoader. | |||
| * | |||
| * @author Fabien Potencier <fabien@symfony.com> | |||
| * @author Jordi Boggiano <j.boggiano@seld.be> | |||
| * @see http://www.php-fig.org/psr/psr-0/ | |||
| * @see http://www.php-fig.org/psr/psr-4/ | |||
| */ | |||
| class ClassLoader | |||
| { | |||
| // PSR-4 | |||
| private $prefixLengthsPsr4 = array(); | |||
| private $prefixDirsPsr4 = array(); | |||
| private $fallbackDirsPsr4 = array(); | |||
| // PSR-0 | |||
| private $prefixesPsr0 = array(); | |||
| private $fallbackDirsPsr0 = array(); | |||
| private $useIncludePath = false; | |||
| private $classMap = array(); | |||
| private $classMapAuthoritative = false; | |||
| private $missingClasses = array(); | |||
| private $apcuPrefix; | |||
| public function getPrefixes() | |||
| { | |||
| if (!empty($this->prefixesPsr0)) { | |||
| return call_user_func_array('array_merge', $this->prefixesPsr0); | |||
| } | |||
| return array(); | |||
| } | |||
| public function getPrefixesPsr4() | |||
| { | |||
| return $this->prefixDirsPsr4; | |||
| } | |||
| public function getFallbackDirs() | |||
| { | |||
| return $this->fallbackDirsPsr0; | |||
| } | |||
| public function getFallbackDirsPsr4() | |||
| { | |||
| return $this->fallbackDirsPsr4; | |||
| } | |||
| public function getClassMap() | |||
| { | |||
| return $this->classMap; | |||
| } | |||
| /** | |||
| * @param array $classMap Class to filename map | |||
| */ | |||
| public function addClassMap(array $classMap) | |||
| { | |||
| if ($this->classMap) { | |||
| $this->classMap = array_merge($this->classMap, $classMap); | |||
| } else { | |||
| $this->classMap = $classMap; | |||
| } | |||
| } | |||
| /** | |||
| * Registers a set of PSR-0 directories for a given prefix, either | |||
| * appending or prepending to the ones previously set for this prefix. | |||
| * | |||
| * @param string $prefix The prefix | |||
| * @param array|string $paths The PSR-0 root directories | |||
| * @param bool $prepend Whether to prepend the directories | |||
| */ | |||
| public function add($prefix, $paths, $prepend = false) | |||
| { | |||
| if (!$prefix) { | |||
| if ($prepend) { | |||
| $this->fallbackDirsPsr0 = array_merge( | |||
| (array) $paths, | |||
| $this->fallbackDirsPsr0 | |||
| ); | |||
| } else { | |||
| $this->fallbackDirsPsr0 = array_merge( | |||
| $this->fallbackDirsPsr0, | |||
| (array) $paths | |||
| ); | |||
| } | |||
| return; | |||
| } | |||
| $first = $prefix[0]; | |||
| if (!isset($this->prefixesPsr0[$first][$prefix])) { | |||
| $this->prefixesPsr0[$first][$prefix] = (array) $paths; | |||
| return; | |||
| } | |||
| if ($prepend) { | |||
| $this->prefixesPsr0[$first][$prefix] = array_merge( | |||
| (array) $paths, | |||
| $this->prefixesPsr0[$first][$prefix] | |||
| ); | |||
| } else { | |||
| $this->prefixesPsr0[$first][$prefix] = array_merge( | |||
| $this->prefixesPsr0[$first][$prefix], | |||
| (array) $paths | |||
| ); | |||
| } | |||
| } | |||
| /** | |||
| * Registers a set of PSR-4 directories for a given namespace, either | |||
| * appending or prepending to the ones previously set for this namespace. | |||
| * | |||
| * @param string $prefix The prefix/namespace, with trailing '\\' | |||
| * @param array|string $paths The PSR-4 base directories | |||
| * @param bool $prepend Whether to prepend the directories | |||
| * | |||
| * @throws \InvalidArgumentException | |||
| */ | |||
| public function addPsr4($prefix, $paths, $prepend = false) | |||
| { | |||
| if (!$prefix) { | |||
| // Register directories for the root namespace. | |||
| if ($prepend) { | |||
| $this->fallbackDirsPsr4 = array_merge( | |||
| (array) $paths, | |||
| $this->fallbackDirsPsr4 | |||
| ); | |||
| } else { | |||
| $this->fallbackDirsPsr4 = array_merge( | |||
| $this->fallbackDirsPsr4, | |||
| (array) $paths | |||
| ); | |||
| } | |||
| } elseif (!isset($this->prefixDirsPsr4[$prefix])) { | |||
| // Register directories for a new namespace. | |||
| $length = strlen($prefix); | |||
| if ('\\' !== $prefix[$length - 1]) { | |||
| throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); | |||
| } | |||
| $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; | |||
| $this->prefixDirsPsr4[$prefix] = (array) $paths; | |||
| } elseif ($prepend) { | |||
| // Prepend directories for an already registered namespace. | |||
| $this->prefixDirsPsr4[$prefix] = array_merge( | |||
| (array) $paths, | |||
| $this->prefixDirsPsr4[$prefix] | |||
| ); | |||
| } else { | |||
| // Append directories for an already registered namespace. | |||
| $this->prefixDirsPsr4[$prefix] = array_merge( | |||
| $this->prefixDirsPsr4[$prefix], | |||
| (array) $paths | |||
| ); | |||
| } | |||
| } | |||
| /** | |||
| * Registers a set of PSR-0 directories for a given prefix, | |||
| * replacing any others previously set for this prefix. | |||
| * | |||
| * @param string $prefix The prefix | |||
| * @param array|string $paths The PSR-0 base directories | |||
| */ | |||
| public function set($prefix, $paths) | |||
| { | |||
| if (!$prefix) { | |||
| $this->fallbackDirsPsr0 = (array) $paths; | |||
| } else { | |||
| $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; | |||
| } | |||
| } | |||
| /** | |||
| * Registers a set of PSR-4 directories for a given namespace, | |||
| * replacing any others previously set for this namespace. | |||
| * | |||
| * @param string $prefix The prefix/namespace, with trailing '\\' | |||
| * @param array|string $paths The PSR-4 base directories | |||
| * | |||
| * @throws \InvalidArgumentException | |||
| */ | |||
| public function setPsr4($prefix, $paths) | |||
| { | |||
| if (!$prefix) { | |||
| $this->fallbackDirsPsr4 = (array) $paths; | |||
| } else { | |||
| $length = strlen($prefix); | |||
| if ('\\' !== $prefix[$length - 1]) { | |||
| throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); | |||
| } | |||
| $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; | |||
| $this->prefixDirsPsr4[$prefix] = (array) $paths; | |||
| } | |||
| } | |||
| /** | |||
| * Turns on searching the include path for class files. | |||
| * | |||
| * @param bool $useIncludePath | |||
| */ | |||
| public function setUseIncludePath($useIncludePath) | |||
| { | |||
| $this->useIncludePath = $useIncludePath; | |||
| } | |||
| /** | |||
| * Can be used to check if the autoloader uses the include path to check | |||
| * for classes. | |||
| * | |||
| * @return bool | |||
| */ | |||
| public function getUseIncludePath() | |||
| { | |||
| return $this->useIncludePath; | |||
| } | |||
| /** | |||
| * Turns off searching the prefix and fallback directories for classes | |||
| * that have not been registered with the class map. | |||
| * | |||
| * @param bool $classMapAuthoritative | |||
| */ | |||
| public function setClassMapAuthoritative($classMapAuthoritative) | |||
| { | |||
| $this->classMapAuthoritative = $classMapAuthoritative; | |||
| } | |||
| /** | |||
| * Should class lookup fail if not found in the current class map? | |||
| * | |||
| * @return bool | |||
| */ | |||
| public function isClassMapAuthoritative() | |||
| { | |||
| return $this->classMapAuthoritative; | |||
| } | |||
| /** | |||
| * APCu prefix to use to cache found/not-found classes, if the extension is enabled. | |||
| * | |||
| * @param string|null $apcuPrefix | |||
| */ | |||
| public function setApcuPrefix($apcuPrefix) | |||
| { | |||
| $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null; | |||
| } | |||
| /** | |||
| * The APCu prefix in use, or null if APCu caching is not enabled. | |||
| * | |||
| * @return string|null | |||
| */ | |||
| public function getApcuPrefix() | |||
| { | |||
| return $this->apcuPrefix; | |||
| } | |||
| /** | |||
| * Registers this instance as an autoloader. | |||
| * | |||
| * @param bool $prepend Whether to prepend the autoloader or not | |||
| */ | |||
| public function register($prepend = false) | |||
| { | |||
| spl_autoload_register(array($this, 'loadClass'), true, $prepend); | |||
| } | |||
| /** | |||
| * Unregisters this instance as an autoloader. | |||
| */ | |||
| public function unregister() | |||
| { | |||
| spl_autoload_unregister(array($this, 'loadClass')); | |||
| } | |||
| /** | |||
| * Loads the given class or interface. | |||
| * | |||
| * @param string $class The name of the class | |||
| * @return bool|null True if loaded, null otherwise | |||
| */ | |||
| public function loadClass($class) | |||
| { | |||
| if ($file = $this->findFile($class)) { | |||
| includeFile($file); | |||
| return true; | |||
| } | |||
| } | |||
| /** | |||
| * Finds the path to the file where the class is defined. | |||
| * | |||
| * @param string $class The name of the class | |||
| * | |||
| * @return string|false The path if found, false otherwise | |||
| */ | |||
| public function findFile($class) | |||
| { | |||
| // class map lookup | |||
| if (isset($this->classMap[$class])) { | |||
| return $this->classMap[$class]; | |||
| } | |||
| if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { | |||
| return false; | |||
| } | |||
| if (null !== $this->apcuPrefix) { | |||
| $file = apcu_fetch($this->apcuPrefix.$class, $hit); | |||
| if ($hit) { | |||
| return $file; | |||
| } | |||
| } | |||
| $file = $this->findFileWithExtension($class, '.php'); | |||
| // Search for Hack files if we are running on HHVM | |||
| if (false === $file && defined('HHVM_VERSION')) { | |||
| $file = $this->findFileWithExtension($class, '.hh'); | |||
| } | |||
| if (null !== $this->apcuPrefix) { | |||
| apcu_add($this->apcuPrefix.$class, $file); | |||
| } | |||
| if (false === $file) { | |||
| // Remember that this class does not exist. | |||
| $this->missingClasses[$class] = true; | |||
| } | |||
| return $file; | |||
| } | |||
| private function findFileWithExtension($class, $ext) | |||
| { | |||
| // PSR-4 lookup | |||
| $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; | |||
| $first = $class[0]; | |||
| if (isset($this->prefixLengthsPsr4[$first])) { | |||
| $subPath = $class; | |||
| while (false !== $lastPos = strrpos($subPath, '\\')) { | |||
| $subPath = substr($subPath, 0, $lastPos); | |||
| $search = $subPath.'\\'; | |||
| if (isset($this->prefixDirsPsr4[$search])) { | |||
| $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); | |||
| foreach ($this->prefixDirsPsr4[$search] as $dir) { | |||
| if (file_exists($file = $dir . $pathEnd)) { | |||
| return $file; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| // PSR-4 fallback dirs | |||
| foreach ($this->fallbackDirsPsr4 as $dir) { | |||
| if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { | |||
| return $file; | |||
| } | |||
| } | |||
| // PSR-0 lookup | |||
| if (false !== $pos = strrpos($class, '\\')) { | |||
| // namespaced class name | |||
| $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) | |||
| . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); | |||
| } else { | |||
| // PEAR-like class name | |||
| $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; | |||
| } | |||
| if (isset($this->prefixesPsr0[$first])) { | |||
| foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { | |||
| if (0 === strpos($class, $prefix)) { | |||
| foreach ($dirs as $dir) { | |||
| if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { | |||
| return $file; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| // PSR-0 fallback dirs | |||
| foreach ($this->fallbackDirsPsr0 as $dir) { | |||
| if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { | |||
| return $file; | |||
| } | |||
| } | |||
| // PSR-0 include paths. | |||
| if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { | |||
| return $file; | |||
| } | |||
| return false; | |||
| } | |||
| } | |||
| /** | |||
| * Scope isolated include. | |||
| * | |||
| * Prevents access to $this/self from included files. | |||
| */ | |||
| function includeFile($file) | |||
| { | |||
| include $file; | |||
| } | |||
| @@ -0,0 +1,56 @@ | |||
| Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ | |||
| Upstream-Name: Composer | |||
| Upstream-Contact: Jordi Boggiano <j.boggiano@seld.be> | |||
| Source: https://github.com/composer/composer | |||
| Files: * | |||
| Copyright: 2016, Nils Adermann <naderman@naderman.de> | |||
| 2016, Jordi Boggiano <j.boggiano@seld.be> | |||
| License: Expat | |||
| Files: src/Composer/Util/TlsHelper.php | |||
| Copyright: 2016, Nils Adermann <naderman@naderman.de> | |||
| 2016, Jordi Boggiano <j.boggiano@seld.be> | |||
| 2013, Evan Coury <me@evancoury.com> | |||
| License: Expat and BSD-2-Clause | |||
| License: BSD-2-Clause | |||
| Redistribution and use in source and binary forms, with or without modification, | |||
| are permitted provided that the following conditions are met: | |||
| . | |||
| * Redistributions of source code must retain the above copyright notice, | |||
| this list of conditions and the following disclaimer. | |||
| . | |||
| * Redistributions in binary form must reproduce the above copyright notice, | |||
| this list of conditions and the following disclaimer in the documentation | |||
| and/or other materials provided with the distribution. | |||
| . | |||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |||
| ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |||
| WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |||
| DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | |||
| ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |||
| (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |||
| LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | |||
| ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |||
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |||
| SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
| License: Expat | |||
| Permission is hereby granted, free of charge, to any person obtaining a copy | |||
| of this software and associated documentation files (the "Software"), to deal | |||
| in the Software without restriction, including without limitation the rights | |||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
| copies of the Software, and to permit persons to whom the Software is furnished | |||
| to do so, subject to the following conditions: | |||
| . | |||
| The above copyright notice and this permission notice shall be included in all | |||
| copies or substantial portions of the Software. | |||
| . | |||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
| THE SOFTWARE. | |||
| @@ -0,0 +1,9 @@ | |||
| <?php | |||
| // autoload_classmap.php @generated by Composer | |||
| $vendorDir = dirname(dirname(__FILE__)); | |||
| $baseDir = dirname($vendorDir); | |||
| return array( | |||
| ); | |||
| @@ -0,0 +1,9 @@ | |||
| <?php | |||
| // autoload_namespaces.php @generated by Composer | |||
| $vendorDir = dirname(dirname(__FILE__)); | |||
| $baseDir = dirname($vendorDir); | |||
| return array( | |||
| ); | |||
| @@ -0,0 +1,14 @@ | |||
| <?php | |||
| // autoload_psr4.php @generated by Composer | |||
| $vendorDir = dirname(dirname(__FILE__)); | |||
| $baseDir = dirname($vendorDir); | |||
| return array( | |||
| 'MaxMind\\WebService\\' => array($vendorDir . '/maxmind/web-service-common/src/WebService'), | |||
| 'MaxMind\\Exception\\' => array($vendorDir . '/maxmind/web-service-common/src/Exception'), | |||
| 'MaxMind\\Db\\' => array($vendorDir . '/maxmind-db/reader/src/MaxMind/Db'), | |||
| 'GeoIp2\\' => array($vendorDir . '/geoip2/geoip2/src'), | |||
| 'Composer\\CaBundle\\' => array($vendorDir . '/composer/ca-bundle/src'), | |||
| ); | |||
| @@ -0,0 +1,52 @@ | |||
| <?php | |||
| // autoload_real.php @generated by Composer | |||
| class ComposerAutoloaderIniteabaa05010c1f1c15dd62473ac209085 | |||
| { | |||
| private static $loader; | |||
| public static function loadClassLoader($class) | |||
| { | |||
| if ('Composer\Autoload\ClassLoader' === $class) { | |||
| require __DIR__ . '/ClassLoader.php'; | |||
| } | |||
| } | |||
| public static function getLoader() | |||
| { | |||
| if (null !== self::$loader) { | |||
| return self::$loader; | |||
| } | |||
| spl_autoload_register(array('ComposerAutoloaderIniteabaa05010c1f1c15dd62473ac209085', 'loadClassLoader'), true, true); | |||
| self::$loader = $loader = new \Composer\Autoload\ClassLoader(); | |||
| spl_autoload_unregister(array('ComposerAutoloaderIniteabaa05010c1f1c15dd62473ac209085', 'loadClassLoader')); | |||
| $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); | |||
| if ($useStaticLoader) { | |||
| require_once __DIR__ . '/autoload_static.php'; | |||
| call_user_func(\Composer\Autoload\ComposerStaticIniteabaa05010c1f1c15dd62473ac209085::getInitializer($loader)); | |||
| } else { | |||
| $map = require __DIR__ . '/autoload_namespaces.php'; | |||
| foreach ($map as $namespace => $path) { | |||
| $loader->set($namespace, $path); | |||
| } | |||
| $map = require __DIR__ . '/autoload_psr4.php'; | |||
| foreach ($map as $namespace => $path) { | |||
| $loader->setPsr4($namespace, $path); | |||
| } | |||
| $classMap = require __DIR__ . '/autoload_classmap.php'; | |||
| if ($classMap) { | |||
| $loader->addClassMap($classMap); | |||
| } | |||
| } | |||
| $loader->register(true); | |||
| return $loader; | |||
| } | |||
| } | |||
| @@ -0,0 +1,57 @@ | |||
| <?php | |||
| // autoload_static.php @generated by Composer | |||
| namespace Composer\Autoload; | |||
| class ComposerStaticIniteabaa05010c1f1c15dd62473ac209085 | |||
| { | |||
| public static $prefixLengthsPsr4 = array ( | |||
| 'M' => | |||
| array ( | |||
| 'MaxMind\\WebService\\' => 19, | |||
| 'MaxMind\\Exception\\' => 18, | |||
| 'MaxMind\\Db\\' => 11, | |||
| ), | |||
| 'G' => | |||
| array ( | |||
| 'GeoIp2\\' => 7, | |||
| ), | |||
| 'C' => | |||
| array ( | |||
| 'Composer\\CaBundle\\' => 18, | |||
| ), | |||
| ); | |||
| public static $prefixDirsPsr4 = array ( | |||
| 'MaxMind\\WebService\\' => | |||
| array ( | |||
| 0 => __DIR__ . '/..' . '/maxmind/web-service-common/src/WebService', | |||
| ), | |||
| 'MaxMind\\Exception\\' => | |||
| array ( | |||
| 0 => __DIR__ . '/..' . '/maxmind/web-service-common/src/Exception', | |||
| ), | |||
| 'MaxMind\\Db\\' => | |||
| array ( | |||
| 0 => __DIR__ . '/..' . '/maxmind-db/reader/src/MaxMind/Db', | |||
| ), | |||
| 'GeoIp2\\' => | |||
| array ( | |||
| 0 => __DIR__ . '/..' . '/geoip2/geoip2/src', | |||
| ), | |||
| 'Composer\\CaBundle\\' => | |||
| array ( | |||
| 0 => __DIR__ . '/..' . '/composer/ca-bundle/src', | |||
| ), | |||
| ); | |||
| public static function getInitializer(ClassLoader $loader) | |||
| { | |||
| return \Closure::bind(function () use ($loader) { | |||
| $loader->prefixLengthsPsr4 = ComposerStaticIniteabaa05010c1f1c15dd62473ac209085::$prefixLengthsPsr4; | |||
| $loader->prefixDirsPsr4 = ComposerStaticIniteabaa05010c1f1c15dd62473ac209085::$prefixDirsPsr4; | |||
| }, null, ClassLoader::class); | |||
| } | |||
| } | |||
| @@ -0,0 +1,19 @@ | |||
| Copyright (C) 2016 Composer | |||
| Permission is hereby granted, free of charge, to any person obtaining a copy of | |||
| this software and associated documentation files (the "Software"), to deal in | |||
| the Software without restriction, including without limitation the rights to | |||
| use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies | |||
| of the Software, and to permit persons to whom the Software is furnished to do | |||
| so, subject to the following conditions: | |||
| The above copyright notice and this permission notice shall be included in all | |||
| copies or substantial portions of the Software. | |||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
| SOFTWARE. | |||
| @@ -0,0 +1,85 @@ | |||
| composer/ca-bundle | |||
| ================== | |||
| Small utility library that lets you find a path to the system CA bundle, | |||
| and includes a fallback to the Mozilla CA bundle. | |||
| Originally written as part of [composer/composer](https://github.com/composer/composer), | |||
| now extracted and made available as a stand-alone library. | |||
| Installation | |||
| ------------ | |||
| Install the latest version with: | |||
| ```bash | |||
| $ composer require composer/ca-bundle | |||
| ``` | |||
| Requirements | |||
| ------------ | |||
| * PHP 5.3.2 is required but using the latest version of PHP is highly recommended. | |||
| Basic usage | |||
| ----------- | |||
| ### `Composer\CaBundle\CaBundle` | |||
| - `CaBundle::getSystemCaRootBundlePath()`: Returns the system CA bundle path, or a path to the bundled one as fallback | |||
| - `CaBundle::getBundledCaBundlePath()`: Returns the path to the bundled CA file | |||
| - `CaBundle::validateCaFile($filename)`: Validates a CA file using openssl_x509_parse only if it is safe to use | |||
| - `CaBundle::isOpensslParseSafe()`: Test if it is safe to use the PHP function openssl_x509_parse() | |||
| - `CaBundle::reset()`: Resets the static caches | |||
| #### To use with curl | |||
| ```php | |||
| $curl = curl_init("https://example.org/"); | |||
| $caPathOrFile = \Composer\CaBundle\CaBundle::getSystemCaRootBundlePath(); | |||
| if (is_dir($caPathOrFile)) { | |||
| curl_setopt($curl, CURLOPT_CAPATH, $caPathOrFile); | |||
| } else { | |||
| curl_setopt($curl, CURLOPT_CAINFO, $caPathOrFile); | |||
| } | |||
| $result = curl_exec($curl); | |||
| ``` | |||
| #### To use with php streams | |||
| ```php | |||
| $opts = array( | |||
| 'http' => array( | |||
| 'method' => "GET" | |||
| ) | |||
| ); | |||
| $caPathOrFile = \Composer\CaBundle\CaBundle::getSystemCaRootBundlePath(); | |||
| if (is_dir($caPathOrFile)) { | |||
| $opts['ssl']['capath'] = $caPathOrFile; | |||
| } else { | |||
| $opts['ssl']['cafile'] = $caPathOrFile; | |||
| } | |||
| $context = stream_context_create($opts); | |||
| $result = file_get_contents('https://example.com', false, $context); | |||
| ``` | |||
| #### To use with Guzzle | |||
| ```php | |||
| $client = new \GuzzleHttp\Client([ | |||
| \GuzzleHttp\RequestOptions::VERIFY => \Composer\CaBundle\CaBundle::getSystemCaRootBundlePath() | |||
| ]); | |||
| ``` | |||
| License | |||
| ------- | |||
| composer/ca-bundle is licensed under the MIT License, see the LICENSE file for details. | |||
| @@ -0,0 +1,54 @@ | |||
| { | |||
| "name": "composer/ca-bundle", | |||
| "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", | |||
| "type": "library", | |||
| "license": "MIT", | |||
| "keywords": [ | |||
| "cabundle", | |||
| "cacert", | |||
| "certificate", | |||
| "ssl", | |||
| "tls" | |||
| ], | |||
| "authors": [ | |||
| { | |||
| "name": "Jordi Boggiano", | |||
| "email": "j.boggiano@seld.be", | |||
| "homepage": "http://seld.be" | |||
| } | |||
| ], | |||
| "support": { | |||
| "irc": "irc://irc.freenode.org/composer", | |||
| "issues": "https://github.com/composer/ca-bundle/issues" | |||
| }, | |||
| "require": { | |||
| "ext-openssl": "*", | |||
| "ext-pcre": "*", | |||
| "php": "^5.3.2 || ^7.0 || ^8.0" | |||
| }, | |||
| "require-dev": { | |||
| "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8", | |||
| "psr/log": "^1.0", | |||
| "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0" | |||
| }, | |||
| "autoload": { | |||
| "psr-4": { | |||
| "Composer\\CaBundle\\": "src" | |||
| } | |||
| }, | |||
| "autoload-dev": { | |||
| "psr-4": { | |||
| "Composer\\CaBundle\\": "tests" | |||
| } | |||
| }, | |||
| "extra": { | |||
| "branch-alias": { | |||
| "dev-master": "1.x-dev" | |||
| } | |||
| }, | |||
| "config": { | |||
| "platform": { | |||
| "php": "5.3.9" | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,324 @@ | |||
| <?php | |||
| /* | |||
| * This file is part of composer/ca-bundle. | |||
| * | |||
| * (c) Composer <https://github.com/composer> | |||
| * | |||
| * For the full copyright and license information, please view | |||
| * the LICENSE file that was distributed with this source code. | |||
| */ | |||
| namespace Composer\CaBundle; | |||
| use Psr\Log\LoggerInterface; | |||
| use Symfony\Component\Process\PhpProcess; | |||
| /** | |||
| * @author Chris Smith <chris@cs278.org> | |||
| * @author Jordi Boggiano <j.boggiano@seld.be> | |||
| */ | |||
| class CaBundle | |||
| { | |||
| private static $caPath; | |||
| private static $caFileValidity = array(); | |||
| private static $useOpensslParse; | |||
| /** | |||
| * Returns the system CA bundle path, or a path to the bundled one | |||
| * | |||
| * This method was adapted from Sslurp. | |||
| * https://github.com/EvanDotPro/Sslurp | |||
| * | |||
| * (c) Evan Coury <me@evancoury.com> | |||
| * | |||
| * For the full copyright and license information, please see below: | |||
| * | |||
| * Copyright (c) 2013, Evan Coury | |||
| * All rights reserved. | |||
| * | |||
| * Redistribution and use in source and binary forms, with or without modification, | |||
| * are permitted provided that the following conditions are met: | |||
| * | |||
| * * Redistributions of source code must retain the above copyright notice, | |||
| * this list of conditions and the following disclaimer. | |||
| * | |||
| * * Redistributions in binary form must reproduce the above copyright notice, | |||
| * this list of conditions and the following disclaimer in the documentation | |||
| * and/or other materials provided with the distribution. | |||
| * | |||
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |||
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |||
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |||
| * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | |||
| * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |||
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |||
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | |||
| * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |||
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |||
| * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
| * | |||
| * @param LoggerInterface $logger optional logger for information about which CA files were loaded | |||
| * @return string path to a CA bundle file or directory | |||
| */ | |||
| public static function getSystemCaRootBundlePath(LoggerInterface $logger = null) | |||
| { | |||
| if (self::$caPath !== null) { | |||
| return self::$caPath; | |||
| } | |||
| $caBundlePaths = array(); | |||
| // If SSL_CERT_FILE env variable points to a valid certificate/bundle, use that. | |||
| // This mimics how OpenSSL uses the SSL_CERT_FILE env variable. | |||
| $caBundlePaths[] = self::getEnvVariable('SSL_CERT_FILE'); | |||
| // If SSL_CERT_DIR env variable points to a valid certificate/bundle, use that. | |||
| // This mimics how OpenSSL uses the SSL_CERT_FILE env variable. | |||
| $caBundlePaths[] = self::getEnvVariable('SSL_CERT_DIR'); | |||
| $caBundlePaths[] = ini_get('openssl.cafile'); | |||
| $caBundlePaths[] = ini_get('openssl.capath'); | |||
| $otherLocations = array( | |||
| '/etc/pki/tls/certs/ca-bundle.crt', // Fedora, RHEL, CentOS (ca-certificates package) | |||
| '/etc/ssl/certs/ca-certificates.crt', // Debian, Ubuntu, Gentoo, Arch Linux (ca-certificates package) | |||
| '/etc/ssl/ca-bundle.pem', // SUSE, openSUSE (ca-certificates package) | |||
| '/usr/local/share/certs/ca-root-nss.crt', // FreeBSD (ca_root_nss_package) | |||
| '/usr/ssl/certs/ca-bundle.crt', // Cygwin | |||
| '/opt/local/share/curl/curl-ca-bundle.crt', // OS X macports, curl-ca-bundle package | |||
| '/usr/local/share/curl/curl-ca-bundle.crt', // Default cURL CA bunde path (without --with-ca-bundle option) | |||
| '/usr/share/ssl/certs/ca-bundle.crt', // Really old RedHat? | |||
| '/etc/ssl/cert.pem', // OpenBSD | |||
| '/usr/local/etc/ssl/cert.pem', // FreeBSD 10.x | |||
| '/usr/local/etc/openssl/cert.pem', // OS X homebrew, openssl package | |||
| '/usr/local/etc/openssl@1.1/cert.pem', // OS X homebrew, openssl@1.1 package | |||
| ); | |||
| foreach($otherLocations as $location) { | |||
| $otherLocations[] = dirname($location); | |||
| } | |||
| $caBundlePaths = array_merge($caBundlePaths, $otherLocations); | |||
| foreach ($caBundlePaths as $caBundle) { | |||
| if (self::caFileUsable($caBundle, $logger)) { | |||
| return self::$caPath = $caBundle; | |||
| } | |||
| if (self::caDirUsable($caBundle)) { | |||
| return self::$caPath = $caBundle; | |||
| } | |||
| } | |||
| return self::$caPath = static::getBundledCaBundlePath(); // Bundled CA file, last resort | |||
| } | |||
| /** | |||
| * Returns the path to the bundled CA file | |||
| * | |||
| * In case you don't want to trust the user or the system, you can use this directly | |||
| * | |||
| * @return string path to a CA bundle file | |||
| */ | |||
| public static function getBundledCaBundlePath() | |||
| { | |||
| $caBundleFile = __DIR__.'/../res/cacert.pem'; | |||
| // cURL does not understand 'phar://' paths | |||
| // see https://github.com/composer/ca-bundle/issues/10 | |||
| if (0 === strpos($caBundleFile, 'phar://')) { | |||
| file_put_contents( | |||
| $tempCaBundleFile = tempnam(sys_get_temp_dir(), 'openssl-ca-bundle-'), | |||
| file_get_contents($caBundleFile) | |||
| ); | |||
| register_shutdown_function(function() use ($tempCaBundleFile) { | |||
| @unlink($tempCaBundleFile); | |||
| }); | |||
| $caBundleFile = $tempCaBundleFile; | |||
| } | |||
| return $caBundleFile; | |||
| } | |||
| /** | |||
| * Validates a CA file using opensl_x509_parse only if it is safe to use | |||
| * | |||
| * @param string $filename | |||
| * @param LoggerInterface $logger optional logger for information about which CA files were loaded | |||
| * | |||
| * @return bool | |||
| */ | |||
| public static function validateCaFile($filename, LoggerInterface $logger = null) | |||
| { | |||
| static $warned = false; | |||
| if (isset(self::$caFileValidity[$filename])) { | |||
| return self::$caFileValidity[$filename]; | |||
| } | |||
| $contents = file_get_contents($filename); | |||
| // assume the CA is valid if php is vulnerable to | |||
| // https://www.sektioneins.de/advisories/advisory-012013-php-openssl_x509_parse-memory-corruption-vulnerability.html | |||
| if (!static::isOpensslParseSafe()) { | |||
| if (!$warned && $logger) { | |||
| $logger->warning(sprintf( | |||
| 'Your version of PHP, %s, is affected by CVE-2013-6420 and cannot safely perform certificate validation, we strongly suggest you upgrade.', | |||
| PHP_VERSION | |||
| )); | |||
| $warned = true; | |||
| } | |||
| $isValid = !empty($contents); | |||
| } else { | |||
| $isValid = (bool) openssl_x509_parse($contents); | |||
| } | |||
| if ($logger) { | |||
| $logger->debug('Checked CA file '.realpath($filename).': '.($isValid ? 'valid' : 'invalid')); | |||
| } | |||
| return self::$caFileValidity[$filename] = $isValid; | |||
| } | |||
| /** | |||
| * Test if it is safe to use the PHP function openssl_x509_parse(). | |||
| * | |||
| * This checks if OpenSSL extensions is vulnerable to remote code execution | |||
| * via the exploit documented as CVE-2013-6420. | |||
| * | |||
| * @return bool | |||
| */ | |||
| public static function isOpensslParseSafe() | |||
| { | |||
| if (null !== self::$useOpensslParse) { | |||
| return self::$useOpensslParse; | |||
| } | |||
| if (PHP_VERSION_ID >= 50600) { | |||
| return self::$useOpensslParse = true; | |||
| } | |||
| // Vulnerable: | |||
| // PHP 5.3.0 - PHP 5.3.27 | |||
| // PHP 5.4.0 - PHP 5.4.22 | |||
| // PHP 5.5.0 - PHP 5.5.6 | |||
| if ( | |||
| (PHP_VERSION_ID < 50400 && PHP_VERSION_ID >= 50328) | |||
| || (PHP_VERSION_ID < 50500 && PHP_VERSION_ID >= 50423) | |||
| || (PHP_VERSION_ID < 50600 && PHP_VERSION_ID >= 50507) | |||
| ) { | |||
| // This version of PHP has the fix for CVE-2013-6420 applied. | |||
| return self::$useOpensslParse = true; | |||
| } | |||
| if (defined('PHP_WINDOWS_VERSION_BUILD')) { | |||
| // Windows is probably insecure in this case. | |||
| return self::$useOpensslParse = false; | |||
| } | |||
| $compareDistroVersionPrefix = function ($prefix, $fixedVersion) { | |||
| $regex = '{^'.preg_quote($prefix).'([0-9]+)$}'; | |||
| if (preg_match($regex, PHP_VERSION, $m)) { | |||
| return ((int) $m[1]) >= $fixedVersion; | |||
| } | |||
| return false; | |||
| }; | |||
| // Hard coded list of PHP distributions with the fix backported. | |||
| if ( | |||
| $compareDistroVersionPrefix('5.3.3-7+squeeze', 18) // Debian 6 (Squeeze) | |||
| || $compareDistroVersionPrefix('5.4.4-14+deb7u', 7) // Debian 7 (Wheezy) | |||
| || $compareDistroVersionPrefix('5.3.10-1ubuntu3.', 9) // Ubuntu 12.04 (Precise) | |||
| ) { | |||
| return self::$useOpensslParse = true; | |||
| } | |||
| // Symfony Process component is missing so we assume it is unsafe at this point | |||
| if (!class_exists('Symfony\Component\Process\PhpProcess')) { | |||
| return self::$useOpensslParse = false; | |||
| } | |||
| // This is where things get crazy, because distros backport security | |||
| // fixes the chances are on NIX systems the fix has been applied but | |||
| // it's not possible to verify that from the PHP version. | |||
| // | |||
| // To verify exec a new PHP process and run the issue testcase with | |||
| // known safe input that replicates the bug. | |||
| // Based on testcase in https://github.com/php/php-src/commit/c1224573c773b6845e83505f717fbf820fc18415 | |||
| // changes in https://github.com/php/php-src/commit/76a7fd893b7d6101300cc656058704a73254d593 | |||
| $cert = 'LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVwRENDQTR5Z0F3SUJBZ0lKQUp6dThyNnU2ZUJjTUEwR0NTcUdTSWIzRFFFQkJRVUFNSUhETVFzd0NRWUQKVlFRR0V3SkVSVEVjTUJvR0ExVUVDQXdUVG05eVpISm9aV2x1TFZkbGMzUm1ZV3hsYmpFUU1BNEdBMVVFQnd3SApTOE9Ed3Jac2JqRVVNQklHQTFVRUNnd0xVMlZyZEdsdmJrVnBibk14SHpBZEJnTlZCQXNNRmsxaGJHbGphVzkxCmN5QkRaWEowSUZObFkzUnBiMjR4SVRBZkJnTlZCQU1NR0cxaGJHbGphVzkxY3k1elpXdDBhVzl1WldsdWN5NWsKWlRFcU1DZ0dDU3FHU0liM0RRRUpBUlliYzNSbFptRnVMbVZ6YzJWeVFITmxhM1JwYjI1bGFXNXpMbVJsTUhVWQpaREU1TnpBd01UQXhNREF3TURBd1dnQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBCkFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUEKQUFBQUFBQVhEVEUwTVRFeU9ERXhNemt6TlZvd2djTXhDekFKQmdOVkJBWVRBa1JGTVJ3d0dnWURWUVFJREJOTwpiM0prY21obGFXNHRWMlZ6ZEdaaGJHVnVNUkF3RGdZRFZRUUhEQWRMdzRQQ3RteHVNUlF3RWdZRFZRUUtEQXRUClpXdDBhVzl1UldsdWN6RWZNQjBHQTFVRUN3d1dUV0ZzYVdOcGIzVnpJRU5sY25RZ1UyVmpkR2x2YmpFaE1COEcKQTFVRUF3d1liV0ZzYVdOcGIzVnpMbk5sYTNScGIyNWxhVzV6TG1SbE1Tb3dLQVlKS29aSWh2Y05BUWtCRmh0egpkR1ZtWVc0dVpYTnpaWEpBYzJWcmRHbHZibVZwYm5NdVpHVXdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCCkR3QXdnZ0VLQW9JQkFRRERBZjNobDdKWTBYY0ZuaXlFSnBTU0RxbjBPcUJyNlFQNjV1c0pQUnQvOFBhRG9xQnUKd0VZVC9OYSs2ZnNnUGpDMHVLOURaZ1dnMnRIV1dvYW5TYmxBTW96NVBINlorUzRTSFJaN2UyZERJalBqZGhqaAowbUxnMlVNTzV5cDBWNzk3R2dzOWxOdDZKUmZIODFNTjJvYlhXczROdHp0TE11RDZlZ3FwcjhkRGJyMzRhT3M4CnBrZHVpNVVhd1Raa3N5NXBMUEhxNWNNaEZHbTA2djY1Q0xvMFYyUGQ5K0tBb2tQclBjTjVLTEtlYno3bUxwazYKU01lRVhPS1A0aWRFcXh5UTdPN2ZCdUhNZWRzUWh1K3ByWTNzaTNCVXlLZlF0UDVDWm5YMmJwMHdLSHhYMTJEWAoxbmZGSXQ5RGJHdkhUY3lPdU4rblpMUEJtM3ZXeG50eUlJdlZBZ01CQUFHalFqQkFNQWtHQTFVZEV3UUNNQUF3CkVRWUpZSVpJQVliNFFnRUJCQVFEQWdlQU1Bc0dBMVVkRHdRRUF3SUZvREFUQmdOVkhTVUVEREFLQmdnckJnRUYKQlFjREFqQU5CZ2txaGtpRzl3MEJBUVVGQUFPQ0FRRUFHMGZaWVlDVGJkajFYWWMrMVNub2FQUit2SThDOENhRAo4KzBVWWhkbnlVNGdnYTBCQWNEclk5ZTk0ZUVBdTZacXljRjZGakxxWFhkQWJvcHBXb2NyNlQ2R0QxeDMzQ2tsClZBcnpHL0t4UW9oR0QySmVxa2hJTWxEb214SE83a2EzOStPYThpMnZXTFZ5alU4QVp2V01BcnVIYTRFRU55RzcKbFcyQWFnYUZLRkNyOVRuWFRmcmR4R1ZFYnY3S1ZRNmJkaGc1cDVTanBXSDErTXEwM3VSM1pYUEJZZHlWODMxOQpvMGxWajFLRkkyRENML2xpV2lzSlJvb2YrMWNSMzVDdGQwd1lCY3BCNlRac2xNY09QbDc2ZHdLd0pnZUpvMlFnClpzZm1jMnZDMS9xT2xOdU5xLzBUenprVkd2OEVUVDNDZ2FVK1VYZTRYT1Z2a2NjZWJKbjJkZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K'; | |||
| $script = <<<'EOT' | |||
| error_reporting(-1); | |||
| $info = openssl_x509_parse(base64_decode('%s')); | |||
| var_dump(PHP_VERSION, $info['issuer']['emailAddress'], $info['validFrom_time_t']); | |||
| EOT; | |||
| $script = '<'."?php\n".sprintf($script, $cert); | |||
| try { | |||
| $process = new PhpProcess($script); | |||
| $process->mustRun(); | |||
| } catch (\Exception $e) { | |||
| // In the case of any exceptions just accept it is not possible to | |||
| // determine the safety of openssl_x509_parse and bail out. | |||
| return self::$useOpensslParse = false; | |||
| } | |||
| $output = preg_split('{\r?\n}', trim($process->getOutput())); | |||
| $errorOutput = trim($process->getErrorOutput()); | |||
| if ( | |||
| count($output) === 3 | |||
| && $output[0] === sprintf('string(%d) "%s"', strlen(PHP_VERSION), PHP_VERSION) | |||
| && $output[1] === 'string(27) "stefan.esser@sektioneins.de"' | |||
| && $output[2] === 'int(-1)' | |||
| && preg_match('{openssl_x509_parse\(\): illegal (?:ASN1 data type for|length in) timestamp in - on line \d+}', $errorOutput) | |||
| ) { | |||
| // This PHP has the fix backported probably by a distro security team. | |||
| return self::$useOpensslParse = true; | |||
| } | |||
| return self::$useOpensslParse = false; | |||
| } | |||
| /** | |||
| * Resets the static caches | |||
| */ | |||
| public static function reset() | |||
| { | |||
| self::$caFileValidity = array(); | |||
| self::$caPath = null; | |||
| self::$useOpensslParse = null; | |||
| } | |||
| private static function getEnvVariable($name) | |||
| { | |||
| if (isset($_SERVER[$name])) { | |||
| return (string) $_SERVER[$name]; | |||
| } | |||
| if (PHP_SAPI === 'cli' && ($value = getenv($name)) !== false && $value !== null) { | |||
| return (string) $value; | |||
| } | |||
| return false; | |||
| } | |||
| private static function caFileUsable($certFile, LoggerInterface $logger = null) | |||
| { | |||
| return $certFile && @is_file($certFile) && @is_readable($certFile) && static::validateCaFile($certFile, $logger); | |||
| } | |||
| private static function caDirUsable($certDir) | |||
| { | |||
| return $certDir && @is_dir($certDir) && @is_readable($certDir) && glob($certDir . '/*'); | |||
| } | |||
| } | |||
| @@ -0,0 +1,225 @@ | |||
| [ | |||
| { | |||
| "name": "composer/ca-bundle", | |||
| "version": "1.2.6", | |||
| "version_normalized": "1.2.6.0", | |||
| "source": { | |||
| "type": "git", | |||
| "url": "https://github.com/composer/ca-bundle.git", | |||
| "reference": "47fe531de31fca4a1b997f87308e7d7804348f7e" | |||
| }, | |||
| "dist": { | |||
| "type": "zip", | |||
| "url": "https://api.github.com/repos/composer/ca-bundle/zipball/47fe531de31fca4a1b997f87308e7d7804348f7e", | |||
| "reference": "47fe531de31fca4a1b997f87308e7d7804348f7e", | |||
| "shasum": "" | |||
| }, | |||
| "require": { | |||
| "ext-openssl": "*", | |||
| "ext-pcre": "*", | |||
| "php": "^5.3.2 || ^7.0 || ^8.0" | |||
| }, | |||
| "require-dev": { | |||
| "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8", | |||
| "psr/log": "^1.0", | |||
| "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0" | |||
| }, | |||
| "time": "2020-01-13T10:02:55+00:00", | |||
| "type": "library", | |||
| "extra": { | |||
| "branch-alias": { | |||
| "dev-master": "1.x-dev" | |||
| } | |||
| }, | |||
| "installation-source": "dist", | |||
| "autoload": { | |||
| "psr-4": { | |||
| "Composer\\CaBundle\\": "src" | |||
| } | |||
| }, | |||
| "notification-url": "https://packagist.org/downloads/", | |||
| "license": [ | |||
| "MIT" | |||
| ], | |||
| "authors": [ | |||
| { | |||
| "name": "Jordi Boggiano", | |||
| "email": "j.boggiano@seld.be", | |||
| "homepage": "http://seld.be" | |||
| } | |||
| ], | |||
| "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", | |||
| "keywords": [ | |||
| "cabundle", | |||
| "cacert", | |||
| "certificate", | |||
| "ssl", | |||
| "tls" | |||
| ] | |||
| }, | |||
| { | |||
| "name": "geoip2/geoip2", | |||
| "version": "v2.10.0", | |||
| "version_normalized": "2.10.0.0", | |||
| "source": { | |||
| "type": "git", | |||
| "url": "https://github.com/maxmind/GeoIP2-php.git", | |||
| "reference": "419557cd21d9fe039721a83490701a58c8ce784a" | |||
| }, | |||
| "dist": { | |||
| "type": "zip", | |||
| "url": "https://api.github.com/repos/maxmind/GeoIP2-php/zipball/419557cd21d9fe039721a83490701a58c8ce784a", | |||
| "reference": "419557cd21d9fe039721a83490701a58c8ce784a", | |||
| "shasum": "" | |||
| }, | |||
| "require": { | |||
| "ext-json": "*", | |||
| "maxmind-db/reader": "~1.5", | |||
| "maxmind/web-service-common": "~0.6", | |||
| "php": ">=5.6" | |||
| }, | |||
| "require-dev": { | |||
| "friendsofphp/php-cs-fixer": "2.*", | |||
| "phpunit/phpunit": "5.*", | |||
| "squizlabs/php_codesniffer": "3.*" | |||
| }, | |||
| "time": "2019-12-12T18:48:39+00:00", | |||
| "type": "library", | |||
| "installation-source": "dist", | |||
| "autoload": { | |||
| "psr-4": { | |||
| "GeoIp2\\": "src" | |||
| } | |||
| }, | |||
| "notification-url": "https://packagist.org/downloads/", | |||
| "license": [ | |||
| "Apache-2.0" | |||
| ], | |||
| "authors": [ | |||
| { | |||
| "name": "Gregory J. Oschwald", | |||
| "email": "goschwald@maxmind.com", | |||
| "homepage": "https://www.maxmind.com/" | |||
| } | |||
| ], | |||
| "description": "MaxMind GeoIP2 PHP API", | |||
| "homepage": "https://github.com/maxmind/GeoIP2-php", | |||
| "keywords": [ | |||
| "IP", | |||
| "geoip", | |||
| "geoip2", | |||
| "geolocation", | |||
| "maxmind" | |||
| ] | |||
| }, | |||
| { | |||
| "name": "maxmind-db/reader", | |||
| "version": "v1.6.0", | |||
| "version_normalized": "1.6.0.0", | |||
| "source": { | |||
| "type": "git", | |||
| "url": "https://github.com/maxmind/MaxMind-DB-Reader-php.git", | |||
| "reference": "febd4920bf17c1da84cef58e56a8227dfb37fbe4" | |||
| }, | |||
| "dist": { | |||
| "type": "zip", | |||
| "url": "https://api.github.com/repos/maxmind/MaxMind-DB-Reader-php/zipball/febd4920bf17c1da84cef58e56a8227dfb37fbe4", | |||
| "reference": "febd4920bf17c1da84cef58e56a8227dfb37fbe4", | |||
| "shasum": "" | |||
| }, | |||
| "require": { | |||
| "php": ">=5.6" | |||
| }, | |||
| "conflict": { | |||
| "ext-maxminddb": "<1.6.0,>=2.0.0" | |||
| }, | |||
| "require-dev": { | |||
| "friendsofphp/php-cs-fixer": "2.*", | |||
| "php-coveralls/php-coveralls": "^2.1", | |||
| "phpunit/phpcov": "^3.0", | |||
| "phpunit/phpunit": "5.*", | |||
| "squizlabs/php_codesniffer": "3.*" | |||
| }, | |||
| "suggest": { | |||
| "ext-bcmath": "bcmath or gmp is required for decoding larger integers with the pure PHP decoder", | |||
| "ext-gmp": "bcmath or gmp is required for decoding larger integers with the pure PHP decoder", | |||
| "ext-maxminddb": "A C-based database decoder that provides significantly faster lookups" | |||
| }, | |||
| "time": "2019-12-19T22:59:03+00:00", | |||
| "type": "library", | |||
| "installation-source": "dist", | |||
| "autoload": { | |||
| "psr-4": { | |||
| "MaxMind\\Db\\": "src/MaxMind/Db" | |||
| } | |||
| }, | |||
| "notification-url": "https://packagist.org/downloads/", | |||
| "license": [ | |||
| "Apache-2.0" | |||
| ], | |||
| "authors": [ | |||
| { | |||
| "name": "Gregory J. Oschwald", | |||
| "email": "goschwald@maxmind.com", | |||
| "homepage": "https://www.maxmind.com/" | |||
| } | |||
| ], | |||
| "description": "MaxMind DB Reader API", | |||
| "homepage": "https://github.com/maxmind/MaxMind-DB-Reader-php", | |||
| "keywords": [ | |||
| "database", | |||
| "geoip", | |||
| "geoip2", | |||
| "geolocation", | |||
| "maxmind" | |||
| ] | |||
| }, | |||
| { | |||
| "name": "maxmind/web-service-common", | |||
| "version": "v0.6.0", | |||
| "version_normalized": "0.6.0.0", | |||
| "source": { | |||
| "type": "git", | |||
| "url": "https://github.com/maxmind/web-service-common-php.git", | |||
| "reference": "40c928bb0194c45088b369a17f9baef9c3fc7460" | |||
| }, | |||
| "dist": { | |||
| "type": "zip", | |||
| "url": "https://api.github.com/repos/maxmind/web-service-common-php/zipball/40c928bb0194c45088b369a17f9baef9c3fc7460", | |||
| "reference": "40c928bb0194c45088b369a17f9baef9c3fc7460", | |||
| "shasum": "" | |||
| }, | |||
| "require": { | |||
| "composer/ca-bundle": "^1.0.3", | |||
| "ext-curl": "*", | |||
| "ext-json": "*", | |||
| "php": ">=5.6" | |||
| }, | |||
| "require-dev": { | |||
| "friendsofphp/php-cs-fixer": "2.*", | |||
| "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0", | |||
| "squizlabs/php_codesniffer": "3.*" | |||
| }, | |||
| "time": "2019-12-12T15:56:05+00:00", | |||
| "type": "library", | |||
| "installation-source": "dist", | |||
| "autoload": { | |||
| "psr-4": { | |||
| "MaxMind\\Exception\\": "src/Exception", | |||
| "MaxMind\\WebService\\": "src/WebService" | |||
| } | |||
| }, | |||
| "notification-url": "https://packagist.org/downloads/", | |||
| "license": [ | |||
| "Apache-2.0" | |||
| ], | |||
| "authors": [ | |||
| { | |||
| "name": "Gregory Oschwald", | |||
| "email": "goschwald@maxmind.com" | |||
| } | |||
| ], | |||
| "description": "Internal MaxMind Web Service API", | |||
| "homepage": "https://github.com/maxmind/web-service-common-php" | |||
| } | |||
| ] | |||
| @@ -0,0 +1,282 @@ | |||
| CHANGELOG | |||
| ========= | |||
| 2.10.0 (2019-12-12) | |||
| ------------------- | |||
| * PHP 5.6 or greater is now required. | |||
| * The `network` property was added to `GeoIp2\Record\Traits`, | |||
| `GeoIp2\Model\AnonymousIp`, `GeoIp2\Model\Asn`, | |||
| `GeoIp2\Model\ConnectionType`, `Geoip2\Model\Domain`, | |||
| and `GeoIp2\Model\Isp`. This is a string in CIDR format representing the | |||
| largest network where all of the properties besides `ipAddress` have the | |||
| same value. | |||
| * Updated documentation of anonymizer properties - `isAnonymousVpn` | |||
| and `isHostingProvider` - to be more descriptive. | |||
| * The `userCount` property was added to `GeoIp2\Record\Traits`. This is an | |||
| integer which indicates the estimated number of users sharing the | |||
| IP/network during the past 24 hours. This output is available from GeoIP2 | |||
| Precision Insights. | |||
| * The `staticIpScore` property was added to `GeoIp2\Record\Traits`. This is | |||
| a float which indicates how static or dynamic an IP address is. This | |||
| output is available from GeoIP2 Precision Insights. | |||
| 2.9.0 (2018-04-10) | |||
| ------------------ | |||
| * Refer to account IDs using the terminology "account" rather than "user". | |||
| 2.8.0 (2018-01-18) | |||
| ------------------ | |||
| * The `isInEuropeanUnion` property was added to `GeoIp2\Record\Country` | |||
| and `GeoIp2\Record\RepresentedCountry`. This property is `true` if the | |||
| country is a member state of the European Union. | |||
| 2.7.0 (2017-10-27) | |||
| ------------------ | |||
| * The following new anonymizer properties were added to `GeoIp2\Record\Traits` | |||
| for use with GeoIP2 Precision Insights: `isAnonymous`, `isAnonymousVpn`, | |||
| `isHostingProvider`, `isPublicProxy`, and `isTorExitNode`. | |||
| 2.6.0 (2017-07-10) | |||
| ----------------- | |||
| * Code clean-up and tidying. | |||
| * Set minimum required PHP version to 5.4 in `composer.json`. Previously, | |||
| 5.3 would work but was not tested. Now 5.4 is hard minimum version. | |||
| 2.5.0 (2017-05-08) | |||
| ------------------ | |||
| * Support for PHP 5.3 was dropped. | |||
| * Added support for GeoLite2 ASN database. | |||
| 2.4.5 (2017-01-31) | |||
| ------------------ | |||
| * Additional error checking on the data returned from `MaxMind\Db\Reader` | |||
| was added to help detect corrupt databases. GitHub #83. | |||
| 2.4.4 (2016-10-11) | |||
| ------------------ | |||
| * `isset()` on `mostSpecificSubdivision` attribute now returns the | |||
| correct value. Reported by Juan Francisco Giordana. GitHub #81. | |||
| 2.4.3 (2016-10-11) | |||
| ------------------ | |||
| * `isset()` on `name` attribute now returns the correct value. Reported by | |||
| Juan Francisco Giordana. GitHub #79. | |||
| 2.4.2 (2016-08-17) | |||
| ------------------ | |||
| * Updated documentation to clarify what the accuracy radius refers to. | |||
| * Upgraded `maxmind/web-service-common` to 0.3.0. This version uses | |||
| `composer/ca-bundle` rather than our own CA bundle. GitHub #75. | |||
| * Improved PHP documentation generation. | |||
| 2.4.1 (2016-06-10) | |||
| ------------------ | |||
| * Corrected type annotations in documentation. GitHub #66. | |||
| * Updated documentation to reflect that the accuracy radius is now included | |||
| in City. | |||
| * Upgraded web service client, which supports setting a proxy. GitHub #59. | |||
| 2.4.0 (2016-04-15) | |||
| ------------------ | |||
| * Added support for the GeoIP2 Enterprise database. | |||
| 2.3.3 (2015-09-24) | |||
| ------------------ | |||
| * Corrected case on `JsonSerializable` interface. Reported by Axel Etcheverry. | |||
| GitHub #56. | |||
| 2.3.2 (2015-09-23) | |||
| ------------------ | |||
| * `JsonSerializable` compatibility interface was moved to `GeoIp2\Compat` | |||
| rather than the global namespace to prevent autoloading issues. Reported by | |||
| Tomas Buteler. GitHub #54. | |||
| * Missing documentation for the `$postal` property was added to the | |||
| `GeoIp2\Model\City` class. Fix by Roy Sindre Norangshol. GitHub #51. | |||
| * In the Phar distribution, source files for this module no longer have their | |||
| documentation stripped, allowing IDE introspection to work properly. | |||
| Reported by Dominic Black. GitHub #52. | |||
| 2.3.1 (2015-06-30) | |||
| ------------------ | |||
| * Updated `maxmind/web-service-common` to version with fixes for PHP 5.3 and | |||
| 5.4. | |||
| 2.3.0 (2015-06-29) | |||
| ------------------ | |||
| * Support for demographics fields `averageIncome` and `populationDensity` in | |||
| the `Location` record, returned by the Insights endpoint. | |||
| * The `isAnonymousProxy` and `isSatelliteProvider` properties on | |||
| `GeoIP2\Record\Traits` have been deprecated. Please use our [GeoIP2 | |||
| Anonymous IP database](https://www.maxmind.com/en/geoip2-anonymous-ip-database) | |||
| to determine whether an IP address is used by an anonymizing service. | |||
| 2.2.0-beta1 (2015-06-09) | |||
| ------------------------ | |||
| * Typo fix in documentation. | |||
| 2.2.0-alpha2 (2015-06-01) | |||
| ------------------------- | |||
| * `maxmind-ws/web-service-common` was renamed to `maxmind/web-service-common`. | |||
| 2.2.0-alpha1 (2015-05-22) | |||
| ------------------------- | |||
| * The library no longer uses Guzzle and instead uses curl directly. | |||
| * Support for `timeout` and `connectTimout` were added to the `$options` array | |||
| passed to the `GeoIp2\WebService\Client` constructor. Pull request by Will | |||
| Bradley. GitHub #36. | |||
| 2.1.1 (2014-12-03) | |||
| ------------------ | |||
| * The 2.1.0 Phar builds included a shebang line, causing issues when loading | |||
| it as a library. This has been corrected. GitHub #33. | |||
| 2.1.0 (2014-10-29) | |||
| ------------------ | |||
| * Update ApiGen dependency to version that isn't broken on case sensitive | |||
| file systems. | |||
| * Added support for the GeoIP2 Anonymous IP database. The | |||
| `GeoIP2\Database\Reader` class now has an `anonymousIp` method which returns | |||
| a `GeoIP2\Model\AnonymousIp` object. | |||
| * Boolean attributes like those in the `GeoIP2\Record\Traits` class now return | |||
| `false` instead of `null` when they were not true. | |||
| 2.0.0 (2014-09-22) | |||
| ------------------ | |||
| * First production release. | |||
| 0.9.0 (2014-09-15) | |||
| ------------------ | |||
| * IMPORTANT: The deprecated `omni()` and `cityIspOrg()` methods have been | |||
| removed from `GeoIp2\WebService\Client`. | |||
| 0.8.1 (2014-09-12) | |||
| ------------------ | |||
| * The check added to the `GeoIP2\Database\Reader` lookup methods in 0.8.0 did | |||
| not work with the GeoIP2 City Database Subset by Continent with World | |||
| Countries. This has been fixed. Fixes GitHub issue #23. | |||
| 0.8.0 (2014-09-10) | |||
| ------------------ | |||
| * The `GeoIp2\Database\Reader` lookup methods (e.g., `city()`, `isp()`) now | |||
| throw a `BadMethodCallException` if they are used with a database that | |||
| does not match the method. In particular, doing a `city()` lookup on a | |||
| GeoIP2 Country database will result in an exception, and vice versa. | |||
| * A `metadata()` method has been added to the `GeoIP2\Database\Reader` class. | |||
| This returns a `MaxMind\Db\Reader\Metadata` class with information about the | |||
| database. | |||
| * The name attribute was missing from the RepresentedCountry class. | |||
| 0.7.0 (2014-07-22) | |||
| ------------------ | |||
| * The web service client API has been updated for the v2.1 release of the web | |||
| service. In particular, the `cityIspOrg` and `omni` methods on | |||
| `GeoIp2\WebService\Client` should be considered deprecated. The `city` | |||
| method now provides all of the data formerly provided by `cityIspOrg`, and | |||
| the `omni` method has been replaced by the `insights` method. | |||
| * Support was added for GeoIP2 Connection Type, Domain and ISP databases. | |||
| 0.6.3 (2014-05-12) | |||
| ------------------ | |||
| * With the previous Phar builds, some users received `phar error: invalid url | |||
| or non-existent phar` errors. The correct alias is now used for the Phar, | |||
| and this should no longer be an issue. | |||
| 0.6.2 (2014-05-08) | |||
| ------------------ | |||
| * The Phar build was broken with Guzzle 3.9.0+. This has been fixed. | |||
| 0.6.1 (2014-05-01) | |||
| ------------------ | |||
| * This API now officially supports HHVM. | |||
| * The `maxmind-db/reader` dependency was updated to a version that does not | |||
| require BC Math. | |||
| * The Composer compatibility autoload rules are now targeted more narrowly. | |||
| * A `box.json` file is included to build a Phar package. | |||
| 0.6.0 (2014-02-19) | |||
| ------------------ | |||
| * This API is now licensed under the Apache License, Version 2.0. | |||
| * Model and record classes now implement `JsonSerializable`. | |||
| * `isset` now works with model and record classes. | |||
| 0.5.0 (2013-10-21) | |||
| ------------------ | |||
| * Renamed $languages constructor parameters to $locales for both the Client | |||
| and Reader classes. | |||
| * Documentation and code clean-up (Ben Morel). | |||
| * Added the interface `GeoIp2\ProviderInterface`, which is implemented by both | |||
| `\GeoIp2\Database\Reader` and `\GeoIp2\WebService\Client`. | |||
| 0.4.0 (2013-07-16) | |||
| ------------------ | |||
| * This is the first release with the GeoIP2 database reader. Please see the | |||
| `README.md` file and the `\GeoIp2\Database\Reader` class. | |||
| * The general exception classes were replaced with specific exception classes | |||
| representing particular types of errors, such as an authentication error. | |||
| 0.3.0 (2013-07-12) | |||
| ------------------ | |||
| * In namespaces and class names, "GeoIP2" was renamed to "GeoIp2" to improve | |||
| consistency. | |||
| 0.2.1 (2013-06-10) | |||
| ------------------ | |||
| * First official beta release. | |||
| * Documentation updates and corrections. | |||
| 0.2.0 (2013-05-29) | |||
| ------------------ | |||
| * `GenericException` was renamed to `GeoIP2Exception`. | |||
| * We now support more languages. The new languages are de, es, fr, and pt-BR. | |||
| * The REST API now returns a record with data about your account. There is | |||
| a new `GeoIP\Records\MaxMind` class for this data. | |||
| * The `continentCode` attribute on `Continent` was renamed to `code`. | |||
| * Documentation updates. | |||
| 0.1.1 (2013-05-14) | |||
| ------------------ | |||
| * Updated Guzzle version requirement. | |||
| * Fixed Composer example in README.md. | |||
| 0.1.0 (2013-05-13) | |||
| ------------------ | |||
| * Initial release. | |||
| @@ -0,0 +1,202 @@ | |||
| Apache License | |||
| Version 2.0, January 2004 | |||
| http://www.apache.org/licenses/ | |||
| TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | |||
| 1. Definitions. | |||
| "License" shall mean the terms and conditions for use, reproduction, | |||
| and distribution as defined by Sections 1 through 9 of this document. | |||
| "Licensor" shall mean the copyright owner or entity authorized by | |||
| the copyright owner that is granting the License. | |||
| "Legal Entity" shall mean the union of the acting entity and all | |||
| other entities that control, are controlled by, or are under common | |||
| control with that entity. For the purposes of this definition, | |||
| "control" means (i) the power, direct or indirect, to cause the | |||
| direction or management of such entity, whether by contract or | |||
| otherwise, or (ii) ownership of fifty percent (50%) or more of the | |||
| outstanding shares, or (iii) beneficial ownership of such entity. | |||
| "You" (or "Your") shall mean an individual or Legal Entity | |||
| exercising permissions granted by this License. | |||
| "Source" form shall mean the preferred form for making modifications, | |||
| including but not limited to software source code, documentation | |||
| source, and configuration files. | |||
| "Object" form shall mean any form resulting from mechanical | |||
| transformation or translation of a Source form, including but | |||
| not limited to compiled object code, generated documentation, | |||
| and conversions to other media types. | |||
| "Work" shall mean the work of authorship, whether in Source or | |||
| Object form, made available under the License, as indicated by a | |||
| copyright notice that is included in or attached to the work | |||
| (an example is provided in the Appendix below). | |||
| "Derivative Works" shall mean any work, whether in Source or Object | |||
| form, that is based on (or derived from) the Work and for which the | |||
| editorial revisions, annotations, elaborations, or other modifications | |||
| represent, as a whole, an original work of authorship. For the purposes | |||
| of this License, Derivative Works shall not include works that remain | |||
| separable from, or merely link (or bind by name) to the interfaces of, | |||
| the Work and Derivative Works thereof. | |||
| "Contribution" shall mean any work of authorship, including | |||
| the original version of the Work and any modifications or additions | |||
| to that Work or Derivative Works thereof, that is intentionally | |||
| submitted to Licensor for inclusion in the Work by the copyright owner | |||
| or by an individual or Legal Entity authorized to submit on behalf of | |||
| the copyright owner. For the purposes of this definition, "submitted" | |||
| means any form of electronic, verbal, or written communication sent | |||
| to the Licensor or its representatives, including but not limited to | |||
| communication on electronic mailing lists, source code control systems, | |||
| and issue tracking systems that are managed by, or on behalf of, the | |||
| Licensor for the purpose of discussing and improving the Work, but | |||
| excluding communication that is conspicuously marked or otherwise | |||
| designated in writing by the copyright owner as "Not a Contribution." | |||
| "Contributor" shall mean Licensor and any individual or Legal Entity | |||
| on behalf of whom a Contribution has been received by Licensor and | |||
| subsequently incorporated within the Work. | |||
| 2. Grant of Copyright License. Subject to the terms and conditions of | |||
| this License, each Contributor hereby grants to You a perpetual, | |||
| worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |||
| copyright license to reproduce, prepare Derivative Works of, | |||
| publicly display, publicly perform, sublicense, and distribute the | |||
| Work and such Derivative Works in Source or Object form. | |||
| 3. Grant of Patent License. Subject to the terms and conditions of | |||
| this License, each Contributor hereby grants to You a perpetual, | |||
| worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |||
| (except as stated in this section) patent license to make, have made, | |||
| use, offer to sell, sell, import, and otherwise transfer the Work, | |||
| where such license applies only to those patent claims licensable | |||
| by such Contributor that are necessarily infringed by their | |||
| Contribution(s) alone or by combination of their Contribution(s) | |||
| with the Work to which such Contribution(s) was submitted. If You | |||
| institute patent litigation against any entity (including a | |||
| cross-claim or counterclaim in a lawsuit) alleging that the Work | |||
| or a Contribution incorporated within the Work constitutes direct | |||
| or contributory patent infringement, then any patent licenses | |||
| granted to You under this License for that Work shall terminate | |||
| as of the date such litigation is filed. | |||
| 4. Redistribution. You may reproduce and distribute copies of the | |||
| Work or Derivative Works thereof in any medium, with or without | |||
| modifications, and in Source or Object form, provided that You | |||
| meet the following conditions: | |||
| (a) You must give any other recipients of the Work or | |||
| Derivative Works a copy of this License; and | |||
| (b) You must cause any modified files to carry prominent notices | |||
| stating that You changed the files; and | |||
| (c) You must retain, in the Source form of any Derivative Works | |||
| that You distribute, all copyright, patent, trademark, and | |||
| attribution notices from the Source form of the Work, | |||
| excluding those notices that do not pertain to any part of | |||
| the Derivative Works; and | |||
| (d) If the Work includes a "NOTICE" text file as part of its | |||
| distribution, then any Derivative Works that You distribute must | |||
| include a readable copy of the attribution notices contained | |||
| within such NOTICE file, excluding those notices that do not | |||
| pertain to any part of the Derivative Works, in at least one | |||
| of the following places: within a NOTICE text file distributed | |||
| as part of the Derivative Works; within the Source form or | |||
| documentation, if provided along with the Derivative Works; or, | |||
| within a display generated by the Derivative Works, if and | |||
| wherever such third-party notices normally appear. The contents | |||
| of the NOTICE file are for informational purposes only and | |||
| do not modify the License. You may add Your own attribution | |||
| notices within Derivative Works that You distribute, alongside | |||
| or as an addendum to the NOTICE text from the Work, provided | |||
| that such additional attribution notices cannot be construed | |||
| as modifying the License. | |||
| You may add Your own copyright statement to Your modifications and | |||
| may provide additional or different license terms and conditions | |||
| for use, reproduction, or distribution of Your modifications, or | |||
| for any such Derivative Works as a whole, provided Your use, | |||
| reproduction, and distribution of the Work otherwise complies with | |||
| the conditions stated in this License. | |||
| 5. Submission of Contributions. Unless You explicitly state otherwise, | |||
| any Contribution intentionally submitted for inclusion in the Work | |||
| by You to the Licensor shall be under the terms and conditions of | |||
| this License, without any additional terms or conditions. | |||
| Notwithstanding the above, nothing herein shall supersede or modify | |||
| the terms of any separate license agreement you may have executed | |||
| with Licensor regarding such Contributions. | |||
| 6. Trademarks. This License does not grant permission to use the trade | |||
| names, trademarks, service marks, or product names of the Licensor, | |||
| except as required for reasonable and customary use in describing the | |||
| origin of the Work and reproducing the content of the NOTICE file. | |||
| 7. Disclaimer of Warranty. Unless required by applicable law or | |||
| agreed to in writing, Licensor provides the Work (and each | |||
| Contributor provides its Contributions) on an "AS IS" BASIS, | |||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | |||
| implied, including, without limitation, any warranties or conditions | |||
| of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | |||
| PARTICULAR PURPOSE. You are solely responsible for determining the | |||
| appropriateness of using or redistributing the Work and assume any | |||
| risks associated with Your exercise of permissions under this License. | |||
| 8. Limitation of Liability. In no event and under no legal theory, | |||
| whether in tort (including negligence), contract, or otherwise, | |||
| unless required by applicable law (such as deliberate and grossly | |||
| negligent acts) or agreed to in writing, shall any Contributor be | |||
| liable to You for damages, including any direct, indirect, special, | |||
| incidental, or consequential damages of any character arising as a | |||
| result of this License or out of the use or inability to use the | |||
| Work (including but not limited to damages for loss of goodwill, | |||
| work stoppage, computer failure or malfunction, or any and all | |||
| other commercial damages or losses), even if such Contributor | |||
| has been advised of the possibility of such damages. | |||
| 9. Accepting Warranty or Additional Liability. While redistributing | |||
| the Work or Derivative Works thereof, You may choose to offer, | |||
| and charge a fee for, acceptance of support, warranty, indemnity, | |||
| or other liability obligations and/or rights consistent with this | |||
| License. However, in accepting such obligations, You may act only | |||
| on Your own behalf and on Your sole responsibility, not on behalf | |||
| of any other Contributor, and only if You agree to indemnify, | |||
| defend, and hold each Contributor harmless for any liability | |||
| incurred by, or claims asserted against, such Contributor by reason | |||
| of your accepting any such warranty or additional liability. | |||
| END OF TERMS AND CONDITIONS | |||
| APPENDIX: How to apply the Apache License to your work. | |||
| To apply the Apache License to your work, attach the following | |||
| boilerplate notice, with the fields enclosed by brackets "[]" | |||
| replaced with your own identifying information. (Don't include | |||
| the brackets!) The text should be enclosed in the appropriate | |||
| comment syntax for the file format. We also recommend that a | |||
| file or class name and description of purpose be included on the | |||
| same "printed page" as the copyright notice for easier | |||
| identification within third-party archives. | |||
| Copyright [yyyy] [name of copyright owner] | |||
| Licensed under the Apache License, Version 2.0 (the "License"); | |||
| you may not use this file except in compliance with the License. | |||
| You may obtain a copy of the License at | |||
| http://www.apache.org/licenses/LICENSE-2.0 | |||
| Unless required by applicable law or agreed to in writing, software | |||
| distributed under the License is distributed on an "AS IS" BASIS, | |||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| See the License for the specific language governing permissions and | |||
| limitations under the License. | |||
| @@ -0,0 +1,409 @@ | |||
| # GeoIP2 PHP API # | |||
| ## Description ## | |||
| This package provides an API for the GeoIP2 | |||
| [web services](https://dev.maxmind.com/geoip/geoip2/web-services) and | |||
| [databases](https://dev.maxmind.com/geoip/geoip2/downloadable). The API also | |||
| works with the free | |||
| [GeoLite2 databases](https://dev.maxmind.com/geoip/geoip2/geolite2/). | |||
| ## Install via Composer ## | |||
| We recommend installing this package with [Composer](https://getcomposer.org/). | |||
| ### Download Composer ### | |||
| To download Composer, run in the root directory of your project: | |||
| ```bash | |||
| curl -sS https://getcomposer.org/installer | php | |||
| ``` | |||
| You should now have the file `composer.phar` in your project directory. | |||
| ### Install Dependencies ### | |||
| Run in your project root: | |||
| ``` | |||
| php composer.phar require geoip2/geoip2:~2.0 | |||
| ``` | |||
| You should now have the files `composer.json` and `composer.lock` as well as | |||
| the directory `vendor` in your project directory. If you use a version control | |||
| system, `composer.json` should be added to it. | |||
| ### Require Autoloader ### | |||
| After installing the dependencies, you need to require the Composer autoloader | |||
| from your code: | |||
| ```php | |||
| require 'vendor/autoload.php'; | |||
| ``` | |||
| ## Install via Phar ## | |||
| Although we strongly recommend using Composer, we also provide a | |||
| [phar archive](https://php.net/manual/en/book.phar.php) containing most of the | |||
| dependencies for GeoIP2. Our latest phar archive is available on | |||
| [our releases page](https://github.com/maxmind/GeoIP2-php/releases). | |||
| ### Install Dependencies ### | |||
| In order to use the phar archive, you must have the PHP | |||
| [Phar extension](https://php.net/manual/en/book.phar.php) installed and | |||
| enabled. | |||
| If you will be making web service requests, you must have the PHP | |||
| [cURL extension](https://php.net/manual/en/book.curl.php) | |||
| installed to use this archive. For Debian based distributions, this can | |||
| typically be found in the the `php-curl` package. For other operating | |||
| systems, please consult the relevant documentation. After installing the | |||
| extension you may need to restart your web server. | |||
| If you are missing this extension, you will see errors like the following: | |||
| ``` | |||
| PHP Fatal error: Uncaught Error: Call to undefined function MaxMind\WebService\curl_version() | |||
| ``` | |||
| ### Require Package ### | |||
| To use the archive, just require it from your script: | |||
| ```php | |||
| require 'geoip2.phar'; | |||
| ``` | |||
| ## Optional C Extension ## | |||
| The [MaxMind DB API](https://github.com/maxmind/MaxMind-DB-Reader-php) | |||
| includes an optional C extension that you may install to dramatically increase | |||
| the performance of lookups in GeoIP2 or GeoLite2 databases. To install, please | |||
| follow the instructions included with that API. | |||
| The extension has no effect on web-service lookups. | |||
| ## IP Geolocation Usage ## | |||
| IP geolocation is inherently imprecise. Locations are often near the center of | |||
| the population. Any location provided by a GeoIP2 database or web service | |||
| should not be used to identify a particular address or household. | |||
| ## Database Reader ## | |||
| ### Usage ### | |||
| To use this API, you must create a new `\GeoIp2\Database\Reader` object with | |||
| the path to the database file as the first argument to the constructor. You | |||
| may then call the method corresponding to the database you are using. | |||
| If the lookup succeeds, the method call will return a model class for the | |||
| record in the database. This model in turn contains multiple container | |||
| classes for the different parts of the data such as the city in which the | |||
| IP address is located. | |||
| If the record is not found, a `\GeoIp2\Exception\AddressNotFoundException` | |||
| is thrown. If the database is invalid or corrupt, a | |||
| `\MaxMind\Db\InvalidDatabaseException` will be thrown. | |||
| See the API documentation for more details. | |||
| ### City Example ### | |||
| ```php | |||
| <?php | |||
| require_once 'vendor/autoload.php'; | |||
| use GeoIp2\Database\Reader; | |||
| // This creates the Reader object, which should be reused across | |||
| // lookups. | |||
| $reader = new Reader('/usr/local/share/GeoIP/GeoIP2-City.mmdb'); | |||
| // Replace "city" with the appropriate method for your database, e.g., | |||
| // "country". | |||
| $record = $reader->city('128.101.101.101'); | |||
| print($record->country->isoCode . "\n"); // 'US' | |||
| print($record->country->name . "\n"); // 'United States' | |||
| print($record->country->names['zh-CN'] . "\n"); // '美国' | |||
| print($record->mostSpecificSubdivision->name . "\n"); // 'Minnesota' | |||
| print($record->mostSpecificSubdivision->isoCode . "\n"); // 'MN' | |||
| print($record->city->name . "\n"); // 'Minneapolis' | |||
| print($record->postal->code . "\n"); // '55455' | |||
| print($record->location->latitude . "\n"); // 44.9733 | |||
| print($record->location->longitude . "\n"); // -93.2323 | |||
| print($record->traits->network . "\n"); // '128.101.101.101/32' | |||
| ``` | |||
| ### Anonymous IP Example ### | |||
| ```php | |||
| <?php | |||
| require_once 'vendor/autoload.php'; | |||
| use GeoIp2\Database\Reader; | |||
| // This creates the Reader object, which should be reused across | |||
| // lookups. | |||
| $reader = new Reader('/usr/local/share/GeoIP/GeoIP2-Anonymous-IP.mmdb'); | |||
| $record = $reader->anonymousIp('128.101.101.101'); | |||
| if ($record->isAnonymous) { print "anon\n"; } | |||
| print($record->ipAddress . "\n"); // '128.101.101.101' | |||
| print($record->network . "\n"); // '128.101.101.101/32' | |||
| ``` | |||
| ### Connection-Type Example ### | |||
| ```php | |||
| <?php | |||
| require_once 'vendor/autoload.php'; | |||
| use GeoIp2\Database\Reader; | |||
| // This creates the Reader object, which should be reused across | |||
| // lookups. | |||
| $reader = new Reader('/usr/local/share/GeoIP/GeoIP2-Connection-Type.mmdb'); | |||
| $record = $reader->connectionType('128.101.101.101'); | |||
| print($record->connectionType . "\n"); // 'Corporate' | |||
| print($record->ipAddress . "\n"); // '128.101.101.101' | |||
| print($record->network . "\n"); // '128.101.101.101/32' | |||
| ``` | |||
| ### Domain Example ### | |||
| ```php | |||
| <?php | |||
| require_once 'vendor/autoload.php'; | |||
| use GeoIp2\Database\Reader; | |||
| // This creates the Reader object, which should be reused across | |||
| // lookups. | |||
| $reader = new Reader('/usr/local/share/GeoIP/GeoIP2-Domain.mmdb'); | |||
| $record = $reader->domain('128.101.101.101'); | |||
| print($record->domain . "\n"); // 'umn.edu' | |||
| print($record->ipAddress . "\n"); // '128.101.101.101' | |||
| print($record->network . "\n"); // '128.101.101.101/32' | |||
| ``` | |||
| ### Enterprise Example ### | |||
| ```php | |||
| <?php | |||
| require_once 'vendor/autoload.php'; | |||
| use GeoIp2\Database\Reader; | |||
| // This creates the Reader object, which should be reused across | |||
| // lookups. | |||
| $reader = new Reader('/usr/local/share/GeoIP/GeoIP2-Enterprise.mmdb'); | |||
| // Use the ->enterprise method to do a lookup in the Enterprise database | |||
| $record = $reader->enterprise('128.101.101.101'); | |||
| print($record->country->confidence . "\n"); // 99 | |||
| print($record->country->isoCode . "\n"); // 'US' | |||
| print($record->country->name . "\n"); // 'United States' | |||
| print($record->country->names['zh-CN'] . "\n"); // '美国' | |||
| print($record->mostSpecificSubdivision->confidence . "\n"); // 77 | |||
| print($record->mostSpecificSubdivision->name . "\n"); // 'Minnesota' | |||
| print($record->mostSpecificSubdivision->isoCode . "\n"); // 'MN' | |||
| print($record->city->confidence . "\n"); // 60 | |||
| print($record->city->name . "\n"); // 'Minneapolis' | |||
| print($record->postal->code . "\n"); // '55455' | |||
| print($record->location->accuracyRadius . "\n"); // 50 | |||
| print($record->location->latitude . "\n"); // 44.9733 | |||
| print($record->location->longitude . "\n"); // -93.2323 | |||
| print($record->traits->network . "\n"); // '128.101.101.101/32' | |||
| ``` | |||
| ### ISP Example ### | |||
| ```php | |||
| <?php | |||
| require_once 'vendor/autoload.php'; | |||
| use GeoIp2\Database\Reader; | |||
| // This creates the Reader object, which should be reused across | |||
| // lookups. | |||
| $reader = new Reader('/usr/local/share/GeoIP/GeoIP2-ISP.mmdb'); | |||
| $record = $reader->isp('128.101.101.101'); | |||
| print($record->autonomousSystemNumber . "\n"); // 217 | |||
| print($record->autonomousSystemOrganization . "\n"); // 'University of Minnesota' | |||
| print($record->isp . "\n"); // 'University of Minnesota' | |||
| print($record->organization . "\n"); // 'University of Minnesota' | |||
| print($record->ipAddress . "\n"); // '128.101.101.101' | |||
| print($record->network . "\n"); // '128.101.101.101/32' | |||
| ``` | |||
| ## Web Service Client ## | |||
| ### Usage ### | |||
| To use this API, you must create a new `\GeoIp2\WebService\Client` | |||
| object with your `$accountId` and `$licenseKey`, then you call the method | |||
| corresponding to a specific end point, passing it the IP address you want to | |||
| look up. | |||
| If the request succeeds, the method call will return a model class for the end | |||
| point you called. This model in turn contains multiple record classes, each of | |||
| which represents part of the data returned by the web service. | |||
| If there is an error, a structured exception is thrown. | |||
| See the API documentation for more details. | |||
| ### Example ### | |||
| ```php | |||
| <?php | |||
| require_once 'vendor/autoload.php'; | |||
| use GeoIp2\WebService\Client; | |||
| // This creates a Client object that can be reused across requests. | |||
| // Replace "42" with your account ID and "license_key" with your license | |||
| // key. | |||
| $client = new Client(42, 'abcdef123456'); | |||
| // Replace "city" with the method corresponding to the web service that | |||
| // you are using, e.g., "country", "insights". | |||
| $record = $client->city('128.101.101.101'); | |||
| print($record->country->isoCode . "\n"); // 'US' | |||
| print($record->country->name . "\n"); // 'United States' | |||
| print($record->country->names['zh-CN'] . "\n"); // '美国' | |||
| print($record->mostSpecificSubdivision->name . "\n"); // 'Minnesota' | |||
| print($record->mostSpecificSubdivision->isoCode . "\n"); // 'MN' | |||
| print($record->city->name . "\n"); // 'Minneapolis' | |||
| print($record->postal->code . "\n"); // '55455' | |||
| print($record->location->latitude . "\n"); // 44.9733 | |||
| print($record->location->longitude . "\n"); // -93.2323 | |||
| print($record->traits->network . "\n"); // '128.101.101.101/32' | |||
| ``` | |||
| ## Values to use for Database or Array Keys ## | |||
| **We strongly discourage you from using a value from any `names` property as | |||
| a key in a database or array.** | |||
| These names may change between releases. Instead we recommend using one of the | |||
| following: | |||
| * `GeoIp2\Record\City` - `$city->geonameId` | |||
| * `GeoIp2\Record\Continent` - `$continent->code` or `$continent->geonameId` | |||
| * `GeoIp2\Record\Country` and `GeoIp2\Record\RepresentedCountry` - | |||
| `$country->isoCode` or `$country->geonameId` | |||
| * `GeoIp2\Record\Subdivision` - `$subdivision->isoCode` or `$subdivision->geonameId` | |||
| ### What data is returned? ### | |||
| While many of the end points return the same basic records, the attributes | |||
| which can be populated vary between end points. In addition, while an end | |||
| point may offer a particular piece of data, MaxMind does not always have every | |||
| piece of data for any given IP address. | |||
| Because of these factors, it is possible for any end point to return a record | |||
| where some or all of the attributes are unpopulated. | |||
| See the | |||
| [GeoIP2 Precision web service docs](https://dev.maxmind.com/geoip/geoip2/web-services) | |||
| for details on what data each end point may return. | |||
| The only piece of data which is always returned is the `ipAddress` | |||
| attribute in the `GeoIp2\Record\Traits` record. | |||
| ## Integration with GeoNames ## | |||
| [GeoNames](https://www.geonames.org/) offers web services and downloadable | |||
| databases with data on geographical features around the world, including | |||
| populated places. They offer both free and paid premium data. Each | |||
| feature is unique identified by a `geonameId`, which is an integer. | |||
| Many of the records returned by the GeoIP2 web services and databases | |||
| include a `geonameId` property. This is the ID of a geographical feature | |||
| (city, region, country, etc.) in the GeoNames database. | |||
| Some of the data that MaxMind provides is also sourced from GeoNames. We | |||
| source things like place names, ISO codes, and other similar data from | |||
| the GeoNames premium data set. | |||
| ## Reporting data problems ## | |||
| If the problem you find is that an IP address is incorrectly mapped, | |||
| please | |||
| [submit your correction to MaxMind](https://www.maxmind.com/en/correction). | |||
| If you find some other sort of mistake, like an incorrect spelling, | |||
| please check the [GeoNames site](https://www.geonames.org/) first. Once | |||
| you've searched for a place and found it on the GeoNames map view, there | |||
| are a number of links you can use to correct data ("move", "edit", | |||
| "alternate names", etc.). Once the correction is part of the GeoNames | |||
| data set, it will be automatically incorporated into future MaxMind | |||
| releases. | |||
| If you are a paying MaxMind customer and you're not sure where to submit | |||
| a correction, please | |||
| [contact MaxMind support](https://www.maxmind.com/en/support) for help. | |||
| ## Other Support ## | |||
| Please report all issues with this code using the | |||
| [GitHub issue tracker](https://github.com/maxmind/GeoIP2-php/issues). | |||
| If you are having an issue with a MaxMind service that is not specific | |||
| to the client API, please see | |||
| [our support page](https://www.maxmind.com/en/support). | |||
| ## Requirements ## | |||
| This library requires PHP 5.6 or greater. | |||
| This library also relies on the [MaxMind DB Reader](https://github.com/maxmind/MaxMind-DB-Reader-php). | |||
| ## Contributing ## | |||
| Patches and pull requests are encouraged. All code should follow the PSR-2 | |||
| style guidelines. Please include unit tests whenever possible. You may obtain | |||
| the test data for the maxmind-db folder by running `git submodule update | |||
| --init --recursive` or adding `--recursive` to your initial clone, or from | |||
| https://github.com/maxmind/MaxMind-DB | |||
| ## Versioning ## | |||
| The GeoIP2 PHP API uses [Semantic Versioning](https://semver.org/). | |||
| ## Copyright and License ## | |||
| This software is Copyright (c) 2013-2019 by MaxMind, Inc. | |||
| This is free software, licensed under the Apache License, Version 2.0. | |||
| @@ -0,0 +1,31 @@ | |||
| { | |||
| "name": "geoip2/geoip2", | |||
| "description": "MaxMind GeoIP2 PHP API", | |||
| "keywords": ["geoip", "geoip2", "geolocation", "ip", "maxmind"], | |||
| "homepage": "https://github.com/maxmind/GeoIP2-php", | |||
| "type": "library", | |||
| "license": "Apache-2.0", | |||
| "authors": [ | |||
| { | |||
| "name": "Gregory J. Oschwald", | |||
| "email": "goschwald@maxmind.com", | |||
| "homepage": "https://www.maxmind.com/" | |||
| } | |||
| ], | |||
| "require": { | |||
| "maxmind-db/reader": "~1.5", | |||
| "maxmind/web-service-common": "~0.6", | |||
| "php": ">=5.6", | |||
| "ext-json": "*" | |||
| }, | |||
| "require-dev": { | |||
| "friendsofphp/php-cs-fixer": "2.*", | |||
| "phpunit/phpunit": "5.*", | |||
| "squizlabs/php_codesniffer": "3.*" | |||
| }, | |||
| "autoload": { | |||
| "psr-4": { | |||
| "GeoIp2\\": "src" | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,25 @@ | |||
| <?php | |||
| require __DIR__ . '/../vendor/autoload.php'; | |||
| use GeoIp2\Database\Reader; | |||
| srand(0); | |||
| $reader = new Reader('GeoIP2-City.mmdb'); | |||
| $count = 500000; | |||
| $startTime = microtime(true); | |||
| for ($i = 0; $i < $count; ++$i) { | |||
| $ip = long2ip(rand(0, pow(2, 32) - 1)); | |||
| try { | |||
| $t = $reader->city($ip); | |||
| } catch (\GeoIp2\Exception\AddressNotFoundException $e) { | |||
| } | |||
| if ($i % 10000 === 0) { | |||
| echo $i . ' ' . $ip . "\n"; | |||
| } | |||
| } | |||
| $endTime = microtime(true); | |||
| $duration = $endTime - $startTime; | |||
| echo 'Requests per second: ' . $count / $duration . "\n"; | |||
| @@ -0,0 +1,287 @@ | |||
| <?php | |||
| namespace GeoIp2\Database; | |||
| use GeoIp2\Exception\AddressNotFoundException; | |||
| use GeoIp2\ProviderInterface; | |||
| use MaxMind\Db\Reader as DbReader; | |||
| use MaxMind\Db\Reader\InvalidDatabaseException; | |||
| /** | |||
| * Instances of this class provide a reader for the GeoIP2 database format. | |||
| * IP addresses can be looked up using the database specific methods. | |||
| * | |||
| * ## Usage ## | |||
| * | |||
| * The basic API for this class is the same for every database. First, you | |||
| * create a reader object, specifying a file name. You then call the method | |||
| * corresponding to the specific database, passing it the IP address you want | |||
| * to look up. | |||
| * | |||
| * If the request succeeds, the method call will return a model class for | |||
| * the method you called. This model in turn contains multiple record classes, | |||
| * each of which represents part of the data returned by the database. If | |||
| * the database does not contain the requested information, the attributes | |||
| * on the record class will have a `null` value. | |||
| * | |||
| * If the address is not in the database, an | |||
| * {@link \GeoIp2\Exception\AddressNotFoundException} exception will be | |||
| * thrown. If an invalid IP address is passed to one of the methods, a | |||
| * SPL {@link \InvalidArgumentException} will be thrown. If the database is | |||
| * corrupt or invalid, a {@link \MaxMind\Db\Reader\InvalidDatabaseException} | |||
| * will be thrown. | |||
| */ | |||
| class Reader implements ProviderInterface | |||
| { | |||
| private $dbReader; | |||
| private $dbType; | |||
| private $locales; | |||
| /** | |||
| * Constructor. | |||
| * | |||
| * @param string $filename the path to the GeoIP2 database file | |||
| * @param array $locales list of locale codes to use in name property | |||
| * from most preferred to least preferred | |||
| * | |||
| * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database | |||
| * is corrupt or invalid | |||
| */ | |||
| public function __construct( | |||
| $filename, | |||
| $locales = ['en'] | |||
| ) { | |||
| $this->dbReader = new DbReader($filename); | |||
| $this->dbType = $this->dbReader->metadata()->databaseType; | |||
| $this->locales = $locales; | |||
| } | |||
| /** | |||
| * This method returns a GeoIP2 City model. | |||
| * | |||
| * @param string $ipAddress an IPv4 or IPv6 address as a string | |||
| * | |||
| * @throws \GeoIp2\Exception\AddressNotFoundException if the address is | |||
| * not in the database | |||
| * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database | |||
| * is corrupt or invalid | |||
| * | |||
| * @return \GeoIp2\Model\City | |||
| */ | |||
| public function city($ipAddress) | |||
| { | |||
| return $this->modelFor('City', 'City', $ipAddress); | |||
| } | |||
| /** | |||
| * This method returns a GeoIP2 Country model. | |||
| * | |||
| * @param string $ipAddress an IPv4 or IPv6 address as a string | |||
| * | |||
| * @throws \GeoIp2\Exception\AddressNotFoundException if the address is | |||
| * not in the database | |||
| * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database | |||
| * is corrupt or invalid | |||
| * | |||
| * @return \GeoIp2\Model\Country | |||
| */ | |||
| public function country($ipAddress) | |||
| { | |||
| return $this->modelFor('Country', 'Country', $ipAddress); | |||
| } | |||
| /** | |||
| * This method returns a GeoIP2 Anonymous IP model. | |||
| * | |||
| * @param string $ipAddress an IPv4 or IPv6 address as a string | |||
| * | |||
| * @throws \GeoIp2\Exception\AddressNotFoundException if the address is | |||
| * not in the database | |||
| * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database | |||
| * is corrupt or invalid | |||
| * | |||
| * @return \GeoIp2\Model\AnonymousIp | |||
| */ | |||
| public function anonymousIp($ipAddress) | |||
| { | |||
| return $this->flatModelFor( | |||
| 'AnonymousIp', | |||
| 'GeoIP2-Anonymous-IP', | |||
| $ipAddress | |||
| ); | |||
| } | |||
| /** | |||
| * This method returns a GeoLite2 ASN model. | |||
| * | |||
| * @param string $ipAddress an IPv4 or IPv6 address as a string | |||
| * | |||
| * @throws \GeoIp2\Exception\AddressNotFoundException if the address is | |||
| * not in the database | |||
| * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database | |||
| * is corrupt or invalid | |||
| * | |||
| * @return \GeoIp2\Model\Asn | |||
| */ | |||
| public function asn($ipAddress) | |||
| { | |||
| return $this->flatModelFor( | |||
| 'Asn', | |||
| 'GeoLite2-ASN', | |||
| $ipAddress | |||
| ); | |||
| } | |||
| /** | |||
| * This method returns a GeoIP2 Connection Type model. | |||
| * | |||
| * @param string $ipAddress an IPv4 or IPv6 address as a string | |||
| * | |||
| * @throws \GeoIp2\Exception\AddressNotFoundException if the address is | |||
| * not in the database | |||
| * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database | |||
| * is corrupt or invalid | |||
| * | |||
| * @return \GeoIp2\Model\ConnectionType | |||
| */ | |||
| public function connectionType($ipAddress) | |||
| { | |||
| return $this->flatModelFor( | |||
| 'ConnectionType', | |||
| 'GeoIP2-Connection-Type', | |||
| $ipAddress | |||
| ); | |||
| } | |||
| /** | |||
| * This method returns a GeoIP2 Domain model. | |||
| * | |||
| * @param string $ipAddress an IPv4 or IPv6 address as a string | |||
| * | |||
| * @throws \GeoIp2\Exception\AddressNotFoundException if the address is | |||
| * not in the database | |||
| * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database | |||
| * is corrupt or invalid | |||
| * | |||
| * @return \GeoIp2\Model\Domain | |||
| */ | |||
| public function domain($ipAddress) | |||
| { | |||
| return $this->flatModelFor( | |||
| 'Domain', | |||
| 'GeoIP2-Domain', | |||
| $ipAddress | |||
| ); | |||
| } | |||
| /** | |||
| * This method returns a GeoIP2 Enterprise model. | |||
| * | |||
| * @param string $ipAddress an IPv4 or IPv6 address as a string | |||
| * | |||
| * @throws \GeoIp2\Exception\AddressNotFoundException if the address is | |||
| * not in the database | |||
| * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database | |||
| * is corrupt or invalid | |||
| * | |||
| * @return \GeoIp2\Model\Enterprise | |||
| */ | |||
| public function enterprise($ipAddress) | |||
| { | |||
| return $this->modelFor('Enterprise', 'Enterprise', $ipAddress); | |||
| } | |||
| /** | |||
| * This method returns a GeoIP2 ISP model. | |||
| * | |||
| * @param string $ipAddress an IPv4 or IPv6 address as a string | |||
| * | |||
| * @throws \GeoIp2\Exception\AddressNotFoundException if the address is | |||
| * not in the database | |||
| * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database | |||
| * is corrupt or invalid | |||
| * | |||
| * @return \GeoIp2\Model\Isp | |||
| */ | |||
| public function isp($ipAddress) | |||
| { | |||
| return $this->flatModelFor( | |||
| 'Isp', | |||
| 'GeoIP2-ISP', | |||
| $ipAddress | |||
| ); | |||
| } | |||
| private function modelFor($class, $type, $ipAddress) | |||
| { | |||
| list($record, $prefixLen) = $this->getRecord($class, $type, $ipAddress); | |||
| $record['traits']['ip_address'] = $ipAddress; | |||
| $record['traits']['prefix_len'] = $prefixLen; | |||
| $class = 'GeoIp2\\Model\\' . $class; | |||
| return new $class($record, $this->locales); | |||
| } | |||
| private function flatModelFor($class, $type, $ipAddress) | |||
| { | |||
| list($record, $prefixLen) = $this->getRecord($class, $type, $ipAddress); | |||
| $record['ip_address'] = $ipAddress; | |||
| $record['prefix_len'] = $prefixLen; | |||
| $class = 'GeoIp2\\Model\\' . $class; | |||
| return new $class($record); | |||
| } | |||
| private function getRecord($class, $type, $ipAddress) | |||
| { | |||
| if (strpos($this->dbType, $type) === false) { | |||
| $method = lcfirst($class); | |||
| throw new \BadMethodCallException( | |||
| "The $method method cannot be used to open a {$this->dbType} database" | |||
| ); | |||
| } | |||
| list($record, $prefixLen) = $this->dbReader->getWithPrefixLen($ipAddress); | |||
| if ($record === null) { | |||
| throw new AddressNotFoundException( | |||
| "The address $ipAddress is not in the database." | |||
| ); | |||
| } | |||
| if (!\is_array($record)) { | |||
| // This can happen on corrupt databases. Generally, | |||
| // MaxMind\Db\Reader will throw a | |||
| // MaxMind\Db\Reader\InvalidDatabaseException, but occasionally | |||
| // the lookup may result in a record that looks valid but is not | |||
| // an array. This mostly happens when the user is ignoring all | |||
| // exceptions and the more frequent InvalidDatabaseException | |||
| // exceptions go unnoticed. | |||
| throw new InvalidDatabaseException( | |||
| "Expected an array when looking up $ipAddress but received: " | |||
| . \gettype($record) | |||
| ); | |||
| } | |||
| return [$record, $prefixLen]; | |||
| } | |||
| /** | |||
| * @throws \InvalidArgumentException if arguments are passed to the method | |||
| * @throws \BadMethodCallException if the database has been closed | |||
| * | |||
| * @return \MaxMind\Db\Reader\Metadata object for the database | |||
| */ | |||
| public function metadata() | |||
| { | |||
| return $this->dbReader->metadata(); | |||
| } | |||
| /** | |||
| * Closes the GeoIP2 database and returns the resources to the system. | |||
| */ | |||
| public function close() | |||
| { | |||
| $this->dbReader->close(); | |||
| } | |||
| } | |||
| @@ -0,0 +1,10 @@ | |||
| <?php | |||
| namespace GeoIp2\Exception; | |||
| /** | |||
| * This class represents a generic error. | |||
| */ | |||
| class AddressNotFoundException extends GeoIp2Exception | |||
| { | |||
| } | |||
| @@ -0,0 +1,10 @@ | |||
| <?php | |||
| namespace GeoIp2\Exception; | |||
| /** | |||
| * This class represents a generic error. | |||
| */ | |||
| class AuthenticationException extends GeoIp2Exception | |||
| { | |||
| } | |||
| @@ -0,0 +1,10 @@ | |||
| <?php | |||
| namespace GeoIp2\Exception; | |||
| /** | |||
| * This class represents a generic error. | |||
| */ | |||
| class GeoIp2Exception extends \Exception | |||
| { | |||
| } | |||
| @@ -0,0 +1,24 @@ | |||
| <?php | |||
| namespace GeoIp2\Exception; | |||
| /** | |||
| * This class represents an HTTP transport error. | |||
| */ | |||
| class HttpException extends GeoIp2Exception | |||
| { | |||
| /** | |||
| * The URI queried. | |||
| */ | |||
| public $uri; | |||
| public function __construct( | |||
| $message, | |||
| $httpStatus, | |||
| $uri, | |||
| \Exception $previous = null | |||
| ) { | |||
| $this->uri = $uri; | |||
| parent::__construct($message, $httpStatus, $previous); | |||
| } | |||
| } | |||
| @@ -0,0 +1,26 @@ | |||
| <?php | |||
| namespace GeoIp2\Exception; | |||
| /** | |||
| * This class represents an error returned by MaxMind's GeoIP2 | |||
| * web service. | |||
| */ | |||
| class InvalidRequestException extends HttpException | |||
| { | |||
| /** | |||
| * The code returned by the MaxMind web service. | |||
| */ | |||
| public $error; | |||
| public function __construct( | |||
| $message, | |||
| $error, | |||
| $httpStatus, | |||
| $uri, | |||
| \Exception $previous = null | |||
| ) { | |||
| $this->error = $error; | |||
| parent::__construct($message, $httpStatus, $uri, $previous); | |||
| } | |||
| } | |||
| @@ -0,0 +1,10 @@ | |||
| <?php | |||
| namespace GeoIp2\Exception; | |||
| /** | |||
| * This class represents a generic error. | |||
| */ | |||
| class OutOfQueriesException extends GeoIp2Exception | |||
| { | |||
| } | |||
| @@ -0,0 +1,67 @@ | |||
| <?php | |||
| namespace GeoIp2\Model; | |||
| /** | |||
| * @ignore | |||
| */ | |||
| abstract class AbstractModel implements \JsonSerializable | |||
| { | |||
| protected $raw; | |||
| /** | |||
| * @ignore | |||
| * | |||
| * @param mixed $raw | |||
| */ | |||
| public function __construct($raw) | |||
| { | |||
| $this->raw = $raw; | |||
| } | |||
| /** | |||
| * @ignore | |||
| * | |||
| * @param mixed $field | |||
| */ | |||
| protected function get($field) | |||
| { | |||
| if (isset($this->raw[$field])) { | |||
| return $this->raw[$field]; | |||
| } | |||
| if (preg_match('/^is_/', $field)) { | |||
| return false; | |||
| } | |||
| return null; | |||
| } | |||
| /** | |||
| * @ignore | |||
| * | |||
| * @param mixed $attr | |||
| */ | |||
| public function __get($attr) | |||
| { | |||
| if ($attr !== 'instance' && property_exists($this, $attr)) { | |||
| return $this->$attr; | |||
| } | |||
| throw new \RuntimeException("Unknown attribute: $attr"); | |||
| } | |||
| /** | |||
| * @ignore | |||
| * | |||
| * @param mixed $attr | |||
| */ | |||
| public function __isset($attr) | |||
| { | |||
| return $attr !== 'instance' && isset($this->$attr); | |||
| } | |||
| public function jsonSerialize() | |||
| { | |||
| return $this->raw; | |||
| } | |||
| } | |||
| @@ -0,0 +1,56 @@ | |||
| <?php | |||
| namespace GeoIp2\Model; | |||
| use GeoIp2\Util; | |||
| /** | |||
| * This class provides the GeoIP2 Anonymous IP model. | |||
| * | |||
| * @property-read bool $isAnonymous This is true if the IP address belongs to | |||
| * any sort of anonymous network. | |||
| * @property-read bool $isAnonymousVpn This is true if the IP address is | |||
| * registered to an anonymous VPN provider. If a VPN provider does not | |||
| * register subnets under names associated with them, we will likely only | |||
| * flag their IP ranges using the isHostingProvider property. | |||
| * @property-read bool $isHostingProvider This is true if the IP address belongs | |||
| * to a hosting or VPN provider (see description of isAnonymousVpn property). | |||
| * @property-read bool $isPublicProxy This is true if the IP address belongs to | |||
| * a public proxy. | |||
| * @property-read bool $isTorExitNode This is true if the IP address is a Tor | |||
| * exit node. | |||
| * @property-read string $ipAddress The IP address that the data in the model is | |||
| * for. | |||
| * @property-read string $network The network in CIDR notation associated with | |||
| * the record. In particular, this is the largest network where all of the | |||
| * fields besides $ipAddress have the same value. | |||
| */ | |||
| class AnonymousIp extends AbstractModel | |||
| { | |||
| protected $isAnonymous; | |||
| protected $isAnonymousVpn; | |||
| protected $isHostingProvider; | |||
| protected $isPublicProxy; | |||
| protected $isTorExitNode; | |||
| protected $ipAddress; | |||
| protected $network; | |||
| /** | |||
| * @ignore | |||
| * | |||
| * @param mixed $raw | |||
| */ | |||
| public function __construct($raw) | |||
| { | |||
| parent::__construct($raw); | |||
| $this->isAnonymous = $this->get('is_anonymous'); | |||
| $this->isAnonymousVpn = $this->get('is_anonymous_vpn'); | |||
| $this->isHostingProvider = $this->get('is_hosting_provider'); | |||
| $this->isPublicProxy = $this->get('is_public_proxy'); | |||
| $this->isTorExitNode = $this->get('is_tor_exit_node'); | |||
| $ipAddress = $this->get('ip_address'); | |||
| $this->ipAddress = $ipAddress; | |||
| $this->network = Util::cidr($ipAddress, $this->get('prefix_len')); | |||
| } | |||
| } | |||
| @@ -0,0 +1,43 @@ | |||
| <?php | |||
| namespace GeoIp2\Model; | |||
| use GeoIp2\Util; | |||
| /** | |||
| * This class provides the GeoLite2 ASN model. | |||
| * | |||
| * @property-read int|null $autonomousSystemNumber The autonomous system number | |||
| * associated with the IP address. | |||
| * @property-read string|null $autonomousSystemOrganization The organization | |||
| * associated with the registered autonomous system number for the IP | |||
| * address. | |||
| * @property-read string $ipAddress The IP address that the data in the model is | |||
| * for. | |||
| * @property-read string $network The network in CIDR notation associated with | |||
| * the record. In particular, this is the largest network where all of the | |||
| * fields besides $ipAddress have the same value. | |||
| */ | |||
| class Asn extends AbstractModel | |||
| { | |||
| protected $autonomousSystemNumber; | |||
| protected $autonomousSystemOrganization; | |||
| protected $ipAddress; | |||
| protected $network; | |||
| /** | |||
| * @ignore | |||
| * | |||
| * @param mixed $raw | |||
| */ | |||
| public function __construct($raw) | |||
| { | |||
| parent::__construct($raw); | |||
| $this->autonomousSystemNumber = $this->get('autonomous_system_number'); | |||
| $this->autonomousSystemOrganization = | |||
| $this->get('autonomous_system_organization'); | |||
| $ipAddress = $this->get('ip_address'); | |||
| $this->ipAddress = $ipAddress; | |||
| $this->network = Util::cidr($ipAddress, $this->get('prefix_len')); | |||
| } | |||
| } | |||
| @@ -0,0 +1,116 @@ | |||
| <?php | |||
| namespace GeoIp2\Model; | |||
| /** | |||
| * Model class for the data returned by GeoIP2 City web service and database. | |||
| * | |||
| * The only difference between the City and Insights model classes is which | |||
| * fields in each record may be populated. See | |||
| * https://dev.maxmind.com/geoip/geoip2/web-services for more details. | |||
| * | |||
| * @property-read \GeoIp2\Record\City $city City data for the requested IP | |||
| * address. | |||
| * @property-read \GeoIp2\Record\Location $location Location data for the | |||
| * requested IP address. | |||
| * @property-read \GeoIp2\Record\Postal $postal Postal data for the | |||
| * requested IP address. | |||
| * @property-read array $subdivisions An array \GeoIp2\Record\Subdivision | |||
| * objects representing the country subdivisions for the requested IP | |||
| * address. The number and type of subdivisions varies by country, but a | |||
| * subdivision is typically a state, province, county, etc. Subdivisions | |||
| * are ordered from most general (largest) to most specific (smallest). | |||
| * If the response did not contain any subdivisions, this method returns | |||
| * an empty array. | |||
| * @property-read \GeoIp2\Record\Subdivision $mostSpecificSubdivision An object | |||
| * representing the most specific subdivision returned. If the response | |||
| * did not contain any subdivisions, this method returns an empty | |||
| * \GeoIp2\Record\Subdivision object. | |||
| */ | |||
| class City extends Country | |||
| { | |||
| /** | |||
| * @ignore | |||
| */ | |||
| protected $city; | |||
| /** | |||
| * @ignore | |||
| */ | |||
| protected $location; | |||
| /** | |||
| * @ignore | |||
| */ | |||
| protected $postal; | |||
| /** | |||
| * @ignore | |||
| */ | |||
| protected $subdivisions = []; | |||
| /** | |||
| * @ignore | |||
| * | |||
| * @param mixed $raw | |||
| * @param mixed $locales | |||
| */ | |||
| public function __construct($raw, $locales = ['en']) | |||
| { | |||
| parent::__construct($raw, $locales); | |||
| $this->city = new \GeoIp2\Record\City($this->get('city'), $locales); | |||
| $this->location = new \GeoIp2\Record\Location($this->get('location')); | |||
| $this->postal = new \GeoIp2\Record\Postal($this->get('postal')); | |||
| $this->createSubdivisions($raw, $locales); | |||
| } | |||
| private function createSubdivisions($raw, $locales) | |||
| { | |||
| if (!isset($raw['subdivisions'])) { | |||
| return; | |||
| } | |||
| foreach ($raw['subdivisions'] as $sub) { | |||
| array_push( | |||
| $this->subdivisions, | |||
| new \GeoIp2\Record\Subdivision($sub, $locales) | |||
| ); | |||
| } | |||
| } | |||
| /** | |||
| * @ignore | |||
| * | |||
| * @param mixed $attr | |||
| */ | |||
| public function __get($attr) | |||
| { | |||
| if ($attr === 'mostSpecificSubdivision') { | |||
| return $this->$attr(); | |||
| } | |||
| return parent::__get($attr); | |||
| } | |||
| /** | |||
| * @ignore | |||
| * | |||
| * @param mixed $attr | |||
| */ | |||
| public function __isset($attr) | |||
| { | |||
| if ($attr === 'mostSpecificSubdivision') { | |||
| // We always return a mostSpecificSubdivision, even if it is the | |||
| // empty subdivision | |||
| return true; | |||
| } | |||
| return parent::__isset($attr); | |||
| } | |||
| private function mostSpecificSubdivision() | |||
| { | |||
| return empty($this->subdivisions) ? | |||
| new \GeoIp2\Record\Subdivision([], $this->locales) : | |||
| end($this->subdivisions); | |||
| } | |||
| } | |||
| @@ -0,0 +1,39 @@ | |||
| <?php | |||
| namespace GeoIp2\Model; | |||
| use GeoIp2\Util; | |||
| /** | |||
| * This class provides the GeoIP2 Connection-Type model. | |||
| * | |||
| * @property-read string|null $connectionType The connection type may take the | |||
| * following values: "Dialup", "Cable/DSL", "Corporate", "Cellular". | |||
| * Additional values may be added in the future. | |||
| * @property-read string $ipAddress The IP address that the data in the model is | |||
| * for. | |||
| * @property-read string $network The network in CIDR notation associated with | |||
| * the record. In particular, this is the largest network where all of the | |||
| * fields besides $ipAddress have the same value. | |||
| */ | |||
| class ConnectionType extends AbstractModel | |||
| { | |||
| protected $connectionType; | |||
| protected $ipAddress; | |||
| protected $network; | |||
| /** | |||
| * @ignore | |||
| * | |||
| * @param mixed $raw | |||
| */ | |||
| public function __construct($raw) | |||
| { | |||
| parent::__construct($raw); | |||
| $this->connectionType = $this->get('connection_type'); | |||
| $ipAddress = $this->get('ip_address'); | |||
| $this->ipAddress = $ipAddress; | |||
| $this->network = Util::cidr($ipAddress, $this->get('prefix_len')); | |||
| } | |||
| } | |||
| @@ -0,0 +1,71 @@ | |||
| <?php | |||
| namespace GeoIp2\Model; | |||
| /** | |||
| * Model class for the data returned by GeoIP2 Country web service and database. | |||
| * | |||
| * The only difference between the City and Insights model classes is which | |||
| * fields in each record may be populated. See | |||
| * https://dev.maxmind.com/geoip/geoip2/web-services for more details. | |||
| * | |||
| * @property-read \GeoIp2\Record\Continent $continent Continent data for the | |||
| * requested IP address. | |||
| * @property-read \GeoIp2\Record\Country $country Country data for the requested | |||
| * IP address. This object represents the country where MaxMind believes the | |||
| * end user is located. | |||
| * @property-read \GeoIp2\Record\MaxMind $maxmind Data related to your MaxMind | |||
| * account. | |||
| * @property-read \GeoIp2\Record\Country $registeredCountry Registered country | |||
| * data for the requested IP address. This record represents the country | |||
| * where the ISP has registered a given IP block and may differ from the | |||
| * user's country. | |||
| * @property-read \GeoIp2\Record\RepresentedCountry $representedCountry | |||
| * Represented country data for the requested IP address. The represented | |||
| * country is used for things like military bases. It is only present when | |||
| * the represented country differs from the country. | |||
| * @property-read \GeoIp2\Record\Traits $traits Data for the traits of the | |||
| * requested IP address. | |||
| */ | |||
| class Country extends AbstractModel | |||
| { | |||
| protected $continent; | |||
| protected $country; | |||
| protected $locales; | |||
| protected $maxmind; | |||
| protected $registeredCountry; | |||
| protected $representedCountry; | |||
| protected $traits; | |||
| /** | |||
| * @ignore | |||
| * | |||
| * @param mixed $raw | |||
| * @param mixed $locales | |||
| */ | |||
| public function __construct($raw, $locales = ['en']) | |||
| { | |||
| parent::__construct($raw); | |||
| $this->continent = new \GeoIp2\Record\Continent( | |||
| $this->get('continent'), | |||
| $locales | |||
| ); | |||
| $this->country = new \GeoIp2\Record\Country( | |||
| $this->get('country'), | |||
| $locales | |||
| ); | |||
| $this->maxmind = new \GeoIp2\Record\MaxMind($this->get('maxmind')); | |||
| $this->registeredCountry = new \GeoIp2\Record\Country( | |||
| $this->get('registered_country'), | |||
| $locales | |||
| ); | |||
| $this->representedCountry = new \GeoIp2\Record\RepresentedCountry( | |||
| $this->get('represented_country'), | |||
| $locales | |||
| ); | |||
| $this->traits = new \GeoIp2\Record\Traits($this->get('traits')); | |||
| $this->locales = $locales; | |||
| } | |||
| } | |||
| @@ -0,0 +1,39 @@ | |||
| <?php | |||
| namespace GeoIp2\Model; | |||
| use GeoIp2\Util; | |||
| /** | |||
| * This class provides the GeoIP2 Domain model. | |||
| * | |||
| * @property-read string|null $domain The second level domain associated with the | |||
| * IP address. This will be something like "example.com" or | |||
| * "example.co.uk", not "foo.example.com". | |||
| * @property-read string $ipAddress The IP address that the data in the model is | |||
| * for. | |||
| * @property-read string $network The network in CIDR notation associated with | |||
| * the record. In particular, this is the largest network where all of the | |||
| * fields besides $ipAddress have the same value. | |||
| */ | |||
| class Domain extends AbstractModel | |||
| { | |||
| protected $domain; | |||
| protected $ipAddress; | |||
| protected $network; | |||
| /** | |||
| * @ignore | |||
| * | |||
| * @param mixed $raw | |||
| */ | |||
| public function __construct($raw) | |||
| { | |||
| parent::__construct($raw); | |||
| $this->domain = $this->get('domain'); | |||
| $ipAddress = $this->get('ip_address'); | |||
| $this->ipAddress = $ipAddress; | |||
| $this->network = Util::cidr($ipAddress, $this->get('prefix_len')); | |||
| } | |||
| } | |||
| @@ -0,0 +1,14 @@ | |||
| <?php | |||
| namespace GeoIp2\Model; | |||
| /** | |||
| * Model class for the data returned by GeoIP2 Enterprise database lookups. | |||
| * | |||
| * The only difference between the City and Enterprise model classes is which | |||
| * fields in each record may be populated. See | |||
| * https://dev.maxmind.com/geoip/geoip2/web-services for more details. | |||
| */ | |||
| class Enterprise extends City | |||
| { | |||
| } | |||
| @@ -0,0 +1,14 @@ | |||
| <?php | |||
| namespace GeoIp2\Model; | |||
| /** | |||
| * Model class for the data returned by GeoIP2 Precision: Insights web service. | |||
| * | |||
| * The only difference between the City and Insights model classes is which | |||
| * fields in each record may be populated. See | |||
| * https://dev.maxmind.com/geoip/geoip2/web-services for more details. | |||
| */ | |||
| class Insights extends City | |||
| { | |||
| } | |||
| @@ -0,0 +1,52 @@ | |||
| <?php | |||
| namespace GeoIp2\Model; | |||
| use GeoIp2\Util; | |||
| /** | |||
| * This class provides the GeoIP2 ISP model. | |||
| * | |||
| * @property-read int|null $autonomousSystemNumber The autonomous system number | |||
| * associated with the IP address. | |||
| * @property-read string|null $autonomousSystemOrganization The organization | |||
| * associated with the registered autonomous system number for the IP | |||
| * address. | |||
| * @property-read string|null $isp The name of the ISP associated with the IP | |||
| * address. | |||
| * @property-read string|null $organization The name of the organization associated | |||
| * with the IP address. | |||
| * @property-read string $ipAddress The IP address that the data in the model is | |||
| * for. | |||
| * @property-read string $network The network in CIDR notation associated with | |||
| * the record. In particular, this is the largest network where all of the | |||
| * fields besides $ipAddress have the same value. | |||
| */ | |||
| class Isp extends AbstractModel | |||
| { | |||
| protected $autonomousSystemNumber; | |||
| protected $autonomousSystemOrganization; | |||
| protected $isp; | |||
| protected $organization; | |||
| protected $ipAddress; | |||
| protected $network; | |||
| /** | |||
| * @ignore | |||
| * | |||
| * @param mixed $raw | |||
| */ | |||
| public function __construct($raw) | |||
| { | |||
| parent::__construct($raw); | |||
| $this->autonomousSystemNumber = $this->get('autonomous_system_number'); | |||
| $this->autonomousSystemOrganization = | |||
| $this->get('autonomous_system_organization'); | |||
| $this->isp = $this->get('isp'); | |||
| $this->organization = $this->get('organization'); | |||
| $ipAddress = $this->get('ip_address'); | |||
| $this->ipAddress = $ipAddress; | |||
| $this->network = Util::cidr($ipAddress, $this->get('prefix_len')); | |||
| } | |||
| } | |||
| @@ -0,0 +1,20 @@ | |||
| <?php | |||
| namespace GeoIp2; | |||
| interface ProviderInterface | |||
| { | |||
| /** | |||
| * @param string $ipAddress an IPv4 or IPv6 address to lookup | |||
| * | |||
| * @return \GeoIp2\Model\Country a Country model for the requested IP address | |||
| */ | |||
| public function country($ipAddress); | |||
| /** | |||
| * @param string $ipAddress an IPv4 or IPv6 address to lookup | |||
| * | |||
| * @return \GeoIp2\Model\City a City model for the requested IP address | |||
| */ | |||
| public function city($ipAddress); | |||
| } | |||
| @@ -0,0 +1,66 @@ | |||
| <?php | |||
| namespace GeoIp2\Record; | |||
| abstract class AbstractPlaceRecord extends AbstractRecord | |||
| { | |||
| private $locales; | |||
| /** | |||
| * @ignore | |||
| * | |||
| * @param mixed $record | |||
| * @param mixed $locales | |||
| */ | |||
| public function __construct($record, $locales = ['en']) | |||
| { | |||
| $this->locales = $locales; | |||
| parent::__construct($record); | |||
| } | |||
| /** | |||
| * @ignore | |||
| * | |||
| * @param mixed $attr | |||
| */ | |||
| public function __get($attr) | |||
| { | |||
| if ($attr === 'name') { | |||
| return $this->name(); | |||
| } | |||
| return parent::__get($attr); | |||
| } | |||
| /** | |||
| * @ignore | |||
| * | |||
| * @param mixed $attr | |||
| */ | |||
| public function __isset($attr) | |||
| { | |||
| if ($attr === 'name') { | |||
| return $this->firstSetNameLocale() === null ? false : true; | |||
| } | |||
| return parent::__isset($attr); | |||
| } | |||
| private function name() | |||
| { | |||
| $locale = $this->firstSetNameLocale(); | |||
| return $locale === null ? null : $this->names[$locale]; | |||
| } | |||
| private function firstSetNameLocale() | |||
| { | |||
| foreach ($this->locales as $locale) { | |||
| if (isset($this->names[$locale])) { | |||
| return $locale; | |||
| } | |||
| } | |||
| return null; | |||
| } | |||
| } | |||
| @@ -0,0 +1,61 @@ | |||
| <?php | |||
| namespace GeoIp2\Record; | |||
| abstract class AbstractRecord implements \JsonSerializable | |||
| { | |||
| private $record; | |||
| /** | |||
| * @ignore | |||
| * | |||
| * @param mixed $record | |||
| */ | |||
| public function __construct($record) | |||
| { | |||
| $this->record = isset($record) ? $record : []; | |||
| } | |||
| /** | |||
| * @ignore | |||
| * | |||
| * @param mixed $attr | |||
| */ | |||
| public function __get($attr) | |||
| { | |||
| // XXX - kind of ugly but greatly reduces boilerplate code | |||
| $key = $this->attributeToKey($attr); | |||
| if ($this->__isset($attr)) { | |||
| return $this->record[$key]; | |||
| } elseif ($this->validAttribute($attr)) { | |||
| if (preg_match('/^is_/', $key)) { | |||
| return false; | |||
| } | |||
| return null; | |||
| } | |||
| throw new \RuntimeException("Unknown attribute: $attr"); | |||
| } | |||
| public function __isset($attr) | |||
| { | |||
| return $this->validAttribute($attr) && | |||
| isset($this->record[$this->attributeToKey($attr)]); | |||
| } | |||
| private function attributeToKey($attr) | |||
| { | |||
| return strtolower(preg_replace('/([A-Z])/', '_\1', $attr)); | |||
| } | |||
| private function validAttribute($attr) | |||
| { | |||
| return \in_array($attr, $this->validAttributes, true); | |||
| } | |||
| public function jsonSerialize() | |||
| { | |||
| return $this->record; | |||
| } | |||
| } | |||
| @@ -0,0 +1,29 @@ | |||
| <?php | |||
| namespace GeoIp2\Record; | |||
| /** | |||
| * City-level data associated with an IP address. | |||
| * | |||
| * This record is returned by all location services and databases besides | |||
| * Country. | |||
| * | |||
| * @property-read int|null $confidence A value from 0-100 indicating MaxMind's | |||
| * confidence that the city is correct. This attribute is only available | |||
| * from the Insights service and the GeoIP2 Enterprise database. | |||
| * @property-read int|null $geonameId The GeoName ID for the city. This attribute | |||
| * is returned by all location services and databases. | |||
| * @property-read string|null $name The name of the city based on the locales list | |||
| * passed to the constructor. This attribute is returned by all location | |||
| * services and databases. | |||
| * @property-read array|null $names A array map where the keys are locale codes | |||
| * and the values are names. This attribute is returned by all location | |||
| * services and databases. | |||
| */ | |||
| class City extends AbstractPlaceRecord | |||
| { | |||
| /** | |||
| * @ignore | |||
| */ | |||
| protected $validAttributes = ['confidence', 'geonameId', 'names']; | |||
| } | |||
| @@ -0,0 +1,32 @@ | |||
| <?php | |||
| namespace GeoIp2\Record; | |||
| /** | |||
| * Contains data for the continent record associated with an IP address. | |||
| * | |||
| * This record is returned by all location services and databases. | |||
| * | |||
| * @property-read string|null $code A two character continent code like "NA" (North | |||
| * America) or "OC" (Oceania). This attribute is returned by all location | |||
| * services and databases. | |||
| * @property-read int|null $geonameId The GeoName ID for the continent. This | |||
| * attribute is returned by all location services and databases. | |||
| * @property-read string|null $name Returns the name of the continent based on the | |||
| * locales list passed to the constructor. This attribute is returned by all location | |||
| * services and databases. | |||
| * @property-read array|null $names An array map where the keys are locale codes | |||
| * and the values are names. This attribute is returned by all location | |||
| * services and databases. | |||
| */ | |||
| class Continent extends AbstractPlaceRecord | |||
| { | |||
| /** | |||
| * @ignore | |||
| */ | |||
| protected $validAttributes = [ | |||
| 'code', | |||
| 'geonameId', | |||
| 'names', | |||
| ]; | |||
| } | |||
| @@ -0,0 +1,40 @@ | |||
| <?php | |||
| namespace GeoIp2\Record; | |||
| /** | |||
| * Contains data for the country record associated with an IP address. | |||
| * | |||
| * This record is returned by all location services and databases. | |||
| * | |||
| * @property-read int|null $confidence A value from 0-100 indicating MaxMind's | |||
| * confidence that the country is correct. This attribute is only available | |||
| * from the Insights service and the GeoIP2 Enterprise database. | |||
| * @property-read int|null $geonameId The GeoName ID for the country. This | |||
| * attribute is returned by all location services and databases. | |||
| * @property-read bool $isInEuropeanUnion This is true if the country is a | |||
| * member state of the European Union. This attribute is returned by all | |||
| * location services and databases. | |||
| * @property-read string|null $isoCode The two-character ISO 3166-1 alpha code | |||
| * for the country. See https://en.wikipedia.org/wiki/ISO_3166-1. This | |||
| * attribute is returned by all location services and databases. | |||
| * @property-read string|null $name The name of the country based on the locales | |||
| * list passed to the constructor. This attribute is returned by all location | |||
| * services and databases. | |||
| * @property-read array|null $names An array map where the keys are locale codes | |||
| * and the values are names. This attribute is returned by all location | |||
| * services and databases. | |||
| */ | |||
| class Country extends AbstractPlaceRecord | |||
| { | |||
| /** | |||
| * @ignore | |||
| */ | |||
| protected $validAttributes = [ | |||
| 'confidence', | |||
| 'geonameId', | |||
| 'isInEuropeanUnion', | |||
| 'isoCode', | |||
| 'names', | |||
| ]; | |||
| } | |||
| @@ -0,0 +1,52 @@ | |||
| <?php | |||
| namespace GeoIp2\Record; | |||
| /** | |||
| * Contains data for the location record associated with an IP address. | |||
| * | |||
| * This record is returned by all location services and databases besides | |||
| * Country. | |||
| * | |||
| * @property-read int|null $averageIncome The average income in US dollars | |||
| * associated with the requested IP address. This attribute is only available | |||
| * from the Insights service. | |||
| * @property-read int|null $accuracyRadius The approximate accuracy radius in | |||
| * kilometers around the latitude and longitude for the IP address. This is | |||
| * the radius where we have a 67% confidence that the device using the IP | |||
| * address resides within the circle centered at the latitude and longitude | |||
| * with the provided radius. | |||
| * @property-read float|null $latitude The approximate latitude of the location | |||
| * associated with the IP address. This value is not precise and should not be | |||
| * used to identify a particular address or household. | |||
| * @property-read float|null $longitude The approximate longitude of the location | |||
| * associated with the IP address. This value is not precise and should not be | |||
| * used to identify a particular address or household. | |||
| * @property-read int|null $populationDensity The estimated population per square | |||
| * kilometer associated with the IP address. This attribute is only available | |||
| * from the Insights service. | |||
| * @property-read int|null $metroCode The metro code of the location if the location | |||
| * is in the US. MaxMind returns the same metro codes as the | |||
| * Google AdWords API. See | |||
| * https://developers.google.com/adwords/api/docs/appendix/cities-DMAregions. | |||
| * @property-read string|null $timeZone The time zone associated with location, as | |||
| * specified by the IANA Time Zone Database, e.g., "America/New_York". See | |||
| * https://www.iana.org/time-zones. | |||
| */ | |||
| class Location extends AbstractRecord | |||
| { | |||
| /** | |||
| * @ignore | |||
| */ | |||
| protected $validAttributes = [ | |||
| 'averageIncome', | |||
| 'accuracyRadius', | |||
| 'latitude', | |||
| 'longitude', | |||
| 'metroCode', | |||
| 'populationDensity', | |||
| 'postalCode', | |||
| 'postalConfidence', | |||
| 'timeZone', | |||
| ]; | |||
| } | |||
| @@ -0,0 +1,19 @@ | |||
| <?php | |||
| namespace GeoIp2\Record; | |||
| /** | |||
| * Contains data about your account. | |||
| * | |||
| * This record is returned by all location services and databases. | |||
| * | |||
| * @property-read int|null $queriesRemaining The number of remaining queries you | |||
| * have for the service you are calling. | |||
| */ | |||
| class MaxMind extends AbstractRecord | |||
| { | |||
| /** | |||
| * @ignore | |||
| */ | |||
| protected $validAttributes = ['queriesRemaining']; | |||
| } | |||
| @@ -0,0 +1,26 @@ | |||
| <?php | |||
| namespace GeoIp2\Record; | |||
| /** | |||
| * Contains data for the postal record associated with an IP address. | |||
| * | |||
| * This record is returned by all location databases and services besides | |||
| * Country. | |||
| * | |||
| * @property-read string|null $code The postal code of the location. Postal codes | |||
| * are not available for all countries. In some countries, this will only | |||
| * contain part of the postal code. This attribute is returned by all location | |||
| * databases and services besides Country. | |||
| * @property-read int|null $confidence A value from 0-100 indicating MaxMind's | |||
| * confidence that the postal code is correct. This attribute is only | |||
| * available from the Insights service and the GeoIP2 Enterprise | |||
| * database. | |||
| */ | |||
| class Postal extends AbstractRecord | |||
| { | |||
| /** | |||
| * @ignore | |||
| */ | |||
| protected $validAttributes = ['code', 'confidence']; | |||
| } | |||
| @@ -0,0 +1,26 @@ | |||
| <?php | |||
| namespace GeoIp2\Record; | |||
| /** | |||
| * Contains data for the represented country associated with an IP address. | |||
| * | |||
| * This class contains the country-level data associated with an IP address | |||
| * for the IP's represented country. The represented country is the country | |||
| * represented by something like a military base. | |||
| * | |||
| * @property-read string|null $type A string indicating the type of entity that is | |||
| * representing the country. Currently we only return <code>military</code> | |||
| * but this could expand to include other types in the future. | |||
| */ | |||
| class RepresentedCountry extends Country | |||
| { | |||
| protected $validAttributes = [ | |||
| 'confidence', | |||
| 'geonameId', | |||
| 'isInEuropeanUnion', | |||
| 'isoCode', | |||
| 'names', | |||
| 'type', | |||
| ]; | |||
| } | |||
| @@ -0,0 +1,40 @@ | |||
| <?php | |||
| namespace GeoIp2\Record; | |||
| /** | |||
| * Contains data for the subdivisions associated with an IP address. | |||
| * | |||
| * This record is returned by all location databases and services besides | |||
| * Country. | |||
| * | |||
| * @property-read int|null $confidence This is a value from 0-100 indicating | |||
| * MaxMind's confidence that the subdivision is correct. This attribute is | |||
| * only available from the Insights service and the GeoIP2 Enterprise | |||
| * database. | |||
| * @property-read int|null $geonameId This is a GeoName ID for the subdivision. | |||
| * This attribute is returned by all location databases and services besides | |||
| * Country. | |||
| * @property-read string|null $isoCode This is a string up to three characters long | |||
| * contain the subdivision portion of the ISO 3166-2 code. See | |||
| * https://en.wikipedia.org/wiki/ISO_3166-2. This attribute is returned by all | |||
| * location databases and services except Country. | |||
| * @property-read string|null $name The name of the subdivision based on the | |||
| * locales list passed to the constructor. This attribute is returned by all | |||
| * location databases and services besides Country. | |||
| * @property-read array|null $names An array map where the keys are locale codes | |||
| * and the values are names. This attribute is returned by all location | |||
| * databases and services besides Country. | |||
| */ | |||
| class Subdivision extends AbstractPlaceRecord | |||
| { | |||
| /** | |||
| * @ignore | |||
| */ | |||
| protected $validAttributes = [ | |||
| 'confidence', | |||
| 'geonameId', | |||
| 'isoCode', | |||
| 'names', | |||
| ]; | |||
| } | |||
| @@ -0,0 +1,140 @@ | |||
| <?php | |||
| namespace GeoIp2\Record; | |||
| use GeoIp2\Util; | |||
| /** | |||
| * Contains data for the traits record associated with an IP address. | |||
| * | |||
| * This record is returned by all location services and databases. | |||
| * | |||
| * @property-read int|null $autonomousSystemNumber The autonomous system number | |||
| * associated with the IP address. See | |||
| * https://en.wikipedia.org/wiki/Autonomous_system_(Internet%29. This attribute | |||
| * is only available from the City and Insights web service and the GeoIP2 | |||
| * Enterprise database. | |||
| * @property-read string|null $autonomousSystemOrganization The organization | |||
| * associated with the registered autonomous system number for the IP address. | |||
| * See https://en.wikipedia.org/wiki/Autonomous_system_(Internet%29. This | |||
| * attribute is only available from the City and Insights web service and the | |||
| * GeoIP2 Enterprise database. | |||
| * @property-read string|null $connectionType The connection type may take the | |||
| * following values: "Dialup", "Cable/DSL", "Corporate", "Cellular". | |||
| * Additional values may be added in the future. This attribute is only | |||
| * available in the GeoIP2 Enterprise database. | |||
| * @property-read string|null $domain The second level domain associated with the | |||
| * IP address. This will be something like "example.com" or "example.co.uk", | |||
| * not "foo.example.com". This attribute is only available from the | |||
| * City and Insights web service and the GeoIP2 Enterprise | |||
| * database. | |||
| * @property-read string $ipAddress The IP address that the data in the model | |||
| * is for. If you performed a "me" lookup against the web service, this | |||
| * will be the externally routable IP address for the system the code is | |||
| * running on. If the system is behind a NAT, this may differ from the IP | |||
| * address locally assigned to it. This attribute is returned by all end | |||
| * points. | |||
| * @property-read bool $isAnonymous This is true if the IP address belongs to | |||
| * any sort of anonymous network. This property is only available from GeoIP2 | |||
| * Precision Insights. | |||
| * @property-read bool $isAnonymousProxy *Deprecated.* Please see our GeoIP2 | |||
| * Anonymous IP database | |||
| * (https://www.maxmind.com/en/geoip2-anonymous-ip-database) to determine | |||
| * whether the IP address is used by an anonymizing service. | |||
| * @property-read bool $isAnonymousVpn This is true if the IP address is | |||
| * registered to an anonymous VPN provider. If a VPN provider does not register | |||
| * subnets under names associated with them, we will likely only flag their IP | |||
| * ranges using the isHostingProvider property. This property is only available | |||
| * from GeoIP2 Precision Insights. | |||
| * @property-read bool $isHostingProvider This is true if the IP address belongs | |||
| * to a hosting or VPN provider (see description of isAnonymousVpn property). | |||
| * This property is only available from GeoIP2 Precision Insights. | |||
| * @property-read bool $isLegitimateProxy This attribute is true if MaxMind | |||
| * believes this IP address to be a legitimate proxy, such as an internal | |||
| * VPN used by a corporation. This attribute is only available in the GeoIP2 | |||
| * Enterprise database. | |||
| * @property-read bool $isPublicProxy This is true if the IP address belongs to | |||
| * a public proxy. This property is only available from GeoIP2 Precision | |||
| * Insights. | |||
| * @property-read bool $isSatelliteProvider *Deprecated.* Due to the | |||
| * increased coverage by mobile carriers, very few satellite providers now | |||
| * serve multiple countries. As a result, the output does not provide | |||
| * sufficiently relevant data for us to maintain it. | |||
| * @property-read bool $isTorExitNode This is true if the IP address is a Tor | |||
| * exit node. This property is only available from GeoIP2 Precision Insights. | |||
| * @property-read string|null $isp The name of the ISP associated with the IP | |||
| * address. This attribute is only available from the City and Insights web | |||
| * services and the GeoIP2 Enterprise database. | |||
| * @property-read string $network The network in CIDR notation associated with | |||
| * the record. In particular, this is the largest network where all of the | |||
| * fields besides $ipAddress have the same value. | |||
| * @property-read string|null $organization The name of the organization associated | |||
| * with the IP address. This attribute is only available from the City and | |||
| * Insights web services and the GeoIP2 Enterprise database. | |||
| * @property-read float|null $staticIPScore An indicator of how static or | |||
| * dynamic an IP address is. This property is only available from GeoIP2 | |||
| * Precision Insights. | |||
| * @property-read int|null $userCount The estimated number of users sharing | |||
| * the IP/network during the past 24 hours. For IPv4, the count is for the | |||
| * individual IP. For IPv6, the count is for the /64 network. This property is | |||
| * only available from GeoIP2 Precision Insights. | |||
| * @property-read string|null $userType <p>The user type associated with the IP | |||
| * address. This can be one of the following values:</p> | |||
| * <ul> | |||
| * <li>business | |||
| * <li>cafe | |||
| * <li>cellular | |||
| * <li>college | |||
| * <li>content_delivery_network | |||
| * <li>dialup | |||
| * <li>government | |||
| * <li>hosting | |||
| * <li>library | |||
| * <li>military | |||
| * <li>residential | |||
| * <li>router | |||
| * <li>school | |||
| * <li>search_engine_spider | |||
| * <li>traveler | |||
| * </ul> | |||
| * <p> | |||
| * This attribute is only available from the Insights web service and the | |||
| * GeoIP2 Enterprise database. | |||
| * </p> | |||
| */ | |||
| class Traits extends AbstractRecord | |||
| { | |||
| /** | |||
| * @ignore | |||
| */ | |||
| protected $validAttributes = [ | |||
| 'autonomousSystemNumber', | |||
| 'autonomousSystemOrganization', | |||
| 'connectionType', | |||
| 'domain', | |||
| 'ipAddress', | |||
| 'isAnonymous', | |||
| 'isAnonymousProxy', | |||
| 'isAnonymousVpn', | |||
| 'isHostingProvider', | |||
| 'isLegitimateProxy', | |||
| 'isp', | |||
| 'isPublicProxy', | |||
| 'isSatelliteProvider', | |||
| 'isTorExitNode', | |||
| 'network', | |||
| 'organization', | |||
| 'staticIpScore', | |||
| 'userCount', | |||
| 'userType', | |||
| ]; | |||
| public function __construct($record) | |||
| { | |||
| if (!isset($record['network']) && isset($record['ip_address']) && isset($record['prefix_len'])) { | |||
| $record['network'] = Util::cidr($record['ip_address'], $record['prefix_len']); | |||
| } | |||
| parent::__construct($record); | |||
| } | |||
| } | |||
| @@ -0,0 +1,37 @@ | |||
| <?php | |||
| namespace GeoIp2; | |||
| class Util | |||
| { | |||
| /** | |||
| * This returns the network in CIDR notation for the given IP and prefix | |||
| * length. This is for internal use only. | |||
| * | |||
| * @internal | |||
| * @ignore | |||
| * | |||
| * @param mixed $ipAddress | |||
| * @param mixed $prefixLen | |||
| */ | |||
| public static function cidr($ipAddress, $prefixLen) | |||
| { | |||
| $ipBytes = inet_pton($ipAddress); | |||
| $networkBytes = str_repeat("\0", \strlen($ipBytes)); | |||
| $curPrefix = $prefixLen; | |||
| for ($i = 0; $i < \strlen($ipBytes) && $curPrefix > 0; $i++) { | |||
| $b = $ipBytes[$i]; | |||
| if ($curPrefix < 8) { | |||
| $shiftN = 8 - $curPrefix; | |||
| $b = \chr(0xFF & (\ord($b) >> $shiftN) << $shiftN); | |||
| } | |||
| $networkBytes[$i] = $b; | |||
| $curPrefix -= 8; | |||
| } | |||
| $network = inet_ntop($networkBytes); | |||
| return "$network/$prefixLen"; | |||
| } | |||
| } | |||
| @@ -0,0 +1,239 @@ | |||
| <?php | |||
| namespace GeoIp2\WebService; | |||
| use GeoIp2\Exception\AddressNotFoundException; | |||
| use GeoIp2\Exception\AuthenticationException; | |||
| use GeoIp2\Exception\GeoIp2Exception; | |||
| use GeoIp2\Exception\HttpException; | |||
| use GeoIp2\Exception\InvalidRequestException; | |||
| use GeoIp2\Exception\OutOfQueriesException; | |||
| use GeoIp2\ProviderInterface; | |||
| use MaxMind\WebService\Client as WsClient; | |||
| /** | |||
| * This class provides a client API for all the GeoIP2 Precision web services. | |||
| * The services are Country, City, and Insights. Each service returns a | |||
| * different set of data about an IP address, with Country returning the | |||
| * least data and Insights the most. | |||
| * | |||
| * Each web service is represented by a different model class, and these model | |||
| * classes in turn contain multiple record classes. The record classes have | |||
| * attributes which contain data about the IP address. | |||
| * | |||
| * If the web service does not return a particular piece of data for an IP | |||
| * address, the associated attribute is not populated. | |||
| * | |||
| * The web service may not return any information for an entire record, in | |||
| * which case all of the attributes for that record class will be empty. | |||
| * | |||
| * ## Usage ## | |||
| * | |||
| * The basic API for this class is the same for all of the web service end | |||
| * points. First you create a web service object with your MaxMind `$accountId` | |||
| * and `$licenseKey`, then you call the method corresponding to a specific end | |||
| * point, passing it the IP address you want to look up. | |||
| * | |||
| * If the request succeeds, the method call will return a model class for | |||
| * the service you called. This model in turn contains multiple record | |||
| * classes, each of which represents part of the data returned by the web | |||
| * service. | |||
| * | |||
| * If the request fails, the client class throws an exception. | |||
| */ | |||
| class Client implements ProviderInterface | |||
| { | |||
| private $locales; | |||
| private $client; | |||
| private static $basePath = '/geoip/v2.1'; | |||
| const VERSION = 'v2.10.0'; | |||
| /** | |||
| * Constructor. | |||
| * | |||
| * @param int $accountId your MaxMind account ID | |||
| * @param string $licenseKey your MaxMind license key | |||
| * @param array $locales list of locale codes to use in name property | |||
| * from most preferred to least preferred | |||
| * @param array $options array of options. Valid options include: | |||
| * * `host` - The host to use when querying the web service. | |||
| * * `timeout` - Timeout in seconds. | |||
| * * `connectTimeout` - Initial connection timeout in seconds. | |||
| * * `proxy` - The HTTP proxy to use. May include a schema, port, | |||
| * username, and password, e.g., | |||
| * `http://username:password@127.0.0.1:10`. | |||
| */ | |||
| public function __construct( | |||
| $accountId, | |||
| $licenseKey, | |||
| $locales = ['en'], | |||
| $options = [] | |||
| ) { | |||
| $this->locales = $locales; | |||
| // This is for backwards compatibility. Do not remove except for a | |||
| // major version bump. | |||
| if (\is_string($options)) { | |||
| $options = ['host' => $options]; | |||
| } | |||
| if (!isset($options['host'])) { | |||
| $options['host'] = 'geoip.maxmind.com'; | |||
| } | |||
| $options['userAgent'] = $this->userAgent(); | |||
| $this->client = new WsClient($accountId, $licenseKey, $options); | |||
| } | |||
| private function userAgent() | |||
| { | |||
| return 'GeoIP2-API/' . self::VERSION; | |||
| } | |||
| /** | |||
| * This method calls the GeoIP2 Precision: City service. | |||
| * | |||
| * @param string $ipAddress IPv4 or IPv6 address as a string. If no | |||
| * address is provided, the address that the web service is called | |||
| * from will be used. | |||
| * | |||
| * @throws \GeoIp2\Exception\AddressNotFoundException if the address you | |||
| * provided is not in our database (e.g., a private address). | |||
| * @throws \GeoIp2\Exception\AuthenticationException if there is a problem | |||
| * with the account ID or license key that you provided | |||
| * @throws \GeoIp2\Exception\OutOfQueriesException if your account is out | |||
| * of queries | |||
| * @throws \GeoIp2\Exception\InvalidRequestException} if your request was received by the web service but is | |||
| * invalid for some other reason. This may indicate an issue | |||
| * with this API. Please report the error to MaxMind. | |||
| * @throws \GeoIp2\Exception\HttpException if an unexpected HTTP error code or message was returned. | |||
| * This could indicate a problem with the connection between | |||
| * your server and the web service or that the web service | |||
| * returned an invalid document or 500 error code | |||
| * @throws \GeoIp2\Exception\GeoIp2Exception This serves as the parent | |||
| * class to the above exceptions. It will be thrown directly | |||
| * if a 200 status code is returned but the body is invalid. | |||
| * | |||
| * @return \GeoIp2\Model\City | |||
| */ | |||
| public function city($ipAddress = 'me') | |||
| { | |||
| return $this->responseFor('city', 'City', $ipAddress); | |||
| } | |||
| /** | |||
| * This method calls the GeoIP2 Precision: Country service. | |||
| * | |||
| * @param string $ipAddress IPv4 or IPv6 address as a string. If no | |||
| * address is provided, the address that the web service is called | |||
| * from will be used. | |||
| * | |||
| * @throws \GeoIp2\Exception\AddressNotFoundException if the address you provided is not in our database (e.g., | |||
| * a private address). | |||
| * @throws \GeoIp2\Exception\AuthenticationException if there is a problem | |||
| * with the account ID or license key that you provided | |||
| * @throws \GeoIp2\Exception\OutOfQueriesException if your account is out of queries | |||
| * @throws \GeoIp2\Exception\InvalidRequestException} if your request was received by the web service but is | |||
| * invalid for some other reason. This may indicate an | |||
| * issue with this API. Please report the error to MaxMind. | |||
| * @throws \GeoIp2\Exception\HttpException if an unexpected HTTP error | |||
| * code or message was returned. This could indicate a problem | |||
| * with the connection between your server and the web service | |||
| * or that the web service returned an invalid document or 500 | |||
| * error code. | |||
| * @throws \GeoIp2\Exception\GeoIp2Exception This serves as the parent class to the above exceptions. It | |||
| * will be thrown directly if a 200 status code is returned but | |||
| * the body is invalid. | |||
| * | |||
| * @return \GeoIp2\Model\Country | |||
| */ | |||
| public function country($ipAddress = 'me') | |||
| { | |||
| return $this->responseFor('country', 'Country', $ipAddress); | |||
| } | |||
| /** | |||
| * This method calls the GeoIP2 Precision: Insights service. | |||
| * | |||
| * @param string $ipAddress IPv4 or IPv6 address as a string. If no | |||
| * address is provided, the address that the web service is called | |||
| * from will be used. | |||
| * | |||
| * @throws \GeoIp2\Exception\AddressNotFoundException if the address you | |||
| * provided is not in our database (e.g., a private address). | |||
| * @throws \GeoIp2\Exception\AuthenticationException if there is a problem | |||
| * with the account ID or license key that you provided | |||
| * @throws \GeoIp2\Exception\OutOfQueriesException if your account is out | |||
| * of queries | |||
| * @throws \GeoIp2\Exception\InvalidRequestException} if your request was received by the web service but is | |||
| * invalid for some other reason. This may indicate an | |||
| * issue with this API. Please report the error to MaxMind. | |||
| * @throws \GeoIp2\Exception\HttpException if an unexpected HTTP error code or message was returned. | |||
| * This could indicate a problem with the connection between | |||
| * your server and the web service or that the web service | |||
| * returned an invalid document or 500 error code | |||
| * @throws \GeoIp2\Exception\GeoIp2Exception This serves as the parent | |||
| * class to the above exceptions. It will be thrown directly | |||
| * if a 200 status code is returned but the body is invalid. | |||
| * | |||
| * @return \GeoIp2\Model\Insights | |||
| */ | |||
| public function insights($ipAddress = 'me') | |||
| { | |||
| return $this->responseFor('insights', 'Insights', $ipAddress); | |||
| } | |||
| private function responseFor($endpoint, $class, $ipAddress) | |||
| { | |||
| $path = implode('/', [self::$basePath, $endpoint, $ipAddress]); | |||
| try { | |||
| $body = $this->client->get('GeoIP2 ' . $class, $path); | |||
| } catch (\MaxMind\Exception\IpAddressNotFoundException $ex) { | |||
| throw new AddressNotFoundException( | |||
| $ex->getMessage(), | |||
| $ex->getStatusCode(), | |||
| $ex | |||
| ); | |||
| } catch (\MaxMind\Exception\AuthenticationException $ex) { | |||
| throw new AuthenticationException( | |||
| $ex->getMessage(), | |||
| $ex->getStatusCode(), | |||
| $ex | |||
| ); | |||
| } catch (\MaxMind\Exception\InsufficientFundsException $ex) { | |||
| throw new OutOfQueriesException( | |||
| $ex->getMessage(), | |||
| $ex->getStatusCode(), | |||
| $ex | |||
| ); | |||
| } catch (\MaxMind\Exception\InvalidRequestException $ex) { | |||
| throw new InvalidRequestException( | |||
| $ex->getMessage(), | |||
| $ex->getErrorCode(), | |||
| $ex->getStatusCode(), | |||
| $ex->getUri(), | |||
| $ex | |||
| ); | |||
| } catch (\MaxMind\Exception\HttpException $ex) { | |||
| throw new HttpException( | |||
| $ex->getMessage(), | |||
| $ex->getStatusCode(), | |||
| $ex->getUri(), | |||
| $ex | |||
| ); | |||
| } catch (\MaxMind\Exception\WebServiceException $ex) { | |||
| throw new GeoIp2Exception( | |||
| $ex->getMessage(), | |||
| $ex->getCode(), | |||
| $ex | |||
| ); | |||
| } | |||
| $class = 'GeoIp2\\Model\\' . $class; | |||
| return new $class($body, $this->locales); | |||
| } | |||
| } | |||
| @@ -0,0 +1,180 @@ | |||
| CHANGELOG | |||
| ========= | |||
| 1.6.0 (2019-12-19) | |||
| ------------------ | |||
| * 1.5.0 and 1.5.1 contained a possible memory corruptions when using | |||
| `getWithPrefixLen`. This has been fixed. Reported by proton-ab. | |||
| GitHub #96. | |||
| * The `composer.json` file now conflicts with all versions of the | |||
| `maxminddb` C extension less than the Composer version. This is to | |||
| reduce the chance of having an older, conflicting version of the | |||
| extension installed. You will need to upgrade the extension before | |||
| running `composer update`. Pull request by Benoît Burnichon. GitHub | |||
| #97. | |||
| 1.5.1 (2019-12-12) | |||
| ------------------ | |||
| * Minor performance improvements. | |||
| * Make tests pass with older versions of libmaxminddb. PR by Remi | |||
| Collet. GitHub #90. | |||
| * Test enhancements. PR by Chun-Sheng, Li. GitHub #91. | |||
| 1.5.0 (2019-09-30) | |||
| ------------------ | |||
| * PHP 5.6 or greater is now required. | |||
| * The C extension now supports PHP 8. Pull request by John Boehr. | |||
| GitHub #87. | |||
| * A new method, `getWithPrefixLen`, was added to the `Reader` class. | |||
| This method returns an array containing the record and the prefix | |||
| length for that record. GitHub #89. | |||
| 1.4.1 (2019-01-04) | |||
| ------------------ | |||
| * The `maxminddb` extension now returns a string when a `uint32` | |||
| value is greater than `LONG_MAX`. Previously, the value would | |||
| overflow. This generally only affects 32-bit machines. Reported | |||
| by Remi Collet. GitHub #79. | |||
| * For `uint64` values, the `maxminddb` extension now returns an | |||
| integer rather than a string when the value is less than or equal | |||
| to `LONG_MAX`. This more closely matches the behavior of the pure | |||
| PHP reader. | |||
| 1.4.0 (2018-11-20) | |||
| ------------------ | |||
| * The `maxminddb` extension now has the arginfo when using reflection. | |||
| PR by Remi Collet. GitHub #75. | |||
| * The `maxminddb` extension now provides `MINFO()` function that | |||
| displays the extension version and the libmaxminddb version. PR by | |||
| Remi Collet. GitHub #74. | |||
| * The `maxminddb` `configure` script now uses `pkg-config` when | |||
| available to get libmaxmindb build info. PR by Remi Collet. | |||
| GitHub #73. | |||
| * The pure PHP reader now correctly decodes integers on 32-bit platforms. | |||
| Previously, large integers would overflow. Reported by Remi Collet. | |||
| GitHub #77. | |||
| * There are small performance improvements for the pure PHP reader. | |||
| 1.3.0 (2018-02-21) | |||
| ------------------ | |||
| * IMPORTANT: The `maxminddb` extension now obeys `open_basedir`. If | |||
| `open_basedir` is set, you _must_ store the database within the | |||
| specified directory. Placing the file outside of this directory | |||
| will result in an exception. Please test your integration before | |||
| upgrading the extension. This does not affect the pure PHP | |||
| implementation, which has always had this restriction. Reported | |||
| by Benoît Burnichon. GitHub #61. | |||
| * A custom `autoload.php` file is provided for installations without | |||
| Composer. GitHub #56. | |||
| 1.2.0 (2017-10-27) | |||
| ------------------ | |||
| * PHP 5.4 or greater is now required. | |||
| * The `Reader` class for the `maxminddb` extension is no longer final. | |||
| This was change to match the behavior of the pure PHP class. | |||
| Reported and fixed by venyii. GitHub #52 & #54. | |||
| 1.1.3 (2017-01-19) | |||
| ------------------ | |||
| * Fix incorrect version in `ext/php_maxminddb.h`. GitHub #48. | |||
| 1.1.2 (2016-11-22) | |||
| ------------------ | |||
| * Searching for database metadata only occurs within the last 128KB | |||
| (128 * 1024 bytes) of the file, speeding detection of corrupt | |||
| datafiles. Reported by Eric Teubert. GitHub #42. | |||
| * Suggest relevant extensions when installing with Composer. GitHub #37. | |||
| 1.1.1 (2016-09-15) | |||
| ------------------ | |||
| * Development files were added to the `.gitattributes` as `export-ignore` so | |||
| that they are not part of the Composer release. Pull request by Michele | |||
| Locati. GitHub #39. | |||
| 1.1.0 (2016-01-04) | |||
| ------------------ | |||
| * The MaxMind DB extension now supports PHP 7. Pull request by John Boehr. | |||
| GitHub #27. | |||
| 1.0.3 (2015-03-13) | |||
| ------------------ | |||
| * All uses of `strlen` were removed. This should prevent issues in situations | |||
| where the function is overloaded or otherwise broken. | |||
| 1.0.2 (2015-01-19) | |||
| ------------------ | |||
| * Previously the MaxMind DB extension would cause a segfault if the Reader | |||
| object's destructor was called without first having called the constructor. | |||
| (Reported by Matthias Saou & Juan Peri. GitHub #20.) | |||
| 1.0.1 (2015-01-12) | |||
| ------------------ | |||
| * In the last several releases, the version number in the extension was | |||
| incorrect. This release is being done to correct it. No other code changes | |||
| are included. | |||
| 1.0.0 (2014-09-22) | |||
| ------------------ | |||
| * First production release. | |||
| * In the pure PHP reader, a string length test after `fread()` was replaced | |||
| with the difference between the start pointer and the end pointer. This | |||
| provided a 15% speed increase. | |||
| 0.3.3 (2014-09-15) | |||
| ------------------ | |||
| * Clarified behavior of 128-bit type in documentation. | |||
| * Updated phpunit and fixed some test breakage from the newer version. | |||
| 0.3.2 (2014-09-10) | |||
| ------------------ | |||
| * Fixed invalid reference to global class RuntimeException from namespaced | |||
| code. Fixed by Steven Don. GitHub issue #15. | |||
| * Additional documentation of `Metadata` class as well as misc. documentation | |||
| cleanup. | |||
| 0.3.1 (2014-05-01) | |||
| ------------------ | |||
| * The API now works when `mbstring.func_overload` is set. | |||
| * BCMath is no longer required. If the decoder encounters a big integer, | |||
| it will try to use GMP and then BCMath. If both of those fail, it will | |||
| throw an exception. No databases released by MaxMind currently use big | |||
| integers. | |||
| * The API now officially supports HHVM when using the pure PHP reader. | |||
| 0.3.0 (2014-02-19) | |||
| ------------------ | |||
| * This API is now licensed under the Apache License, Version 2.0. | |||
| * The code for the C extension was cleaned up, fixing several potential | |||
| issues. | |||
| 0.2.0 (2013-10-21) | |||
| ------------------ | |||
| * Added optional C extension for using libmaxminddb in place of the pure PHP | |||
| reader. | |||
| * Significantly improved error handling in pure PHP reader. | |||
| * Improved performance for IPv4 lookups in an IPv6 database. | |||
| 0.1.0 (2013-07-16) | |||
| ------------------ | |||
| * Initial release | |||
| @@ -0,0 +1,202 @@ | |||
| Apache License | |||
| Version 2.0, January 2004 | |||
| http://www.apache.org/licenses/ | |||
| TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | |||
| 1. Definitions. | |||
| "License" shall mean the terms and conditions for use, reproduction, | |||
| and distribution as defined by Sections 1 through 9 of this document. | |||
| "Licensor" shall mean the copyright owner or entity authorized by | |||
| the copyright owner that is granting the License. | |||
| "Legal Entity" shall mean the union of the acting entity and all | |||
| other entities that control, are controlled by, or are under common | |||
| control with that entity. For the purposes of this definition, | |||
| "control" means (i) the power, direct or indirect, to cause the | |||
| direction or management of such entity, whether by contract or | |||
| otherwise, or (ii) ownership of fifty percent (50%) or more of the | |||
| outstanding shares, or (iii) beneficial ownership of such entity. | |||
| "You" (or "Your") shall mean an individual or Legal Entity | |||
| exercising permissions granted by this License. | |||
| "Source" form shall mean the preferred form for making modifications, | |||
| including but not limited to software source code, documentation | |||
| source, and configuration files. | |||
| "Object" form shall mean any form resulting from mechanical | |||
| transformation or translation of a Source form, including but | |||
| not limited to compiled object code, generated documentation, | |||
| and conversions to other media types. | |||
| "Work" shall mean the work of authorship, whether in Source or | |||
| Object form, made available under the License, as indicated by a | |||
| copyright notice that is included in or attached to the work | |||
| (an example is provided in the Appendix below). | |||
| "Derivative Works" shall mean any work, whether in Source or Object | |||
| form, that is based on (or derived from) the Work and for which the | |||
| editorial revisions, annotations, elaborations, or other modifications | |||
| represent, as a whole, an original work of authorship. For the purposes | |||
| of this License, Derivative Works shall not include works that remain | |||
| separable from, or merely link (or bind by name) to the interfaces of, | |||
| the Work and Derivative Works thereof. | |||
| "Contribution" shall mean any work of authorship, including | |||
| the original version of the Work and any modifications or additions | |||
| to that Work or Derivative Works thereof, that is intentionally | |||
| submitted to Licensor for inclusion in the Work by the copyright owner | |||
| or by an individual or Legal Entity authorized to submit on behalf of | |||
| the copyright owner. For the purposes of this definition, "submitted" | |||
| means any form of electronic, verbal, or written communication sent | |||
| to the Licensor or its representatives, including but not limited to | |||
| communication on electronic mailing lists, source code control systems, | |||
| and issue tracking systems that are managed by, or on behalf of, the | |||
| Licensor for the purpose of discussing and improving the Work, but | |||
| excluding communication that is conspicuously marked or otherwise | |||
| designated in writing by the copyright owner as "Not a Contribution." | |||
| "Contributor" shall mean Licensor and any individual or Legal Entity | |||
| on behalf of whom a Contribution has been received by Licensor and | |||
| subsequently incorporated within the Work. | |||
| 2. Grant of Copyright License. Subject to the terms and conditions of | |||
| this License, each Contributor hereby grants to You a perpetual, | |||
| worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |||
| copyright license to reproduce, prepare Derivative Works of, | |||
| publicly display, publicly perform, sublicense, and distribute the | |||
| Work and such Derivative Works in Source or Object form. | |||
| 3. Grant of Patent License. Subject to the terms and conditions of | |||
| this License, each Contributor hereby grants to You a perpetual, | |||
| worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |||
| (except as stated in this section) patent license to make, have made, | |||
| use, offer to sell, sell, import, and otherwise transfer the Work, | |||
| where such license applies only to those patent claims licensable | |||
| by such Contributor that are necessarily infringed by their | |||
| Contribution(s) alone or by combination of their Contribution(s) | |||
| with the Work to which such Contribution(s) was submitted. If You | |||
| institute patent litigation against any entity (including a | |||
| cross-claim or counterclaim in a lawsuit) alleging that the Work | |||
| or a Contribution incorporated within the Work constitutes direct | |||
| or contributory patent infringement, then any patent licenses | |||
| granted to You under this License for that Work shall terminate | |||
| as of the date such litigation is filed. | |||
| 4. Redistribution. You may reproduce and distribute copies of the | |||
| Work or Derivative Works thereof in any medium, with or without | |||
| modifications, and in Source or Object form, provided that You | |||
| meet the following conditions: | |||
| (a) You must give any other recipients of the Work or | |||
| Derivative Works a copy of this License; and | |||
| (b) You must cause any modified files to carry prominent notices | |||
| stating that You changed the files; and | |||
| (c) You must retain, in the Source form of any Derivative Works | |||
| that You distribute, all copyright, patent, trademark, and | |||
| attribution notices from the Source form of the Work, | |||
| excluding those notices that do not pertain to any part of | |||
| the Derivative Works; and | |||
| (d) If the Work includes a "NOTICE" text file as part of its | |||
| distribution, then any Derivative Works that You distribute must | |||
| include a readable copy of the attribution notices contained | |||
| within such NOTICE file, excluding those notices that do not | |||
| pertain to any part of the Derivative Works, in at least one | |||
| of the following places: within a NOTICE text file distributed | |||
| as part of the Derivative Works; within the Source form or | |||
| documentation, if provided along with the Derivative Works; or, | |||
| within a display generated by the Derivative Works, if and | |||
| wherever such third-party notices normally appear. The contents | |||
| of the NOTICE file are for informational purposes only and | |||
| do not modify the License. You may add Your own attribution | |||
| notices within Derivative Works that You distribute, alongside | |||
| or as an addendum to the NOTICE text from the Work, provided | |||
| that such additional attribution notices cannot be construed | |||
| as modifying the License. | |||
| You may add Your own copyright statement to Your modifications and | |||
| may provide additional or different license terms and conditions | |||
| for use, reproduction, or distribution of Your modifications, or | |||
| for any such Derivative Works as a whole, provided Your use, | |||
| reproduction, and distribution of the Work otherwise complies with | |||
| the conditions stated in this License. | |||
| 5. Submission of Contributions. Unless You explicitly state otherwise, | |||
| any Contribution intentionally submitted for inclusion in the Work | |||
| by You to the Licensor shall be under the terms and conditions of | |||
| this License, without any additional terms or conditions. | |||
| Notwithstanding the above, nothing herein shall supersede or modify | |||
| the terms of any separate license agreement you may have executed | |||
| with Licensor regarding such Contributions. | |||
| 6. Trademarks. This License does not grant permission to use the trade | |||
| names, trademarks, service marks, or product names of the Licensor, | |||
| except as required for reasonable and customary use in describing the | |||
| origin of the Work and reproducing the content of the NOTICE file. | |||
| 7. Disclaimer of Warranty. Unless required by applicable law or | |||
| agreed to in writing, Licensor provides the Work (and each | |||
| Contributor provides its Contributions) on an "AS IS" BASIS, | |||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | |||
| implied, including, without limitation, any warranties or conditions | |||
| of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | |||
| PARTICULAR PURPOSE. You are solely responsible for determining the | |||
| appropriateness of using or redistributing the Work and assume any | |||
| risks associated with Your exercise of permissions under this License. | |||
| 8. Limitation of Liability. In no event and under no legal theory, | |||
| whether in tort (including negligence), contract, or otherwise, | |||
| unless required by applicable law (such as deliberate and grossly | |||
| negligent acts) or agreed to in writing, shall any Contributor be | |||
| liable to You for damages, including any direct, indirect, special, | |||
| incidental, or consequential damages of any character arising as a | |||
| result of this License or out of the use or inability to use the | |||
| Work (including but not limited to damages for loss of goodwill, | |||
| work stoppage, computer failure or malfunction, or any and all | |||
| other commercial damages or losses), even if such Contributor | |||
| has been advised of the possibility of such damages. | |||
| 9. Accepting Warranty or Additional Liability. While redistributing | |||
| the Work or Derivative Works thereof, You may choose to offer, | |||
| and charge a fee for, acceptance of support, warranty, indemnity, | |||
| or other liability obligations and/or rights consistent with this | |||
| License. However, in accepting such obligations, You may act only | |||
| on Your own behalf and on Your sole responsibility, not on behalf | |||
| of any other Contributor, and only if You agree to indemnify, | |||
| defend, and hold each Contributor harmless for any liability | |||
| incurred by, or claims asserted against, such Contributor by reason | |||
| of your accepting any such warranty or additional liability. | |||
| END OF TERMS AND CONDITIONS | |||
| APPENDIX: How to apply the Apache License to your work. | |||
| To apply the Apache License to your work, attach the following | |||
| boilerplate notice, with the fields enclosed by brackets "[]" | |||
| replaced with your own identifying information. (Don't include | |||
| the brackets!) The text should be enclosed in the appropriate | |||
| comment syntax for the file format. We also recommend that a | |||
| file or class name and description of purpose be included on the | |||
| same "printed page" as the copyright notice for easier | |||
| identification within third-party archives. | |||
| Copyright [yyyy] [name of copyright owner] | |||
| Licensed under the Apache License, Version 2.0 (the "License"); | |||
| you may not use this file except in compliance with the License. | |||
| You may obtain a copy of the License at | |||
| http://www.apache.org/licenses/LICENSE-2.0 | |||
| Unless required by applicable law or agreed to in writing, software | |||
| distributed under the License is distributed on an "AS IS" BASIS, | |||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| See the License for the specific language governing permissions and | |||
| limitations under the License. | |||
| @@ -0,0 +1,178 @@ | |||
| # MaxMind DB Reader PHP API # | |||
| ## Description ## | |||
| This is the PHP API for reading MaxMind DB files. MaxMind DB is a binary file | |||
| format that stores data indexed by IP address subnets (IPv4 or IPv6). | |||
| ## Installation (Composer) ## | |||
| We recommend installing this package with [Composer](https://getcomposer.org/). | |||
| ### Download Composer ### | |||
| To download Composer, run in the root directory of your project: | |||
| ```bash | |||
| curl -sS https://getcomposer.org/installer | php | |||
| ``` | |||
| You should now have the file `composer.phar` in your project directory. | |||
| ### Install Dependencies ### | |||
| Run in your project root: | |||
| ``` | |||
| php composer.phar require maxmind-db/reader:~1.0 | |||
| ``` | |||
| You should now have the files `composer.json` and `composer.lock` as well as | |||
| the directory `vendor` in your project directory. If you use a version control | |||
| system, `composer.json` should be added to it. | |||
| ### Require Autoloader ### | |||
| After installing the dependencies, you need to require the Composer autoloader | |||
| from your code: | |||
| ```php | |||
| require 'vendor/autoload.php'; | |||
| ``` | |||
| ## Installation (Standalone) ## | |||
| If you don't want to use Composer for some reason, a custom | |||
| `autoload.php` is provided for you in the project root. To use the | |||
| library, simply include that file, | |||
| ```php | |||
| require('/path/to/MaxMind-DB-Reader-php/autoload.php'); | |||
| ``` | |||
| and then instantiate the reader class normally: | |||
| ```php | |||
| use MaxMind\Db\Reader; | |||
| $reader = new Reader('example.mmdb'); | |||
| ``` | |||
| ## Installation (RPM) | |||
| RPMs are available in the [official Fedora repository](https://apps.fedoraproject.org/packages/php-maxminddb). | |||
| To install on Fedora, run: | |||
| ```bash | |||
| dnf install php-maxminddb | |||
| ``` | |||
| To install on CentOS or RHEL 7, first [enable the EPEL repository](https://fedoraproject.org/wiki/EPEL) | |||
| and then run: | |||
| ```bash | |||
| yum install php-maxminddb | |||
| ``` | |||
| Please note that these packages are *not* maintained by MaxMind. | |||
| ## Usage ## | |||
| ## Example ## | |||
| ```php | |||
| <?php | |||
| require_once 'vendor/autoload.php'; | |||
| use MaxMind\Db\Reader; | |||
| $ipAddress = '24.24.24.24'; | |||
| $databaseFile = 'GeoIP2-City.mmdb'; | |||
| $reader = new Reader($databaseFile); | |||
| // get returns just the record for the IP address | |||
| print_r($reader->get($ipAddress)); | |||
| // getWithPrefixLen returns an array containing the record and the | |||
| // associated prefix length for that record. | |||
| print_r($reader->getWithPrefixLen($ipAddress)); | |||
| $reader->close(); | |||
| ``` | |||
| ## Optional PHP C Extension ## | |||
| MaxMind provides an optional C extension that is a drop-in replacement for | |||
| `MaxMind\Db\Reader`. In order to use this extension, you must install the | |||
| Reader API as described above and install the extension as described below. If | |||
| you are using an autoloader, no changes to your code should be necessary. | |||
| ### Installing Extension ### | |||
| First install [libmaxminddb](https://github.com/maxmind/libmaxminddb) as | |||
| described in its [README.md | |||
| file](https://github.com/maxmind/libmaxminddb/blob/master/README.md#installing-from-a-tarball). | |||
| After successfully installing libmaxmindb, run the following commands from the | |||
| top-level directory of this distribution: | |||
| ``` | |||
| cd ext | |||
| phpize | |||
| ./configure | |||
| make | |||
| make test | |||
| sudo make install | |||
| ``` | |||
| You then must load your extension. The recommend method is to add the | |||
| following to your `php.ini` file: | |||
| ``` | |||
| extension=maxminddb.so | |||
| ``` | |||
| Note: You may need to install the PHP development package on your OS such as | |||
| php5-dev for Debian-based systems or php-devel for RedHat/Fedora-based ones. | |||
| ## 128-bit Integer Support ## | |||
| The MaxMind DB format includes 128-bit unsigned integer as a type. Although | |||
| no MaxMind-distributed database currently makes use of this type, both the | |||
| pure PHP reader and the C extension support this type. The pure PHP reader | |||
| requires gmp or bcmath to read databases with 128-bit unsigned integers. | |||
| The integer is currently returned as a hexadecimal string (prefixed with "0x") | |||
| by the C extension and a decimal string (no prefix) by the pure PHP reader. | |||
| Any change to make the reader implementations always return either a | |||
| hexadecimal or decimal representation of the integer will NOT be considered a | |||
| breaking change. | |||
| ## Support ## | |||
| Please report all issues with this code using the [GitHub issue tracker](https://github.com/maxmind/MaxMind-DB-Reader-php/issues). | |||
| If you are having an issue with a MaxMind service that is not specific to the | |||
| client API, please see [our support page](https://www.maxmind.com/en/support). | |||
| ## Requirements ## | |||
| This library requires PHP 5.6 or greater. | |||
| The GMP or BCMath extension may be required to read some databases | |||
| using the pure PHP API. | |||
| ## Contributing ## | |||
| Patches and pull requests are encouraged. All code should follow the PSR-1 and | |||
| PSR-2 style guidelines. Please include unit tests whenever possible. | |||
| ## Versioning ## | |||
| The MaxMind DB Reader PHP API uses [Semantic Versioning](https://semver.org/). | |||
| ## Copyright and License ## | |||
| This software is Copyright (c) 2014-2019 by MaxMind, Inc. | |||
| This is free software, licensed under the Apache License, Version 2.0. | |||
| @@ -0,0 +1,45 @@ | |||
| <?php | |||
| /** | |||
| * PSR-4 autoloader implementation for the MaxMind\DB namespace. | |||
| * First we define the 'mmdb_autoload' function, and then we register | |||
| * it with 'spl_autoload_register' so that PHP knows to use it. | |||
| * | |||
| * @param mixed $class | |||
| */ | |||
| /** | |||
| * Automatically include the file that defines <code>class</code>. | |||
| * | |||
| * @param string $class | |||
| * the name of the class to load | |||
| */ | |||
| function mmdb_autoload($class) | |||
| { | |||
| /* | |||
| * A project-specific mapping between the namespaces and where | |||
| * they're located. By convention, we include the trailing | |||
| * slashes. The one-element array here simply makes things easy | |||
| * to extend in the future if (for example) the test classes | |||
| * begin to use one another. | |||
| */ | |||
| $namespace_map = ['MaxMind\\Db\\' => __DIR__ . '/src/MaxMind/Db/']; | |||
| foreach ($namespace_map as $prefix => $dir) { | |||
| /* First swap out the namespace prefix with a directory... */ | |||
| $path = str_replace($prefix, $dir, $class); | |||
| /* replace the namespace separator with a directory separator... */ | |||
| $path = str_replace('\\', '/', $path); | |||
| /* and finally, add the PHP file extension to the result. */ | |||
| $path = $path . '.php'; | |||
| /* $path should now contain the path to a PHP file defining $class */ | |||
| if (file_exists($path)) { | |||
| include $path; | |||
| } | |||
| } | |||
| } | |||
| spl_autoload_register('mmdb_autoload'); | |||
| @@ -0,0 +1,43 @@ | |||
| { | |||
| "name": "maxmind-db/reader", | |||
| "description": "MaxMind DB Reader API", | |||
| "keywords": ["database", "geoip", "geoip2", "geolocation", "maxmind"], | |||
| "homepage": "https://github.com/maxmind/MaxMind-DB-Reader-php", | |||
| "type": "library", | |||
| "license": "Apache-2.0", | |||
| "authors": [ | |||
| { | |||
| "name": "Gregory J. Oschwald", | |||
| "email": "goschwald@maxmind.com", | |||
| "homepage": "https://www.maxmind.com/" | |||
| } | |||
| ], | |||
| "require": { | |||
| "php": ">=5.6" | |||
| }, | |||
| "suggest": { | |||
| "ext-bcmath": "bcmath or gmp is required for decoding larger integers with the pure PHP decoder", | |||
| "ext-gmp": "bcmath or gmp is required for decoding larger integers with the pure PHP decoder", | |||
| "ext-maxminddb": "A C-based database decoder that provides significantly faster lookups" | |||
| }, | |||
| "conflict": { | |||
| "ext-maxminddb": "<1.6.0,>=2.0.0" | |||
| }, | |||
| "require-dev": { | |||
| "friendsofphp/php-cs-fixer": "2.*", | |||
| "phpunit/phpunit": "5.*", | |||
| "php-coveralls/php-coveralls": "^2.1", | |||
| "phpunit/phpcov": "^3.0", | |||
| "squizlabs/php_codesniffer": "3.*" | |||
| }, | |||
| "autoload": { | |||
| "psr-4": { | |||
| "MaxMind\\Db\\": "src/MaxMind/Db" | |||
| } | |||
| }, | |||
| "autoload-dev": { | |||
| "psr-4": { | |||
| "MaxMind\\Db\\Test\\Reader\\": "tests/MaxMind/Db/Test/Reader" | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,40 @@ | |||
| PHP_ARG_WITH(maxminddb, | |||
| [Whether to enable the MaxMind DB Reader extension], | |||
| [ --with-maxminddb Enable MaxMind DB Reader extension support]) | |||
| PHP_ARG_ENABLE(maxminddb-debug, for MaxMind DB debug support, | |||
| [ --enable-maxminddb-debug Enable enable MaxMind DB deubg support], no, no) | |||
| if test $PHP_MAXMINDDB != "no"; then | |||
| AC_PATH_PROG(PKG_CONFIG, pkg-config, no) | |||
| AC_MSG_CHECKING(for libmaxminddb) | |||
| if test -x "$PKG_CONFIG" && $PKG_CONFIG --exists libmaxminddb; then | |||
| dnl retrieve build options from pkg-config | |||
| if $PKG_CONFIG libmaxminddb --atleast-version 1.0.0; then | |||
| LIBMAXMINDDB_INC=`$PKG_CONFIG libmaxminddb --cflags` | |||
| LIBMAXMINDDB_LIB=`$PKG_CONFIG libmaxminddb --libs` | |||
| LIBMAXMINDDB_VER=`$PKG_CONFIG libmaxminddb --modversion` | |||
| AC_MSG_RESULT(found version $LIBMAXMINDDB_VER) | |||
| else | |||
| AC_MSG_ERROR(system libmaxminddb must be upgraded to version >= 1.0.0) | |||
| fi | |||
| PHP_EVAL_LIBLINE($LIBMAXMINDDB_LIB, MAXMINDDB_SHARED_LIBADD) | |||
| PHP_EVAL_INCLINE($LIBMAXMINDDB_INC) | |||
| else | |||
| AC_MSG_RESULT(pkg-config information missing) | |||
| AC_MSG_WARN(will use libmaxmxinddb from compiler default path) | |||
| PHP_CHECK_LIBRARY(maxminddb, MMDB_open) | |||
| PHP_ADD_LIBRARY(maxminddb, 1, MAXMINDDB_SHARED_LIBADD) | |||
| fi | |||
| if test $PHP_MAXMINDDB_DEBUG != "no"; then | |||
| CFLAGS="$CFLAGS -Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -Werror" | |||
| fi | |||
| PHP_SUBST(MAXMINDDB_SHARED_LIBADD) | |||
| PHP_NEW_EXTENSION(maxminddb, maxminddb.c, $ext_shared) | |||
| fi | |||
| @@ -0,0 +1,704 @@ | |||
| /* MaxMind, Inc., licenses this file to you under the Apache License, Version | |||
| * 2.0 (the "License"); you may not use this file except in compliance with | |||
| * the License. You may obtain a copy of the License at | |||
| * | |||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||
| * | |||
| * Unless required by applicable law or agreed to in writing, software | |||
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
| * License for the specific language governing permissions and limitations | |||
| * under the License. | |||
| */ | |||
| #include "php_maxminddb.h" | |||
| #ifdef HAVE_CONFIG_H | |||
| #include "config.h" | |||
| #endif | |||
| #include <php.h> | |||
| #include <zend.h> | |||
| #include "Zend/zend_exceptions.h" | |||
| #include "ext/standard/info.h" | |||
| #include <maxminddb.h> | |||
| #ifdef ZTS | |||
| #include <TSRM.h> | |||
| #endif | |||
| #define __STDC_FORMAT_MACROS | |||
| #include <inttypes.h> | |||
| #define PHP_MAXMINDDB_NS ZEND_NS_NAME("MaxMind", "Db") | |||
| #define PHP_MAXMINDDB_READER_NS ZEND_NS_NAME(PHP_MAXMINDDB_NS, "Reader") | |||
| #define PHP_MAXMINDDB_READER_EX_NS \ | |||
| ZEND_NS_NAME(PHP_MAXMINDDB_READER_NS, "InvalidDatabaseException") | |||
| #ifdef ZEND_ENGINE_3 | |||
| #define Z_MAXMINDDB_P(zv) php_maxminddb_fetch_object(Z_OBJ_P(zv)) | |||
| #define _ZVAL_STRING ZVAL_STRING | |||
| #define _ZVAL_STRINGL ZVAL_STRINGL | |||
| typedef size_t strsize_t; | |||
| typedef zend_object free_obj_t; | |||
| #else | |||
| #define Z_MAXMINDDB_P(zv) \ | |||
| (maxminddb_obj *)zend_object_store_get_object(zv TSRMLS_CC) | |||
| #define _ZVAL_STRING(a, b) ZVAL_STRING(a, b, 1) | |||
| #define _ZVAL_STRINGL(a, b, c) ZVAL_STRINGL(a, b, c, 1) | |||
| typedef int strsize_t; | |||
| typedef void free_obj_t; | |||
| #endif | |||
| /* For PHP 8 compatibility */ | |||
| #ifndef TSRMLS_C | |||
| #define TSRMLS_C | |||
| #endif | |||
| #ifndef TSRMLS_CC | |||
| #define TSRMLS_CC | |||
| #endif | |||
| #ifndef TSRMLS_DC | |||
| #define TSRMLS_DC | |||
| #endif | |||
| #ifndef ZEND_ACC_CTOR | |||
| #define ZEND_ACC_CTOR 0 | |||
| #endif | |||
| #ifdef ZEND_ENGINE_3 | |||
| typedef struct _maxminddb_obj { | |||
| MMDB_s *mmdb; | |||
| zend_object std; | |||
| } maxminddb_obj; | |||
| #else | |||
| typedef struct _maxminddb_obj { | |||
| zend_object std; | |||
| MMDB_s *mmdb; | |||
| } maxminddb_obj; | |||
| #endif | |||
| PHP_FUNCTION(maxminddb); | |||
| static int | |||
| get_record(INTERNAL_FUNCTION_PARAMETERS, zval *record, int *prefix_len); | |||
| static const MMDB_entry_data_list_s * | |||
| handle_entry_data_list(const MMDB_entry_data_list_s *entry_data_list, | |||
| zval *z_value TSRMLS_DC); | |||
| static const MMDB_entry_data_list_s * | |||
| handle_array(const MMDB_entry_data_list_s *entry_data_list, | |||
| zval *z_value TSRMLS_DC); | |||
| static const MMDB_entry_data_list_s * | |||
| handle_map(const MMDB_entry_data_list_s *entry_data_list, | |||
| zval *z_value TSRMLS_DC); | |||
| static void handle_uint128(const MMDB_entry_data_list_s *entry_data_list, | |||
| zval *z_value TSRMLS_DC); | |||
| static void handle_uint64(const MMDB_entry_data_list_s *entry_data_list, | |||
| zval *z_value TSRMLS_DC); | |||
| static void handle_uint32(const MMDB_entry_data_list_s *entry_data_list, | |||
| zval *z_value TSRMLS_DC); | |||
| static zend_class_entry *lookup_class(const char *name TSRMLS_DC); | |||
| #define CHECK_ALLOCATED(val) \ | |||
| if (!val) { \ | |||
| zend_error(E_ERROR, "Out of memory"); \ | |||
| return; \ | |||
| } | |||
| #define THROW_EXCEPTION(name, ...) \ | |||
| { \ | |||
| zend_class_entry *exception_ce = lookup_class(name TSRMLS_CC); \ | |||
| zend_throw_exception_ex(exception_ce, 0 TSRMLS_CC, __VA_ARGS__); \ | |||
| } | |||
| #if PHP_VERSION_ID < 50399 | |||
| #define object_properties_init(zo, class_type) \ | |||
| { \ | |||
| zval *tmp; \ | |||
| zend_hash_copy((*zo).properties, \ | |||
| &class_type->default_properties, \ | |||
| (copy_ctor_func_t)zval_add_ref, \ | |||
| (void *)&tmp, \ | |||
| sizeof(zval *)); \ | |||
| } | |||
| #endif | |||
| static zend_object_handlers maxminddb_obj_handlers; | |||
| static zend_class_entry *maxminddb_ce; | |||
| static inline maxminddb_obj * | |||
| php_maxminddb_fetch_object(zend_object *obj TSRMLS_DC) { | |||
| #ifdef ZEND_ENGINE_3 | |||
| return (maxminddb_obj *)((char *)(obj)-XtOffsetOf(maxminddb_obj, std)); | |||
| #else | |||
| return (maxminddb_obj *)obj; | |||
| #endif | |||
| } | |||
| ZEND_BEGIN_ARG_INFO_EX(arginfo_maxmindbreader_construct, 0, 0, 1) | |||
| ZEND_ARG_INFO(0, db_file) | |||
| ZEND_END_ARG_INFO() | |||
| PHP_METHOD(MaxMind_Db_Reader, __construct) { | |||
| char *db_file = NULL; | |||
| strsize_t name_len; | |||
| zval *_this_zval = NULL; | |||
| if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, | |||
| getThis(), | |||
| "Os", | |||
| &_this_zval, | |||
| maxminddb_ce, | |||
| &db_file, | |||
| &name_len) == FAILURE) { | |||
| THROW_EXCEPTION("InvalidArgumentException", | |||
| "The constructor takes exactly one argument."); | |||
| return; | |||
| } | |||
| if (0 != php_check_open_basedir(db_file TSRMLS_CC) || | |||
| 0 != access(db_file, R_OK)) { | |||
| THROW_EXCEPTION("InvalidArgumentException", | |||
| "The file \"%s\" does not exist or is not readable.", | |||
| db_file); | |||
| return; | |||
| } | |||
| MMDB_s *mmdb = (MMDB_s *)ecalloc(1, sizeof(MMDB_s)); | |||
| uint16_t status = MMDB_open(db_file, MMDB_MODE_MMAP, mmdb); | |||
| if (MMDB_SUCCESS != status) { | |||
| THROW_EXCEPTION(PHP_MAXMINDDB_READER_EX_NS, | |||
| "Error opening database file (%s). Is this a valid " | |||
| "MaxMind DB file?", | |||
| db_file); | |||
| efree(mmdb); | |||
| return; | |||
| } | |||
| maxminddb_obj *mmdb_obj = Z_MAXMINDDB_P(getThis()); | |||
| mmdb_obj->mmdb = mmdb; | |||
| } | |||
| ZEND_BEGIN_ARG_INFO_EX(arginfo_maxmindbreader_get, 0, 0, 1) | |||
| ZEND_ARG_INFO(0, ip_address) | |||
| ZEND_END_ARG_INFO() | |||
| PHP_METHOD(MaxMind_Db_Reader, get) { | |||
| int prefix_len = 0; | |||
| get_record(INTERNAL_FUNCTION_PARAM_PASSTHRU, return_value, &prefix_len); | |||
| } | |||
| PHP_METHOD(MaxMind_Db_Reader, getWithPrefixLen) { | |||
| zval *record, *z_prefix_len; | |||
| #ifdef ZEND_ENGINE_3 | |||
| zval _record, _z_prefix_len; | |||
| record = &_record; | |||
| z_prefix_len = &_z_prefix_len; | |||
| #else | |||
| ALLOC_INIT_ZVAL(record); | |||
| ALLOC_INIT_ZVAL(z_prefix_len); | |||
| #endif | |||
| int prefix_len = 0; | |||
| if (get_record(INTERNAL_FUNCTION_PARAM_PASSTHRU, record, &prefix_len)) { | |||
| return; | |||
| } | |||
| array_init(return_value); | |||
| add_next_index_zval(return_value, record); | |||
| ZVAL_LONG(z_prefix_len, prefix_len); | |||
| add_next_index_zval(return_value, z_prefix_len); | |||
| } | |||
| static int | |||
| get_record(INTERNAL_FUNCTION_PARAMETERS, zval *record, int *prefix_len) { | |||
| char *ip_address = NULL; | |||
| strsize_t name_len; | |||
| zval *_this_zval = NULL; | |||
| if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, | |||
| getThis(), | |||
| "Os", | |||
| &_this_zval, | |||
| maxminddb_ce, | |||
| &ip_address, | |||
| &name_len) == FAILURE) { | |||
| THROW_EXCEPTION("InvalidArgumentException", | |||
| "Method takes exactly one argument."); | |||
| return 1; | |||
| } | |||
| const maxminddb_obj *mmdb_obj = (maxminddb_obj *)Z_MAXMINDDB_P(getThis()); | |||
| MMDB_s *mmdb = mmdb_obj->mmdb; | |||
| if (NULL == mmdb) { | |||
| THROW_EXCEPTION("BadMethodCallException", | |||
| "Attempt to read from a closed MaxMind DB."); | |||
| return 1; | |||
| } | |||
| struct addrinfo hints = { | |||
| .ai_family = AF_UNSPEC, | |||
| .ai_flags = AI_NUMERICHOST, | |||
| // We set ai_socktype so that we only get one result back | |||
| .ai_socktype = SOCK_STREAM}; | |||
| struct addrinfo *addresses = NULL; | |||
| int gai_status = getaddrinfo(ip_address, NULL, &hints, &addresses); | |||
| if (gai_status) { | |||
| THROW_EXCEPTION("InvalidArgumentException", | |||
| "The value \"%s\" is not a valid IP address.", | |||
| ip_address); | |||
| return 1; | |||
| } | |||
| if (!addresses || !addresses->ai_addr) { | |||
| THROW_EXCEPTION( | |||
| "InvalidArgumentException", | |||
| "getaddrinfo was successful but failed to set the addrinfo"); | |||
| return 1; | |||
| } | |||
| int sa_family = addresses->ai_addr->sa_family; | |||
| int mmdb_error = MMDB_SUCCESS; | |||
| MMDB_lookup_result_s result = | |||
| MMDB_lookup_sockaddr(mmdb, addresses->ai_addr, &mmdb_error); | |||
| freeaddrinfo(addresses); | |||
| if (MMDB_SUCCESS != mmdb_error) { | |||
| char *exception_name; | |||
| if (MMDB_IPV6_LOOKUP_IN_IPV4_DATABASE_ERROR == mmdb_error) { | |||
| exception_name = "InvalidArgumentException"; | |||
| } else { | |||
| exception_name = PHP_MAXMINDDB_READER_EX_NS; | |||
| } | |||
| THROW_EXCEPTION(exception_name, | |||
| "Error looking up %s. %s", | |||
| ip_address, | |||
| MMDB_strerror(mmdb_error)); | |||
| return 1; | |||
| } | |||
| *prefix_len = result.netmask; | |||
| if (sa_family == AF_INET && mmdb->metadata.ip_version == 6) { | |||
| // We return the prefix length given the IPv4 address. If there is | |||
| // no IPv4 subtree, we return a prefix length of 0. | |||
| *prefix_len = *prefix_len >= 96 ? *prefix_len - 96 : 0; | |||
| } | |||
| if (!result.found_entry) { | |||
| ZVAL_NULL(record); | |||
| return 0; | |||
| } | |||
| MMDB_entry_data_list_s *entry_data_list = NULL; | |||
| int status = MMDB_get_entry_data_list(&result.entry, &entry_data_list); | |||
| if (MMDB_SUCCESS != status) { | |||
| THROW_EXCEPTION(PHP_MAXMINDDB_READER_EX_NS, | |||
| "Error while looking up data for %s. %s", | |||
| ip_address, | |||
| MMDB_strerror(status)); | |||
| MMDB_free_entry_data_list(entry_data_list); | |||
| return 1; | |||
| } else if (NULL == entry_data_list) { | |||
| THROW_EXCEPTION(PHP_MAXMINDDB_READER_EX_NS, | |||
| "Error while looking up data for %s. Your database may " | |||
| "be corrupt or you have found a bug in libmaxminddb.", | |||
| ip_address); | |||
| return 1; | |||
| } | |||
| handle_entry_data_list(entry_data_list, record TSRMLS_CC); | |||
| MMDB_free_entry_data_list(entry_data_list); | |||
| return 0; | |||
| } | |||
| ZEND_BEGIN_ARG_INFO_EX(arginfo_maxmindbreader_void, 0, 0, 0) | |||
| ZEND_END_ARG_INFO() | |||
| PHP_METHOD(MaxMind_Db_Reader, metadata) { | |||
| if (ZEND_NUM_ARGS() != 0) { | |||
| THROW_EXCEPTION("InvalidArgumentException", | |||
| "Method takes no arguments."); | |||
| return; | |||
| } | |||
| const maxminddb_obj *const mmdb_obj = | |||
| (maxminddb_obj *)Z_MAXMINDDB_P(getThis()); | |||
| if (NULL == mmdb_obj->mmdb) { | |||
| THROW_EXCEPTION("BadMethodCallException", | |||
| "Attempt to read from a closed MaxMind DB."); | |||
| return; | |||
| } | |||
| const char *const name = ZEND_NS_NAME(PHP_MAXMINDDB_READER_NS, "Metadata"); | |||
| zend_class_entry *metadata_ce = lookup_class(name TSRMLS_CC); | |||
| object_init_ex(return_value, metadata_ce); | |||
| #ifdef ZEND_ENGINE_3 | |||
| zval _metadata_array; | |||
| zval *metadata_array = &_metadata_array; | |||
| ZVAL_NULL(metadata_array); | |||
| #else | |||
| zval *metadata_array; | |||
| ALLOC_INIT_ZVAL(metadata_array); | |||
| #endif | |||
| MMDB_entry_data_list_s *entry_data_list; | |||
| MMDB_get_metadata_as_entry_data_list(mmdb_obj->mmdb, &entry_data_list); | |||
| handle_entry_data_list(entry_data_list, metadata_array TSRMLS_CC); | |||
| MMDB_free_entry_data_list(entry_data_list); | |||
| #if PHP_VERSION_ID >= 80000 | |||
| zend_call_method_with_1_params(Z_OBJ_P(return_value), | |||
| metadata_ce, | |||
| &metadata_ce->constructor, | |||
| ZEND_CONSTRUCTOR_FUNC_NAME, | |||
| NULL, | |||
| metadata_array); | |||
| zval_ptr_dtor(metadata_array); | |||
| #elif defined(ZEND_ENGINE_3) | |||
| zend_call_method_with_1_params(return_value, | |||
| metadata_ce, | |||
| &metadata_ce->constructor, | |||
| ZEND_CONSTRUCTOR_FUNC_NAME, | |||
| NULL, | |||
| metadata_array); | |||
| zval_ptr_dtor(metadata_array); | |||
| #else | |||
| zend_call_method_with_1_params(&return_value, | |||
| metadata_ce, | |||
| &metadata_ce->constructor, | |||
| ZEND_CONSTRUCTOR_FUNC_NAME, | |||
| NULL, | |||
| metadata_array); | |||
| zval_ptr_dtor(&metadata_array); | |||
| #endif | |||
| } | |||
| PHP_METHOD(MaxMind_Db_Reader, close) { | |||
| if (ZEND_NUM_ARGS() != 0) { | |||
| THROW_EXCEPTION("InvalidArgumentException", | |||
| "Method takes no arguments."); | |||
| return; | |||
| } | |||
| maxminddb_obj *mmdb_obj = (maxminddb_obj *)Z_MAXMINDDB_P(getThis()); | |||
| if (NULL == mmdb_obj->mmdb) { | |||
| THROW_EXCEPTION("BadMethodCallException", | |||
| "Attempt to close a closed MaxMind DB."); | |||
| return; | |||
| } | |||
| MMDB_close(mmdb_obj->mmdb); | |||
| efree(mmdb_obj->mmdb); | |||
| mmdb_obj->mmdb = NULL; | |||
| } | |||
| static const MMDB_entry_data_list_s * | |||
| handle_entry_data_list(const MMDB_entry_data_list_s *entry_data_list, | |||
| zval *z_value TSRMLS_DC) { | |||
| switch (entry_data_list->entry_data.type) { | |||
| case MMDB_DATA_TYPE_MAP: | |||
| return handle_map(entry_data_list, z_value TSRMLS_CC); | |||
| case MMDB_DATA_TYPE_ARRAY: | |||
| return handle_array(entry_data_list, z_value TSRMLS_CC); | |||
| case MMDB_DATA_TYPE_UTF8_STRING: | |||
| _ZVAL_STRINGL(z_value, | |||
| (char *)entry_data_list->entry_data.utf8_string, | |||
| entry_data_list->entry_data.data_size); | |||
| break; | |||
| case MMDB_DATA_TYPE_BYTES: | |||
| _ZVAL_STRINGL(z_value, | |||
| (char *)entry_data_list->entry_data.bytes, | |||
| entry_data_list->entry_data.data_size); | |||
| break; | |||
| case MMDB_DATA_TYPE_DOUBLE: | |||
| ZVAL_DOUBLE(z_value, entry_data_list->entry_data.double_value); | |||
| break; | |||
| case MMDB_DATA_TYPE_FLOAT: | |||
| ZVAL_DOUBLE(z_value, entry_data_list->entry_data.float_value); | |||
| break; | |||
| case MMDB_DATA_TYPE_UINT16: | |||
| ZVAL_LONG(z_value, entry_data_list->entry_data.uint16); | |||
| break; | |||
| case MMDB_DATA_TYPE_UINT32: | |||
| handle_uint32(entry_data_list, z_value TSRMLS_CC); | |||
| break; | |||
| case MMDB_DATA_TYPE_BOOLEAN: | |||
| ZVAL_BOOL(z_value, entry_data_list->entry_data.boolean); | |||
| break; | |||
| case MMDB_DATA_TYPE_UINT64: | |||
| handle_uint64(entry_data_list, z_value TSRMLS_CC); | |||
| break; | |||
| case MMDB_DATA_TYPE_UINT128: | |||
| handle_uint128(entry_data_list, z_value TSRMLS_CC); | |||
| break; | |||
| case MMDB_DATA_TYPE_INT32: | |||
| ZVAL_LONG(z_value, entry_data_list->entry_data.int32); | |||
| break; | |||
| default: | |||
| THROW_EXCEPTION(PHP_MAXMINDDB_READER_EX_NS, | |||
| "Invalid data type arguments: %d", | |||
| entry_data_list->entry_data.type); | |||
| return NULL; | |||
| } | |||
| return entry_data_list; | |||
| } | |||
| static const MMDB_entry_data_list_s * | |||
| handle_map(const MMDB_entry_data_list_s *entry_data_list, | |||
| zval *z_value TSRMLS_DC) { | |||
| array_init(z_value); | |||
| const uint32_t map_size = entry_data_list->entry_data.data_size; | |||
| uint i; | |||
| for (i = 0; i < map_size && entry_data_list; i++) { | |||
| entry_data_list = entry_data_list->next; | |||
| char *key = estrndup((char *)entry_data_list->entry_data.utf8_string, | |||
| entry_data_list->entry_data.data_size); | |||
| if (NULL == key) { | |||
| THROW_EXCEPTION(PHP_MAXMINDDB_READER_EX_NS, | |||
| "Invalid data type arguments"); | |||
| return NULL; | |||
| } | |||
| entry_data_list = entry_data_list->next; | |||
| #ifdef ZEND_ENGINE_3 | |||
| zval _new_value; | |||
| zval *new_value = &_new_value; | |||
| ZVAL_NULL(new_value); | |||
| #else | |||
| zval *new_value; | |||
| ALLOC_INIT_ZVAL(new_value); | |||
| #endif | |||
| entry_data_list = | |||
| handle_entry_data_list(entry_data_list, new_value TSRMLS_CC); | |||
| add_assoc_zval(z_value, key, new_value); | |||
| efree(key); | |||
| } | |||
| return entry_data_list; | |||
| } | |||
| static const MMDB_entry_data_list_s * | |||
| handle_array(const MMDB_entry_data_list_s *entry_data_list, | |||
| zval *z_value TSRMLS_DC) { | |||
| const uint32_t size = entry_data_list->entry_data.data_size; | |||
| array_init(z_value); | |||
| uint i; | |||
| for (i = 0; i < size && entry_data_list; i++) { | |||
| entry_data_list = entry_data_list->next; | |||
| #ifdef ZEND_ENGINE_3 | |||
| zval _new_value; | |||
| zval *new_value = &_new_value; | |||
| ZVAL_NULL(new_value); | |||
| #else | |||
| zval *new_value; | |||
| ALLOC_INIT_ZVAL(new_value); | |||
| #endif | |||
| entry_data_list = | |||
| handle_entry_data_list(entry_data_list, new_value TSRMLS_CC); | |||
| add_next_index_zval(z_value, new_value); | |||
| } | |||
| return entry_data_list; | |||
| } | |||
| static void handle_uint128(const MMDB_entry_data_list_s *entry_data_list, | |||
| zval *z_value TSRMLS_DC) { | |||
| uint64_t high = 0; | |||
| uint64_t low = 0; | |||
| #if MMDB_UINT128_IS_BYTE_ARRAY | |||
| int i; | |||
| for (i = 0; i < 8; i++) { | |||
| high = (high << 8) | entry_data_list->entry_data.uint128[i]; | |||
| } | |||
| for (i = 8; i < 16; i++) { | |||
| low = (low << 8) | entry_data_list->entry_data.uint128[i]; | |||
| } | |||
| #else | |||
| high = entry_data_list->entry_data.uint128 >> 64; | |||
| low = (uint64_t)entry_data_list->entry_data.uint128; | |||
| #endif | |||
| char *num_str; | |||
| spprintf(&num_str, 0, "0x%016" PRIX64 "%016" PRIX64, high, low); | |||
| CHECK_ALLOCATED(num_str); | |||
| _ZVAL_STRING(z_value, num_str); | |||
| efree(num_str); | |||
| } | |||
| static void handle_uint32(const MMDB_entry_data_list_s *entry_data_list, | |||
| zval *z_value TSRMLS_DC) { | |||
| uint32_t val = entry_data_list->entry_data.uint32; | |||
| #if LONG_MAX >= UINT32_MAX | |||
| ZVAL_LONG(z_value, val); | |||
| return; | |||
| #else | |||
| if (val <= LONG_MAX) { | |||
| ZVAL_LONG(z_value, val); | |||
| return; | |||
| } | |||
| char *int_str; | |||
| spprintf(&int_str, 0, "%" PRIu32, val); | |||
| CHECK_ALLOCATED(int_str); | |||
| _ZVAL_STRING(z_value, int_str); | |||
| efree(int_str); | |||
| #endif | |||
| } | |||
| static void handle_uint64(const MMDB_entry_data_list_s *entry_data_list, | |||
| zval *z_value TSRMLS_DC) { | |||
| uint64_t val = entry_data_list->entry_data.uint64; | |||
| #if LONG_MAX >= UINT64_MAX | |||
| ZVAL_LONG(z_value, val); | |||
| return; | |||
| #else | |||
| if (val <= LONG_MAX) { | |||
| ZVAL_LONG(z_value, val); | |||
| return; | |||
| } | |||
| char *int_str; | |||
| spprintf(&int_str, 0, "%" PRIu64, val); | |||
| CHECK_ALLOCATED(int_str); | |||
| _ZVAL_STRING(z_value, int_str); | |||
| efree(int_str); | |||
| #endif | |||
| } | |||
| static zend_class_entry *lookup_class(const char *name TSRMLS_DC) { | |||
| #ifdef ZEND_ENGINE_3 | |||
| zend_string *n = zend_string_init(name, strlen(name), 0); | |||
| zend_class_entry *ce = zend_lookup_class(n); | |||
| zend_string_release(n); | |||
| if (NULL == ce) { | |||
| zend_error(E_ERROR, "Class %s not found", name); | |||
| } | |||
| return ce; | |||
| #else | |||
| zend_class_entry **ce; | |||
| if (FAILURE == zend_lookup_class(name, strlen(name), &ce TSRMLS_CC)) { | |||
| zend_error(E_ERROR, "Class %s not found", name); | |||
| } | |||
| return *ce; | |||
| #endif | |||
| } | |||
| static void maxminddb_free_storage(free_obj_t *object TSRMLS_DC) { | |||
| maxminddb_obj *obj = | |||
| php_maxminddb_fetch_object((zend_object *)object TSRMLS_CC); | |||
| if (obj->mmdb != NULL) { | |||
| MMDB_close(obj->mmdb); | |||
| efree(obj->mmdb); | |||
| } | |||
| zend_object_std_dtor(&obj->std TSRMLS_CC); | |||
| #ifndef ZEND_ENGINE_3 | |||
| efree(object); | |||
| #endif | |||
| } | |||
| #ifdef ZEND_ENGINE_3 | |||
| static zend_object *maxminddb_create_handler(zend_class_entry *type TSRMLS_DC) { | |||
| maxminddb_obj *obj = (maxminddb_obj *)ecalloc(1, sizeof(maxminddb_obj)); | |||
| zend_object_std_init(&obj->std, type TSRMLS_CC); | |||
| object_properties_init(&(obj->std), type); | |||
| obj->std.handlers = &maxminddb_obj_handlers; | |||
| return &obj->std; | |||
| } | |||
| #else | |||
| static zend_object_value | |||
| maxminddb_create_handler(zend_class_entry *type TSRMLS_DC) { | |||
| zend_object_value retval; | |||
| maxminddb_obj *obj = (maxminddb_obj *)ecalloc(1, sizeof(maxminddb_obj)); | |||
| zend_object_std_init(&obj->std, type TSRMLS_CC); | |||
| object_properties_init(&(obj->std), type); | |||
| retval.handle = zend_objects_store_put( | |||
| obj, NULL, maxminddb_free_storage, NULL TSRMLS_CC); | |||
| retval.handlers = &maxminddb_obj_handlers; | |||
| return retval; | |||
| } | |||
| #endif | |||
| // clang-format off | |||
| static zend_function_entry maxminddb_methods[] = { | |||
| PHP_ME(MaxMind_Db_Reader, __construct, arginfo_maxmindbreader_construct, | |||
| ZEND_ACC_PUBLIC | ZEND_ACC_CTOR) | |||
| PHP_ME(MaxMind_Db_Reader, close, arginfo_maxmindbreader_void, ZEND_ACC_PUBLIC) | |||
| PHP_ME(MaxMind_Db_Reader, get, arginfo_maxmindbreader_get, ZEND_ACC_PUBLIC) | |||
| PHP_ME(MaxMind_Db_Reader, getWithPrefixLen, arginfo_maxmindbreader_get, ZEND_ACC_PUBLIC) | |||
| PHP_ME(MaxMind_Db_Reader, metadata, arginfo_maxmindbreader_void, ZEND_ACC_PUBLIC) | |||
| { NULL, NULL, NULL } | |||
| }; | |||
| // clang-format on | |||
| PHP_MINIT_FUNCTION(maxminddb) { | |||
| zend_class_entry ce; | |||
| INIT_CLASS_ENTRY(ce, PHP_MAXMINDDB_READER_NS, maxminddb_methods); | |||
| maxminddb_ce = zend_register_internal_class(&ce TSRMLS_CC); | |||
| maxminddb_ce->create_object = maxminddb_create_handler; | |||
| memcpy(&maxminddb_obj_handlers, | |||
| zend_get_std_object_handlers(), | |||
| sizeof(zend_object_handlers)); | |||
| maxminddb_obj_handlers.clone_obj = NULL; | |||
| #ifdef ZEND_ENGINE_3 | |||
| maxminddb_obj_handlers.offset = XtOffsetOf(maxminddb_obj, std); | |||
| maxminddb_obj_handlers.free_obj = maxminddb_free_storage; | |||
| #endif | |||
| zend_declare_class_constant_string(maxminddb_ce, | |||
| "MMDB_LIB_VERSION", | |||
| sizeof("MMDB_LIB_VERSION") - 1, | |||
| MMDB_lib_version() TSRMLS_CC); | |||
| return SUCCESS; | |||
| } | |||
| static PHP_MINFO_FUNCTION(maxminddb) { | |||
| php_info_print_table_start(); | |||
| php_info_print_table_row(2, "MaxMind DB Reader", "enabled"); | |||
| php_info_print_table_row( | |||
| 2, "maxminddb extension version", PHP_MAXMINDDB_VERSION); | |||
| php_info_print_table_row( | |||
| 2, "libmaxminddb library version", MMDB_lib_version()); | |||
| php_info_print_table_end(); | |||
| } | |||
| zend_module_entry maxminddb_module_entry = {STANDARD_MODULE_HEADER, | |||
| PHP_MAXMINDDB_EXTNAME, | |||
| NULL, | |||
| PHP_MINIT(maxminddb), | |||
| NULL, | |||
| NULL, | |||
| NULL, | |||
| PHP_MINFO(maxminddb), | |||
| PHP_MAXMINDDB_VERSION, | |||
| STANDARD_MODULE_PROPERTIES}; | |||
| #ifdef COMPILE_DL_MAXMINDDB | |||
| ZEND_GET_MODULE(maxminddb) | |||
| #endif | |||
| @@ -0,0 +1,24 @@ | |||
| /* MaxMind, Inc., licenses this file to you under the Apache License, Version | |||
| * 2.0 (the "License"); you may not use this file except in compliance with | |||
| * the License. You may obtain a copy of the License at | |||
| * | |||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||
| * | |||
| * Unless required by applicable law or agreed to in writing, software | |||
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
| * License for the specific language governing permissions and limitations | |||
| * under the License. | |||
| */ | |||
| #include <zend_interfaces.h> | |||
| #ifndef PHP_MAXMINDDB_H | |||
| #define PHP_MAXMINDDB_H 1 | |||
| #define PHP_MAXMINDDB_VERSION "1.6.0" | |||
| #define PHP_MAXMINDDB_EXTNAME "maxminddb" | |||
| extern zend_module_entry maxminddb_module_entry; | |||
| #define phpext_maxminddb_ptr &maxminddb_module_entry | |||
| #endif | |||
| @@ -0,0 +1,12 @@ | |||
| --TEST-- | |||
| Check for maxminddb presence | |||
| --SKIPIF-- | |||
| <?php if (!extension_loaded('maxminddb')) { | |||
| echo 'skip'; | |||
| } ?> | |||
| --FILE-- | |||
| <?php | |||
| echo 'maxminddb extension is available'; | |||
| ?> | |||
| --EXPECT-- | |||
| maxminddb extension is available | |||
| @@ -0,0 +1,13 @@ | |||
| --TEST-- | |||
| Check that Reader class is not final | |||
| --SKIPIF-- | |||
| <?php if (!extension_loaded('maxminddb')) { | |||
| echo 'skip'; | |||
| } ?> | |||
| --FILE-- | |||
| <?php | |||
| $reflectionClass = new \ReflectionClass('MaxMind\Db\Reader'); | |||
| var_dump($reflectionClass->isFinal()); | |||
| ?> | |||
| --EXPECT-- | |||
| bool(false) | |||
| @@ -0,0 +1,12 @@ | |||
| --TEST-- | |||
| openbase_dir is followed | |||
| --INI-- | |||
| open_basedir=/--dne-- | |||
| --FILE-- | |||
| <?php | |||
| use MaxMind\Db\Reader; | |||
| $reader = new Reader('/usr/local/share/GeoIP/GeoIP2-City.mmdb'); | |||
| ?> | |||
| --EXPECTREGEX-- | |||
| .*open_basedir restriction in effect.* | |||
| @@ -0,0 +1,327 @@ | |||
| <?php | |||
| namespace MaxMind\Db; | |||
| use BadMethodCallException; | |||
| use Exception; | |||
| use InvalidArgumentException; | |||
| use MaxMind\Db\Reader\Decoder; | |||
| use MaxMind\Db\Reader\InvalidDatabaseException; | |||
| use MaxMind\Db\Reader\Metadata; | |||
| use MaxMind\Db\Reader\Util; | |||
| use UnexpectedValueException; | |||
| /** | |||
| * Instances of this class provide a reader for the MaxMind DB format. IP | |||
| * addresses can be looked up using the get method. | |||
| */ | |||
| class Reader | |||
| { | |||
| private static $DATA_SECTION_SEPARATOR_SIZE = 16; | |||
| private static $METADATA_START_MARKER = "\xAB\xCD\xEFMaxMind.com"; | |||
| private static $METADATA_START_MARKER_LENGTH = 14; | |||
| private static $METADATA_MAX_SIZE = 131072; // 128 * 1024 = 128KB | |||
| private $decoder; | |||
| private $fileHandle; | |||
| private $fileSize; | |||
| private $ipV4Start; | |||
| private $metadata; | |||
| /** | |||
| * Constructs a Reader for the MaxMind DB format. The file passed to it must | |||
| * be a valid MaxMind DB file such as a GeoIp2 database file. | |||
| * | |||
| * @param string $database | |||
| * the MaxMind DB file to use | |||
| * | |||
| * @throws InvalidArgumentException for invalid database path or unknown arguments | |||
| * @throws \MaxMind\Db\Reader\InvalidDatabaseException | |||
| * if the database is invalid or there is an error reading | |||
| * from it | |||
| */ | |||
| public function __construct($database) | |||
| { | |||
| if (\func_num_args() !== 1) { | |||
| throw new InvalidArgumentException( | |||
| 'The constructor takes exactly one argument.' | |||
| ); | |||
| } | |||
| if (!is_readable($database)) { | |||
| throw new InvalidArgumentException( | |||
| "The file \"$database\" does not exist or is not readable." | |||
| ); | |||
| } | |||
| $this->fileHandle = @fopen($database, 'rb'); | |||
| if ($this->fileHandle === false) { | |||
| throw new InvalidArgumentException( | |||
| "Error opening \"$database\"." | |||
| ); | |||
| } | |||
| $this->fileSize = @filesize($database); | |||
| if ($this->fileSize === false) { | |||
| throw new UnexpectedValueException( | |||
| "Error determining the size of \"$database\"." | |||
| ); | |||
| } | |||
| $start = $this->findMetadataStart($database); | |||
| $metadataDecoder = new Decoder($this->fileHandle, $start); | |||
| list($metadataArray) = $metadataDecoder->decode($start); | |||
| $this->metadata = new Metadata($metadataArray); | |||
| $this->decoder = new Decoder( | |||
| $this->fileHandle, | |||
| $this->metadata->searchTreeSize + self::$DATA_SECTION_SEPARATOR_SIZE | |||
| ); | |||
| $this->ipV4Start = $this->ipV4StartNode(); | |||
| } | |||
| /** | |||
| * Retrieves the record for the IP address. | |||
| * | |||
| * @param string $ipAddress | |||
| * the IP address to look up | |||
| * | |||
| * @throws BadMethodCallException if this method is called on a closed database | |||
| * @throws InvalidArgumentException if something other than a single IP address is passed to the method | |||
| * @throws InvalidDatabaseException | |||
| * if the database is invalid or there is an error reading | |||
| * from it | |||
| * | |||
| * @return mixed the record for the IP address | |||
| */ | |||
| public function get($ipAddress) | |||
| { | |||
| if (\func_num_args() !== 1) { | |||
| throw new InvalidArgumentException( | |||
| 'Method takes exactly one argument.' | |||
| ); | |||
| } | |||
| list($record) = $this->getWithPrefixLen($ipAddress); | |||
| return $record; | |||
| } | |||
| /** | |||
| * Retrieves the record for the IP address and its associated network prefix length. | |||
| * | |||
| * @param string $ipAddress | |||
| * the IP address to look up | |||
| * | |||
| * @throws BadMethodCallException if this method is called on a closed database | |||
| * @throws InvalidArgumentException if something other than a single IP address is passed to the method | |||
| * @throws InvalidDatabaseException | |||
| * if the database is invalid or there is an error reading | |||
| * from it | |||
| * | |||
| * @return array an array where the first element is the record and the | |||
| * second the network prefix length for the record | |||
| */ | |||
| public function getWithPrefixLen($ipAddress) | |||
| { | |||
| if (\func_num_args() !== 1) { | |||
| throw new InvalidArgumentException( | |||
| 'Method takes exactly one argument.' | |||
| ); | |||
| } | |||
| if (!\is_resource($this->fileHandle)) { | |||
| throw new BadMethodCallException( | |||
| 'Attempt to read from a closed MaxMind DB.' | |||
| ); | |||
| } | |||
| if (!filter_var($ipAddress, FILTER_VALIDATE_IP)) { | |||
| throw new InvalidArgumentException( | |||
| "The value \"$ipAddress\" is not a valid IP address." | |||
| ); | |||
| } | |||
| list($pointer, $prefixLen) = $this->findAddressInTree($ipAddress); | |||
| if ($pointer === 0) { | |||
| return [null, $prefixLen]; | |||
| } | |||
| return [$this->resolveDataPointer($pointer), $prefixLen]; | |||
| } | |||
| private function findAddressInTree($ipAddress) | |||
| { | |||
| $rawAddress = unpack('C*', inet_pton($ipAddress)); | |||
| $bitCount = \count($rawAddress) * 8; | |||
| // The first node of the tree is always node 0, at the beginning of the | |||
| // value | |||
| $node = 0; | |||
| $metadata = $this->metadata; | |||
| // Check if we are looking up an IPv4 address in an IPv6 tree. If this | |||
| // is the case, we can skip over the first 96 nodes. | |||
| if ($metadata->ipVersion === 6) { | |||
| if ($bitCount === 32) { | |||
| $node = $this->ipV4Start; | |||
| } | |||
| } elseif ($metadata->ipVersion === 4 && $bitCount === 128) { | |||
| throw new InvalidArgumentException( | |||
| "Error looking up $ipAddress. You attempted to look up an" | |||
| . ' IPv6 address in an IPv4-only database.' | |||
| ); | |||
| } | |||
| $nodeCount = $metadata->nodeCount; | |||
| for ($i = 0; $i < $bitCount && $node < $nodeCount; ++$i) { | |||
| $tempBit = 0xFF & $rawAddress[($i >> 3) + 1]; | |||
| $bit = 1 & ($tempBit >> 7 - ($i % 8)); | |||
| $node = $this->readNode($node, $bit); | |||
| } | |||
| if ($node === $nodeCount) { | |||
| // Record is empty | |||
| return [0, $i]; | |||
| } elseif ($node > $nodeCount) { | |||
| // Record is a data pointer | |||
| return [$node, $i]; | |||
| } | |||
| throw new InvalidDatabaseException('Something bad happened'); | |||
| } | |||
| private function ipV4StartNode() | |||
| { | |||
| // If we have an IPv4 database, the start node is the first node | |||
| if ($this->metadata->ipVersion === 4) { | |||
| return 0; | |||
| } | |||
| $node = 0; | |||
| for ($i = 0; $i < 96 && $node < $this->metadata->nodeCount; ++$i) { | |||
| $node = $this->readNode($node, 0); | |||
| } | |||
| return $node; | |||
| } | |||
| private function readNode($nodeNumber, $index) | |||
| { | |||
| $baseOffset = $nodeNumber * $this->metadata->nodeByteSize; | |||
| switch ($this->metadata->recordSize) { | |||
| case 24: | |||
| $bytes = Util::read($this->fileHandle, $baseOffset + $index * 3, 3); | |||
| list(, $node) = unpack('N', "\x00" . $bytes); | |||
| return $node; | |||
| case 28: | |||
| $bytes = Util::read($this->fileHandle, $baseOffset + 3 * $index, 4); | |||
| if ($index === 0) { | |||
| $middle = (0xF0 & \ord($bytes[3])) >> 4; | |||
| } else { | |||
| $middle = 0x0F & \ord($bytes[0]); | |||
| } | |||
| list(, $node) = unpack('N', \chr($middle) . substr($bytes, $index, 3)); | |||
| return $node; | |||
| case 32: | |||
| $bytes = Util::read($this->fileHandle, $baseOffset + $index * 4, 4); | |||
| list(, $node) = unpack('N', $bytes); | |||
| return $node; | |||
| default: | |||
| throw new InvalidDatabaseException( | |||
| 'Unknown record size: ' | |||
| . $this->metadata->recordSize | |||
| ); | |||
| } | |||
| } | |||
| private function resolveDataPointer($pointer) | |||
| { | |||
| $resolved = $pointer - $this->metadata->nodeCount | |||
| + $this->metadata->searchTreeSize; | |||
| if ($resolved >= $this->fileSize) { | |||
| throw new InvalidDatabaseException( | |||
| "The MaxMind DB file's search tree is corrupt" | |||
| ); | |||
| } | |||
| list($data) = $this->decoder->decode($resolved); | |||
| return $data; | |||
| } | |||
| /* | |||
| * This is an extremely naive but reasonably readable implementation. There | |||
| * are much faster algorithms (e.g., Boyer-Moore) for this if speed is ever | |||
| * an issue, but I suspect it won't be. | |||
| */ | |||
| private function findMetadataStart($filename) | |||
| { | |||
| $handle = $this->fileHandle; | |||
| $fstat = fstat($handle); | |||
| $fileSize = $fstat['size']; | |||
| $marker = self::$METADATA_START_MARKER; | |||
| $markerLength = self::$METADATA_START_MARKER_LENGTH; | |||
| $minStart = $fileSize - min(self::$METADATA_MAX_SIZE, $fileSize); | |||
| for ($offset = $fileSize - $markerLength; $offset >= $minStart; --$offset) { | |||
| if (fseek($handle, $offset) !== 0) { | |||
| break; | |||
| } | |||
| $value = fread($handle, $markerLength); | |||
| if ($value === $marker) { | |||
| return $offset + $markerLength; | |||
| } | |||
| } | |||
| throw new InvalidDatabaseException( | |||
| "Error opening database file ($filename). " . | |||
| 'Is this a valid MaxMind DB file?' | |||
| ); | |||
| } | |||
| /** | |||
| * @throws InvalidArgumentException if arguments are passed to the method | |||
| * @throws BadMethodCallException if the database has been closed | |||
| * | |||
| * @return Metadata object for the database | |||
| */ | |||
| public function metadata() | |||
| { | |||
| if (\func_num_args()) { | |||
| throw new InvalidArgumentException( | |||
| 'Method takes no arguments.' | |||
| ); | |||
| } | |||
| // Not technically required, but this makes it consistent with | |||
| // C extension and it allows us to change our implementation later. | |||
| if (!\is_resource($this->fileHandle)) { | |||
| throw new BadMethodCallException( | |||
| 'Attempt to read from a closed MaxMind DB.' | |||
| ); | |||
| } | |||
| return $this->metadata; | |||
| } | |||
| /** | |||
| * Closes the MaxMind DB and returns resources to the system. | |||
| * | |||
| * @throws Exception | |||
| * if an I/O error occurs | |||
| */ | |||
| public function close() | |||
| { | |||
| if (!\is_resource($this->fileHandle)) { | |||
| throw new BadMethodCallException( | |||
| 'Attempt to close a closed MaxMind DB.' | |||
| ); | |||
| } | |||
| fclose($this->fileHandle); | |||
| } | |||
| } | |||
| @@ -0,0 +1,356 @@ | |||
| <?php | |||
| namespace MaxMind\Db\Reader; | |||
| // @codingStandardsIgnoreLine | |||
| use RuntimeException; | |||
| /** | |||
| * @ignore | |||
| * | |||
| * We subtract 1 from the log to protect against precision loss. | |||
| */ | |||
| \define(__NAMESPACE__ . '\_MM_MAX_INT_BYTES', (log(PHP_INT_MAX, 2) - 1) / 8); | |||
| class Decoder | |||
| { | |||
| private $fileStream; | |||
| private $pointerBase; | |||
| private $pointerBaseByteSize; | |||
| // This is only used for unit testing | |||
| private $pointerTestHack; | |||
| private $switchByteOrder; | |||
| /** @ignore */ | |||
| const _EXTENDED = 0; | |||
| /** @ignore */ | |||
| const _POINTER = 1; | |||
| /** @ignore */ | |||
| const _UTF8_STRING = 2; | |||
| /** @ignore */ | |||
| const _DOUBLE = 3; | |||
| /** @ignore */ | |||
| const _BYTES = 4; | |||
| /** @ignore */ | |||
| const _UINT16 = 5; | |||
| /** @ignore */ | |||
| const _UINT32 = 6; | |||
| /** @ignore */ | |||
| const _MAP = 7; | |||
| /** @ignore */ | |||
| const _INT32 = 8; | |||
| /** @ignore */ | |||
| const _UINT64 = 9; | |||
| /** @ignore */ | |||
| const _UINT128 = 10; | |||
| /** @ignore */ | |||
| const _ARRAY = 11; | |||
| /** @ignore */ | |||
| const _CONTAINER = 12; | |||
| /** @ignore */ | |||
| const _END_MARKER = 13; | |||
| /** @ignore */ | |||
| const _BOOLEAN = 14; | |||
| /** @ignore */ | |||
| const _FLOAT = 15; | |||
| public function __construct( | |||
| $fileStream, | |||
| $pointerBase = 0, | |||
| $pointerTestHack = false | |||
| ) { | |||
| $this->fileStream = $fileStream; | |||
| $this->pointerBase = $pointerBase; | |||
| $this->pointerBaseByteSize = $pointerBase > 0 ? log($pointerBase, 2) / 8 : 0; | |||
| $this->pointerTestHack = $pointerTestHack; | |||
| $this->switchByteOrder = $this->isPlatformLittleEndian(); | |||
| } | |||
| public function decode($offset) | |||
| { | |||
| $ctrlByte = \ord(Util::read($this->fileStream, $offset, 1)); | |||
| ++$offset; | |||
| $type = $ctrlByte >> 5; | |||
| // Pointers are a special case, we don't read the next $size bytes, we | |||
| // use the size to determine the length of the pointer and then follow | |||
| // it. | |||
| if ($type === self::_POINTER) { | |||
| list($pointer, $offset) = $this->decodePointer($ctrlByte, $offset); | |||
| // for unit testing | |||
| if ($this->pointerTestHack) { | |||
| return [$pointer]; | |||
| } | |||
| list($result) = $this->decode($pointer); | |||
| return [$result, $offset]; | |||
| } | |||
| if ($type === self::_EXTENDED) { | |||
| $nextByte = \ord(Util::read($this->fileStream, $offset, 1)); | |||
| $type = $nextByte + 7; | |||
| if ($type < 8) { | |||
| throw new InvalidDatabaseException( | |||
| 'Something went horribly wrong in the decoder. An extended type ' | |||
| . 'resolved to a type number < 8 (' | |||
| . $type | |||
| . ')' | |||
| ); | |||
| } | |||
| ++$offset; | |||
| } | |||
| list($size, $offset) = $this->sizeFromCtrlByte($ctrlByte, $offset); | |||
| return $this->decodeByType($type, $offset, $size); | |||
| } | |||
| private function decodeByType($type, $offset, $size) | |||
| { | |||
| switch ($type) { | |||
| case self::_MAP: | |||
| return $this->decodeMap($size, $offset); | |||
| case self::_ARRAY: | |||
| return $this->decodeArray($size, $offset); | |||
| case self::_BOOLEAN: | |||
| return [$this->decodeBoolean($size), $offset]; | |||
| } | |||
| $newOffset = $offset + $size; | |||
| $bytes = Util::read($this->fileStream, $offset, $size); | |||
| switch ($type) { | |||
| case self::_BYTES: | |||
| case self::_UTF8_STRING: | |||
| return [$bytes, $newOffset]; | |||
| case self::_DOUBLE: | |||
| $this->verifySize(8, $size); | |||
| return [$this->decodeDouble($bytes), $newOffset]; | |||
| case self::_FLOAT: | |||
| $this->verifySize(4, $size); | |||
| return [$this->decodeFloat($bytes), $newOffset]; | |||
| case self::_INT32: | |||
| return [$this->decodeInt32($bytes, $size), $newOffset]; | |||
| case self::_UINT16: | |||
| case self::_UINT32: | |||
| case self::_UINT64: | |||
| case self::_UINT128: | |||
| return [$this->decodeUint($bytes, $size), $newOffset]; | |||
| default: | |||
| throw new InvalidDatabaseException( | |||
| 'Unknown or unexpected type: ' . $type | |||
| ); | |||
| } | |||
| } | |||
| private function verifySize($expected, $actual) | |||
| { | |||
| if ($expected !== $actual) { | |||
| throw new InvalidDatabaseException( | |||
| "The MaxMind DB file's data section contains bad data (unknown data type or corrupt data)" | |||
| ); | |||
| } | |||
| } | |||
| private function decodeArray($size, $offset) | |||
| { | |||
| $array = []; | |||
| for ($i = 0; $i < $size; ++$i) { | |||
| list($value, $offset) = $this->decode($offset); | |||
| array_push($array, $value); | |||
| } | |||
| return [$array, $offset]; | |||
| } | |||
| private function decodeBoolean($size) | |||
| { | |||
| return $size === 0 ? false : true; | |||
| } | |||
| private function decodeDouble($bits) | |||
| { | |||
| // This assumes IEEE 754 doubles, but most (all?) modern platforms | |||
| // use them. | |||
| // | |||
| // We are not using the "E" format as that was only added in | |||
| // 7.0.15 and 7.1.1. As such, we must switch byte order on | |||
| // little endian machines. | |||
| list(, $double) = unpack('d', $this->maybeSwitchByteOrder($bits)); | |||
| return $double; | |||
| } | |||
| private function decodeFloat($bits) | |||
| { | |||
| // This assumes IEEE 754 floats, but most (all?) modern platforms | |||
| // use them. | |||
| // | |||
| // We are not using the "G" format as that was only added in | |||
| // 7.0.15 and 7.1.1. As such, we must switch byte order on | |||
| // little endian machines. | |||
| list(, $float) = unpack('f', $this->maybeSwitchByteOrder($bits)); | |||
| return $float; | |||
| } | |||
| private function decodeInt32($bytes, $size) | |||
| { | |||
| switch ($size) { | |||
| case 0: | |||
| return 0; | |||
| case 1: | |||
| case 2: | |||
| case 3: | |||
| $bytes = str_pad($bytes, 4, "\x00", STR_PAD_LEFT); | |||
| break; | |||
| case 4: | |||
| break; | |||
| default: | |||
| throw new InvalidDatabaseException( | |||
| "The MaxMind DB file's data section contains bad data (unknown data type or corrupt data)" | |||
| ); | |||
| } | |||
| list(, $int) = unpack('l', $this->maybeSwitchByteOrder($bytes)); | |||
| return $int; | |||
| } | |||
| private function decodeMap($size, $offset) | |||
| { | |||
| $map = []; | |||
| for ($i = 0; $i < $size; ++$i) { | |||
| list($key, $offset) = $this->decode($offset); | |||
| list($value, $offset) = $this->decode($offset); | |||
| $map[$key] = $value; | |||
| } | |||
| return [$map, $offset]; | |||
| } | |||
| private function decodePointer($ctrlByte, $offset) | |||
| { | |||
| $pointerSize = (($ctrlByte >> 3) & 0x3) + 1; | |||
| $buffer = Util::read($this->fileStream, $offset, $pointerSize); | |||
| $offset = $offset + $pointerSize; | |||
| switch ($pointerSize) { | |||
| case 1: | |||
| $packed = \chr($ctrlByte & 0x7) . $buffer; | |||
| list(, $pointer) = unpack('n', $packed); | |||
| $pointer += $this->pointerBase; | |||
| break; | |||
| case 2: | |||
| $packed = "\x00" . \chr($ctrlByte & 0x7) . $buffer; | |||
| list(, $pointer) = unpack('N', $packed); | |||
| $pointer += $this->pointerBase + 2048; | |||
| break; | |||
| case 3: | |||
| $packed = \chr($ctrlByte & 0x7) . $buffer; | |||
| // It is safe to use 'N' here, even on 32 bit machines as the | |||
| // first bit is 0. | |||
| list(, $pointer) = unpack('N', $packed); | |||
| $pointer += $this->pointerBase + 526336; | |||
| break; | |||
| case 4: | |||
| // We cannot use unpack here as we might overflow on 32 bit | |||
| // machines | |||
| $pointerOffset = $this->decodeUint($buffer, $pointerSize); | |||
| $byteLength = $pointerSize + $this->pointerBaseByteSize; | |||
| if ($byteLength <= _MM_MAX_INT_BYTES) { | |||
| $pointer = $pointerOffset + $this->pointerBase; | |||
| } elseif (\extension_loaded('gmp')) { | |||
| $pointer = gmp_strval(gmp_add($pointerOffset, $this->pointerBase)); | |||
| } elseif (\extension_loaded('bcmath')) { | |||
| $pointer = bcadd($pointerOffset, $this->pointerBase); | |||
| } else { | |||
| throw new RuntimeException( | |||
| 'The gmp or bcmath extension must be installed to read this database.' | |||
| ); | |||
| } | |||
| } | |||
| return [$pointer, $offset]; | |||
| } | |||
| private function decodeUint($bytes, $byteLength) | |||
| { | |||
| if ($byteLength === 0) { | |||
| return 0; | |||
| } | |||
| $integer = 0; | |||
| for ($i = 0; $i < $byteLength; ++$i) { | |||
| $part = \ord($bytes[$i]); | |||
| // We only use gmp or bcmath if the final value is too big | |||
| if ($byteLength <= _MM_MAX_INT_BYTES) { | |||
| $integer = ($integer << 8) + $part; | |||
| } elseif (\extension_loaded('gmp')) { | |||
| $integer = gmp_strval(gmp_add(gmp_mul($integer, 256), $part)); | |||
| } elseif (\extension_loaded('bcmath')) { | |||
| $integer = bcadd(bcmul($integer, 256), $part); | |||
| } else { | |||
| throw new RuntimeException( | |||
| 'The gmp or bcmath extension must be installed to read this database.' | |||
| ); | |||
| } | |||
| } | |||
| return $integer; | |||
| } | |||
| private function sizeFromCtrlByte($ctrlByte, $offset) | |||
| { | |||
| $size = $ctrlByte & 0x1f; | |||
| if ($size < 29) { | |||
| return [$size, $offset]; | |||
| } | |||
| $bytesToRead = $size - 28; | |||
| $bytes = Util::read($this->fileStream, $offset, $bytesToRead); | |||
| if ($size === 29) { | |||
| $size = 29 + \ord($bytes); | |||
| } elseif ($size === 30) { | |||
| list(, $adjust) = unpack('n', $bytes); | |||
| $size = 285 + $adjust; | |||
| } elseif ($size > 30) { | |||
| list(, $adjust) = unpack('N', "\x00" . $bytes); | |||
| $size = $adjust + 65821; | |||
| } | |||
| return [$size, $offset + $bytesToRead]; | |||
| } | |||
| private function maybeSwitchByteOrder($bytes) | |||
| { | |||
| return $this->switchByteOrder ? strrev($bytes) : $bytes; | |||
| } | |||
| private function isPlatformLittleEndian() | |||
| { | |||
| $testint = 0x00FF; | |||
| $packed = pack('S', $testint); | |||
| return $testint === current(unpack('v', $packed)); | |||
| } | |||
| } | |||
| @@ -0,0 +1,12 @@ | |||
| <?php | |||
| namespace MaxMind\Db\Reader; | |||
| use Exception; | |||
| /** | |||
| * This class should be thrown when unexpected data is found in the database. | |||
| */ | |||
| class InvalidDatabaseException extends Exception | |||
| { | |||
| } | |||
| @@ -0,0 +1,88 @@ | |||
| <?php | |||
| namespace MaxMind\Db\Reader; | |||
| /** | |||
| * This class provides the metadata for the MaxMind DB file. | |||
| * | |||
| * @property int $nodeCount This is an unsigned 32-bit | |||
| * integer indicating the number of | |||
| * nodes in the search tree. | |||
| * @property int $recordSize This is an unsigned 16-bit | |||
| * integer. It indicates the number | |||
| * of bits in a record in the search | |||
| * tree. Note that each node | |||
| * consists of two records. | |||
| * @property int $ipVersion This is an unsigned 16-bit | |||
| * integer which is always 4 or 6. | |||
| * It indicates whether the database | |||
| * contains IPv4 or IPv6 address | |||
| * data. | |||
| * @property string $databaseType This is a string that indicates | |||
| * the structure of each data record | |||
| * associated with an IP address. | |||
| * The actual definition of these | |||
| * structures is left up to the | |||
| * database creator. | |||
| * @property array $languages An array of strings, each of | |||
| * which is a language code. A given | |||
| * record may contain data items | |||
| * that have been localized to some | |||
| * or all of these languages. This | |||
| * may be undefined. | |||
| * @property int $binaryFormatMajorVersion This is an unsigned 16-bit | |||
| * integer indicating the major | |||
| * version number for the database's | |||
| * binary format. | |||
| * @property int $binaryFormatMinorVersion This is an unsigned 16-bit | |||
| * integer indicating the minor | |||
| * version number for the database's | |||
| * binary format. | |||
| * @property int $buildEpoch This is an unsigned 64-bit | |||
| * integer that contains the | |||
| * database build timestamp as a | |||
| * Unix epoch value. | |||
| * @property array $description This key will always point to a | |||
| * map (associative array). The keys | |||
| * of that map will be language | |||
| * codes, and the values will be a | |||
| * description in that language as a | |||
| * UTF-8 string. May be undefined | |||
| * for some databases. | |||
| */ | |||
| class Metadata | |||
| { | |||
| private $binaryFormatMajorVersion; | |||
| private $binaryFormatMinorVersion; | |||
| private $buildEpoch; | |||
| private $databaseType; | |||
| private $description; | |||
| private $ipVersion; | |||
| private $languages; | |||
| private $nodeByteSize; | |||
| private $nodeCount; | |||
| private $recordSize; | |||
| private $searchTreeSize; | |||
| public function __construct($metadata) | |||
| { | |||
| $this->binaryFormatMajorVersion = | |||
| $metadata['binary_format_major_version']; | |||
| $this->binaryFormatMinorVersion = | |||
| $metadata['binary_format_minor_version']; | |||
| $this->buildEpoch = $metadata['build_epoch']; | |||
| $this->databaseType = $metadata['database_type']; | |||
| $this->languages = $metadata['languages']; | |||
| $this->description = $metadata['description']; | |||
| $this->ipVersion = $metadata['ip_version']; | |||
| $this->nodeCount = $metadata['node_count']; | |||
| $this->recordSize = $metadata['record_size']; | |||
| $this->nodeByteSize = $this->recordSize / 4; | |||
| $this->searchTreeSize = $this->nodeCount * $this->nodeByteSize; | |||
| } | |||
| public function __get($var) | |||
| { | |||
| return $this->$var; | |||
| } | |||
| } | |||
| @@ -0,0 +1,26 @@ | |||
| <?php | |||
| namespace MaxMind\Db\Reader; | |||
| class Util | |||
| { | |||
| public static function read($stream, $offset, $numberOfBytes) | |||
| { | |||
| if ($numberOfBytes === 0) { | |||
| return ''; | |||
| } | |||
| if (fseek($stream, $offset) === 0) { | |||
| $value = fread($stream, $numberOfBytes); | |||
| // We check that the number of bytes read is equal to the number | |||
| // asked for. We use ftell as getting the length of $value is | |||
| // much slower. | |||
| if (ftell($stream) - $offset === $numberOfBytes) { | |||
| return $value; | |||
| } | |||
| } | |||
| throw new InvalidDatabaseException( | |||
| 'The MaxMind DB file contains bad data' | |||
| ); | |||
| } | |||
| } | |||
| @@ -0,0 +1,86 @@ | |||
| CHANGELOG | |||
| ========= | |||
| 0.6.0 (2019-12-12) | |||
| ------------------ | |||
| * Curl handles are now reused across requests. Pull request by Willem | |||
| Stuursma-Ruwen. GitHub #24. | |||
| * PHP 5.6 is now required. | |||
| 0.5.0 (2018-02-12) | |||
| ------------------ | |||
| * Refer to account IDs using the terminology "account" rather than "user". | |||
| 0.4.0 (2017-07-10) | |||
| ------------------ | |||
| * PHP 5.4 is now required. | |||
| 0.3.1 (2016-08-10) | |||
| ------------------ | |||
| * On Mac OS X when using a curl built against SecureTransport, the certs | |||
| in the system's keychain will now be used instead of the CA bundle on | |||
| the file system. | |||
| 0.3.0 (2016-08-09) | |||
| ------------------ | |||
| * This package now uses `composer/ca-bundle` by default rather than a CA | |||
| bundle distributed with this package. `composer/ca-bundle` will first try | |||
| to use the system CA bundle and will fall back to the Mozilla CA bundle | |||
| when no system bundle is available. You may still specify your own bundle | |||
| using the `caBundle` option. | |||
| 0.2.1 (2016-06-13) | |||
| ------------------ | |||
| * Fix typo in code to copy cert to temp directory. | |||
| 0.2.0 (2016-06-10) | |||
| ------------------ | |||
| * Added handling of additional error codes that the web service may return. | |||
| * A `USER_ID_UNKNOWN` error will now throw a | |||
| `MaxMind\Exception\AuthenticationException`. | |||
| * Added support for `proxy` option. Closes #6. | |||
| 0.1.0 (2016-05-23) | |||
| ------------------ | |||
| * A `PERMISSION_REQUIRED` error will now throw a `PermissionRequiredException` | |||
| exception. | |||
| * Added a `.gitattributes` file to exclude tests from Composer releases. | |||
| GitHub #7. | |||
| * Updated included cert bundle. | |||
| 0.0.4 (2015-07-21) | |||
| ------------------ | |||
| * Added extremely basic tests for the curl calls. | |||
| * Fixed broken POSTs. | |||
| 0.0.3 (2015-06-30) | |||
| ------------------ | |||
| * Floats now work with the `timeout` and `connectTimeout` options. Fix by | |||
| Benjamin Pick. GitHub PR #2. | |||
| * `curl_error` is now used instead of `curl_strerror`. The latter is only | |||
| available for PHP 5.5 or later. Fix by Benjamin Pick. GitHub PR #1. | |||
| 0.0.2 (2015-06-09) | |||
| ------------------ | |||
| * An exception is now immediately thrown curl error rather than letting later | |||
| status code checks throw an exception. This improves the exception message | |||
| greatly. | |||
| * If this library is inside a phar archive, the CA certs are copied out of the | |||
| archive to a temporary file so that curl can use them. | |||
| 0.0.1 (2015-06-01) | |||
| ------------------ | |||
| * Initial release. | |||
| @@ -0,0 +1,202 @@ | |||
| Apache License | |||
| Version 2.0, January 2004 | |||
| http://www.apache.org/licenses/ | |||
| TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | |||
| 1. Definitions. | |||
| "License" shall mean the terms and conditions for use, reproduction, | |||
| and distribution as defined by Sections 1 through 9 of this document. | |||
| "Licensor" shall mean the copyright owner or entity authorized by | |||
| the copyright owner that is granting the License. | |||
| "Legal Entity" shall mean the union of the acting entity and all | |||
| other entities that control, are controlled by, or are under common | |||
| control with that entity. For the purposes of this definition, | |||
| "control" means (i) the power, direct or indirect, to cause the | |||
| direction or management of such entity, whether by contract or | |||
| otherwise, or (ii) ownership of fifty percent (50%) or more of the | |||
| outstanding shares, or (iii) beneficial ownership of such entity. | |||
| "You" (or "Your") shall mean an individual or Legal Entity | |||
| exercising permissions granted by this License. | |||
| "Source" form shall mean the preferred form for making modifications, | |||
| including but not limited to software source code, documentation | |||
| source, and configuration files. | |||
| "Object" form shall mean any form resulting from mechanical | |||
| transformation or translation of a Source form, including but | |||
| not limited to compiled object code, generated documentation, | |||
| and conversions to other media types. | |||
| "Work" shall mean the work of authorship, whether in Source or | |||
| Object form, made available under the License, as indicated by a | |||
| copyright notice that is included in or attached to the work | |||
| (an example is provided in the Appendix below). | |||
| "Derivative Works" shall mean any work, whether in Source or Object | |||
| form, that is based on (or derived from) the Work and for which the | |||
| editorial revisions, annotations, elaborations, or other modifications | |||
| represent, as a whole, an original work of authorship. For the purposes | |||
| of this License, Derivative Works shall not include works that remain | |||
| separable from, or merely link (or bind by name) to the interfaces of, | |||
| the Work and Derivative Works thereof. | |||
| "Contribution" shall mean any work of authorship, including | |||
| the original version of the Work and any modifications or additions | |||
| to that Work or Derivative Works thereof, that is intentionally | |||
| submitted to Licensor for inclusion in the Work by the copyright owner | |||
| or by an individual or Legal Entity authorized to submit on behalf of | |||
| the copyright owner. For the purposes of this definition, "submitted" | |||
| means any form of electronic, verbal, or written communication sent | |||
| to the Licensor or its representatives, including but not limited to | |||
| communication on electronic mailing lists, source code control systems, | |||
| and issue tracking systems that are managed by, or on behalf of, the | |||
| Licensor for the purpose of discussing and improving the Work, but | |||
| excluding communication that is conspicuously marked or otherwise | |||
| designated in writing by the copyright owner as "Not a Contribution." | |||
| "Contributor" shall mean Licensor and any individual or Legal Entity | |||
| on behalf of whom a Contribution has been received by Licensor and | |||
| subsequently incorporated within the Work. | |||
| 2. Grant of Copyright License. Subject to the terms and conditions of | |||
| this License, each Contributor hereby grants to You a perpetual, | |||
| worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |||
| copyright license to reproduce, prepare Derivative Works of, | |||
| publicly display, publicly perform, sublicense, and distribute the | |||
| Work and such Derivative Works in Source or Object form. | |||
| 3. Grant of Patent License. Subject to the terms and conditions of | |||
| this License, each Contributor hereby grants to You a perpetual, | |||
| worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |||
| (except as stated in this section) patent license to make, have made, | |||
| use, offer to sell, sell, import, and otherwise transfer the Work, | |||
| where such license applies only to those patent claims licensable | |||
| by such Contributor that are necessarily infringed by their | |||
| Contribution(s) alone or by combination of their Contribution(s) | |||
| with the Work to which such Contribution(s) was submitted. If You | |||
| institute patent litigation against any entity (including a | |||
| cross-claim or counterclaim in a lawsuit) alleging that the Work | |||
| or a Contribution incorporated within the Work constitutes direct | |||
| or contributory patent infringement, then any patent licenses | |||
| granted to You under this License for that Work shall terminate | |||
| as of the date such litigation is filed. | |||
| 4. Redistribution. You may reproduce and distribute copies of the | |||
| Work or Derivative Works thereof in any medium, with or without | |||
| modifications, and in Source or Object form, provided that You | |||
| meet the following conditions: | |||
| (a) You must give any other recipients of the Work or | |||
| Derivative Works a copy of this License; and | |||
| (b) You must cause any modified files to carry prominent notices | |||
| stating that You changed the files; and | |||
| (c) You must retain, in the Source form of any Derivative Works | |||
| that You distribute, all copyright, patent, trademark, and | |||
| attribution notices from the Source form of the Work, | |||
| excluding those notices that do not pertain to any part of | |||
| the Derivative Works; and | |||
| (d) If the Work includes a "NOTICE" text file as part of its | |||
| distribution, then any Derivative Works that You distribute must | |||
| include a readable copy of the attribution notices contained | |||
| within such NOTICE file, excluding those notices that do not | |||
| pertain to any part of the Derivative Works, in at least one | |||
| of the following places: within a NOTICE text file distributed | |||
| as part of the Derivative Works; within the Source form or | |||
| documentation, if provided along with the Derivative Works; or, | |||
| within a display generated by the Derivative Works, if and | |||
| wherever such third-party notices normally appear. The contents | |||
| of the NOTICE file are for informational purposes only and | |||
| do not modify the License. You may add Your own attribution | |||
| notices within Derivative Works that You distribute, alongside | |||
| or as an addendum to the NOTICE text from the Work, provided | |||
| that such additional attribution notices cannot be construed | |||
| as modifying the License. | |||
| You may add Your own copyright statement to Your modifications and | |||
| may provide additional or different license terms and conditions | |||
| for use, reproduction, or distribution of Your modifications, or | |||
| for any such Derivative Works as a whole, provided Your use, | |||
| reproduction, and distribution of the Work otherwise complies with | |||
| the conditions stated in this License. | |||
| 5. Submission of Contributions. Unless You explicitly state otherwise, | |||
| any Contribution intentionally submitted for inclusion in the Work | |||
| by You to the Licensor shall be under the terms and conditions of | |||
| this License, without any additional terms or conditions. | |||
| Notwithstanding the above, nothing herein shall supersede or modify | |||
| the terms of any separate license agreement you may have executed | |||
| with Licensor regarding such Contributions. | |||
| 6. Trademarks. This License does not grant permission to use the trade | |||
| names, trademarks, service marks, or product names of the Licensor, | |||
| except as required for reasonable and customary use in describing the | |||
| origin of the Work and reproducing the content of the NOTICE file. | |||
| 7. Disclaimer of Warranty. Unless required by applicable law or | |||
| agreed to in writing, Licensor provides the Work (and each | |||
| Contributor provides its Contributions) on an "AS IS" BASIS, | |||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | |||
| implied, including, without limitation, any warranties or conditions | |||
| of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | |||
| PARTICULAR PURPOSE. You are solely responsible for determining the | |||
| appropriateness of using or redistributing the Work and assume any | |||
| risks associated with Your exercise of permissions under this License. | |||
| 8. Limitation of Liability. In no event and under no legal theory, | |||
| whether in tort (including negligence), contract, or otherwise, | |||
| unless required by applicable law (such as deliberate and grossly | |||
| negligent acts) or agreed to in writing, shall any Contributor be | |||
| liable to You for damages, including any direct, indirect, special, | |||
| incidental, or consequential damages of any character arising as a | |||
| result of this License or out of the use or inability to use the | |||
| Work (including but not limited to damages for loss of goodwill, | |||
| work stoppage, computer failure or malfunction, or any and all | |||
| other commercial damages or losses), even if such Contributor | |||
| has been advised of the possibility of such damages. | |||
| 9. Accepting Warranty or Additional Liability. While redistributing | |||
| the Work or Derivative Works thereof, You may choose to offer, | |||
| and charge a fee for, acceptance of support, warranty, indemnity, | |||
| or other liability obligations and/or rights consistent with this | |||
| License. However, in accepting such obligations, You may act only | |||
| on Your own behalf and on Your sole responsibility, not on behalf | |||
| of any other Contributor, and only if You agree to indemnify, | |||
| defend, and hold each Contributor harmless for any liability | |||
| incurred by, or claims asserted against, such Contributor by reason | |||
| of your accepting any such warranty or additional liability. | |||
| END OF TERMS AND CONDITIONS | |||
| APPENDIX: How to apply the Apache License to your work. | |||
| To apply the Apache License to your work, attach the following | |||
| boilerplate notice, with the fields enclosed by brackets "[]" | |||
| replaced with your own identifying information. (Don't include | |||
| the brackets!) The text should be enclosed in the appropriate | |||
| comment syntax for the file format. We also recommend that a | |||
| file or class name and description of purpose be included on the | |||
| same "printed page" as the copyright notice for easier | |||
| identification within third-party archives. | |||
| Copyright [yyyy] [name of copyright owner] | |||
| Licensed under the Apache License, Version 2.0 (the "License"); | |||
| you may not use this file except in compliance with the License. | |||
| You may obtain a copy of the License at | |||
| http://www.apache.org/licenses/LICENSE-2.0 | |||
| Unless required by applicable law or agreed to in writing, software | |||
| distributed under the License is distributed on an "AS IS" BASIS, | |||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| See the License for the specific language governing permissions and | |||
| limitations under the License. | |||
| @@ -0,0 +1,25 @@ | |||
| # Common Code for MaxMind Web Service Clients # | |||
| This is _not_ intended for direct use by third parties. Rather, it is for | |||
| shared code between MaxMind's various web service client APIs. | |||
| ## Requirements ## | |||
| The library requires PHP 5.6 or greater. | |||
| There are several other dependencies as defined in the `composer.json` file. | |||
| ## Contributing ## | |||
| Patches and pull requests are encouraged. All code should follow the PSR-2 | |||
| style guidelines. Please include unit tests whenever possible. | |||
| ## Versioning ## | |||
| This API uses [Semantic Versioning](http://semver.org/). | |||
| ## Copyright and License ## | |||
| This software is Copyright (c) 2015-2019 by MaxMind, Inc. | |||
| This is free software, licensed under the Apache License, Version 2.0. | |||
| @@ -0,0 +1,31 @@ | |||
| { | |||
| "name": "maxmind/web-service-common", | |||
| "description": "Internal MaxMind Web Service API", | |||
| "minimum-stability": "stable", | |||
| "homepage": "https://github.com/maxmind/web-service-common-php", | |||
| "type": "library", | |||
| "license": "Apache-2.0", | |||
| "authors": [ | |||
| { | |||
| "name": "Gregory Oschwald", | |||
| "email": "goschwald@maxmind.com" | |||
| } | |||
| ], | |||
| "require": { | |||
| "php": ">=5.6", | |||
| "composer/ca-bundle": "^1.0.3", | |||
| "ext-curl": "*", | |||
| "ext-json": "*" | |||
| }, | |||
| "require-dev": { | |||
| "friendsofphp/php-cs-fixer": "2.*", | |||
| "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0", | |||
| "squizlabs/php_codesniffer": "3.*" | |||
| }, | |||
| "autoload": { | |||
| "psr-4": { | |||
| "MaxMind\\Exception\\": "src/Exception", | |||
| "MaxMind\\WebService\\": "src/WebService" | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,60 @@ | |||
| #!/bin/bash | |||
| set -eu -o pipefail | |||
| changelog=$(cat CHANGELOG.md) | |||
| regex=' | |||
| ([0-9]+\.[0-9]+\.[0-9]+) \(([0-9]{4}-[0-9]{2}-[0-9]{2})\) | |||
| -* | |||
| ((.| | |||
| )*) | |||
| ' | |||
| if [[ ! $changelog =~ $regex ]]; then | |||
| echo "Could not find date line in change log!" | |||
| exit 1 | |||
| fi | |||
| version="${BASH_REMATCH[1]}" | |||
| date="${BASH_REMATCH[2]}" | |||
| notes="$(echo "${BASH_REMATCH[3]}" | sed -n -e '/^[0-9]\+\.[0-9]\+\.[0-9]\+/,$!p')" | |||
| if [[ "$date" != $(date +"%Y-%m-%d") ]]; then | |||
| echo "$date is not today!" | |||
| exit 1 | |||
| fi | |||
| tag="v$version" | |||
| if [ -n "$(git status --porcelain)" ]; then | |||
| echo ". is not clean." >&2 | |||
| exit 1 | |||
| fi | |||
| php composer.phar self-update | |||
| php composer.phar update | |||
| ./vendor/bin/phpunit | |||
| echo "Release notes for $tag:" | |||
| echo "$notes" | |||
| read -e -p "Commit changes and push to origin? " should_push | |||
| if [ "$should_push" != "y" ]; then | |||
| echo "Aborting" | |||
| exit 1 | |||
| fi | |||
| git push | |||
| message="$version | |||
| $notes" | |||
| hub release create -m "$message" "$tag" | |||
| git push --tags | |||
| @@ -0,0 +1,10 @@ | |||
| <?php | |||
| namespace MaxMind\Exception; | |||
| /** | |||
| * This class represents an error authenticating. | |||
| */ | |||
| class AuthenticationException extends InvalidRequestException | |||
| { | |||
| } | |||
| @@ -0,0 +1,40 @@ | |||
| <?php | |||
| namespace MaxMind\Exception; | |||
| /** | |||
| * This class represents an HTTP transport error. | |||
| */ | |||
| class HttpException extends WebServiceException | |||
| { | |||
| /** | |||
| * The URI queried. | |||
| */ | |||
| private $uri; | |||
| /** | |||
| * @param string $message a message describing the error | |||
| * @param int $httpStatus the HTTP status code of the response | |||
| * @param string $uri the URI used in the request | |||
| * @param \Exception $previous the previous exception, if any | |||
| */ | |||
| public function __construct( | |||
| $message, | |||
| $httpStatus, | |||
| $uri, | |||
| \Exception $previous = null | |||
| ) { | |||
| $this->uri = $uri; | |||
| parent::__construct($message, $httpStatus, $previous); | |||
| } | |||
| public function getUri() | |||
| { | |||
| return $this->uri; | |||
| } | |||
| public function getStatusCode() | |||
| { | |||
| return $this->getCode(); | |||
| } | |||
| } | |||
| @@ -0,0 +1,10 @@ | |||
| <?php | |||
| namespace MaxMind\Exception; | |||
| /** | |||
| * Thrown when the account is out of credits. | |||
| */ | |||
| class InsufficientFundsException extends InvalidRequestException | |||
| { | |||
| } | |||
| @@ -0,0 +1,12 @@ | |||
| <?php | |||
| namespace MaxMind\Exception; | |||
| /** | |||
| * This class represents an error in creating the request to be sent to the | |||
| * web service. For example, if the array cannot be encoded as JSON or if there | |||
| * is a missing or invalid field. | |||
| */ | |||
| class InvalidInputException extends WebServiceException | |||
| { | |||
| } | |||
| @@ -0,0 +1,37 @@ | |||
| <?php | |||
| namespace MaxMind\Exception; | |||
| /** | |||
| * Thrown when a MaxMind web service returns an error relating to the request. | |||
| */ | |||
| class InvalidRequestException extends HttpException | |||
| { | |||
| /** | |||
| * The code returned by the MaxMind web service. | |||
| */ | |||
| private $error; | |||
| /** | |||
| * @param string $message the exception message | |||
| * @param int $error the error code returned by the MaxMind web service | |||
| * @param int $httpStatus the HTTP status code of the response | |||
| * @param string $uri the URI queries | |||
| * @param \Exception $previous the previous exception, if any | |||
| */ | |||
| public function __construct( | |||
| $message, | |||
| $error, | |||
| $httpStatus, | |||
| $uri, | |||
| \Exception $previous = null | |||
| ) { | |||
| $this->error = $error; | |||
| parent::__construct($message, $httpStatus, $uri, $previous); | |||
| } | |||
| public function getErrorCode() | |||
| { | |||
| return $this->error; | |||
| } | |||
| } | |||
| @@ -0,0 +1,7 @@ | |||
| <?php | |||
| namespace MaxMind\Exception; | |||
| class IpAddressNotFoundException extends InvalidRequestException | |||
| { | |||
| } | |||
| @@ -0,0 +1,10 @@ | |||
| <?php | |||
| namespace MaxMind\Exception; | |||
| /** | |||
| * This exception is thrown when the service requires permission to access. | |||
| */ | |||
| class PermissionRequiredException extends InvalidRequestException | |||
| { | |||
| } | |||
| @@ -0,0 +1,10 @@ | |||
| <?php | |||
| namespace MaxMind\Exception; | |||
| /** | |||
| * This class represents a generic web service error. | |||
| */ | |||
| class WebServiceException extends \Exception | |||
| { | |||
| } | |||
| @@ -0,0 +1,472 @@ | |||
| <?php | |||
| namespace MaxMind\WebService; | |||
| use Composer\CaBundle\CaBundle; | |||
| use MaxMind\Exception\AuthenticationException; | |||
| use MaxMind\Exception\HttpException; | |||
| use MaxMind\Exception\InsufficientFundsException; | |||
| use MaxMind\Exception\InvalidInputException; | |||
| use MaxMind\Exception\InvalidRequestException; | |||
| use MaxMind\Exception\IpAddressNotFoundException; | |||
| use MaxMind\Exception\PermissionRequiredException; | |||
| use MaxMind\Exception\WebServiceException; | |||
| use MaxMind\WebService\Http\RequestFactory; | |||
| /** | |||
| * This class is not intended to be used directly by an end-user of a | |||
| * MaxMind web service. Please use the appropriate client API for the service | |||
| * that you are using. | |||
| * | |||
| * @internal | |||
| */ | |||
| class Client | |||
| { | |||
| const VERSION = '0.2.0'; | |||
| private $caBundle; | |||
| private $connectTimeout; | |||
| private $host = 'api.maxmind.com'; | |||
| private $httpRequestFactory; | |||
| private $licenseKey; | |||
| private $proxy; | |||
| private $timeout; | |||
| private $userAgentPrefix; | |||
| private $accountId; | |||
| /** | |||
| * @param int $accountId your MaxMind account ID | |||
| * @param string $licenseKey your MaxMind license key | |||
| * @param array $options an array of options. Possible keys: | |||
| * * `host` - The host to use when connecting to the web service. | |||
| * * `userAgent` - The prefix of the User-Agent to use in the request. | |||
| * * `caBundle` - The bundle of CA root certificates to use in the request. | |||
| * * `connectTimeout` - The connect timeout to use for the request. | |||
| * * `timeout` - The timeout to use for the request. | |||
| * * `proxy` - The HTTP proxy to use. May include a schema, port, | |||
| * username, and password, e.g., `http://username:password@127.0.0.1:10`. | |||
| */ | |||
| public function __construct( | |||
| $accountId, | |||
| $licenseKey, | |||
| $options = [] | |||
| ) { | |||
| $this->accountId = $accountId; | |||
| $this->licenseKey = $licenseKey; | |||
| $this->httpRequestFactory = isset($options['httpRequestFactory']) | |||
| ? $options['httpRequestFactory'] | |||
| : new RequestFactory(); | |||
| if (isset($options['host'])) { | |||
| $this->host = $options['host']; | |||
| } | |||
| if (isset($options['userAgent'])) { | |||
| $this->userAgentPrefix = $options['userAgent'] . ' '; | |||
| } | |||
| $this->caBundle = isset($options['caBundle']) ? | |||
| $this->caBundle = $options['caBundle'] : $this->getCaBundle(); | |||
| if (isset($options['connectTimeout'])) { | |||
| $this->connectTimeout = $options['connectTimeout']; | |||
| } | |||
| if (isset($options['timeout'])) { | |||
| $this->timeout = $options['timeout']; | |||
| } | |||
| if (isset($options['proxy'])) { | |||
| $this->proxy = $options['proxy']; | |||
| } | |||
| } | |||
| /** | |||
| * @param string $service name of the service querying | |||
| * @param string $path the URI path to use | |||
| * @param array $input the data to be posted as JSON | |||
| * | |||
| * @throws InvalidInputException when the request has missing or invalid | |||
| * data | |||
| * @throws AuthenticationException when there is an issue authenticating the | |||
| * request | |||
| * @throws InsufficientFundsException when your account is out of funds | |||
| * @throws InvalidRequestException when the request is invalid for some | |||
| * other reason, e.g., invalid JSON in the POST. | |||
| * @throws HttpException when an unexpected HTTP error occurs | |||
| * @throws WebServiceException when some other error occurs. This also | |||
| * serves as the base class for the above exceptions. | |||
| * | |||
| * @return array The decoded content of a successful response | |||
| */ | |||
| public function post($service, $path, $input) | |||
| { | |||
| $requestBody = json_encode($input); | |||
| if ($requestBody === false) { | |||
| throw new InvalidInputException( | |||
| 'Error encoding input as JSON: ' | |||
| . $this->jsonErrorDescription() | |||
| ); | |||
| } | |||
| $request = $this->createRequest( | |||
| $path, | |||
| ['Content-Type: application/json'] | |||
| ); | |||
| list($statusCode, $contentType, $responseBody) = $request->post($requestBody); | |||
| return $this->handleResponse( | |||
| $statusCode, | |||
| $contentType, | |||
| $responseBody, | |||
| $service, | |||
| $path | |||
| ); | |||
| } | |||
| public function get($service, $path) | |||
| { | |||
| $request = $this->createRequest($path); | |||
| list($statusCode, $contentType, $responseBody) = $request->get(); | |||
| return $this->handleResponse( | |||
| $statusCode, | |||
| $contentType, | |||
| $responseBody, | |||
| $service, | |||
| $path | |||
| ); | |||
| } | |||
| private function userAgent() | |||
| { | |||
| $curlVersion = curl_version(); | |||
| return $this->userAgentPrefix . 'MaxMind-WS-API/' . self::VERSION . ' PHP/' . PHP_VERSION . | |||
| ' curl/' . $curlVersion['version']; | |||
| } | |||
| private function createRequest($path, $headers = []) | |||
| { | |||
| array_push( | |||
| $headers, | |||
| 'Authorization: Basic ' | |||
| . base64_encode($this->accountId . ':' . $this->licenseKey), | |||
| 'Accept: application/json' | |||
| ); | |||
| return $this->httpRequestFactory->request( | |||
| $this->urlFor($path), | |||
| [ | |||
| 'caBundle' => $this->caBundle, | |||
| 'connectTimeout' => $this->connectTimeout, | |||
| 'headers' => $headers, | |||
| 'proxy' => $this->proxy, | |||
| 'timeout' => $this->timeout, | |||
| 'userAgent' => $this->userAgent(), | |||
| ] | |||
| ); | |||
| } | |||
| /** | |||
| * @param int $statusCode the HTTP status code of the response | |||
| * @param string $contentType the Content-Type of the response | |||
| * @param string $responseBody the response body | |||
| * @param string $service the name of the service | |||
| * @param string $path the path used in the request | |||
| * | |||
| * @throws AuthenticationException when there is an issue authenticating the | |||
| * request | |||
| * @throws InsufficientFundsException when your account is out of funds | |||
| * @throws InvalidRequestException when the request is invalid for some | |||
| * other reason, e.g., invalid JSON in the POST. | |||
| * @throws HttpException when an unexpected HTTP error occurs | |||
| * @throws WebServiceException when some other error occurs. This also | |||
| * serves as the base class for the above exceptions | |||
| * | |||
| * @return array The decoded content of a successful response | |||
| */ | |||
| private function handleResponse( | |||
| $statusCode, | |||
| $contentType, | |||
| $responseBody, | |||
| $service, | |||
| $path | |||
| ) { | |||
| if ($statusCode >= 400 && $statusCode <= 499) { | |||
| $this->handle4xx($statusCode, $contentType, $responseBody, $service, $path); | |||
| } elseif ($statusCode >= 500) { | |||
| $this->handle5xx($statusCode, $service, $path); | |||
| } elseif ($statusCode !== 200) { | |||
| $this->handleUnexpectedStatus($statusCode, $service, $path); | |||
| } | |||
| return $this->handleSuccess($responseBody, $service); | |||
| } | |||
| /** | |||
| * @return string describing the JSON error | |||
| */ | |||
| private function jsonErrorDescription() | |||
| { | |||
| $errno = json_last_error(); | |||
| switch ($errno) { | |||
| case JSON_ERROR_DEPTH: | |||
| return 'The maximum stack depth has been exceeded.'; | |||
| case JSON_ERROR_STATE_MISMATCH: | |||
| return 'Invalid or malformed JSON.'; | |||
| case JSON_ERROR_CTRL_CHAR: | |||
| return 'Control character error.'; | |||
| case JSON_ERROR_SYNTAX: | |||
| return 'Syntax error.'; | |||
| case JSON_ERROR_UTF8: | |||
| return 'Malformed UTF-8 characters.'; | |||
| default: | |||
| return "Other JSON error ($errno)."; | |||
| } | |||
| } | |||
| /** | |||
| * @param string $path the path to use in the URL | |||
| * | |||
| * @return string the constructed URL | |||
| */ | |||
| private function urlFor($path) | |||
| { | |||
| return 'https://' . $this->host . $path; | |||
| } | |||
| /** | |||
| * @param int $statusCode the HTTP status code | |||
| * @param string $contentType the response content-type | |||
| * @param string $body the response body | |||
| * @param string $service the service name | |||
| * @param string $path the path used in the request | |||
| * | |||
| * @throws AuthenticationException | |||
| * @throws HttpException | |||
| * @throws InsufficientFundsException | |||
| * @throws InvalidRequestException | |||
| */ | |||
| private function handle4xx( | |||
| $statusCode, | |||
| $contentType, | |||
| $body, | |||
| $service, | |||
| $path | |||
| ) { | |||
| if (\strlen($body) === 0) { | |||
| throw new HttpException( | |||
| "Received a $statusCode error for $service with no body", | |||
| $statusCode, | |||
| $this->urlFor($path) | |||
| ); | |||
| } | |||
| if (!strstr($contentType, 'json')) { | |||
| throw new HttpException( | |||
| "Received a $statusCode error for $service with " . | |||
| 'the following body: ' . $body, | |||
| $statusCode, | |||
| $this->urlFor($path) | |||
| ); | |||
| } | |||
| $message = json_decode($body, true); | |||
| if ($message === null) { | |||
| throw new HttpException( | |||
| "Received a $statusCode error for $service but could " . | |||
| 'not decode the response as JSON: ' | |||
| . $this->jsonErrorDescription() . ' Body: ' . $body, | |||
| $statusCode, | |||
| $this->urlFor($path) | |||
| ); | |||
| } | |||
| if (!isset($message['code']) || !isset($message['error'])) { | |||
| throw new HttpException( | |||
| 'Error response contains JSON but it does not ' . | |||
| 'specify code or error keys: ' . $body, | |||
| $statusCode, | |||
| $this->urlFor($path) | |||
| ); | |||
| } | |||
| $this->handleWebServiceError( | |||
| $message['error'], | |||
| $message['code'], | |||
| $statusCode, | |||
| $path | |||
| ); | |||
| } | |||
| /** | |||
| * @param string $message the error message from the web service | |||
| * @param string $code the error code from the web service | |||
| * @param int $statusCode the HTTP status code | |||
| * @param string $path the path used in the request | |||
| * | |||
| * @throws AuthenticationException | |||
| * @throws InvalidRequestException | |||
| * @throws InsufficientFundsException | |||
| */ | |||
| private function handleWebServiceError( | |||
| $message, | |||
| $code, | |||
| $statusCode, | |||
| $path | |||
| ) { | |||
| switch ($code) { | |||
| case 'IP_ADDRESS_NOT_FOUND': | |||
| case 'IP_ADDRESS_RESERVED': | |||
| throw new IpAddressNotFoundException( | |||
| $message, | |||
| $code, | |||
| $statusCode, | |||
| $this->urlFor($path) | |||
| ); | |||
| case 'ACCOUNT_ID_REQUIRED': | |||
| case 'ACCOUNT_ID_UNKNOWN': | |||
| case 'AUTHORIZATION_INVALID': | |||
| case 'LICENSE_KEY_REQUIRED': | |||
| case 'USER_ID_REQUIRED': | |||
| case 'USER_ID_UNKNOWN': | |||
| throw new AuthenticationException( | |||
| $message, | |||
| $code, | |||
| $statusCode, | |||
| $this->urlFor($path) | |||
| ); | |||
| case 'OUT_OF_QUERIES': | |||
| case 'INSUFFICIENT_FUNDS': | |||
| throw new InsufficientFundsException( | |||
| $message, | |||
| $code, | |||
| $statusCode, | |||
| $this->urlFor($path) | |||
| ); | |||
| case 'PERMISSION_REQUIRED': | |||
| throw new PermissionRequiredException( | |||
| $message, | |||
| $code, | |||
| $statusCode, | |||
| $this->urlFor($path) | |||
| ); | |||
| default: | |||
| throw new InvalidRequestException( | |||
| $message, | |||
| $code, | |||
| $statusCode, | |||
| $this->urlFor($path) | |||
| ); | |||
| } | |||
| } | |||
| /** | |||
| * @param int $statusCode the HTTP status code | |||
| * @param string $service the service name | |||
| * @param string $path the URI path used in the request | |||
| * | |||
| * @throws HttpException | |||
| */ | |||
| private function handle5xx($statusCode, $service, $path) | |||
| { | |||
| throw new HttpException( | |||
| "Received a server error ($statusCode) for $service", | |||
| $statusCode, | |||
| $this->urlFor($path) | |||
| ); | |||
| } | |||
| /** | |||
| * @param int $statusCode the HTTP status code | |||
| * @param string $service the service name | |||
| * @param string $path the URI path used in the request | |||
| * | |||
| * @throws HttpException | |||
| */ | |||
| private function handleUnexpectedStatus($statusCode, $service, $path) | |||
| { | |||
| throw new HttpException( | |||
| 'Received an unexpected HTTP status ' . | |||
| "($statusCode) for $service", | |||
| $statusCode, | |||
| $this->urlFor($path) | |||
| ); | |||
| } | |||
| /** | |||
| * @param string $body the successful request body | |||
| * @param string $service the service name | |||
| * | |||
| * @throws WebServiceException if the request body cannot be decoded as | |||
| * JSON | |||
| * | |||
| * @return array the decoded request body | |||
| */ | |||
| private function handleSuccess($body, $service) | |||
| { | |||
| if (\strlen($body) === 0) { | |||
| throw new WebServiceException( | |||
| "Received a 200 response for $service but did not " . | |||
| 'receive a HTTP body.' | |||
| ); | |||
| } | |||
| $decodedContent = json_decode($body, true); | |||
| if ($decodedContent === null) { | |||
| throw new WebServiceException( | |||
| "Received a 200 response for $service but could " . | |||
| 'not decode the response as JSON: ' | |||
| . $this->jsonErrorDescription() . ' Body: ' . $body | |||
| ); | |||
| } | |||
| return $decodedContent; | |||
| } | |||
| private function getCaBundle() | |||
| { | |||
| $curlVersion = curl_version(); | |||
| // On OS X, when the SSL version is "SecureTransport", the system's | |||
| // keychain will be used. | |||
| if ($curlVersion['ssl_version'] === 'SecureTransport') { | |||
| return null; | |||
| } | |||
| $cert = CaBundle::getSystemCaRootBundlePath(); | |||
| // Check if the cert is inside a phar. If so, we need to copy the cert | |||
| // to a temp file so that curl can see it. | |||
| if (substr($cert, 0, 7) === 'phar://') { | |||
| $tempDir = sys_get_temp_dir(); | |||
| $newCert = tempnam($tempDir, 'geoip2-'); | |||
| if ($newCert === false) { | |||
| throw new \RuntimeException( | |||
| "Unable to create temporary file in $tempDir" | |||
| ); | |||
| } | |||
| if (!copy($cert, $newCert)) { | |||
| throw new \RuntimeException( | |||
| "Could not copy $cert to $newCert: " | |||
| . var_export(error_get_last(), true) | |||
| ); | |||
| } | |||
| // We use a shutdown function rather than the destructor as the | |||
| // destructor isn't called on a fatal error such as an uncaught | |||
| // exception. | |||
| register_shutdown_function( | |||
| function () use ($newCert) { | |||
| unlink($newCert); | |||
| } | |||
| ); | |||
| $cert = $newCert; | |||
| } | |||
| if (!file_exists($cert)) { | |||
| throw new \RuntimeException("CA cert does not exist at $cert"); | |||
| } | |||
| return $cert; | |||
| } | |||
| } | |||
| @@ -0,0 +1,136 @@ | |||
| <?php | |||
| namespace MaxMind\WebService\Http; | |||
| use MaxMind\Exception\HttpException; | |||
| /** | |||
| * This class is for internal use only. Semantic versioning does not not apply. | |||
| * | |||
| * @internal | |||
| */ | |||
| class CurlRequest implements Request | |||
| { | |||
| /** | |||
| * @var resource | |||
| */ | |||
| private $ch; | |||
| /** | |||
| * @var string | |||
| */ | |||
| private $url; | |||
| /** | |||
| * @var array | |||
| */ | |||
| private $options; | |||
| /** | |||
| * @param string $url | |||
| * @param array $options | |||
| */ | |||
| public function __construct($url, $options) | |||
| { | |||
| $this->url = $url; | |||
| $this->options = $options; | |||
| $this->ch = $options['curlHandle']; | |||
| } | |||
| /** | |||
| * @param string $body | |||
| * | |||
| * @throws HttpException | |||
| * | |||
| * @return array | |||
| */ | |||
| public function post($body) | |||
| { | |||
| $curl = $this->createCurl(); | |||
| curl_setopt($curl, CURLOPT_POST, true); | |||
| curl_setopt($curl, CURLOPT_POSTFIELDS, $body); | |||
| return $this->execute($curl); | |||
| } | |||
| public function get() | |||
| { | |||
| $curl = $this->createCurl(); | |||
| curl_setopt($curl, CURLOPT_HTTPGET, true); | |||
| return $this->execute($curl); | |||
| } | |||
| /** | |||
| * @return resource | |||
| */ | |||
| private function createCurl() | |||
| { | |||
| curl_reset($this->ch); | |||
| $opts = []; | |||
| $opts[CURLOPT_URL] = $this->url; | |||
| if (!empty($this->options['caBundle'])) { | |||
| $opts[CURLOPT_CAINFO] = $this->options['caBundle']; | |||
| } | |||
| $opts[CURLOPT_ENCODING] = ''; | |||
| $opts[CURLOPT_SSL_VERIFYHOST] = 2; | |||
| $opts[CURLOPT_FOLLOWLOCATION] = false; | |||
| $opts[CURLOPT_SSL_VERIFYPEER] = true; | |||
| $opts[CURLOPT_RETURNTRANSFER] = true; | |||
| $opts[CURLOPT_HTTPHEADER] = $this->options['headers']; | |||
| $opts[CURLOPT_USERAGENT] = $this->options['userAgent']; | |||
| $opts[CURLOPT_PROXY] = $this->options['proxy']; | |||
| // The defined()s are here as the *_MS opts are not available on older | |||
| // cURL versions | |||
| $connectTimeout = $this->options['connectTimeout']; | |||
| if (\defined('CURLOPT_CONNECTTIMEOUT_MS')) { | |||
| $opts[CURLOPT_CONNECTTIMEOUT_MS] = ceil($connectTimeout * 1000); | |||
| } else { | |||
| $opts[CURLOPT_CONNECTTIMEOUT] = ceil($connectTimeout); | |||
| } | |||
| $timeout = $this->options['timeout']; | |||
| if (\defined('CURLOPT_TIMEOUT_MS')) { | |||
| $opts[CURLOPT_TIMEOUT_MS] = ceil($timeout * 1000); | |||
| } else { | |||
| $opts[CURLOPT_TIMEOUT] = ceil($timeout); | |||
| } | |||
| curl_setopt_array($this->ch, $opts); | |||
| return $this->ch; | |||
| } | |||
| /** | |||
| * @param resource $curl | |||
| * | |||
| * @throws HttpException | |||
| * | |||
| * @return array | |||
| */ | |||
| private function execute($curl) | |||
| { | |||
| $body = curl_exec($curl); | |||
| if ($errno = curl_errno($curl)) { | |||
| $errorMessage = curl_error($curl); | |||
| throw new HttpException( | |||
| "cURL error ({$errno}): {$errorMessage}", | |||
| 0, | |||
| $this->url | |||
| ); | |||
| } | |||
| $statusCode = curl_getinfo($curl, CURLINFO_HTTP_CODE); | |||
| $contentType = curl_getinfo($curl, CURLINFO_CONTENT_TYPE); | |||
| return [$statusCode, $contentType, $body]; | |||
| } | |||
| } | |||
| @@ -0,0 +1,29 @@ | |||
| <?php | |||
| namespace MaxMind\WebService\Http; | |||
| /** | |||
| * Interface Request. | |||
| * | |||
| * @internal | |||
| */ | |||
| interface Request | |||
| { | |||
| /** | |||
| * @param string $url | |||
| * @param array $options | |||
| */ | |||
| public function __construct($url, $options); | |||
| /** | |||
| * @param string $body | |||
| * | |||
| * @return mixed | |||
| */ | |||
| public function post($body); | |||
| /** | |||
| * @return mixed | |||
| */ | |||
| public function get(); | |||
| } | |||
| @@ -0,0 +1,43 @@ | |||
| <?php | |||
| namespace MaxMind\WebService\Http; | |||
| /** | |||
| * Class RequestFactory. | |||
| * | |||
| * @internal | |||
| */ | |||
| class RequestFactory | |||
| { | |||
| /** | |||
| * Keep the cURL resource here, so that if there are multiple API requests | |||
| * done the connection is kept alive, SSL resumption can be used | |||
| * etcetera. | |||
| * | |||
| * @var resource | |||
| */ | |||
| private $ch; | |||
| public function __construct() | |||
| { | |||
| $this->ch = curl_init(); | |||
| } | |||
| public function __destruct() | |||
| { | |||
| curl_close($this->ch); | |||
| } | |||
| /** | |||
| * @param string $url | |||
| * @param array $options | |||
| * | |||
| * @return Request | |||
| */ | |||
| public function request($url, $options) | |||
| { | |||
| $options['curlHandle'] = $this->ch; | |||
| return new CurlRequest($url, $options); | |||
| } | |||
| } | |||