Posodobljeni dependancyji (composer produkcija), brisanje vprasanj - sql in osnova (pobrisana vprasanja shranimo podobno kot v knjiznico)

This commit is contained in:
Peter Hrvatin 2022-08-05 09:52:36 +02:00
parent f8700607d2
commit 7a43772acb
88 changed files with 3383 additions and 4238 deletions

View File

@ -0,0 +1,36 @@
<?php
/**
*
* Pobrisana vprasanja - pred brisanjem se vprasanje kopira med pobrisane - podobno kot knjiznica
*
*/
class VprasanjeDeleted {
var $anketa; // trenutna anketa
var $spremenljivka; // spremenljivka
public function __construct ($anketa = 0) {
if (isset ($_GET['anketa']))
$this->anketa = $_GET['anketa'];
elseif (isset ($_POST['anketa']))
$this->anketa = $_POST['anketa'];
elseif ($anketa != 0)
$this->anketa = $anketa;
SurveyInfo::getInstance()->SurveyInit($this->anketa);
}
// Prikazemo seznam pobrisanih vprasanj uporabnika
public function displayDeletedVprasanja(){
global $global_user_id;
}
}
?>

289
composer.lock generated
View File

@ -8,16 +8,16 @@
"packages": [ "packages": [
{ {
"name": "composer/ca-bundle", "name": "composer/ca-bundle",
"version": "1.3.2", "version": "1.3.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/composer/ca-bundle.git", "url": "https://github.com/composer/ca-bundle.git",
"reference": "fd5dd441932a7e10ca6e5b490e272d34c8430640" "reference": "30897edbfb15e784fe55587b4f73ceefd3c4d98c"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/composer/ca-bundle/zipball/fd5dd441932a7e10ca6e5b490e272d34c8430640", "url": "https://api.github.com/repos/composer/ca-bundle/zipball/30897edbfb15e784fe55587b4f73ceefd3c4d98c",
"reference": "fd5dd441932a7e10ca6e5b490e272d34c8430640", "reference": "30897edbfb15e784fe55587b4f73ceefd3c4d98c",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -61,11 +61,6 @@
"ssl", "ssl",
"tls" "tls"
], ],
"support": {
"irc": "irc://irc.freenode.org/composer",
"issues": "https://github.com/composer/ca-bundle/issues",
"source": "https://github.com/composer/ca-bundle/tree/1.3.2"
},
"funding": [ "funding": [
{ {
"url": "https://packagist.com", "url": "https://packagist.com",
@ -80,7 +75,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-05-24T11:56:16+00:00" "time": "2022-07-20T07:14:26+00:00"
}, },
{ {
"name": "fgrosse/phpasn1", "name": "fgrosse/phpasn1",
@ -159,28 +154,27 @@
}, },
{ {
"name": "geoip2/geoip2", "name": "geoip2/geoip2",
"version": "v2.12.2", "version": "v2.10.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/maxmind/GeoIP2-php.git", "url": "https://github.com/maxmind/GeoIP2-php.git",
"reference": "83adb44ac4b9553d36b579a14673ed124583082f" "reference": "419557cd21d9fe039721a83490701a58c8ce784a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/maxmind/GeoIP2-php/zipball/83adb44ac4b9553d36b579a14673ed124583082f", "url": "https://api.github.com/repos/maxmind/GeoIP2-php/zipball/419557cd21d9fe039721a83490701a58c8ce784a",
"reference": "83adb44ac4b9553d36b579a14673ed124583082f", "reference": "419557cd21d9fe039721a83490701a58c8ce784a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"ext-json": "*", "ext-json": "*",
"maxmind-db/reader": "~1.8", "maxmind-db/reader": "~1.5",
"maxmind/web-service-common": "~0.8", "maxmind/web-service-common": "~0.6",
"php": ">=7.2" "php": ">=5.6"
}, },
"require-dev": { "require-dev": {
"friendsofphp/php-cs-fixer": "3.*", "friendsofphp/php-cs-fixer": "2.*",
"phpstan/phpstan": "*", "phpunit/phpunit": "5.*",
"phpunit/phpunit": "^8.0 || ^9.0",
"squizlabs/php_codesniffer": "3.*" "squizlabs/php_codesniffer": "3.*"
}, },
"type": "library", "type": "library",
@ -209,11 +203,7 @@
"geolocation", "geolocation",
"maxmind" "maxmind"
], ],
"support": { "time": "2019-12-12T18:48:39+00:00"
"issues": "https://github.com/maxmind/GeoIP2-php/issues",
"source": "https://github.com/maxmind/GeoIP2-php/tree/v2.12.2"
},
"time": "2021-11-30T18:15:25+00:00"
}, },
{ {
"name": "guzzlehttp/guzzle", "name": "guzzlehttp/guzzle",
@ -357,12 +347,12 @@
} }
}, },
"autoload": { "autoload": {
"files": [
"src/functions_include.php"
],
"psr-4": { "psr-4": {
"GuzzleHttp\\Promise\\": "src/" "GuzzleHttp\\Promise\\": "src/"
} },
"files": [
"src/functions_include.php"
]
}, },
"notification-url": "https://packagist.org/downloads/", "notification-url": "https://packagist.org/downloads/",
"license": [ "license": [
@ -526,30 +516,29 @@
}, },
{ {
"name": "maxmind-db/reader", "name": "maxmind-db/reader",
"version": "v1.11.0", "version": "v1.6.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/maxmind/MaxMind-DB-Reader-php.git", "url": "https://github.com/maxmind/MaxMind-DB-Reader-php.git",
"reference": "b1f3c0699525336d09cc5161a2861268d9f2ae5b" "reference": "febd4920bf17c1da84cef58e56a8227dfb37fbe4"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/maxmind/MaxMind-DB-Reader-php/zipball/b1f3c0699525336d09cc5161a2861268d9f2ae5b", "url": "https://api.github.com/repos/maxmind/MaxMind-DB-Reader-php/zipball/febd4920bf17c1da84cef58e56a8227dfb37fbe4",
"reference": "b1f3c0699525336d09cc5161a2861268d9f2ae5b", "reference": "febd4920bf17c1da84cef58e56a8227dfb37fbe4",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=7.2" "php": ">=5.6"
}, },
"conflict": { "conflict": {
"ext-maxminddb": "<1.10.1,>=2.0.0" "ext-maxminddb": "<1.6.0,>=2.0.0"
}, },
"require-dev": { "require-dev": {
"friendsofphp/php-cs-fixer": "3.*", "friendsofphp/php-cs-fixer": "2.*",
"php-coveralls/php-coveralls": "^2.1", "php-coveralls/php-coveralls": "^2.1",
"phpstan/phpstan": "*", "phpunit/phpcov": "^3.0",
"phpunit/phpcov": ">=6.0.0", "phpunit/phpunit": "5.*",
"phpunit/phpunit": ">=8.0.0,<10.0.0",
"squizlabs/php_codesniffer": "3.*" "squizlabs/php_codesniffer": "3.*"
}, },
"suggest": { "suggest": {
@ -583,36 +572,31 @@
"geolocation", "geolocation",
"maxmind" "maxmind"
], ],
"support": { "time": "2019-12-19T22:59:03+00:00"
"issues": "https://github.com/maxmind/MaxMind-DB-Reader-php/issues",
"source": "https://github.com/maxmind/MaxMind-DB-Reader-php/tree/v1.11.0"
},
"time": "2021-10-18T15:23:10+00:00"
}, },
{ {
"name": "maxmind/web-service-common", "name": "maxmind/web-service-common",
"version": "v0.9.0", "version": "v0.7.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/maxmind/web-service-common-php.git", "url": "https://github.com/maxmind/web-service-common-php.git",
"reference": "4dc5a3e8df38aea4ca3b1096cee3a038094e9b53" "reference": "74c996c218ada5c639c8c2f076756e059f5552fc"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/maxmind/web-service-common-php/zipball/4dc5a3e8df38aea4ca3b1096cee3a038094e9b53", "url": "https://api.github.com/repos/maxmind/web-service-common-php/zipball/74c996c218ada5c639c8c2f076756e059f5552fc",
"reference": "4dc5a3e8df38aea4ca3b1096cee3a038094e9b53", "reference": "74c996c218ada5c639c8c2f076756e059f5552fc",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"composer/ca-bundle": "^1.0.3", "composer/ca-bundle": "^1.0.3",
"ext-curl": "*", "ext-curl": "*",
"ext-json": "*", "ext-json": "*",
"php": ">=7.2" "php": ">=5.6"
}, },
"require-dev": { "require-dev": {
"friendsofphp/php-cs-fixer": "3.*", "friendsofphp/php-cs-fixer": "2.*",
"phpstan/phpstan": "*", "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0",
"phpunit/phpunit": "^8.0 || ^9.0",
"squizlabs/php_codesniffer": "3.*" "squizlabs/php_codesniffer": "3.*"
}, },
"type": "library", "type": "library",
@ -634,11 +618,7 @@
], ],
"description": "Internal MaxMind Web Service API", "description": "Internal MaxMind Web Service API",
"homepage": "https://github.com/maxmind/web-service-common-php", "homepage": "https://github.com/maxmind/web-service-common-php",
"support": { "time": "2020-05-06T14:07:26+00:00"
"issues": "https://github.com/maxmind/web-service-common-php/issues",
"source": "https://github.com/maxmind/web-service-common-php/tree/v0.9.0"
},
"time": "2022-03-28T17:43:20+00:00"
}, },
{ {
"name": "minishlink/web-push", "name": "minishlink/web-push",
@ -694,10 +674,6 @@
"push", "push",
"web" "web"
], ],
"support": {
"issues": "https://github.com/web-push-libs/web-push-php/issues",
"source": "https://github.com/web-push-libs/web-push-php/tree/v5.2.5"
},
"time": "2020-08-02T08:58:01+00:00" "time": "2020-08-02T08:58:01+00:00"
}, },
{ {
@ -743,11 +719,6 @@
"pseudorandom", "pseudorandom",
"random" "random"
], ],
"support": {
"email": "info@paragonie.com",
"issues": "https://github.com/paragonie/random_compat/issues",
"source": "https://github.com/paragonie/random_compat"
},
"time": "2020-10-15T08:29:30+00:00" "time": "2020-10-15T08:29:30+00:00"
}, },
{ {
@ -859,8 +830,8 @@
"type": "library", "type": "library",
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"Sample\\": "samples/", "PayPalCheckoutSdk\\": "lib/PayPalCheckoutSdk",
"PayPalCheckoutSdk\\": "lib/PayPalCheckoutSdk" "Sample\\": "samples/"
} }
}, },
"notification-url": "https://packagist.org/downloads/", "notification-url": "https://packagist.org/downloads/",
@ -1057,9 +1028,6 @@
"request", "request",
"response" "response"
], ],
"support": {
"source": "https://github.com/php-fig/http-message/tree/master"
},
"time": "2016-08-06T14:39:51+00:00" "time": "2016-08-06T14:39:51+00:00"
}, },
{ {
@ -1100,31 +1068,27 @@
} }
], ],
"description": "A polyfill for getallheaders.", "description": "A polyfill for getallheaders.",
"support": {
"issues": "https://github.com/ralouphie/getallheaders/issues",
"source": "https://github.com/ralouphie/getallheaders/tree/develop"
},
"time": "2019-03-08T08:55:37+00:00" "time": "2019-03-08T08:55:37+00:00"
}, },
{ {
"name": "sonata-project/google-authenticator", "name": "sonata-project/google-authenticator",
"version": "2.3.1", "version": "2.2.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sonata-project/GoogleAuthenticator.git", "url": "https://github.com/sonata-project/GoogleAuthenticator.git",
"reference": "71a4189228f93a9662574dc8c65e77ef55061b59" "reference": "feda53899b26af24e3db2fe7a3e5f053ca483762"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sonata-project/GoogleAuthenticator/zipball/71a4189228f93a9662574dc8c65e77ef55061b59", "url": "https://api.github.com/repos/sonata-project/GoogleAuthenticator/zipball/feda53899b26af24e3db2fe7a3e5f053ca483762",
"reference": "71a4189228f93a9662574dc8c65e77ef55061b59", "reference": "feda53899b26af24e3db2fe7a3e5f053ca483762",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "^7.3 || ^8.0" "php": "^7.1"
}, },
"require-dev": { "require-dev": {
"symfony/phpunit-bridge": "^5.1.8" "symfony/phpunit-bridge": "^4.0"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
@ -1143,10 +1107,6 @@
"MIT" "MIT"
], ],
"authors": [ "authors": [
{
"name": "Thomas Rabaix",
"email": "thomas.rabaix@gmail.com"
},
{ {
"name": "Christian Stocker", "name": "Christian Stocker",
"email": "me@chregu.tv" "email": "me@chregu.tv"
@ -1154,6 +1114,10 @@
{ {
"name": "Andre DeMarre", "name": "Andre DeMarre",
"homepage": "http://www.devnetwork.net/viewtopic.php?f=50&t=94989" "homepage": "http://www.devnetwork.net/viewtopic.php?f=50&t=94989"
},
{
"name": "Thomas Rabaix",
"email": "thomas.rabaix@gmail.com"
} }
], ],
"description": "Library to integrate Google Authenticator into a PHP project", "description": "Library to integrate Google Authenticator into a PHP project",
@ -1161,30 +1125,8 @@
"keywords": [ "keywords": [
"google authenticator" "google authenticator"
], ],
"support": {
"issues": "https://github.com/sonata-project/GoogleAuthenticator/issues",
"source": "https://github.com/sonata-project/GoogleAuthenticator/tree/2.3.1"
},
"funding": [
{
"url": "https://github.com/OskarStark",
"type": "github"
},
{
"url": "https://github.com/VincentLanglet",
"type": "github"
},
{
"url": "https://github.com/core23",
"type": "github"
},
{
"url": "https://github.com/wbloszyk",
"type": "github"
}
],
"abandoned": true, "abandoned": true,
"time": "2021-02-15T19:23:18+00:00" "time": "2018-07-18T22:08:02+00:00"
}, },
{ {
"name": "spomky-labs/base64url", "name": "spomky-labs/base64url",
@ -1235,20 +1177,6 @@
"safe", "safe",
"url" "url"
], ],
"support": {
"issues": "https://github.com/Spomky-Labs/base64url/issues",
"source": "https://github.com/Spomky-Labs/base64url/tree/v2.0.4"
},
"funding": [
{
"url": "https://github.com/Spomky",
"type": "github"
},
{
"url": "https://www.patreon.com/FlorentMorselli",
"type": "patreon"
}
],
"time": "2020-11-03T09:10:25+00:00" "time": "2020-11-03T09:10:25+00:00"
}, },
{ {
@ -1631,15 +1559,6 @@
"jwt", "jwt",
"symfony" "symfony"
], ],
"support": {
"source": "https://github.com/web-token/jwt-core/tree/v1.3.10"
},
"funding": [
{
"url": "https://www.patreon.com/FlorentMorselli",
"type": "patreon"
}
],
"time": "2020-03-20T13:29:04+00:00" "time": "2020-03-20T13:29:04+00:00"
}, },
{ {
@ -1717,15 +1636,6 @@
"jwt", "jwt",
"symfony" "symfony"
], ],
"support": {
"source": "https://github.com/web-token/jwt-key-mgmt/tree/v1.3.10"
},
"funding": [
{
"url": "https://www.patreon.com/FlorentMorselli",
"type": "patreon"
}
],
"time": "2020-03-20T13:29:04+00:00" "time": "2020-03-20T13:29:04+00:00"
}, },
{ {
@ -1806,15 +1716,6 @@
"jwt", "jwt",
"symfony" "symfony"
], ],
"support": {
"source": "https://github.com/web-token/jwt-signature/tree/v1.3.10"
},
"funding": [
{
"url": "https://www.patreon.com/FlorentMorselli",
"type": "patreon"
}
],
"time": "2020-03-20T13:29:04+00:00" "time": "2020-03-20T13:29:04+00:00"
}, },
{ {
@ -1882,15 +1783,6 @@
"jwt", "jwt",
"symfony" "symfony"
], ],
"support": {
"source": "https://github.com/web-token/jwt-signature-algorithm-ecdsa/tree/v1.3.10"
},
"funding": [
{
"url": "https://www.patreon.com/FlorentMorselli",
"type": "patreon"
}
],
"time": "2020-03-20T13:29:04+00:00" "time": "2020-03-20T13:29:04+00:00"
}, },
{ {
@ -1958,15 +1850,6 @@
"jwt", "jwt",
"symfony" "symfony"
], ],
"support": {
"source": "https://github.com/web-token/jwt-signature-algorithm-eddsa/tree/v1.3.10"
},
"funding": [
{
"url": "https://www.patreon.com/FlorentMorselli",
"type": "patreon"
}
],
"time": "2020-03-20T13:29:04+00:00" "time": "2020-03-20T13:29:04+00:00"
}, },
{ {
@ -2034,15 +1917,6 @@
"jwt", "jwt",
"symfony" "symfony"
], ],
"support": {
"source": "https://github.com/web-token/jwt-signature-algorithm-hmac/tree/v1.3"
},
"funding": [
{
"url": "https://www.patreon.com/FlorentMorselli",
"type": "patreon"
}
],
"time": "2020-03-20T13:29:04+00:00" "time": "2020-03-20T13:29:04+00:00"
}, },
{ {
@ -2110,15 +1984,6 @@
"jwt", "jwt",
"symfony" "symfony"
], ],
"support": {
"source": "https://github.com/web-token/jwt-signature-algorithm-none/tree/v1.3"
},
"funding": [
{
"url": "https://www.patreon.com/FlorentMorselli",
"type": "patreon"
}
],
"time": "2020-03-20T13:29:04+00:00" "time": "2020-03-20T13:29:04+00:00"
}, },
{ {
@ -2186,15 +2051,6 @@
"jwt", "jwt",
"symfony" "symfony"
], ],
"support": {
"source": "https://github.com/web-token/jwt-signature-algorithm-rsa/tree/v1.3"
},
"funding": [
{
"url": "https://www.patreon.com/FlorentMorselli",
"type": "patreon"
}
],
"time": "2020-03-20T13:29:04+00:00" "time": "2020-03-20T13:29:04+00:00"
}, },
{ {
@ -2264,15 +2120,6 @@
"jwt", "jwt",
"symfony" "symfony"
], ],
"support": {
"source": "https://github.com/web-token/jwt-util-ecc/tree/v1.3"
},
"funding": [
{
"url": "https://www.patreon.com/FlorentMorselli",
"type": "patreon"
}
],
"time": "2020-03-20T13:29:04+00:00" "time": "2020-03-20T13:29:04+00:00"
} }
], ],
@ -2336,10 +2183,6 @@
"throwable", "throwable",
"whoops" "whoops"
], ],
"support": {
"issues": "https://github.com/filp/whoops/issues",
"source": "https://github.com/filp/whoops/tree/2.14.5"
},
"funding": [ "funding": [
{ {
"url": "https://github.com/denis-sokolov", "url": "https://github.com/denis-sokolov",
@ -2397,10 +2240,6 @@
"kint", "kint",
"php" "php"
], ],
"support": {
"issues": "https://github.com/kint-php/kint/issues",
"source": "https://github.com/kint-php/kint/tree/1.1"
},
"time": "2017-01-15T14:23:43+00:00" "time": "2017-01-15T14:23:43+00:00"
}, },
{ {
@ -2463,10 +2302,6 @@
"debug", "debug",
"debugbar" "debugbar"
], ],
"support": {
"issues": "https://github.com/maximebf/php-debugbar/issues",
"source": "https://github.com/maximebf/php-debugbar/tree/v1.18.0"
},
"time": "2021-12-27T18:49:48+00:00" "time": "2021-12-27T18:49:48+00:00"
}, },
{ {
@ -2514,9 +2349,6 @@
"psr", "psr",
"psr-3" "psr-3"
], ],
"support": {
"source": "https://github.com/php-fig/log/tree/1.1.4"
},
"time": "2021-05-03T11:20:27+00:00" "time": "2021-05-03T11:20:27+00:00"
}, },
{ {
@ -2583,9 +2415,6 @@
"portable", "portable",
"shim" "shim"
], ],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0"
},
"funding": [ "funding": [
{ {
"url": "https://symfony.com/sponsor", "url": "https://symfony.com/sponsor",
@ -2666,9 +2495,6 @@
"portable", "portable",
"shim" "shim"
], ],
"support": {
"source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0"
},
"funding": [ "funding": [
{ {
"url": "https://symfony.com/sponsor", "url": "https://symfony.com/sponsor",
@ -2687,16 +2513,16 @@
}, },
{ {
"name": "symfony/var-dumper", "name": "symfony/var-dumper",
"version": "v4.4.42", "version": "v4.4.44",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/var-dumper.git", "url": "https://github.com/symfony/var-dumper.git",
"reference": "742aab50ad097bcb62d91fccb613f66b8047d2ca" "reference": "f19951007dae942cc79b979c1fe26bfdfbeb54ed"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/742aab50ad097bcb62d91fccb613f66b8047d2ca", "url": "https://api.github.com/repos/symfony/var-dumper/zipball/f19951007dae942cc79b979c1fe26bfdfbeb54ed",
"reference": "742aab50ad097bcb62d91fccb613f66b8047d2ca", "reference": "f19951007dae942cc79b979c1fe26bfdfbeb54ed",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2755,9 +2581,6 @@
"debug", "debug",
"dump" "dump"
], ],
"support": {
"source": "https://github.com/symfony/var-dumper/tree/v4.4.42"
},
"funding": [ "funding": [
{ {
"url": "https://symfony.com/sponsor", "url": "https://symfony.com/sponsor",
@ -2772,7 +2595,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-05-21T10:00:54+00:00" "time": "2022-07-20T09:59:04+00:00"
} }
], ],
"aliases": [], "aliases": [],
@ -2784,5 +2607,5 @@
"php": "^5.5|^7.0" "php": "^5.5|^7.0"
}, },
"platform-dev": [], "platform-dev": [],
"plugin-api-version": "2.1.0" "plugin-api-version": "1.1.0"
} }

View File

@ -9641,3 +9641,17 @@ CREATE TABLE kolektor_podjetje_funkcija(
) ENGINE=InnoDB DEFAULT CHARSET=utf8; ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
UPDATE misc SET value='22.07.13' WHERE what="version"; UPDATE misc SET value='22.07.13' WHERE what="version";
## FK za pobrisana vprasanja - po novem jih ne brisemo ampak ohranimo (podoben sistem kot knjiznica vprasanj)
INSERT INTO srv_grupa (id, ank_id, naslov, vrstni_red) VALUES (-3, 0, 'system', 0);
CREATE TABLE IF NOT EXISTS srv_spremenljivka_deleted (
spr_id INT(11) NOT NULL,
ank_id INT(11) NOT NULL,
delete_time DATETIME(3) NOT NULL,
CONSTRAINT fk_srv_spremenljivka_deleted_ank_id FOREIGN KEY (ank_id) REFERENCES srv_anketa (id) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT fk_srv_spremenljivka_deleted_spr_id FOREIGN KEY (spr_id) REFERENCES srv_spremenljivka (id) ON DELETE CASCADE ON UPDATE CASCADE,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;;
UPDATE misc SET value='22.08.05' WHERE what="version";

View File

@ -37,13 +37,11 @@ namespace Composer\Autoload;
* *
* @author Fabien Potencier <fabien@symfony.com> * @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be> * @author Jordi Boggiano <j.boggiano@seld.be>
* @see https://www.php-fig.org/psr/psr-0/ * @see http://www.php-fig.org/psr/psr-0/
* @see https://www.php-fig.org/psr/psr-4/ * @see http://www.php-fig.org/psr/psr-4/
*/ */
class ClassLoader class ClassLoader
{ {
private $vendorDir;
// PSR-4 // PSR-4
private $prefixLengthsPsr4 = array(); private $prefixLengthsPsr4 = array();
private $prefixDirsPsr4 = array(); private $prefixDirsPsr4 = array();
@ -59,13 +57,6 @@ class ClassLoader
private $missingClasses = array(); private $missingClasses = array();
private $apcuPrefix; private $apcuPrefix;
private static $registeredLoaders = array();
public function __construct($vendorDir = null)
{
$this->vendorDir = $vendorDir;
}
public function getPrefixes() public function getPrefixes()
{ {
if (!empty($this->prefixesPsr0)) { if (!empty($this->prefixesPsr0)) {
@ -309,17 +300,6 @@ class ClassLoader
public function register($prepend = false) public function register($prepend = false)
{ {
spl_autoload_register(array($this, 'loadClass'), true, $prepend); spl_autoload_register(array($this, 'loadClass'), true, $prepend);
if (null === $this->vendorDir) {
return;
}
if ($prepend) {
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
} else {
unset(self::$registeredLoaders[$this->vendorDir]);
self::$registeredLoaders[$this->vendorDir] = $this;
}
} }
/** /**
@ -328,17 +308,13 @@ class ClassLoader
public function unregister() public function unregister()
{ {
spl_autoload_unregister(array($this, 'loadClass')); spl_autoload_unregister(array($this, 'loadClass'));
if (null !== $this->vendorDir) {
unset(self::$registeredLoaders[$this->vendorDir]);
}
} }
/** /**
* Loads the given class or interface. * Loads the given class or interface.
* *
* @param string $class The name of the class * @param string $class The name of the class
* @return true|null True if loaded, null otherwise * @return bool|null True if loaded, null otherwise
*/ */
public function loadClass($class) public function loadClass($class)
{ {
@ -347,8 +323,6 @@ class ClassLoader
return true; return true;
} }
return null;
} }
/** /**
@ -393,16 +367,6 @@ class ClassLoader
return $file; return $file;
} }
/**
* Returns the currently registered loaders indexed by their corresponding vendor directories.
*
* @return self[]
*/
public static function getRegisteredLoaders()
{
return self::$registeredLoaders;
}
private function findFileWithExtension($class, $ext) private function findFileWithExtension($class, $ext)
{ {
// PSR-4 lookup // PSR-4 lookup

View File

@ -67,7 +67,6 @@ return array(
'CheckboxXml' => $baseDir . '/admin/survey/export/xmlClasses/Vprasanja/CheckboxXml.php', 'CheckboxXml' => $baseDir . '/admin/survey/export/xmlClasses/Vprasanja/CheckboxXml.php',
'Common' => $baseDir . '/admin/survey/classes/class.Common.php', 'Common' => $baseDir . '/admin/survey/classes/class.Common.php',
'Composer\\CaBundle\\CaBundle' => $vendorDir . '/composer/ca-bundle/src/CaBundle.php', 'Composer\\CaBundle\\CaBundle' => $vendorDir . '/composer/ca-bundle/src/CaBundle.php',
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
'CrossRoad' => $baseDir . '/admin/survey/classes/tracking/CrossRoad.php', 'CrossRoad' => $baseDir . '/admin/survey/classes/tracking/CrossRoad.php',
'DatumLatex' => $baseDir . '/admin/survey/export/latexclasses/Vprasanja/DatumLatex.php', 'DatumLatex' => $baseDir . '/admin/survey/export/latexclasses/Vprasanja/DatumLatex.php',
'Demografija' => $baseDir . '/admin/survey/classes/class.Demografija.php', 'Demografija' => $baseDir . '/admin/survey/classes/class.Demografija.php',
@ -1071,6 +1070,7 @@ return array(
'UserTrackingClass' => $baseDir . '/admin/survey/classes/tracking/UserTrackingClass.php', 'UserTrackingClass' => $baseDir . '/admin/survey/classes/tracking/UserTrackingClass.php',
'VariableView' => $baseDir . '/admin/survey/classes/class.SurveyVariableView.php', 'VariableView' => $baseDir . '/admin/survey/classes/class.SurveyVariableView.php',
'Vprasanje' => $baseDir . '/admin/survey/classes/class.Vprasanje.php', 'Vprasanje' => $baseDir . '/admin/survey/classes/class.Vprasanje.php',
'VprasanjeDeleted' => $baseDir . '/admin/survey/classes/class.VprasanjeDeleted.php',
'VprasanjeInline' => $baseDir . '/admin/survey/classes/class.VprasanjeInline.php', 'VprasanjeInline' => $baseDir . '/admin/survey/classes/class.VprasanjeInline.php',
'VsotaLatex' => $baseDir . '/admin/survey/export/latexclasses/Vprasanja/VsotaLatex.php', 'VsotaLatex' => $baseDir . '/admin/survey/export/latexclasses/Vprasanja/VsotaLatex.php',
'WPN' => $baseDir . '/admin/survey/modules/mod_WPN/class.WPN.php', 'WPN' => $baseDir . '/admin/survey/modules/mod_WPN/class.WPN.php',

View File

@ -22,15 +22,13 @@ class ComposerAutoloaderInit6b03163c371c5541881b55b762b8c779
return self::$loader; return self::$loader;
} }
require __DIR__ . '/platform_check.php';
spl_autoload_register(array('ComposerAutoloaderInit6b03163c371c5541881b55b762b8c779', 'loadClassLoader'), true, true); spl_autoload_register(array('ComposerAutoloaderInit6b03163c371c5541881b55b762b8c779', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__))); self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInit6b03163c371c5541881b55b762b8c779', 'loadClassLoader')); spl_autoload_unregister(array('ComposerAutoloaderInit6b03163c371c5541881b55b762b8c779', 'loadClassLoader'));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
if ($useStaticLoader) { if ($useStaticLoader) {
require __DIR__ . '/autoload_static.php'; require_once __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit6b03163c371c5541881b55b762b8c779::getInitializer($loader)); call_user_func(\Composer\Autoload\ComposerStaticInit6b03163c371c5541881b55b762b8c779::getInitializer($loader));
} else { } else {

View File

@ -271,7 +271,6 @@ class ComposerStaticInit6b03163c371c5541881b55b762b8c779
'CheckboxXml' => __DIR__ . '/../..' . '/admin/survey/export/xmlClasses/Vprasanja/CheckboxXml.php', 'CheckboxXml' => __DIR__ . '/../..' . '/admin/survey/export/xmlClasses/Vprasanja/CheckboxXml.php',
'Common' => __DIR__ . '/../..' . '/admin/survey/classes/class.Common.php', 'Common' => __DIR__ . '/../..' . '/admin/survey/classes/class.Common.php',
'Composer\\CaBundle\\CaBundle' => __DIR__ . '/..' . '/composer/ca-bundle/src/CaBundle.php', 'Composer\\CaBundle\\CaBundle' => __DIR__ . '/..' . '/composer/ca-bundle/src/CaBundle.php',
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
'CrossRoad' => __DIR__ . '/../..' . '/admin/survey/classes/tracking/CrossRoad.php', 'CrossRoad' => __DIR__ . '/../..' . '/admin/survey/classes/tracking/CrossRoad.php',
'DatumLatex' => __DIR__ . '/../..' . '/admin/survey/export/latexclasses/Vprasanja/DatumLatex.php', 'DatumLatex' => __DIR__ . '/../..' . '/admin/survey/export/latexclasses/Vprasanja/DatumLatex.php',
'Demografija' => __DIR__ . '/../..' . '/admin/survey/classes/class.Demografija.php', 'Demografija' => __DIR__ . '/../..' . '/admin/survey/classes/class.Demografija.php',
@ -1275,6 +1274,7 @@ class ComposerStaticInit6b03163c371c5541881b55b762b8c779
'UserTrackingClass' => __DIR__ . '/../..' . '/admin/survey/classes/tracking/UserTrackingClass.php', 'UserTrackingClass' => __DIR__ . '/../..' . '/admin/survey/classes/tracking/UserTrackingClass.php',
'VariableView' => __DIR__ . '/../..' . '/admin/survey/classes/class.SurveyVariableView.php', 'VariableView' => __DIR__ . '/../..' . '/admin/survey/classes/class.SurveyVariableView.php',
'Vprasanje' => __DIR__ . '/../..' . '/admin/survey/classes/class.Vprasanje.php', 'Vprasanje' => __DIR__ . '/../..' . '/admin/survey/classes/class.Vprasanje.php',
'VprasanjeDeleted' => __DIR__ . '/../..' . '/admin/survey/classes/class.VprasanjeDeleted.php',
'VprasanjeInline' => __DIR__ . '/../..' . '/admin/survey/classes/class.VprasanjeInline.php', 'VprasanjeInline' => __DIR__ . '/../..' . '/admin/survey/classes/class.VprasanjeInline.php',
'VsotaLatex' => __DIR__ . '/../..' . '/admin/survey/export/latexclasses/Vprasanja/VsotaLatex.php', 'VsotaLatex' => __DIR__ . '/../..' . '/admin/survey/export/latexclasses/Vprasanja/VsotaLatex.php',
'WPN' => __DIR__ . '/../..' . '/admin/survey/modules/mod_WPN/class.WPN.php', 'WPN' => __DIR__ . '/../..' . '/admin/survey/modules/mod_WPN/class.WPN.php',

View File

@ -1,7 +1,7 @@
## ##
## Bundle of CA Root Certificates ## Bundle of CA Root Certificates
## ##
## Certificate data from Mozilla as of: Tue Apr 26 03:12:05 2022 GMT ## Certificate data from Mozilla as of: Tue Jul 19 03:12:06 2022 GMT
## ##
## This is a bundle of X.509 certificates of public Certificate Authorities ## This is a bundle of X.509 certificates of public Certificate Authorities
## (CA). These were automatically extracted from Mozilla's root certificates ## (CA). These were automatically extracted from Mozilla's root certificates
@ -14,7 +14,7 @@
## Just configure this file as the SSLCACertificateFile. ## Just configure this file as the SSLCACertificateFile.
## ##
## Conversion done with mk-ca-bundle.pl version 1.29. ## Conversion done with mk-ca-bundle.pl version 1.29.
## SHA256: 34a54d5191775c1bd37be6cfd3f09e831e072555dc3a2e51f4a2c4b0f8ada5cc ## SHA256: 9bf3799611fb58197f61d45e71ce3dc19f30e7dd73731915872ce5108a7bb066
## ##
@ -993,30 +993,6 @@ tnRGEmyR7jTV7JqR50S+kDFy1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29
mvVXIwAHIRc/SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 mvVXIwAHIRc/SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03
-----END CERTIFICATE----- -----END CERTIFICATE-----
Hellenic Academic and Research Institutions RootCA 2011
=======================================================
-----BEGIN CERTIFICATE-----
MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1IxRDBCBgNVBAoT
O0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9y
aXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25z
IFJvb3RDQSAyMDExMB4XDTExMTIwNjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYT
AkdSMUQwQgYDVQQKEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25z
IENlcnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNo
IEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPzdYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI
1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJfel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa
71HFK9+WXesyHgLacEnsbgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u
8yBRQlqD75O6aRXxYp2fmTmCobd0LovUxQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSPFEDH
3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNVHRMBAf8EBTADAQH/
MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp5dgTBCPuQSUwRwYDVR0eBEAwPqA8
MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQub3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQu
b3JnMA0GCSqGSIb3DQEBBQUAA4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVt
XdMiKahsog2p6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8
TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7dIsXRSZMFpGD
/md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8AcysNnq/onN694/BtZqhFLKPM58N
7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXIl7WdmplNsDz4SgCbZN2fOUvRJ9e4
-----END CERTIFICATE-----
Actalis Authentication Root CA Actalis Authentication Root CA
============================== ==============================
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
@ -3345,3 +3321,140 @@ PUQtVHJ1c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjOPQQD
AwNpADBmAjEAyjzGKnXCXnViOTYAYFqLwZOZzNnbQTs7h5kXO9XMT8oi96CAy/m0sRtW9XLS/BnR AwNpADBmAjEAyjzGKnXCXnViOTYAYFqLwZOZzNnbQTs7h5kXO9XMT8oi96CAy/m0sRtW9XLS/BnR
AjEAkfcwkz8QRitxpNA7RJvAKQIFskF3UfN5Wp6OFKBOQtJbgfM0agPnIjhQW+0ZT0MW AjEAkfcwkz8QRitxpNA7RJvAKQIFskF3UfN5Wp6OFKBOQtJbgfM0agPnIjhQW+0ZT0MW
-----END CERTIFICATE----- -----END CERTIFICATE-----
DigiCert TLS ECC P384 Root G5
=============================
-----BEGIN CERTIFICATE-----
MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQswCQYDVQQGEwJV
UzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURpZ2lDZXJ0IFRMUyBFQ0MgUDM4
NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2MDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMx
FzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQg
Um9vdCBHNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1Tzvd
lHJS7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp0zVozptj
n4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICISB4CIfBFqMA4GA1UdDwEB
/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQCJao1H5+z8blUD2Wds
Jk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQLgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIx
AJSdYsiJvRmEFOml+wG4DXZDjC5Ty3zfDBeWUA==
-----END CERTIFICATE-----
DigiCert TLS RSA4096 Root G5
============================
-----BEGIN CERTIFICATE-----
MIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBNMQswCQYDVQQG
EwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0
MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcNNDYwMTE0MjM1OTU5WjBNMQswCQYDVQQGEwJV
UzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0MDk2
IFJvb3QgRzUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz0PTJeRGd/fxmgefM1eS8
7IE+ajWOLrfn3q/5B03PMJ3qCQuZvWxX2hhKuHisOjmopkisLnLlvevxGs3npAOpPxG02C+JFvuU
AT27L/gTBaF4HI4o4EXgg/RZG5Wzrn4DReW+wkL+7vI8toUTmDKdFqgpwgscONyfMXdcvyej/Ces
tyu9dJsXLfKB2l2w4SMXPohKEiPQ6s+d3gMXsUJKoBZMpG2T6T867jp8nVid9E6P/DsjyG244gXa
zOvswzH016cpVIDPRFtMbzCe88zdH5RDnU1/cHAN1DrRN/BsnZvAFJNY781BOHW8EwOVfH/jXOnV
DdXifBBiqmvwPXbzP6PosMH976pXTayGpxi0KcEsDr9kvimM2AItzVwv8n/vFfQMFawKsPHTDU9q
TXeXAaDxZre3zu/O7Oyldcqs4+Fj97ihBMi8ez9dLRYiVu1ISf6nL3kwJZu6ay0/nTvEF+cdLvvy
z6b84xQslpghjLSR6Rlgg/IwKwZzUNWYOwbpx4oMYIwo+FKbbuH2TbsGJJvXKyY//SovcfXWJL5/
MZ4PbeiPT02jP/816t9JXkGPhvnxd3lLG7SjXi/7RgLQZhNeXoVPzthwiHvOAbWWl9fNff2C+MIk
wcoBOU+NosEUQB+cZtUMCUbW8tDRSHZWOkPLtgoRObqME2wGtZ7P6wIDAQABo0IwQDAdBgNVHQ4E
FgQUUTMc7TZArxfTJc1paPKvTiM+s0EwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8w
DQYJKoZIhvcNAQEMBQADggIBAGCmr1tfV9qJ20tQqcQjNSH/0GEwhJG3PxDPJY7Jv0Y02cEhJhxw
GXIeo8mH/qlDZJY6yFMECrZBu8RHANmfGBg7sg7zNOok992vIGCukihfNudd5N7HPNtQOa27PShN
lnx2xlv0wdsUpasZYgcYQF+Xkdycx6u1UQ3maVNVzDl92sURVXLFO4uJ+DQtpBflF+aZfTCIITfN
MBc9uPK8qHWgQ9w+iUuQrm0D4ByjoJYJu32jtyoQREtGBzRj7TG5BO6jm5qu5jF49OokYTurWGT/
u4cnYiWB39yhL/btp/96j1EuMPikAdKFOV8BmZZvWltwGUb+hmA+rYAQCd05JS9Yf7vSdPD3Rh9G
OUrYU9DzLjtxpdRv/PNn5AeP3SYZ4Y1b+qOTEZvpyDrDVWiakuFSdjjo4bq9+0/V77PnSIMx8IIh
47a+p6tv75/fTM8BuGJqIz3nCU2AG3swpMPdB380vqQmsvZB6Akd4yCYqjdP//fx4ilwMUc/dNAU
FvohigLVigmUdy7yWSiLfFCSCmZ4OIN1xLVaqBHG5cGdZlXPU8Sv13WFqUITVuwhd4GTWgzqltlJ
yqEI8pc7bZsEGCREjnwB8twl2F6GmrE52/WRMmrRpnCKovfepEWFJqgejF0pW8hL2JpqA15w8oVP
bEtoL8pU9ozaMv7Da4M/OMZ+
-----END CERTIFICATE-----
Certainly Root R1
=================
-----BEGIN CERTIFICATE-----
MIIFRzCCAy+gAwIBAgIRAI4P+UuQcWhlM1T01EQ5t+AwDQYJKoZIhvcNAQELBQAwPTELMAkGA1UE
BhMCVVMxEjAQBgNVBAoTCUNlcnRhaW5seTEaMBgGA1UEAxMRQ2VydGFpbmx5IFJvb3QgUjEwHhcN
MjEwNDAxMDAwMDAwWhcNNDYwNDAxMDAwMDAwWjA9MQswCQYDVQQGEwJVUzESMBAGA1UEChMJQ2Vy
dGFpbmx5MRowGAYDVQQDExFDZXJ0YWlubHkgUm9vdCBSMTCCAiIwDQYJKoZIhvcNAQEBBQADggIP
ADCCAgoCggIBANA21B/q3avk0bbm+yLA3RMNansiExyXPGhjZjKcA7WNpIGD2ngwEc/csiu+kr+O
5MQTvqRoTNoCaBZ0vrLdBORrKt03H2As2/X3oXyVtwxwhi7xOu9S98zTm/mLvg7fMbedaFySpvXl
8wo0tf97ouSHocavFwDvA5HtqRxOcT3Si2yJ9HiG5mpJoM610rCrm/b01C7jcvk2xusVtyWMOvwl
DbMicyF0yEqWYZL1LwsYpfSt4u5BvQF5+paMjRcCMLT5r3gajLQ2EBAHBXDQ9DGQilHFhiZ5shGI
XsXwClTNSaa/ApzSRKft43jvRl5tcdF5cBxGX1HpyTfcX35pe0HfNEXgO4T0oYoKNp43zGJS4YkN
KPl6I7ENPT2a/Z2B7yyQwHtETrtJ4A5KVpK8y7XdeReJkd5hiXSSqOMyhb5OhaRLWcsrxXiOcVTQ
AjeZjOVJ6uBUcqQRBi8LjMFbvrWhsFNunLhgkR9Za/kt9JQKl7XsxXYDVBtlUrpMklZRNaBA2Cnb
rlJ2Oy0wQJuK0EJWtLeIAaSHO1OWzaMWj/Nmqhexx2DgwUMFDO6bW2BvBlyHWyf5QBGenDPBt+U1
VwV/J84XIIwc/PH72jEpSe31C4SnT8H2TsIonPru4K8H+zMReiFPCyEQtkA6qyI6BJyLm4SGcprS
p6XEtHWRqSsjAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
DgQWBBTgqj8ljZ9EXME66C6ud0yEPmcM9DANBgkqhkiG9w0BAQsFAAOCAgEAuVevuBLaV4OPaAsz
HQNTVfSVcOQrPbA56/qJYv331hgELyE03fFo8NWWWt7CgKPBjcZq91l3rhVkz1t5BXdm6ozTaw3d
8VkswTOlMIAVRQdFGjEitpIAq5lNOo93r6kiyi9jyhXWx8bwPWz8HA2YEGGeEaIi1wrykXprOQ4v
MMM2SZ/g6Q8CRFA3lFV96p/2O7qUpUzpvD5RtOjKkjZUbVwlKNrdrRT90+7iIgXr0PK3aBLXWopB
GsaSpVo7Y0VPv+E6dyIvXL9G+VoDhRNCX8reU9ditaY1BMJH/5n9hN9czulegChB8n3nHpDYT3Y+
gjwN/KUD+nsa2UUeYNrEjvn8K8l7lcUq/6qJ34IxD3L/DCfXCh5WAFAeDJDBlrXYFIW7pw0WwfgH
JBu6haEaBQmAupVjyTrsJZ9/nbqkRxWbRHDxakvWOF5D8xh+UG7pWijmZeZ3Gzr9Hb4DJqPb1OG7
fpYnKx3upPvaJVQTA945xsMfTZDsjxtK0hzthZU4UHlG1sGQUDGpXJpuHfUzVounmdLyyCwzk5Iw
x06MZTMQZBf9JBeW0Y3COmor6xOLRPIh80oat3df1+2IpHLlOR+Vnb5nwXARPbv0+Em34yaXOp/S
X3z7wJl8OSngex2/DaeP0ik0biQVy96QXr8axGbqwua6OV+KmalBWQewLK8=
-----END CERTIFICATE-----
Certainly Root E1
=================
-----BEGIN CERTIFICATE-----
MIIB9zCCAX2gAwIBAgIQBiUzsUcDMydc+Y2aub/M+DAKBggqhkjOPQQDAzA9MQswCQYDVQQGEwJV
UzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0YWlubHkgUm9vdCBFMTAeFw0yMTA0
MDEwMDAwMDBaFw00NjA0MDEwMDAwMDBaMD0xCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlDZXJ0YWlu
bHkxGjAYBgNVBAMTEUNlcnRhaW5seSBSb290IEUxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3m/4
fxzf7flHh4axpMCK+IKXgOqPyEpeKn2IaKcBYhSRJHpcnqMXfYqGITQYUBsQ3tA3SybHGWCA6TS9
YBk2QNYphwk8kXr2vBMj3VlOBF7PyAIcGFPBMdjaIOlEjeR2o0IwQDAOBgNVHQ8BAf8EBAMCAQYw
DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ygYy2R17ikq6+2uI1g4hevIIgcwCgYIKoZIzj0E
AwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozmut6Dacpps6kFtZaSF4fC0urQe87YQVt8
rgIwRt7qy12a7DLCZRawTDBcMPPaTnOGBtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR
-----END CERTIFICATE-----
E-Tugra Global Root CA RSA v3
=============================
-----BEGIN CERTIFICATE-----
MIIF8zCCA9ugAwIBAgIUDU3FzRYilZYIfrgLfxUGNPt5EDQwDQYJKoZIhvcNAQELBQAwgYAxCzAJ
BgNVBAYTAlRSMQ8wDQYDVQQHEwZBbmthcmExGTAXBgNVBAoTEEUtVHVncmEgRUJHIEEuUy4xHTAb
BgNVBAsTFEUtVHVncmEgVHJ1c3QgQ2VudGVyMSYwJAYDVQQDEx1FLVR1Z3JhIEdsb2JhbCBSb290
IENBIFJTQSB2MzAeFw0yMDAzMTgwOTA3MTdaFw00NTAzMTIwOTA3MTdaMIGAMQswCQYDVQQGEwJU
UjEPMA0GA1UEBxMGQW5rYXJhMRkwFwYDVQQKExBFLVR1Z3JhIEVCRyBBLlMuMR0wGwYDVQQLExRF
LVR1Z3JhIFRydXN0IENlbnRlcjEmMCQGA1UEAxMdRS1UdWdyYSBHbG9iYWwgUm9vdCBDQSBSU0Eg
djMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCiZvCJt3J77gnJY9LTQ91ew6aEOErx
jYG7FL1H6EAX8z3DeEVypi6Q3po61CBxyryfHUuXCscxuj7X/iWpKo429NEvx7epXTPcMHD4QGxL
sqYxYdE0PD0xesevxKenhOGXpOhL9hd87jwH7eKKV9y2+/hDJVDqJ4GohryPUkqWOmAalrv9c/SF
/YP9f4RtNGx/ardLAQO/rWm31zLZ9Vdq6YaCPqVmMbMWPcLzJmAy01IesGykNz709a/r4d+ABs8q
QedmCeFLl+d3vSFtKbZnwy1+7dZ5ZdHPOrbRsV5WYVB6Ws5OUDGAA5hH5+QYfERaxqSzO8bGwzrw
bMOLyKSRBfP12baqBqG3q+Sx6iEUXIOk/P+2UNOMEiaZdnDpwA+mdPy70Bt4znKS4iicvObpCdg6
04nmvi533wEKb5b25Y08TVJ2Glbhc34XrD2tbKNSEhhw5oBOM/J+JjKsBY04pOZ2PJ8QaQ5tndLB
eSBrW88zjdGUdjXnXVXHt6woq0bM5zshtQoK5EpZ3IE1S0SVEgpnpaH/WwAH0sDM+T/8nzPyAPiM
bIedBi3x7+PmBvrFZhNb/FAHnnGGstpvdDDPk1Po3CLW3iAfYY2jLqN4MpBs3KwytQXk9TwzDdbg
h3cXTJ2w2AmoDVf3RIXwyAS+XF1a4xeOVGNpf0l0ZAWMowIDAQABo2MwYTAPBgNVHRMBAf8EBTAD
AQH/MB8GA1UdIwQYMBaAFLK0ruYt9ybVqnUtdkvAG1Mh0EjvMB0GA1UdDgQWBBSytK7mLfcm1ap1
LXZLwBtTIdBI7zAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggIBAImocn+M684uGMQQ
gC0QDP/7FM0E4BQ8Tpr7nym/Ip5XuYJzEmMmtcyQ6dIqKe6cLcwsmb5FJ+Sxce3kOJUxQfJ9emN4
38o2Fi+CiJ+8EUdPdk3ILY7r3y18Tjvarvbj2l0Upq7ohUSdBm6O++96SmotKygY/r+QLHUWnw/q
ln0F7psTpURs+APQ3SPh/QMSEgj0GDSz4DcLdxEBSL9htLX4GdnLTeqjjO/98Aa1bZL0SmFQhO3s
SdPkvmjmLuMxC1QLGpLWgti2omU8ZgT5Vdps+9u1FGZNlIM7zR6mK7L+d0CGq+ffCsn99t2HVhjY
sCxVYJb6CH5SkPVLpi6HfMsg2wY+oF0Dd32iPBMbKaITVaA9FCKvb7jQmhty3QUBjYZgv6Rn7rWl
DdF/5horYmbDB7rnoEgcOMPpRfunf/ztAmgayncSd6YAVSgU7NbHEqIbZULpkejLPoeJVF3Zr52X
nGnnCv8PWniLYypMfUeUP95L6VPQMPHF9p5J3zugkaOj/s1YzOrfr28oO6Bpm4/srK4rVJ2bBLFH
IK+WEj5jlB0E5y67hscMmoi/dkfv97ALl2bSRM9gUgfh1SxKOidhd8rXj+eHDjD/DLsE4mHDosiX
YY60MGo8bcIHX0pzLz/5FooBZu+6kcpSV3uu1OYP3Qt6f4ueJiDPO++BcYNZ
-----END CERTIFICATE-----
E-Tugra Global Root CA ECC v3
=============================
-----BEGIN CERTIFICATE-----
MIICpTCCAiqgAwIBAgIUJkYZdzHhT28oNt45UYbm1JeIIsEwCgYIKoZIzj0EAwMwgYAxCzAJBgNV
BAYTAlRSMQ8wDQYDVQQHEwZBbmthcmExGTAXBgNVBAoTEEUtVHVncmEgRUJHIEEuUy4xHTAbBgNV
BAsTFEUtVHVncmEgVHJ1c3QgQ2VudGVyMSYwJAYDVQQDEx1FLVR1Z3JhIEdsb2JhbCBSb290IENB
IEVDQyB2MzAeFw0yMDAzMTgwOTQ2NThaFw00NTAzMTIwOTQ2NThaMIGAMQswCQYDVQQGEwJUUjEP
MA0GA1UEBxMGQW5rYXJhMRkwFwYDVQQKExBFLVR1Z3JhIEVCRyBBLlMuMR0wGwYDVQQLExRFLVR1
Z3JhIFRydXN0IENlbnRlcjEmMCQGA1UEAxMdRS1UdWdyYSBHbG9iYWwgUm9vdCBDQSBFQ0MgdjMw
djAQBgcqhkjOPQIBBgUrgQQAIgNiAASOmCm/xxAeJ9urA8woLNheSBkQKczLWYHMjLiSF4mDKpL2
w6QdTGLVn9agRtwcvHbB40fQWxPa56WzZkjnIZpKT4YKfWzqTTKACrJ6CZtpS5iB4i7sAnCWH/31
Rs7K3IKjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU/4Ixcj75xGZsrTie0bBRiKWQ
zPUwHQYDVR0OBBYEFP+CMXI++cRmbK04ntGwUYilkMz1MA4GA1UdDwEB/wQEAwIBBjAKBggqhkjO
PQQDAwNpADBmAjEA5gVYaWHlLcoNy/EZCL3W/VGSGn5jVASQkZo1kTmZ+gepZpO6yGjUij/67W4W
Aie3AjEA3VoXK3YdZUKWpqxdinlW2Iob35reX8dQj7FbcQwm32pAAOwzkSFxvmjkI6TZraE3
-----END CERTIFICATE-----

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +0,0 @@
version: 2
updates:
- package-ecosystem: composer
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 10

View File

@ -1,32 +0,0 @@
name: PHP Lints
on:
push:
pull_request:
schedule:
- cron: '55 3 * * SUN'
jobs:
run:
runs-on: ubuntu-latest
name: "PHP Lints"
steps:
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 7.4
- name: Checkout
uses: actions/checkout@v2
- name: Install dependencies
run: composer install --no-progress --prefer-dist --optimize-autoloader
- name: Lint with php-cs-fixer
run: vendor/bin/php-cs-fixer fix --verbose --diff --dry-run
- name: Lint with phpcs
run: vendor/bin/phpcs --standard=PSR2 src/
- name: Lint with phpstan
run: vendor/bin/phpstan analyze

View File

@ -1,36 +0,0 @@
name: PHPUnit
on:
push:
pull_request:
schedule:
- cron: '55 3 * * SUN'
jobs:
run:
runs-on: ${{ matrix.operating-system }}
strategy:
matrix:
operating-system: [ubuntu-latest, windows-latest, macos-latest]
php-versions: ['7.2', '7.3', '7.4', '8.0']
name: "PHP ${{ matrix.php-versions }} test on ${{ matrix.operating-system }}"
steps:
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
tools: composer
- name: Setup problem matchers for PHPUnit
run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
- name: Checkout
uses: actions/checkout@v2
with:
submodules: true
- name: Install dependencies
run: composer install --no-progress --prefer-dist --optimize-autoloader
- name: Test with phpunit
run: vendor/bin/phpunit --coverage-text

View File

@ -1,45 +0,0 @@
<?php
$config = new PhpCsFixer\Config();
return $config
->setRiskyAllowed(true)
->setRules([
'@Symfony' => true,
'@Symfony:risky' => true,
'@PhpCsFixer' => true,
'@PSR1' => true,
'@PSR2' => true,
'@PSR12' => true,
'@PSR12:risky' => true,
'array_syntax' => ['syntax' => 'short'],
'combine_consecutive_unsets' => true,
'concat_space' => [ 'spacing' => 'one'],
'explicit_string_variable' => false,
'fopen_flags' => ['b_mode' => true],
'heredoc_to_nowdoc' => true,
'increment_style' => false,
'list_syntax' => ['syntax' => 'short'],
'multiline_whitespace_before_semicolons' => false,
'no_unreachable_default_argument_value' => true,
'no_useless_else' => true,
'no_useless_return' => true,
'ordered_imports' => true,
'php_unit_strict' => true,
'php_unit_test_class_requires_covers' => true,
'phpdoc_add_missing_param_annotation' => true,
'phpdoc_no_alias_tag' => false,
'phpdoc_order' => true,
'phpdoc_types_order' => ['sort_algorithm' => 'alpha', 'null_adjustment' => 'always_last'],
'semicolon_after_instruction' => true,
'single_line_throw' => false,
'strict_comparison' => true,
'strict_param' => true,
'yoda_style' => false,
])
->setFinder(
PhpCsFixer\Finder::create()
->exclude('ext')
->in(__DIR__)
)
;

View File

@ -1,39 +1,6 @@
CHANGELOG CHANGELOG
========= =========
2.12.2 (2021-11-30)
-------------------
* The `geoip2.phar` now works when included from another directory.
Reported by Eduardo Ruiz. GitHub #179.
2.12.1 (2021-11-23)
-------------------
* The `geoip2.phar` included in 2.12.0 would only work in CLI applications.
This was due to a change in Box 3.x. The Phar should now work in all
applications. This release only affects users of the Phar file.
2.12.0 (2021-11-18)
-------------------
* Support for mobile country code (MCC) and mobile network codes (MNC) was
added for the GeoIP2 ISP and Enterprise databases as well as the GeoIP2
City and Insights web services. `$mobileCountryCode` and
`$mobileNetworkCode` properties were added to `GeoIp2\Model\Isp`
for the GeoIP2 ISP database and `GeoIp2\Record\Traits` for the Enterprise
database and the GeoIP2 City and Insights web services. We expect this data
to be available by late January, 2022.
* `geoip2.phar` is now generated with Box 3.x.
2.11.0 (2020-10-01)
-------------------
* IMPORTANT: PHP 7.2 or greater is now required.
* Added the `isResidentialProxy` property to `GeoIp2\Model\AnonymousIP` and
`GeoIp2\Record\Traits`.
* Additional type hints have been added.
2.10.0 (2019-12-12) 2.10.0 (2019-12-12)
------------------- -------------------

View File

@ -2,9 +2,11 @@
## Description ## ## Description ##
This package provides an API for the GeoIP2 and GeoLite2 This package provides an API for the GeoIP2
[web services](https://dev.maxmind.com/geoip/docs/web-services?lang=en) and [web services](https://dev.maxmind.com/geoip/geoip2/web-services) and
[databases](https://dev.maxmind.com/geoip/docs/databases?lang=en). [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 ## ## Install via Composer ##
@ -24,7 +26,7 @@ You should now have the file `composer.phar` in your project directory.
Run in your project root: Run in your project root:
```sh ```
php composer.phar require geoip2/geoip2:~2.0 php composer.phar require geoip2/geoip2:~2.0
``` ```
@ -263,33 +265,13 @@ print($record->network . "\n"); // '128.101.101.101/32'
### Usage ### ### Usage ###
To use this API, you must create a new `\GeoIp2\WebService\Client` To use this API, you must create a new `\GeoIp2\WebService\Client`
object with your `$accountId` and `$licenseKey`: 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.
```php If the request succeeds, the method call will return a model class for the end
$client = new Client(42, 'abcdef123456'); point you called. This model in turn contains multiple record classes, each of
``` which represents part of the data returned by the web service.
You may also call the constructor with additional arguments. The third argument
specifies the language preferences when using the `->name` method on the model
classes that this client creates. The fourth argument is additional options
such as `host` and `timeout`.
For instance, to call the GeoLite2 web service instead of GeoIP2 Precision:
```php
$client = new Client(42, 'abcdef123456', ['en'], ['host' => 'geolite.info']);
```
After creating the client, you may now call the method corresponding to a
specific endpoint with the IP address to look up, e.g.:
```php
$record = $client->city('128.101.101.101');
```
If the request succeeds, the method call will return a model class for the
endpoint 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. If there is an error, a structured exception is thrown.
@ -304,8 +286,7 @@ use GeoIp2\WebService\Client;
// This creates a Client object that can be reused across requests. // This creates a Client object that can be reused across requests.
// Replace "42" with your account ID and "license_key" with your license // Replace "42" with your account ID and "license_key" with your license
// key. Set the "host" to "geolite.info" in the fourth argument options // key.
// array to use the GeoLite2 web service instead of GeoIP2 Precision.
$client = new Client(42, 'abcdef123456'); $client = new Client(42, 'abcdef123456');
// Replace "city" with the method corresponding to the web service that // Replace "city" with the method corresponding to the web service that
@ -355,7 +336,7 @@ Because of these factors, it is possible for any end point to return a record
where some or all of the attributes are unpopulated. where some or all of the attributes are unpopulated.
See the See the
[GeoIP2 Precision web service docs](https://dev.maxmind.com/geoip/docs/web-services?lang=en) [GeoIP2 Precision web service docs](https://dev.maxmind.com/geoip/geoip2/web-services)
for details on what data each end point may return. for details on what data each end point may return.
The only piece of data which is always returned is the `ipAddress` The only piece of data which is always returned is the `ipAddress`
@ -405,7 +386,7 @@ to the client API, please see
## Requirements ## ## Requirements ##
This library requires PHP 7.2 or greater. 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). This library also relies on the [MaxMind DB Reader](https://github.com/maxmind/MaxMind-DB-Reader-php).
@ -423,6 +404,6 @@ The GeoIP2 PHP API uses [Semantic Versioning](https://semver.org/).
## Copyright and License ## ## Copyright and License ##
This software is Copyright (c) 2013-2020 by MaxMind, Inc. This software is Copyright (c) 2013-2019 by MaxMind, Inc.
This is free software, licensed under the Apache License, Version 2.0. This is free software, licensed under the Apache License, Version 2.0.

View File

@ -13,16 +13,15 @@
} }
], ],
"require": { "require": {
"maxmind-db/reader": "~1.8", "maxmind-db/reader": "~1.5",
"maxmind/web-service-common": "~0.8", "maxmind/web-service-common": "~0.6",
"php": ">=7.2", "php": ">=5.6",
"ext-json": "*" "ext-json": "*"
}, },
"require-dev": { "require-dev": {
"friendsofphp/php-cs-fixer": "3.*", "friendsofphp/php-cs-fixer": "2.*",
"phpunit/phpunit": "^8.0 || ^9.0", "phpunit/phpunit": "5.*",
"squizlabs/php_codesniffer": "3.*", "squizlabs/php_codesniffer": "3.*"
"phpstan/phpstan": "*"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {

View File

@ -10,8 +10,7 @@ $reader = new Reader('GeoIP2-City.mmdb');
$count = 500000; $count = 500000;
$startTime = microtime(true); $startTime = microtime(true);
for ($i = 0; $i < $count; ++$i) { for ($i = 0; $i < $count; ++$i) {
$ip = long2ip(rand(0, 2 ** 32 - 1)); $ip = long2ip(rand(0, pow(2, 32) - 1));
try { try {
$t = $reader->city($ip); $t = $reader->city($ip);
} catch (\GeoIp2\Exception\AddressNotFoundException $e) { } catch (\GeoIp2\Exception\AddressNotFoundException $e) {

View File

@ -1,7 +0,0 @@
parameters:
level: 6
paths:
- src
- tests
checkMissingIterableValueType: false

View File

@ -1,11 +1,8 @@
<?php <?php
declare(strict_types=1);
namespace GeoIp2\Database; namespace GeoIp2\Database;
use GeoIp2\Exception\AddressNotFoundException; use GeoIp2\Exception\AddressNotFoundException;
use GeoIp2\Model\AbstractModel;
use GeoIp2\ProviderInterface; use GeoIp2\ProviderInterface;
use MaxMind\Db\Reader as DbReader; use MaxMind\Db\Reader as DbReader;
use MaxMind\Db\Reader\InvalidDatabaseException; use MaxMind\Db\Reader\InvalidDatabaseException;
@ -36,17 +33,8 @@ use MaxMind\Db\Reader\InvalidDatabaseException;
*/ */
class Reader implements ProviderInterface class Reader implements ProviderInterface
{ {
/**
* @var DbReader
*/
private $dbReader; private $dbReader;
/**
* @var string
*/
private $dbType; private $dbType;
/**
* @var array<string>
*/
private $locales; private $locales;
/** /**
@ -60,8 +48,8 @@ class Reader implements ProviderInterface
* is corrupt or invalid * is corrupt or invalid
*/ */
public function __construct( public function __construct(
string $filename, $filename,
array $locales = ['en'] $locales = ['en']
) { ) {
$this->dbReader = new DbReader($filename); $this->dbReader = new DbReader($filename);
$this->dbType = $this->dbReader->metadata()->databaseType; $this->dbType = $this->dbReader->metadata()->databaseType;
@ -77,10 +65,11 @@ class Reader implements ProviderInterface
* not in the database * not in the database
* @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
* is corrupt or invalid * is corrupt or invalid
*
* @return \GeoIp2\Model\City
*/ */
public function city(string $ipAddress): \GeoIp2\Model\City public function city($ipAddress)
{ {
// @phpstan-ignore-next-line
return $this->modelFor('City', 'City', $ipAddress); return $this->modelFor('City', 'City', $ipAddress);
} }
@ -93,10 +82,11 @@ class Reader implements ProviderInterface
* not in the database * not in the database
* @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
* is corrupt or invalid * is corrupt or invalid
*
* @return \GeoIp2\Model\Country
*/ */
public function country(string $ipAddress): \GeoIp2\Model\Country public function country($ipAddress)
{ {
// @phpstan-ignore-next-line
return $this->modelFor('Country', 'Country', $ipAddress); return $this->modelFor('Country', 'Country', $ipAddress);
} }
@ -109,10 +99,11 @@ class Reader implements ProviderInterface
* not in the database * not in the database
* @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
* is corrupt or invalid * is corrupt or invalid
*
* @return \GeoIp2\Model\AnonymousIp
*/ */
public function anonymousIp(string $ipAddress): \GeoIp2\Model\AnonymousIp public function anonymousIp($ipAddress)
{ {
// @phpstan-ignore-next-line
return $this->flatModelFor( return $this->flatModelFor(
'AnonymousIp', 'AnonymousIp',
'GeoIP2-Anonymous-IP', 'GeoIP2-Anonymous-IP',
@ -129,10 +120,11 @@ class Reader implements ProviderInterface
* not in the database * not in the database
* @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
* is corrupt or invalid * is corrupt or invalid
*
* @return \GeoIp2\Model\Asn
*/ */
public function asn(string $ipAddress): \GeoIp2\Model\Asn public function asn($ipAddress)
{ {
// @phpstan-ignore-next-line
return $this->flatModelFor( return $this->flatModelFor(
'Asn', 'Asn',
'GeoLite2-ASN', 'GeoLite2-ASN',
@ -149,10 +141,11 @@ class Reader implements ProviderInterface
* not in the database * not in the database
* @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
* is corrupt or invalid * is corrupt or invalid
*
* @return \GeoIp2\Model\ConnectionType
*/ */
public function connectionType(string $ipAddress): \GeoIp2\Model\ConnectionType public function connectionType($ipAddress)
{ {
// @phpstan-ignore-next-line
return $this->flatModelFor( return $this->flatModelFor(
'ConnectionType', 'ConnectionType',
'GeoIP2-Connection-Type', 'GeoIP2-Connection-Type',
@ -169,10 +162,11 @@ class Reader implements ProviderInterface
* not in the database * not in the database
* @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
* is corrupt or invalid * is corrupt or invalid
*
* @return \GeoIp2\Model\Domain
*/ */
public function domain(string $ipAddress): \GeoIp2\Model\Domain public function domain($ipAddress)
{ {
// @phpstan-ignore-next-line
return $this->flatModelFor( return $this->flatModelFor(
'Domain', 'Domain',
'GeoIP2-Domain', 'GeoIP2-Domain',
@ -189,10 +183,11 @@ class Reader implements ProviderInterface
* not in the database * not in the database
* @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
* is corrupt or invalid * is corrupt or invalid
*
* @return \GeoIp2\Model\Enterprise
*/ */
public function enterprise(string $ipAddress): \GeoIp2\Model\Enterprise public function enterprise($ipAddress)
{ {
// @phpstan-ignore-next-line
return $this->modelFor('Enterprise', 'Enterprise', $ipAddress); return $this->modelFor('Enterprise', 'Enterprise', $ipAddress);
} }
@ -205,10 +200,11 @@ class Reader implements ProviderInterface
* not in the database * not in the database
* @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
* is corrupt or invalid * is corrupt or invalid
*
* @return \GeoIp2\Model\Isp
*/ */
public function isp(string $ipAddress): \GeoIp2\Model\Isp public function isp($ipAddress)
{ {
// @phpstan-ignore-next-line
return $this->flatModelFor( return $this->flatModelFor(
'Isp', 'Isp',
'GeoIP2-ISP', 'GeoIP2-ISP',
@ -216,9 +212,9 @@ class Reader implements ProviderInterface
); );
} }
private function modelFor(string $class, string $type, string $ipAddress): AbstractModel private function modelFor($class, $type, $ipAddress)
{ {
[$record, $prefixLen] = $this->getRecord($class, $type, $ipAddress); list($record, $prefixLen) = $this->getRecord($class, $type, $ipAddress);
$record['traits']['ip_address'] = $ipAddress; $record['traits']['ip_address'] = $ipAddress;
$record['traits']['prefix_len'] = $prefixLen; $record['traits']['prefix_len'] = $prefixLen;
@ -228,9 +224,9 @@ class Reader implements ProviderInterface
return new $class($record, $this->locales); return new $class($record, $this->locales);
} }
private function flatModelFor(string $class, string $type, string $ipAddress): AbstractModel private function flatModelFor($class, $type, $ipAddress)
{ {
[$record, $prefixLen] = $this->getRecord($class, $type, $ipAddress); list($record, $prefixLen) = $this->getRecord($class, $type, $ipAddress);
$record['ip_address'] = $ipAddress; $record['ip_address'] = $ipAddress;
$record['prefix_len'] = $prefixLen; $record['prefix_len'] = $prefixLen;
@ -239,16 +235,15 @@ class Reader implements ProviderInterface
return new $class($record); return new $class($record);
} }
private function getRecord(string $class, string $type, string $ipAddress): array private function getRecord($class, $type, $ipAddress)
{ {
if (strpos($this->dbType, $type) === false) { if (strpos($this->dbType, $type) === false) {
$method = lcfirst($class); $method = lcfirst($class);
throw new \BadMethodCallException( throw new \BadMethodCallException(
"The $method method cannot be used to open a {$this->dbType} database" "The $method method cannot be used to open a {$this->dbType} database"
); );
} }
[$record, $prefixLen] = $this->dbReader->getWithPrefixLen($ipAddress); list($record, $prefixLen) = $this->dbReader->getWithPrefixLen($ipAddress);
if ($record === null) { if ($record === null) {
throw new AddressNotFoundException( throw new AddressNotFoundException(
"The address $ipAddress is not in the database." "The address $ipAddress is not in the database."
@ -277,7 +272,7 @@ class Reader implements ProviderInterface
* *
* @return \MaxMind\Db\Reader\Metadata object for the database * @return \MaxMind\Db\Reader\Metadata object for the database
*/ */
public function metadata(): DbReader\Metadata public function metadata()
{ {
return $this->dbReader->metadata(); return $this->dbReader->metadata();
} }
@ -285,7 +280,7 @@ class Reader implements ProviderInterface
/** /**
* Closes the GeoIP2 database and returns the resources to the system. * Closes the GeoIP2 database and returns the resources to the system.
*/ */
public function close(): void public function close()
{ {
$this->dbReader->close(); $this->dbReader->close();
} }

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace GeoIp2\Exception; namespace GeoIp2\Exception;
/** /**

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace GeoIp2\Exception; namespace GeoIp2\Exception;
/** /**

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace GeoIp2\Exception; namespace GeoIp2\Exception;
/** /**

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace GeoIp2\Exception; namespace GeoIp2\Exception;
/** /**
@ -11,15 +9,13 @@ class HttpException extends GeoIp2Exception
{ {
/** /**
* The URI queried. * The URI queried.
*
* @var string
*/ */
public $uri; public $uri;
public function __construct( public function __construct(
string $message, $message,
int $httpStatus, $httpStatus,
string $uri, $uri,
\Exception $previous = null \Exception $previous = null
) { ) {
$this->uri = $uri; $this->uri = $uri;

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace GeoIp2\Exception; namespace GeoIp2\Exception;
/** /**
@ -12,16 +10,14 @@ class InvalidRequestException extends HttpException
{ {
/** /**
* The code returned by the MaxMind web service. * The code returned by the MaxMind web service.
*
* @var string
*/ */
public $error; public $error;
public function __construct( public function __construct(
string $message, $message,
string $error, $error,
int $httpStatus, $httpStatus,
string $uri, $uri,
\Exception $previous = null \Exception $previous = null
) { ) {
$this->error = $error; $this->error = $error;

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace GeoIp2\Exception; namespace GeoIp2\Exception;
/** /**

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace GeoIp2\Model; namespace GeoIp2\Model;
/** /**
@ -9,15 +7,14 @@ namespace GeoIp2\Model;
*/ */
abstract class AbstractModel implements \JsonSerializable abstract class AbstractModel implements \JsonSerializable
{ {
/**
* @var array<string, mixed>
*/
protected $raw; protected $raw;
/** /**
* @ignore * @ignore
*
* @param mixed $raw
*/ */
public function __construct(array $raw) public function __construct($raw)
{ {
$this->raw = $raw; $this->raw = $raw;
} }
@ -25,9 +22,9 @@ abstract class AbstractModel implements \JsonSerializable
/** /**
* @ignore * @ignore
* *
* @return mixed * @param mixed $field
*/ */
protected function get(string $field) protected function get($field)
{ {
if (isset($this->raw[$field])) { if (isset($this->raw[$field])) {
return $this->raw[$field]; return $this->raw[$field];
@ -42,12 +39,12 @@ abstract class AbstractModel implements \JsonSerializable
/** /**
* @ignore * @ignore
* *
* @return mixed * @param mixed $attr
*/ */
public function __get(string $attr) public function __get($attr)
{ {
if ($attr !== 'instance' && property_exists($this, $attr)) { if ($attr !== 'instance' && property_exists($this, $attr)) {
return $this->{$attr}; return $this->$attr;
} }
throw new \RuntimeException("Unknown attribute: $attr"); throw new \RuntimeException("Unknown attribute: $attr");
@ -55,13 +52,15 @@ abstract class AbstractModel implements \JsonSerializable
/** /**
* @ignore * @ignore
*
* @param mixed $attr
*/ */
public function __isset(string $attr): bool public function __isset($attr)
{ {
return $attr !== 'instance' && isset($this->{$attr}); return $attr !== 'instance' && isset($this->$attr);
} }
public function jsonSerialize(): array public function jsonSerialize()
{ {
return $this->raw; return $this->raw;
} }

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace GeoIp2\Model; namespace GeoIp2\Model;
use GeoIp2\Util; use GeoIp2\Util;
@ -19,8 +17,6 @@ use GeoIp2\Util;
* to a hosting or VPN provider (see description of isAnonymousVpn property). * to a hosting or VPN provider (see description of isAnonymousVpn property).
* @property-read bool $isPublicProxy This is true if the IP address belongs to * @property-read bool $isPublicProxy This is true if the IP address belongs to
* a public proxy. * a public proxy.
* @property-read bool $isResidentialProxy This is true if the IP address is
* on a suspected anonymizing network and belongs to a residential ISP.
* @property-read bool $isTorExitNode This is true if the IP address is a Tor * @property-read bool $isTorExitNode This is true if the IP address is a Tor
* exit node. * exit node.
* @property-read string $ipAddress The IP address that the data in the model is * @property-read string $ipAddress The IP address that the data in the model is
@ -31,43 +27,20 @@ use GeoIp2\Util;
*/ */
class AnonymousIp extends AbstractModel class AnonymousIp extends AbstractModel
{ {
/**
* @var bool
*/
protected $isAnonymous; protected $isAnonymous;
/**
* @var bool
*/
protected $isAnonymousVpn; protected $isAnonymousVpn;
/**
* @var bool
*/
protected $isHostingProvider; protected $isHostingProvider;
/**
* @var bool
*/
protected $isPublicProxy; protected $isPublicProxy;
/**
* @var bool
*/
protected $isResidentialProxy;
/**
* @var bool
*/
protected $isTorExitNode; protected $isTorExitNode;
/**
* @var string
*/
protected $ipAddress; protected $ipAddress;
/**
* @var string
*/
protected $network; protected $network;
/** /**
* @ignore * @ignore
*
* @param mixed $raw
*/ */
public function __construct(array $raw) public function __construct($raw)
{ {
parent::__construct($raw); parent::__construct($raw);
@ -75,7 +48,6 @@ class AnonymousIp extends AbstractModel
$this->isAnonymousVpn = $this->get('is_anonymous_vpn'); $this->isAnonymousVpn = $this->get('is_anonymous_vpn');
$this->isHostingProvider = $this->get('is_hosting_provider'); $this->isHostingProvider = $this->get('is_hosting_provider');
$this->isPublicProxy = $this->get('is_public_proxy'); $this->isPublicProxy = $this->get('is_public_proxy');
$this->isResidentialProxy = $this->get('is_residential_proxy');
$this->isTorExitNode = $this->get('is_tor_exit_node'); $this->isTorExitNode = $this->get('is_tor_exit_node');
$ipAddress = $this->get('ip_address'); $ipAddress = $this->get('ip_address');
$this->ipAddress = $ipAddress; $this->ipAddress = $ipAddress;

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace GeoIp2\Model; namespace GeoIp2\Model;
use GeoIp2\Util; use GeoIp2\Util;
@ -22,27 +20,17 @@ use GeoIp2\Util;
*/ */
class Asn extends AbstractModel class Asn extends AbstractModel
{ {
/**
* @var int|null
*/
protected $autonomousSystemNumber; protected $autonomousSystemNumber;
/**
* @var string|null
*/
protected $autonomousSystemOrganization; protected $autonomousSystemOrganization;
/**
* @var string
*/
protected $ipAddress; protected $ipAddress;
/**
* @var string
*/
protected $network; protected $network;
/** /**
* @ignore * @ignore
*
* @param mixed $raw
*/ */
public function __construct(array $raw) public function __construct($raw)
{ {
parent::__construct($raw); parent::__construct($raw);
$this->autonomousSystemNumber = $this->get('autonomous_system_number'); $this->autonomousSystemNumber = $this->get('autonomous_system_number');

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace GeoIp2\Model; namespace GeoIp2\Model;
/** /**
@ -9,7 +7,7 @@ namespace GeoIp2\Model;
* *
* The only difference between the City and Insights model classes is which * The only difference between the City and Insights model classes is which
* fields in each record may be populated. See * fields in each record may be populated. See
* https://dev.maxmind.com/geoip/docs/web-services?lang=en for more details. * https://dev.maxmind.com/geoip/geoip2/web-services for more details.
* *
* @property-read \GeoIp2\Record\City $city City data for the requested IP * @property-read \GeoIp2\Record\City $city City data for the requested IP
* address. * address.
@ -33,33 +31,28 @@ class City extends Country
{ {
/** /**
* @ignore * @ignore
*
* @var \GeoIp2\Record\City
*/ */
protected $city; protected $city;
/** /**
* @ignore * @ignore
*
* @var \GeoIp2\Record\Location
*/ */
protected $location; protected $location;
/** /**
* @ignore * @ignore
*
* @var \GeoIp2\Record\Postal
*/ */
protected $postal; protected $postal;
/** /**
* @ignore * @ignore
*
* @var array<\GeoIp2\Record\Subdivision>
*/ */
protected $subdivisions = []; protected $subdivisions = [];
/** /**
* @ignore * @ignore
*
* @param mixed $raw
* @param mixed $locales
*/ */
public function __construct(array $raw, array $locales = ['en']) public function __construct($raw, $locales = ['en'])
{ {
parent::__construct($raw, $locales); parent::__construct($raw, $locales);
@ -70,28 +63,29 @@ class City extends Country
$this->createSubdivisions($raw, $locales); $this->createSubdivisions($raw, $locales);
} }
private function createSubdivisions(array $raw, array $locales): void private function createSubdivisions($raw, $locales)
{ {
if (!isset($raw['subdivisions'])) { if (!isset($raw['subdivisions'])) {
return; return;
} }
foreach ($raw['subdivisions'] as $sub) { foreach ($raw['subdivisions'] as $sub) {
$this->subdivisions[] = array_push(
$this->subdivisions,
new \GeoIp2\Record\Subdivision($sub, $locales) new \GeoIp2\Record\Subdivision($sub, $locales)
; );
} }
} }
/** /**
* @ignore * @ignore
* *
* @return mixed * @param mixed $attr
*/ */
public function __get(string $attr) public function __get($attr)
{ {
if ($attr === 'mostSpecificSubdivision') { if ($attr === 'mostSpecificSubdivision') {
return $this->{$attr}(); return $this->$attr();
} }
return parent::__get($attr); return parent::__get($attr);
@ -99,8 +93,10 @@ class City extends Country
/** /**
* @ignore * @ignore
*
* @param mixed $attr
*/ */
public function __isset(string $attr): bool public function __isset($attr)
{ {
if ($attr === 'mostSpecificSubdivision') { if ($attr === 'mostSpecificSubdivision') {
// We always return a mostSpecificSubdivision, even if it is the // We always return a mostSpecificSubdivision, even if it is the
@ -111,7 +107,7 @@ class City extends Country
return parent::__isset($attr); return parent::__isset($attr);
} }
private function mostSpecificSubdivision(): \GeoIp2\Record\Subdivision private function mostSpecificSubdivision()
{ {
return empty($this->subdivisions) ? return empty($this->subdivisions) ?
new \GeoIp2\Record\Subdivision([], $this->locales) : new \GeoIp2\Record\Subdivision([], $this->locales) :

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace GeoIp2\Model; namespace GeoIp2\Model;
use GeoIp2\Util; use GeoIp2\Util;
@ -20,23 +18,16 @@ use GeoIp2\Util;
*/ */
class ConnectionType extends AbstractModel class ConnectionType extends AbstractModel
{ {
/**
* @var string|null
*/
protected $connectionType; protected $connectionType;
/**
* @var string
*/
protected $ipAddress; protected $ipAddress;
/**
* @var string
*/
protected $network; protected $network;
/** /**
* @ignore * @ignore
*
* @param mixed $raw
*/ */
public function __construct(array $raw) public function __construct($raw)
{ {
parent::__construct($raw); parent::__construct($raw);

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace GeoIp2\Model; namespace GeoIp2\Model;
/** /**
@ -9,7 +7,7 @@ namespace GeoIp2\Model;
* *
* The only difference between the City and Insights model classes is which * The only difference between the City and Insights model classes is which
* fields in each record may be populated. See * fields in each record may be populated. See
* https://dev.maxmind.com/geoip/docs/web-services?lang=en for more details. * https://dev.maxmind.com/geoip/geoip2/web-services for more details.
* *
* @property-read \GeoIp2\Record\Continent $continent Continent data for the * @property-read \GeoIp2\Record\Continent $continent Continent data for the
* requested IP address. * requested IP address.
@ -28,43 +26,24 @@ namespace GeoIp2\Model;
* the represented country differs from the country. * the represented country differs from the country.
* @property-read \GeoIp2\Record\Traits $traits Data for the traits of the * @property-read \GeoIp2\Record\Traits $traits Data for the traits of the
* requested IP address. * requested IP address.
* @property-read array $raw The raw data from the web service.
*/ */
class Country extends AbstractModel class Country extends AbstractModel
{ {
/**
* @var \GeoIp2\Record\Continent
*/
protected $continent; protected $continent;
/**
* @var \GeoIp2\Record\Country
*/
protected $country; protected $country;
/**
* @var array<string>
*/
protected $locales; protected $locales;
/**
* @var \GeoIp2\Record\MaxMind
*/
protected $maxmind; protected $maxmind;
/**
* @var \GeoIp2\Record\Country
*/
protected $registeredCountry; protected $registeredCountry;
/**
* @var \GeoIp2\Record\RepresentedCountry
*/
protected $representedCountry; protected $representedCountry;
/**
* @var \GeoIp2\Record\Traits
*/
protected $traits; protected $traits;
/** /**
* @ignore * @ignore
*
* @param mixed $raw
* @param mixed $locales
*/ */
public function __construct(array $raw, array $locales = ['en']) public function __construct($raw, $locales = ['en'])
{ {
parent::__construct($raw); parent::__construct($raw);

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace GeoIp2\Model; namespace GeoIp2\Model;
use GeoIp2\Util; use GeoIp2\Util;
@ -20,23 +18,16 @@ use GeoIp2\Util;
*/ */
class Domain extends AbstractModel class Domain extends AbstractModel
{ {
/**
* @var string|null
*/
protected $domain; protected $domain;
/**
* @var string
*/
protected $ipAddress; protected $ipAddress;
/**
* @var string
*/
protected $network; protected $network;
/** /**
* @ignore * @ignore
*
* @param mixed $raw
*/ */
public function __construct(array $raw) public function __construct($raw)
{ {
parent::__construct($raw); parent::__construct($raw);

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace GeoIp2\Model; namespace GeoIp2\Model;
/** /**
@ -9,7 +7,7 @@ namespace GeoIp2\Model;
* *
* The only difference between the City and Enterprise model classes is which * The only difference between the City and Enterprise model classes is which
* fields in each record may be populated. See * fields in each record may be populated. See
* https://dev.maxmind.com/geoip/docs/web-services?lang=en for more details. * https://dev.maxmind.com/geoip/geoip2/web-services for more details.
*/ */
class Enterprise extends City class Enterprise extends City
{ {

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace GeoIp2\Model; namespace GeoIp2\Model;
/** /**
@ -9,7 +7,7 @@ namespace GeoIp2\Model;
* *
* The only difference between the City and Insights model classes is which * The only difference between the City and Insights model classes is which
* fields in each record may be populated. See * fields in each record may be populated. See
* https://dev.maxmind.com/geoip/docs/web-services?lang=en for more details. * https://dev.maxmind.com/geoip/geoip2/web-services for more details.
*/ */
class Insights extends City class Insights extends City
{ {

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace GeoIp2\Model; namespace GeoIp2\Model;
use GeoIp2\Util; use GeoIp2\Util;
@ -16,12 +14,6 @@ use GeoIp2\Util;
* address. * address.
* @property-read string|null $isp The name of the ISP associated with the IP * @property-read string|null $isp The name of the ISP associated with the IP
* address. * address.
* @property-read string|null $mobileCountryCode The [mobile country code
* (MCC)](https://en.wikipedia.org/wiki/Mobile_country_code) associated with
* the IP address and ISP.
* @property-read string|null $mobileNetworkCode The [mobile network code
* (MNC)](https://en.wikipedia.org/wiki/Mobile_country_code) associated with
* the IP address and ISP.
* @property-read string|null $organization The name of the organization associated * @property-read string|null $organization The name of the organization associated
* with the IP address. * with the IP address.
* @property-read string $ipAddress The IP address that the data in the model is * @property-read string $ipAddress The IP address that the data in the model is
@ -32,51 +24,25 @@ use GeoIp2\Util;
*/ */
class Isp extends AbstractModel class Isp extends AbstractModel
{ {
/**
* @var int|null
*/
protected $autonomousSystemNumber; protected $autonomousSystemNumber;
/**
* @var string|null
*/
protected $autonomousSystemOrganization; protected $autonomousSystemOrganization;
/**
* @var string|null
*/
protected $isp; protected $isp;
/**
* @var string|null
*/
protected $mobileCountryCode;
/**
* @var string|null
*/
protected $mobileNetworkCode;
/**
* @var string|null
*/
protected $organization; protected $organization;
/**
* @var string
*/
protected $ipAddress; protected $ipAddress;
/**
* @var string
*/
protected $network; protected $network;
/** /**
* @ignore * @ignore
*
* @param mixed $raw
*/ */
public function __construct(array $raw) public function __construct($raw)
{ {
parent::__construct($raw); parent::__construct($raw);
$this->autonomousSystemNumber = $this->get('autonomous_system_number'); $this->autonomousSystemNumber = $this->get('autonomous_system_number');
$this->autonomousSystemOrganization = $this->autonomousSystemOrganization =
$this->get('autonomous_system_organization'); $this->get('autonomous_system_organization');
$this->isp = $this->get('isp'); $this->isp = $this->get('isp');
$this->mobileCountryCode = $this->get('mobile_country_code');
$this->mobileNetworkCode = $this->get('mobile_network_code');
$this->organization = $this->get('organization'); $this->organization = $this->get('organization');
$ipAddress = $this->get('ip_address'); $ipAddress = $this->get('ip_address');

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace GeoIp2; namespace GeoIp2;
interface ProviderInterface interface ProviderInterface
@ -11,12 +9,12 @@ interface ProviderInterface
* *
* @return \GeoIp2\Model\Country a Country model for the requested IP address * @return \GeoIp2\Model\Country a Country model for the requested IP address
*/ */
public function country(string $ipAddress): Model\Country; public function country($ipAddress);
/** /**
* @param string $ipAddress an IPv4 or IPv6 address to lookup * @param string $ipAddress an IPv4 or IPv6 address to lookup
* *
* @return \GeoIp2\Model\City a City model for the requested IP address * @return \GeoIp2\Model\City a City model for the requested IP address
*/ */
public function city(string $ipAddress): Model\City; public function city($ipAddress);
} }

View File

@ -1,20 +1,18 @@
<?php <?php
declare(strict_types=1);
namespace GeoIp2\Record; namespace GeoIp2\Record;
abstract class AbstractPlaceRecord extends AbstractRecord abstract class AbstractPlaceRecord extends AbstractRecord
{ {
/**
* @var array<string>
*/
private $locales; private $locales;
/** /**
* @ignore * @ignore
*
* @param mixed $record
* @param mixed $locales
*/ */
public function __construct(?array $record, array $locales = ['en']) public function __construct($record, $locales = ['en'])
{ {
$this->locales = $locales; $this->locales = $locales;
parent::__construct($record); parent::__construct($record);
@ -23,9 +21,9 @@ abstract class AbstractPlaceRecord extends AbstractRecord
/** /**
* @ignore * @ignore
* *
* @return mixed * @param mixed $attr
*/ */
public function __get(string $attr) public function __get($attr)
{ {
if ($attr === 'name') { if ($attr === 'name') {
return $this->name(); return $this->name();
@ -36,28 +34,28 @@ abstract class AbstractPlaceRecord extends AbstractRecord
/** /**
* @ignore * @ignore
*
* @param mixed $attr
*/ */
public function __isset(string $attr): bool public function __isset($attr)
{ {
if ($attr === 'name') { if ($attr === 'name') {
return $this->firstSetNameLocale() !== null; return $this->firstSetNameLocale() === null ? false : true;
} }
return parent::__isset($attr); return parent::__isset($attr);
} }
private function name(): ?string private function name()
{ {
$locale = $this->firstSetNameLocale(); $locale = $this->firstSetNameLocale();
// @phpstan-ignore-next-line
return $locale === null ? null : $this->names[$locale]; return $locale === null ? null : $this->names[$locale];
} }
private function firstSetNameLocale(): ?string private function firstSetNameLocale()
{ {
foreach ($this->locales as $locale) { foreach ($this->locales as $locale) {
// @phpstan-ignore-next-line
if (isset($this->names[$locale])) { if (isset($this->names[$locale])) {
return $locale; return $locale;
} }

View File

@ -1,20 +1,17 @@
<?php <?php
declare(strict_types=1);
namespace GeoIp2\Record; namespace GeoIp2\Record;
abstract class AbstractRecord implements \JsonSerializable abstract class AbstractRecord implements \JsonSerializable
{ {
/**
* @var array<string, mixed>
*/
private $record; private $record;
/** /**
* @ignore * @ignore
*
* @param mixed $record
*/ */
public function __construct(?array $record) public function __construct($record)
{ {
$this->record = isset($record) ? $record : []; $this->record = isset($record) ? $record : [];
} }
@ -22,45 +19,42 @@ abstract class AbstractRecord implements \JsonSerializable
/** /**
* @ignore * @ignore
* *
* @return mixed * @param mixed $attr
*/ */
public function __get(string $attr) public function __get($attr)
{ {
// XXX - kind of ugly but greatly reduces boilerplate code // XXX - kind of ugly but greatly reduces boilerplate code
$key = $this->attributeToKey($attr); $key = $this->attributeToKey($attr);
if ($this->__isset($attr)) { if ($this->__isset($attr)) {
return $this->record[$key]; return $this->record[$key];
} } elseif ($this->validAttribute($attr)) {
if ($this->validAttribute($attr)) {
if (preg_match('/^is_/', $key)) { if (preg_match('/^is_/', $key)) {
return false; return false;
} }
return null; return null;
} }
throw new \RuntimeException("Unknown attribute: $attr"); throw new \RuntimeException("Unknown attribute: $attr");
} }
public function __isset(string $attr): bool public function __isset($attr)
{ {
return $this->validAttribute($attr) return $this->validAttribute($attr) &&
&& isset($this->record[$this->attributeToKey($attr)]); isset($this->record[$this->attributeToKey($attr)]);
} }
private function attributeToKey(string $attr): string private function attributeToKey($attr)
{ {
return strtolower(preg_replace('/([A-Z])/', '_\1', $attr)); return strtolower(preg_replace('/([A-Z])/', '_\1', $attr));
} }
private function validAttribute(string $attr): bool private function validAttribute($attr)
{ {
// @phpstan-ignore-next-line
return \in_array($attr, $this->validAttributes, true); return \in_array($attr, $this->validAttributes, true);
} }
public function jsonSerialize(): ?array public function jsonSerialize()
{ {
return $this->record; return $this->record;
} }

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace GeoIp2\Record; namespace GeoIp2\Record;
/** /**
@ -26,8 +24,6 @@ class City extends AbstractPlaceRecord
{ {
/** /**
* @ignore * @ignore
*
* @var array<string>
*/ */
protected $validAttributes = ['confidence', 'geonameId', 'names']; protected $validAttributes = ['confidence', 'geonameId', 'names'];
} }

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace GeoIp2\Record; namespace GeoIp2\Record;
/** /**
@ -25,8 +23,6 @@ class Continent extends AbstractPlaceRecord
{ {
/** /**
* @ignore * @ignore
*
* @var array<string>
*/ */
protected $validAttributes = [ protected $validAttributes = [
'code', 'code',

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace GeoIp2\Record; namespace GeoIp2\Record;
/** /**
@ -31,8 +29,6 @@ class Country extends AbstractPlaceRecord
{ {
/** /**
* @ignore * @ignore
*
* @var array<string>
*/ */
protected $validAttributes = [ protected $validAttributes = [
'confidence', 'confidence',

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace GeoIp2\Record; namespace GeoIp2\Record;
/** /**
@ -39,8 +37,6 @@ class Location extends AbstractRecord
{ {
/** /**
* @ignore * @ignore
*
* @var array<string>
*/ */
protected $validAttributes = [ protected $validAttributes = [
'averageIncome', 'averageIncome',

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace GeoIp2\Record; namespace GeoIp2\Record;
/** /**
@ -16,8 +14,6 @@ class MaxMind extends AbstractRecord
{ {
/** /**
* @ignore * @ignore
*
* @var array<string>
*/ */
protected $validAttributes = ['queriesRemaining']; protected $validAttributes = ['queriesRemaining'];
} }

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace GeoIp2\Record; namespace GeoIp2\Record;
/** /**
@ -23,8 +21,6 @@ class Postal extends AbstractRecord
{ {
/** /**
* @ignore * @ignore
*
* @var array<string>
*/ */
protected $validAttributes = ['code', 'confidence']; protected $validAttributes = ['code', 'confidence'];
} }

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace GeoIp2\Record; namespace GeoIp2\Record;
/** /**
@ -17,11 +15,6 @@ namespace GeoIp2\Record;
*/ */
class RepresentedCountry extends Country class RepresentedCountry extends Country
{ {
/**
* @ignore
*
* @var array<string>
*/
protected $validAttributes = [ protected $validAttributes = [
'confidence', 'confidence',
'geonameId', 'geonameId',

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace GeoIp2\Record; namespace GeoIp2\Record;
/** /**
@ -32,8 +30,6 @@ class Subdivision extends AbstractPlaceRecord
{ {
/** /**
* @ignore * @ignore
*
* @var array<string>
*/ */
protected $validAttributes = [ protected $validAttributes = [
'confidence', 'confidence',

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace GeoIp2\Record; namespace GeoIp2\Record;
use GeoIp2\Util; use GeoIp2\Util;
@ -58,9 +56,6 @@ use GeoIp2\Util;
* @property-read bool $isPublicProxy This is true if the IP address belongs to * @property-read bool $isPublicProxy This is true if the IP address belongs to
* a public proxy. This property is only available from GeoIP2 Precision * a public proxy. This property is only available from GeoIP2 Precision
* Insights. * Insights.
* @property-read bool $isResidentialProxy This is true if the IP address is
* on a suspected anonymizing network and belongs to a residential ISP. This
* property is only available from GeoIP2 Precision Insights.
* @property-read bool $isSatelliteProvider *Deprecated.* Due to the * @property-read bool $isSatelliteProvider *Deprecated.* Due to the
* increased coverage by mobile carriers, very few satellite providers now * increased coverage by mobile carriers, very few satellite providers now
* serve multiple countries. As a result, the output does not provide * serve multiple countries. As a result, the output does not provide
@ -76,15 +71,7 @@ use GeoIp2\Util;
* @property-read string|null $organization The name of the organization associated * @property-read string|null $organization The name of the organization associated
* with the IP address. This attribute is only available from the City and * with the IP address. This attribute is only available from the City and
* Insights web services and the GeoIP2 Enterprise database. * Insights web services and the GeoIP2 Enterprise database.
* @property-read string|null $mobileCountryCode The [mobile country code * @property-read float|null $staticIPScore An indicator of how static or
* (MCC)](https://en.wikipedia.org/wiki/Mobile_country_code) associated with
* the IP address and ISP. This property is available from the City and
* Insights web services and the GeoIP2 Enterprise database.
* @property-read string|null $mobileNetworkCode The [mobile network code
* (MNC)](https://en.wikipedia.org/wiki/Mobile_country_code) associated with
* the IP address and ISP. This property is 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 * dynamic an IP address is. This property is only available from GeoIP2
* Precision Insights. * Precision Insights.
* @property-read int|null $userCount The estimated number of users sharing * @property-read int|null $userCount The estimated number of users sharing
@ -119,8 +106,6 @@ class Traits extends AbstractRecord
{ {
/** /**
* @ignore * @ignore
*
* @var array<string>
*/ */
protected $validAttributes = [ protected $validAttributes = [
'autonomousSystemNumber', 'autonomousSystemNumber',
@ -135,11 +120,8 @@ class Traits extends AbstractRecord
'isLegitimateProxy', 'isLegitimateProxy',
'isp', 'isp',
'isPublicProxy', 'isPublicProxy',
'isResidentialProxy',
'isSatelliteProvider', 'isSatelliteProvider',
'isTorExitNode', 'isTorExitNode',
'mobileCountryCode',
'mobileNetworkCode',
'network', 'network',
'organization', 'organization',
'staticIpScore', 'staticIpScore',
@ -147,9 +129,9 @@ class Traits extends AbstractRecord
'userType', 'userType',
]; ];
public function __construct(?array $record) public function __construct($record)
{ {
if (!isset($record['network']) && isset($record['ip_address'], $record['prefix_len'])) { if (!isset($record['network']) && isset($record['ip_address']) && isset($record['prefix_len'])) {
$record['network'] = Util::cidr($record['ip_address'], $record['prefix_len']); $record['network'] = Util::cidr($record['ip_address'], $record['prefix_len']);
} }

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace GeoIp2; namespace GeoIp2;
class Util class Util
@ -12,8 +10,11 @@ class Util
* *
* @internal * @internal
* @ignore * @ignore
*
* @param mixed $ipAddress
* @param mixed $prefixLen
*/ */
public static function cidr(string $ipAddress, int $prefixLen): string public static function cidr($ipAddress, $prefixLen)
{ {
$ipBytes = inet_pton($ipAddress); $ipBytes = inet_pton($ipAddress);
$networkBytes = str_repeat("\0", \strlen($ipBytes)); $networkBytes = str_repeat("\0", \strlen($ipBytes));

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace GeoIp2\WebService; namespace GeoIp2\WebService;
use GeoIp2\Exception\AddressNotFoundException; use GeoIp2\Exception\AddressNotFoundException;
@ -10,9 +8,6 @@ use GeoIp2\Exception\GeoIp2Exception;
use GeoIp2\Exception\HttpException; use GeoIp2\Exception\HttpException;
use GeoIp2\Exception\InvalidRequestException; use GeoIp2\Exception\InvalidRequestException;
use GeoIp2\Exception\OutOfQueriesException; use GeoIp2\Exception\OutOfQueriesException;
use GeoIp2\Model\City;
use GeoIp2\Model\Country;
use GeoIp2\Model\Insights;
use GeoIp2\ProviderInterface; use GeoIp2\ProviderInterface;
use MaxMind\WebService\Client as WsClient; use MaxMind\WebService\Client as WsClient;
@ -48,20 +43,11 @@ use MaxMind\WebService\Client as WsClient;
*/ */
class Client implements ProviderInterface class Client implements ProviderInterface
{ {
/**
* @var array<string>
*/
private $locales; private $locales;
/**
* @var WsClient
*/
private $client; private $client;
/**
* @var string
*/
private static $basePath = '/geoip/v2.1'; private static $basePath = '/geoip/v2.1';
public const VERSION = 'v2.12.2'; const VERSION = 'v2.10.0';
/** /**
* Constructor. * Constructor.
@ -71,9 +57,7 @@ class Client implements ProviderInterface
* @param array $locales list of locale codes to use in name property * @param array $locales list of locale codes to use in name property
* from most preferred to least preferred * from most preferred to least preferred
* @param array $options array of options. Valid options include: * @param array $options array of options. Valid options include:
* * `host` - The host to use when querying the web service. To * * `host` - The host to use when querying the web service.
* query the GeoLite2 web service instead of GeoIP2 Precision,
* set the host to `geolite.info`.
* * `timeout` - Timeout in seconds. * * `timeout` - Timeout in seconds.
* * `connectTimeout` - Initial connection timeout in seconds. * * `connectTimeout` - Initial connection timeout in seconds.
* * `proxy` - The HTTP proxy to use. May include a schema, port, * * `proxy` - The HTTP proxy to use. May include a schema, port,
@ -81,16 +65,15 @@ class Client implements ProviderInterface
* `http://username:password@127.0.0.1:10`. * `http://username:password@127.0.0.1:10`.
*/ */
public function __construct( public function __construct(
int $accountId, $accountId,
string $licenseKey, $licenseKey,
array $locales = ['en'], $locales = ['en'],
array $options = [] $options = []
) { ) {
$this->locales = $locales; $this->locales = $locales;
// This is for backwards compatibility. Do not remove except for a // This is for backwards compatibility. Do not remove except for a
// major version bump. // major version bump.
// @phpstan-ignore-next-line
if (\is_string($options)) { if (\is_string($options)) {
$options = ['host' => $options]; $options = ['host' => $options];
} }
@ -104,13 +87,13 @@ class Client implements ProviderInterface
$this->client = new WsClient($accountId, $licenseKey, $options); $this->client = new WsClient($accountId, $licenseKey, $options);
} }
private function userAgent(): string private function userAgent()
{ {
return 'GeoIP2-API/' . self::VERSION; return 'GeoIP2-API/' . self::VERSION;
} }
/** /**
* This method calls the City service. * This method calls the GeoIP2 Precision: City service.
* *
* @param string $ipAddress IPv4 or IPv6 address as a string. If no * @param string $ipAddress IPv4 or IPv6 address as a string. If no
* address is provided, the address that the web service is called * address is provided, the address that the web service is called
@ -125,22 +108,23 @@ class Client implements ProviderInterface
* @throws \GeoIp2\Exception\InvalidRequestException} if your request was received by the web service but is * @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 * invalid for some other reason. This may indicate an issue
* with this API. Please report the error to MaxMind. * with this API. Please report the error to MaxMind.
* @throws \GeoIp2\Exception\HttpException if an unexpected HTTP error code or message was returned. * @throws \GeoIp2\Exception\HttpException if an unexpected HTTP error code or message was returned.
* This could indicate a problem with the connection between * This could indicate a problem with the connection between
* your server and the web service or that the web service * your server and the web service or that the web service
* returned an invalid document or 500 error code * returned an invalid document or 500 error code
* @throws \GeoIp2\Exception\GeoIp2Exception This serves as the parent * @throws \GeoIp2\Exception\GeoIp2Exception This serves as the parent
* class to the above exceptions. It will be thrown directly * class to the above exceptions. It will be thrown directly
* if a 200 status code is returned but the body is invalid. * if a 200 status code is returned but the body is invalid.
*
* @return \GeoIp2\Model\City
*/ */
public function city(string $ipAddress = 'me'): City public function city($ipAddress = 'me')
{ {
// @phpstan-ignore-next-line
return $this->responseFor('city', 'City', $ipAddress); return $this->responseFor('city', 'City', $ipAddress);
} }
/** /**
* This method calls the Country service. * This method calls the GeoIP2 Precision: Country service.
* *
* @param string $ipAddress IPv4 or IPv6 address as a string. If no * @param string $ipAddress IPv4 or IPv6 address as a string. If no
* address is provided, the address that the web service is called * address is provided, the address that the web service is called
@ -154,23 +138,24 @@ class Client implements ProviderInterface
* @throws \GeoIp2\Exception\InvalidRequestException} if your request was received by the web service but is * @throws \GeoIp2\Exception\InvalidRequestException} if your request was received by the web service but is
* invalid for some other reason. This may indicate an * invalid for some other reason. This may indicate an
* issue with this API. Please report the error to MaxMind. * issue with this API. Please report the error to MaxMind.
* @throws \GeoIp2\Exception\HttpException if an unexpected HTTP error * @throws \GeoIp2\Exception\HttpException if an unexpected HTTP error
* code or message was returned. This could indicate a problem * code or message was returned. This could indicate a problem
* with the connection between your server and the web service * with the connection between your server and the web service
* or that the web service returned an invalid document or 500 * or that the web service returned an invalid document or 500
* error code. * error code.
* @throws \GeoIp2\Exception\GeoIp2Exception This serves as the parent class to the above exceptions. It * @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 * will be thrown directly if a 200 status code is returned but
* the body is invalid. * the body is invalid.
*
* @return \GeoIp2\Model\Country
*/ */
public function country(string $ipAddress = 'me'): Country public function country($ipAddress = 'me')
{ {
return $this->responseFor('country', 'Country', $ipAddress); return $this->responseFor('country', 'Country', $ipAddress);
} }
/** /**
* This method calls the Insights service. Insights is only supported by GeoIP2 * This method calls the GeoIP2 Precision: Insights service.
* Precision. The GeoLite2 web service does not support it.
* *
* @param string $ipAddress IPv4 or IPv6 address as a string. If no * @param string $ipAddress IPv4 or IPv6 address as a string. If no
* address is provided, the address that the web service is called * address is provided, the address that the web service is called
@ -185,21 +170,22 @@ class Client implements ProviderInterface
* @throws \GeoIp2\Exception\InvalidRequestException} if your request was received by the web service but is * @throws \GeoIp2\Exception\InvalidRequestException} if your request was received by the web service but is
* invalid for some other reason. This may indicate an * invalid for some other reason. This may indicate an
* issue with this API. Please report the error to MaxMind. * issue with this API. Please report the error to MaxMind.
* @throws \GeoIp2\Exception\HttpException if an unexpected HTTP error code or message was returned. * @throws \GeoIp2\Exception\HttpException if an unexpected HTTP error code or message was returned.
* This could indicate a problem with the connection between * This could indicate a problem with the connection between
* your server and the web service or that the web service * your server and the web service or that the web service
* returned an invalid document or 500 error code * returned an invalid document or 500 error code
* @throws \GeoIp2\Exception\GeoIp2Exception This serves as the parent * @throws \GeoIp2\Exception\GeoIp2Exception This serves as the parent
* class to the above exceptions. It will be thrown directly * class to the above exceptions. It will be thrown directly
* if a 200 status code is returned but the body is invalid. * if a 200 status code is returned but the body is invalid.
*
* @return \GeoIp2\Model\Insights
*/ */
public function insights(string $ipAddress = 'me'): Insights public function insights($ipAddress = 'me')
{ {
// @phpstan-ignore-next-line
return $this->responseFor('insights', 'Insights', $ipAddress); return $this->responseFor('insights', 'Insights', $ipAddress);
} }
private function responseFor(string $endpoint, string $class, string $ipAddress): Country private function responseFor($endpoint, $class, $ipAddress)
{ {
$path = implode('/', [self::$basePath, $endpoint, $ipAddress]); $path = implode('/', [self::$basePath, $endpoint, $ipAddress]);

View File

@ -1,56 +1,6 @@
CHANGELOG CHANGELOG
========= =========
1.11.0
-------------------
* Replace runtime define of a constant to facilitate opcache preloading.
Reported by vedadkajtaz. GitHub #134.
* Resolve minor issue found by the Clang static analyzer in the C
extension.
1.10.1 (2021-04-14)
-------------------
* Fix a `TypeError` exception in the pure PHP reader when using large
databases on 32-bit PHP builds with the `bcmath` extension. Reported
by dodo1708. GitHub #124.
1.10.0 (2021-02-09)
-------------------
* When using the pure PHP reader, unsigned integers up to PHP_MAX_INT
will now be integers in PHP rather than strings. Previously integers
greater than 2^24 on 32-bit platforms and 2^56 on 64-bit platforms
would be strings due to the use of `gmp` or `bcmath` to decode them.
Reported by Alejandro Celaya. GitHub #119.
1.9.0 (2021-01-07)
------------------
* The `maxminddb` extension is now buildable on Windows. Pull request
by Jan Ehrhardt. GitHub #115.
1.8.0 (2020-10-01)
------------------
* Fixes for PHP 8.0. Pull Request by Remi Collet. GitHub #108.
1.7.0 (2020-08-07)
------------------
* IMPORTANT: PHP 7.2 or greater is now required.
* The extension no longer depends on the pure PHP classes in
`maxmind-db/reader`. You can use it independently.
* Type hints have been added to both the pure PHP implementation
and the extension.
* The `metadata` method on the reader now returns a new copy of the
metadata object rather than the actual object used by the reader.
* Work around PHP `is_readable()` bug. Reported by Ben Roberts. GitHub
#92.
* This is the first release of the extension as a PECL package.
GitHub #34.
1.6.0 (2019-12-19) 1.6.0 (2019-12-19)
------------------ ------------------

View File

@ -112,16 +112,9 @@ you are using an autoloader, no changes to your code should be necessary.
First install [libmaxminddb](https://github.com/maxmind/libmaxminddb) as First install [libmaxminddb](https://github.com/maxmind/libmaxminddb) as
described in its [README.md described in its [README.md
file](https://github.com/maxmind/libmaxminddb/blob/main/README.md#installing-from-a-tarball). file](https://github.com/maxmind/libmaxminddb/blob/master/README.md#installing-from-a-tarball).
After successfully installing libmaxmindb, you may install the extension After successfully installing libmaxmindb, run the following commands from the
from [pecl](https://pecl.php.net/package/maxminddb): top-level directory of this distribution:
```
pecl install maxminddb
```
Alternatively, you may install it from the source. To do so, run the following
commands from the top-level directory of this distribution:
``` ```
cd ext cd ext
@ -164,7 +157,7 @@ client API, please see [our support page](https://www.maxmind.com/en/support).
## Requirements ## ## Requirements ##
This library requires PHP 7.2 or greater. This library requires PHP 5.6 or greater.
The GMP or BCMath extension may be required to read some databases The GMP or BCMath extension may be required to read some databases
using the pure PHP API. using the pure PHP API.
@ -180,6 +173,6 @@ The MaxMind DB Reader PHP API uses [Semantic Versioning](https://semver.org/).
## Copyright and License ## ## Copyright and License ##
This software is Copyright (c) 2014-2020 by MaxMind, Inc. This software is Copyright (c) 2014-2019 by MaxMind, Inc.
This is free software, licensed under the Apache License, Version 2.0. This is free software, licensed under the Apache License, Version 2.0.

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
/** /**
* PSR-4 autoloader implementation for the MaxMind\DB namespace. * PSR-4 autoloader implementation for the MaxMind\DB namespace.
* First we define the 'mmdb_autoload' function, and then we register * First we define the 'mmdb_autoload' function, and then we register
@ -16,7 +14,7 @@ declare(strict_types=1);
* @param string $class * @param string $class
* the name of the class to load * the name of the class to load
*/ */
function mmdb_autoload($class): void function mmdb_autoload($class)
{ {
/* /*
* A project-specific mapping between the namespaces and where * A project-specific mapping between the namespaces and where
@ -28,16 +26,16 @@ function mmdb_autoload($class): void
$namespace_map = ['MaxMind\\Db\\' => __DIR__ . '/src/MaxMind/Db/']; $namespace_map = ['MaxMind\\Db\\' => __DIR__ . '/src/MaxMind/Db/'];
foreach ($namespace_map as $prefix => $dir) { foreach ($namespace_map as $prefix => $dir) {
// First swap out the namespace prefix with a directory... /* First swap out the namespace prefix with a directory... */
$path = str_replace($prefix, $dir, $class); $path = str_replace($prefix, $dir, $class);
// replace the namespace separator with a directory separator... /* replace the namespace separator with a directory separator... */
$path = str_replace('\\', '/', $path); $path = str_replace('\\', '/', $path);
// and finally, add the PHP file extension to the result. /* and finally, add the PHP file extension to the result. */
$path = $path . '.php'; $path = $path . '.php';
// $path should now contain the path to a PHP file defining $class /* $path should now contain the path to a PHP file defining $class */
if (file_exists($path)) { if (file_exists($path)) {
include $path; include $path;
} }

View File

@ -13,7 +13,7 @@
} }
], ],
"require": { "require": {
"php": ">=7.2" "php": ">=5.6"
}, },
"suggest": { "suggest": {
"ext-bcmath": "bcmath or gmp is required for decoding larger integers with the pure PHP decoder", "ext-bcmath": "bcmath or gmp is required for decoding larger integers with the pure PHP decoder",
@ -21,15 +21,14 @@
"ext-maxminddb": "A C-based database decoder that provides significantly faster lookups" "ext-maxminddb": "A C-based database decoder that provides significantly faster lookups"
}, },
"conflict": { "conflict": {
"ext-maxminddb": "<1.10.1,>=2.0.0" "ext-maxminddb": "<1.6.0,>=2.0.0"
}, },
"require-dev": { "require-dev": {
"friendsofphp/php-cs-fixer": "3.*", "friendsofphp/php-cs-fixer": "2.*",
"phpunit/phpunit": ">=8.0.0,<10.0.0", "phpunit/phpunit": "5.*",
"php-coveralls/php-coveralls": "^2.1", "php-coveralls/php-coveralls": "^2.1",
"phpunit/phpcov": ">=6.0.0", "phpunit/phpcov": "^3.0",
"squizlabs/php_codesniffer": "3.*", "squizlabs/php_codesniffer": "3.*"
"phpstan/phpstan": "*"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {

View File

@ -1,10 +0,0 @@
ARG_WITH("maxminddb", "Enable MaxMind DB Reader extension support", "no");
if (PHP_MAXMINDDB == "yes") {
if (CHECK_HEADER_ADD_INCLUDE("maxminddb.h", "CFLAGS_MAXMINDDB", PHP_MAXMINDDB + ";" + PHP_PHP_BUILD + "\\include\\maxminddb") &&
CHECK_LIB("libmaxminddb.lib", "maxminddb", PHP_MAXMINDDB)) {
EXTENSION("maxminddb", "maxminddb.c");
} else {
WARNING('Could not find maxminddb.h or libmaxminddb.lib; skipping');
}
}

View File

@ -21,8 +21,6 @@
#include <zend.h> #include <zend.h>
#include "Zend/zend_exceptions.h" #include "Zend/zend_exceptions.h"
#include "Zend/zend_types.h"
#include "ext/spl/spl_exceptions.h"
#include "ext/standard/info.h" #include "ext/standard/info.h"
#include <maxminddb.h> #include <maxminddb.h>
@ -35,49 +33,49 @@
#define PHP_MAXMINDDB_NS ZEND_NS_NAME("MaxMind", "Db") #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_NS ZEND_NS_NAME(PHP_MAXMINDDB_NS, "Reader")
#define PHP_MAXMINDDB_METADATA_NS \
ZEND_NS_NAME(PHP_MAXMINDDB_READER_NS, "Metadata")
#define PHP_MAXMINDDB_READER_EX_NS \ #define PHP_MAXMINDDB_READER_EX_NS \
ZEND_NS_NAME(PHP_MAXMINDDB_READER_NS, "InvalidDatabaseException") 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 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 size_t strsize_t;
typedef zend_object free_obj_t; typedef zend_object free_obj_t;
/* For PHP 8 compatibility */
#if PHP_VERSION_ID < 80000
#define PROP_OBJ(zv) (zv)
#else #else
#define Z_MAXMINDDB_P(zv) \
#define PROP_OBJ(zv) Z_OBJ_P(zv) (maxminddb_obj *)zend_object_store_get_object(zv TSRMLS_CC)
#define _ZVAL_STRING(a, b) ZVAL_STRING(a, b, 1)
#define TSRMLS_C #define _ZVAL_STRINGL(a, b, c) ZVAL_STRINGL(a, b, c, 1)
#define TSRMLS_CC typedef int strsize_t;
#define TSRMLS_DC typedef void free_obj_t;
/* End PHP 8 compatibility */
#endif #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 #ifndef ZEND_ACC_CTOR
#define ZEND_ACC_CTOR 0 #define ZEND_ACC_CTOR 0
#endif #endif
/* IS_MIXED was added in 2020 */ #ifdef ZEND_ENGINE_3
#ifndef IS_MIXED
#define IS_MIXED IS_UNDEF
#endif
/* ZEND_THIS was added in 7.4 */
#ifndef ZEND_THIS
#define ZEND_THIS (&EX(This))
#endif
typedef struct _maxminddb_obj { typedef struct _maxminddb_obj {
MMDB_s *mmdb; MMDB_s *mmdb;
zend_object std; zend_object std;
} maxminddb_obj; } maxminddb_obj;
#else
typedef struct _maxminddb_obj {
zend_object std;
MMDB_s *mmdb;
} maxminddb_obj;
#endif
PHP_FUNCTION(maxminddb); PHP_FUNCTION(maxminddb);
@ -98,6 +96,7 @@ static void handle_uint64(const MMDB_entry_data_list_s *entry_data_list,
zval *z_value TSRMLS_DC); zval *z_value TSRMLS_DC);
static void handle_uint32(const MMDB_entry_data_list_s *entry_data_list, static void handle_uint32(const MMDB_entry_data_list_s *entry_data_list,
zval *z_value TSRMLS_DC); zval *z_value TSRMLS_DC);
static zend_class_entry *lookup_class(const char *name TSRMLS_DC);
#define CHECK_ALLOCATED(val) \ #define CHECK_ALLOCATED(val) \
if (!val) { \ if (!val) { \
@ -105,16 +104,38 @@ static void handle_uint32(const MMDB_entry_data_list_s *entry_data_list,
return; \ 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_object_handlers maxminddb_obj_handlers;
static zend_class_entry *maxminddb_ce, *maxminddb_exception_ce, *metadata_ce; static zend_class_entry *maxminddb_ce;
static inline maxminddb_obj * static inline maxminddb_obj *
php_maxminddb_fetch_object(zend_object *obj TSRMLS_DC) { php_maxminddb_fetch_object(zend_object *obj TSRMLS_DC) {
#ifdef ZEND_ENGINE_3
return (maxminddb_obj *)((char *)(obj)-XtOffsetOf(maxminddb_obj, std)); return (maxminddb_obj *)((char *)(obj)-XtOffsetOf(maxminddb_obj, std));
#else
return (maxminddb_obj *)obj;
#endif
} }
ZEND_BEGIN_ARG_INFO_EX(arginfo_maxminddbreader_construct, 0, 0, 1) ZEND_BEGIN_ARG_INFO_EX(arginfo_maxmindbreader_construct, 0, 0, 1)
ZEND_ARG_TYPE_INFO(0, db_file, IS_STRING, 0) ZEND_ARG_INFO(0, db_file)
ZEND_END_ARG_INFO() ZEND_END_ARG_INFO()
PHP_METHOD(MaxMind_Db_Reader, __construct) { PHP_METHOD(MaxMind_Db_Reader, __construct) {
@ -129,16 +150,16 @@ PHP_METHOD(MaxMind_Db_Reader, __construct) {
maxminddb_ce, maxminddb_ce,
&db_file, &db_file,
&name_len) == FAILURE) { &name_len) == FAILURE) {
THROW_EXCEPTION("InvalidArgumentException",
"The constructor takes exactly one argument.");
return; return;
} }
if (0 != php_check_open_basedir(db_file TSRMLS_CC) || if (0 != php_check_open_basedir(db_file TSRMLS_CC) ||
0 != access(db_file, R_OK)) { 0 != access(db_file, R_OK)) {
zend_throw_exception_ex( THROW_EXCEPTION("InvalidArgumentException",
spl_ce_InvalidArgumentException, "The file \"%s\" does not exist or is not readable.",
0 TSRMLS_CC, db_file);
"The file \"%s\" does not exist or is not readable.",
db_file);
return; return;
} }
@ -146,23 +167,20 @@ PHP_METHOD(MaxMind_Db_Reader, __construct) {
uint16_t status = MMDB_open(db_file, MMDB_MODE_MMAP, mmdb); uint16_t status = MMDB_open(db_file, MMDB_MODE_MMAP, mmdb);
if (MMDB_SUCCESS != status) { if (MMDB_SUCCESS != status) {
zend_throw_exception_ex( THROW_EXCEPTION(PHP_MAXMINDDB_READER_EX_NS,
maxminddb_exception_ce, "Error opening database file (%s). Is this a valid "
0 TSRMLS_CC, "MaxMind DB file?",
"Error opening database file (%s). Is this a valid " db_file);
"MaxMind DB file?",
db_file);
efree(mmdb); efree(mmdb);
return; return;
} }
maxminddb_obj *mmdb_obj = Z_MAXMINDDB_P(ZEND_THIS); maxminddb_obj *mmdb_obj = Z_MAXMINDDB_P(getThis());
mmdb_obj->mmdb = mmdb; mmdb_obj->mmdb = mmdb;
} }
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX( ZEND_BEGIN_ARG_INFO_EX(arginfo_maxmindbreader_get, 0, 0, 1)
arginfo_maxminddbreader_get, 0, 1, IS_MIXED, 1) ZEND_ARG_INFO(0, ip_address)
ZEND_ARG_TYPE_INFO(0, ip_address, IS_STRING, 0)
ZEND_END_ARG_INFO() ZEND_END_ARG_INFO()
PHP_METHOD(MaxMind_Db_Reader, get) { PHP_METHOD(MaxMind_Db_Reader, get) {
@ -170,75 +188,76 @@ PHP_METHOD(MaxMind_Db_Reader, get) {
get_record(INTERNAL_FUNCTION_PARAM_PASSTHRU, return_value, &prefix_len); get_record(INTERNAL_FUNCTION_PARAM_PASSTHRU, return_value, &prefix_len);
} }
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(
arginfo_maxminddbreader_getWithPrefixLen, 0, 1, IS_ARRAY, 1)
ZEND_ARG_TYPE_INFO(0, ip_address, IS_STRING, 0)
ZEND_END_ARG_INFO()
PHP_METHOD(MaxMind_Db_Reader, getWithPrefixLen) { PHP_METHOD(MaxMind_Db_Reader, getWithPrefixLen) {
zval record, z_prefix_len; 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; int prefix_len = 0;
if (get_record(INTERNAL_FUNCTION_PARAM_PASSTHRU, &record, &prefix_len) == if (get_record(INTERNAL_FUNCTION_PARAM_PASSTHRU, record, &prefix_len)) {
FAILURE) {
return; return;
} }
array_init(return_value); array_init(return_value);
add_next_index_zval(return_value, &record); add_next_index_zval(return_value, record);
ZVAL_LONG(&z_prefix_len, prefix_len); ZVAL_LONG(z_prefix_len, prefix_len);
add_next_index_zval(return_value, &z_prefix_len); add_next_index_zval(return_value, z_prefix_len);
} }
static int static int
get_record(INTERNAL_FUNCTION_PARAMETERS, zval *record, int *prefix_len) { get_record(INTERNAL_FUNCTION_PARAMETERS, zval *record, int *prefix_len) {
char *ip_address = NULL; char *ip_address = NULL;
strsize_t name_len; strsize_t name_len;
zval *this_zval = NULL; zval *_this_zval = NULL;
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
getThis(), getThis(),
"Os", "Os",
&this_zval, &_this_zval,
maxminddb_ce, maxminddb_ce,
&ip_address, &ip_address,
&name_len) == FAILURE) { &name_len) == FAILURE) {
return FAILURE; THROW_EXCEPTION("InvalidArgumentException",
"Method takes exactly one argument.");
return 1;
} }
const maxminddb_obj *mmdb_obj = (maxminddb_obj *)Z_MAXMINDDB_P(ZEND_THIS); const maxminddb_obj *mmdb_obj = (maxminddb_obj *)Z_MAXMINDDB_P(getThis());
MMDB_s *mmdb = mmdb_obj->mmdb; MMDB_s *mmdb = mmdb_obj->mmdb;
if (NULL == mmdb) { if (NULL == mmdb) {
zend_throw_exception_ex(spl_ce_BadMethodCallException, THROW_EXCEPTION("BadMethodCallException",
0 TSRMLS_CC, "Attempt to read from a closed MaxMind DB.");
"Attempt to read from a closed MaxMind DB."); return 1;
return FAILURE;
} }
struct addrinfo hints = { struct addrinfo hints = {
.ai_family = AF_UNSPEC, .ai_family = AF_UNSPEC,
.ai_flags = AI_NUMERICHOST, .ai_flags = AI_NUMERICHOST,
/* We set ai_socktype so that we only get one result back */ // We set ai_socktype so that we only get one result back
.ai_socktype = SOCK_STREAM}; .ai_socktype = SOCK_STREAM};
struct addrinfo *addresses = NULL; struct addrinfo *addresses = NULL;
int gai_status = getaddrinfo(ip_address, NULL, &hints, &addresses); int gai_status = getaddrinfo(ip_address, NULL, &hints, &addresses);
if (gai_status) { if (gai_status) {
zend_throw_exception_ex(spl_ce_InvalidArgumentException, THROW_EXCEPTION("InvalidArgumentException",
0 TSRMLS_CC, "The value \"%s\" is not a valid IP address.",
"The value \"%s\" is not a valid IP address.", ip_address);
ip_address); return 1;
return FAILURE;
} }
if (!addresses || !addresses->ai_addr) { if (!addresses || !addresses->ai_addr) {
zend_throw_exception_ex( THROW_EXCEPTION(
spl_ce_InvalidArgumentException, "InvalidArgumentException",
0 TSRMLS_CC,
"getaddrinfo was successful but failed to set the addrinfo"); "getaddrinfo was successful but failed to set the addrinfo");
return FAILURE; return 1;
} }
int sa_family = addresses->ai_addr->sa_family; int sa_family = addresses->ai_addr->sa_family;
@ -250,127 +269,132 @@ get_record(INTERNAL_FUNCTION_PARAMETERS, zval *record, int *prefix_len) {
freeaddrinfo(addresses); freeaddrinfo(addresses);
if (MMDB_SUCCESS != mmdb_error) { if (MMDB_SUCCESS != mmdb_error) {
zend_class_entry *ex; char *exception_name;
if (MMDB_IPV6_LOOKUP_IN_IPV4_DATABASE_ERROR == mmdb_error) { if (MMDB_IPV6_LOOKUP_IN_IPV4_DATABASE_ERROR == mmdb_error) {
ex = spl_ce_InvalidArgumentException; exception_name = "InvalidArgumentException";
} else { } else {
ex = maxminddb_exception_ce; exception_name = PHP_MAXMINDDB_READER_EX_NS;
} }
zend_throw_exception_ex(ex, THROW_EXCEPTION(exception_name,
0 TSRMLS_CC, "Error looking up %s. %s",
"Error looking up %s. %s", ip_address,
ip_address, MMDB_strerror(mmdb_error));
MMDB_strerror(mmdb_error)); return 1;
return FAILURE;
} }
*prefix_len = result.netmask; *prefix_len = result.netmask;
if (sa_family == AF_INET && mmdb->metadata.ip_version == 6) { if (sa_family == AF_INET && mmdb->metadata.ip_version == 6) {
/* We return the prefix length given the IPv4 address. If there is // We return the prefix length given the IPv4 address. If there is
no IPv4 subtree, we return a prefix length of 0. */ // no IPv4 subtree, we return a prefix length of 0.
*prefix_len = *prefix_len >= 96 ? *prefix_len - 96 : 0; *prefix_len = *prefix_len >= 96 ? *prefix_len - 96 : 0;
} }
if (!result.found_entry) { if (!result.found_entry) {
ZVAL_NULL(record); ZVAL_NULL(record);
return SUCCESS; return 0;
} }
MMDB_entry_data_list_s *entry_data_list = NULL; MMDB_entry_data_list_s *entry_data_list = NULL;
int status = MMDB_get_entry_data_list(&result.entry, &entry_data_list); int status = MMDB_get_entry_data_list(&result.entry, &entry_data_list);
if (MMDB_SUCCESS != status) { if (MMDB_SUCCESS != status) {
zend_throw_exception_ex(maxminddb_exception_ce, THROW_EXCEPTION(PHP_MAXMINDDB_READER_EX_NS,
0 TSRMLS_CC, "Error while looking up data for %s. %s",
"Error while looking up data for %s. %s", ip_address,
ip_address, MMDB_strerror(status));
MMDB_strerror(status));
MMDB_free_entry_data_list(entry_data_list); MMDB_free_entry_data_list(entry_data_list);
return FAILURE; return 1;
} else if (NULL == entry_data_list) { } else if (NULL == entry_data_list) {
zend_throw_exception_ex( THROW_EXCEPTION(PHP_MAXMINDDB_READER_EX_NS,
maxminddb_exception_ce, "Error while looking up data for %s. Your database may "
0 TSRMLS_CC, "be corrupt or you have found a bug in libmaxminddb.",
"Error while looking up data for %s. Your database may " ip_address);
"be corrupt or you have found a bug in libmaxminddb.", return 1;
ip_address);
return FAILURE;
} }
const MMDB_entry_data_list_s *rv = handle_entry_data_list(entry_data_list, record TSRMLS_CC);
handle_entry_data_list(entry_data_list, record TSRMLS_CC);
if (rv == NULL) {
/* We should have already thrown the exception in handle_entry_data_list
*/
return FAILURE;
}
MMDB_free_entry_data_list(entry_data_list); MMDB_free_entry_data_list(entry_data_list);
return SUCCESS; return 0;
} }
ZEND_BEGIN_ARG_INFO_EX(arginfo_maxminddbreader_void, 0, 0, 0) ZEND_BEGIN_ARG_INFO_EX(arginfo_maxmindbreader_void, 0, 0, 0)
ZEND_END_ARG_INFO() ZEND_END_ARG_INFO()
PHP_METHOD(MaxMind_Db_Reader, metadata) { PHP_METHOD(MaxMind_Db_Reader, metadata) {
zval *this_zval = NULL; if (ZEND_NUM_ARGS() != 0) {
THROW_EXCEPTION("InvalidArgumentException",
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Method takes no arguments.");
getThis(),
"O",
&this_zval,
maxminddb_ce) == FAILURE) {
return; return;
} }
const maxminddb_obj *const mmdb_obj = const maxminddb_obj *const mmdb_obj =
(maxminddb_obj *)Z_MAXMINDDB_P(this_zval); (maxminddb_obj *)Z_MAXMINDDB_P(getThis());
if (NULL == mmdb_obj->mmdb) { if (NULL == mmdb_obj->mmdb) {
zend_throw_exception_ex(spl_ce_BadMethodCallException, THROW_EXCEPTION("BadMethodCallException",
0 TSRMLS_CC, "Attempt to read from a closed MaxMind DB.");
"Attempt to read from a closed MaxMind DB.");
return; 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); 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_entry_data_list_s *entry_data_list;
MMDB_get_metadata_as_entry_data_list(mmdb_obj->mmdb, &entry_data_list); MMDB_get_metadata_as_entry_data_list(mmdb_obj->mmdb, &entry_data_list);
zval metadata_array; handle_entry_data_list(entry_data_list, metadata_array TSRMLS_CC);
const MMDB_entry_data_list_s *rv =
handle_entry_data_list(entry_data_list, &metadata_array TSRMLS_CC);
if (rv == NULL) {
return;
}
MMDB_free_entry_data_list(entry_data_list); MMDB_free_entry_data_list(entry_data_list);
zend_call_method_with_1_params(PROP_OBJ(return_value), #if PHP_VERSION_ID >= 80000
zend_call_method_with_1_params(Z_OBJ_P(return_value),
metadata_ce, metadata_ce,
&metadata_ce->constructor, &metadata_ce->constructor,
ZEND_CONSTRUCTOR_FUNC_NAME, ZEND_CONSTRUCTOR_FUNC_NAME,
NULL, NULL,
&metadata_array); 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); zval_ptr_dtor(&metadata_array);
#endif
} }
PHP_METHOD(MaxMind_Db_Reader, close) { PHP_METHOD(MaxMind_Db_Reader, close) {
zval *this_zval = NULL; if (ZEND_NUM_ARGS() != 0) {
THROW_EXCEPTION("InvalidArgumentException",
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Method takes no arguments.");
getThis(),
"O",
&this_zval,
maxminddb_ce) == FAILURE) {
return; return;
} }
maxminddb_obj *mmdb_obj = (maxminddb_obj *)Z_MAXMINDDB_P(this_zval); maxminddb_obj *mmdb_obj = (maxminddb_obj *)Z_MAXMINDDB_P(getThis());
if (NULL == mmdb_obj->mmdb) { if (NULL == mmdb_obj->mmdb) {
zend_throw_exception_ex(spl_ce_BadMethodCallException, THROW_EXCEPTION("BadMethodCallException",
0 TSRMLS_CC, "Attempt to close a closed MaxMind DB.");
"Attempt to close a closed MaxMind DB.");
return; return;
} }
MMDB_close(mmdb_obj->mmdb); MMDB_close(mmdb_obj->mmdb);
@ -387,14 +411,14 @@ handle_entry_data_list(const MMDB_entry_data_list_s *entry_data_list,
case MMDB_DATA_TYPE_ARRAY: case MMDB_DATA_TYPE_ARRAY:
return handle_array(entry_data_list, z_value TSRMLS_CC); return handle_array(entry_data_list, z_value TSRMLS_CC);
case MMDB_DATA_TYPE_UTF8_STRING: case MMDB_DATA_TYPE_UTF8_STRING:
ZVAL_STRINGL(z_value, _ZVAL_STRINGL(z_value,
(char *)entry_data_list->entry_data.utf8_string, (char *)entry_data_list->entry_data.utf8_string,
entry_data_list->entry_data.data_size); entry_data_list->entry_data.data_size);
break; break;
case MMDB_DATA_TYPE_BYTES: case MMDB_DATA_TYPE_BYTES:
ZVAL_STRINGL(z_value, _ZVAL_STRINGL(z_value,
(char *)entry_data_list->entry_data.bytes, (char *)entry_data_list->entry_data.bytes,
entry_data_list->entry_data.data_size); entry_data_list->entry_data.data_size);
break; break;
case MMDB_DATA_TYPE_DOUBLE: case MMDB_DATA_TYPE_DOUBLE:
ZVAL_DOUBLE(z_value, entry_data_list->entry_data.double_value); ZVAL_DOUBLE(z_value, entry_data_list->entry_data.double_value);
@ -421,10 +445,9 @@ handle_entry_data_list(const MMDB_entry_data_list_s *entry_data_list,
ZVAL_LONG(z_value, entry_data_list->entry_data.int32); ZVAL_LONG(z_value, entry_data_list->entry_data.int32);
break; break;
default: default:
zend_throw_exception_ex(maxminddb_exception_ce, THROW_EXCEPTION(PHP_MAXMINDDB_READER_EX_NS,
0 TSRMLS_CC, "Invalid data type arguments: %d",
"Invalid data type arguments: %d", entry_data_list->entry_data.type);
entry_data_list->entry_data.type);
return NULL; return NULL;
} }
return entry_data_list; return entry_data_list;
@ -436,26 +459,30 @@ handle_map(const MMDB_entry_data_list_s *entry_data_list,
array_init(z_value); array_init(z_value);
const uint32_t map_size = entry_data_list->entry_data.data_size; const uint32_t map_size = entry_data_list->entry_data.data_size;
uint32_t i; uint i;
for (i = 0; i < map_size && entry_data_list; i++) { for (i = 0; i < map_size && entry_data_list; i++) {
entry_data_list = entry_data_list->next; entry_data_list = entry_data_list->next;
char *key = estrndup((char *)entry_data_list->entry_data.utf8_string, char *key = estrndup((char *)entry_data_list->entry_data.utf8_string,
entry_data_list->entry_data.data_size); entry_data_list->entry_data.data_size);
if (NULL == key) { if (NULL == key) {
zend_throw_exception_ex(maxminddb_exception_ce, THROW_EXCEPTION(PHP_MAXMINDDB_READER_EX_NS,
0 TSRMLS_CC, "Invalid data type arguments");
"Invalid data type arguments");
return NULL; return NULL;
} }
entry_data_list = entry_data_list->next; entry_data_list = entry_data_list->next;
zval new_value; #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 = entry_data_list =
handle_entry_data_list(entry_data_list, &new_value TSRMLS_CC); handle_entry_data_list(entry_data_list, new_value TSRMLS_CC);
if (entry_data_list != NULL) { add_assoc_zval(z_value, key, new_value);
add_assoc_zval(z_value, key, &new_value);
}
efree(key); efree(key);
} }
return entry_data_list; return entry_data_list;
@ -468,15 +495,20 @@ handle_array(const MMDB_entry_data_list_s *entry_data_list,
array_init(z_value); array_init(z_value);
uint32_t i; uint i;
for (i = 0; i < size && entry_data_list; i++) { for (i = 0; i < size && entry_data_list; i++) {
entry_data_list = entry_data_list->next; entry_data_list = entry_data_list->next;
zval new_value; #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 = entry_data_list =
handle_entry_data_list(entry_data_list, &new_value TSRMLS_CC); handle_entry_data_list(entry_data_list, new_value TSRMLS_CC);
if (entry_data_list != NULL) { add_next_index_zval(z_value, new_value);
add_next_index_zval(z_value, &new_value);
}
} }
return entry_data_list; return entry_data_list;
} }
@ -503,7 +535,7 @@ static void handle_uint128(const MMDB_entry_data_list_s *entry_data_list,
spprintf(&num_str, 0, "0x%016" PRIX64 "%016" PRIX64, high, low); spprintf(&num_str, 0, "0x%016" PRIX64 "%016" PRIX64, high, low);
CHECK_ALLOCATED(num_str); CHECK_ALLOCATED(num_str);
ZVAL_STRING(z_value, num_str); _ZVAL_STRING(z_value, num_str);
efree(num_str); efree(num_str);
} }
@ -524,7 +556,7 @@ static void handle_uint32(const MMDB_entry_data_list_s *entry_data_list,
spprintf(&int_str, 0, "%" PRIu32, val); spprintf(&int_str, 0, "%" PRIu32, val);
CHECK_ALLOCATED(int_str); CHECK_ALLOCATED(int_str);
ZVAL_STRING(z_value, int_str); _ZVAL_STRING(z_value, int_str);
efree(int_str); efree(int_str);
#endif #endif
} }
@ -546,11 +578,29 @@ static void handle_uint64(const MMDB_entry_data_list_s *entry_data_list,
spprintf(&int_str, 0, "%" PRIu64, val); spprintf(&int_str, 0, "%" PRIu64, val);
CHECK_ALLOCATED(int_str); CHECK_ALLOCATED(int_str);
ZVAL_STRING(z_value, int_str); _ZVAL_STRING(z_value, int_str);
efree(int_str); efree(int_str);
#endif #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) { static void maxminddb_free_storage(free_obj_t *object TSRMLS_DC) {
maxminddb_obj *obj = maxminddb_obj *obj =
php_maxminddb_fetch_object((zend_object *)object TSRMLS_CC); php_maxminddb_fetch_object((zend_object *)object TSRMLS_CC);
@ -560,8 +610,12 @@ static void maxminddb_free_storage(free_obj_t *object TSRMLS_DC) {
} }
zend_object_std_dtor(&obj->std TSRMLS_CC); 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) { static zend_object *maxminddb_create_handler(zend_class_entry *type TSRMLS_DC) {
maxminddb_obj *obj = (maxminddb_obj *)ecalloc(1, sizeof(maxminddb_obj)); maxminddb_obj *obj = (maxminddb_obj *)ecalloc(1, sizeof(maxminddb_obj));
zend_object_std_init(&obj->std, type TSRMLS_CC); zend_object_std_init(&obj->std, type TSRMLS_CC);
@ -571,210 +625,49 @@ static zend_object *maxminddb_create_handler(zend_class_entry *type TSRMLS_DC) {
return &obj->std; return &obj->std;
} }
#else
static zend_object_value
maxminddb_create_handler(zend_class_entry *type TSRMLS_DC) {
zend_object_value retval;
/* clang-format off */ maxminddb_obj *obj = (maxminddb_obj *)ecalloc(1, sizeof(maxminddb_obj));
static zend_function_entry maxminddb_methods[] = { zend_object_std_init(&obj->std, type TSRMLS_CC);
PHP_ME(MaxMind_Db_Reader, __construct, arginfo_maxminddbreader_construct, object_properties_init(&(obj->std), type);
ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
PHP_ME(MaxMind_Db_Reader, close, arginfo_maxminddbreader_void, ZEND_ACC_PUBLIC)
PHP_ME(MaxMind_Db_Reader, get, arginfo_maxminddbreader_get, ZEND_ACC_PUBLIC)
PHP_ME(MaxMind_Db_Reader, getWithPrefixLen, arginfo_maxminddbreader_getWithPrefixLen, ZEND_ACC_PUBLIC)
PHP_ME(MaxMind_Db_Reader, metadata, arginfo_maxminddbreader_void, ZEND_ACC_PUBLIC)
{ NULL, NULL, NULL }
};
/* clang-format on */
ZEND_BEGIN_ARG_INFO_EX(arginfo_metadata_construct, 0, 0, 1) retval.handle = zend_objects_store_put(
ZEND_ARG_TYPE_INFO(0, metadata, IS_ARRAY, 0) obj, NULL, maxminddb_free_storage, NULL TSRMLS_CC);
ZEND_END_ARG_INFO() retval.handlers = &maxminddb_obj_handlers;
PHP_METHOD(MaxMind_Db_Reader_Metadata, __construct) { return retval;
zval *object = NULL;
zval *metadata_array = NULL;
zend_long node_count = 0;
zend_long record_size = 0;
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
getThis(),
"Oa",
&object,
metadata_ce,
&metadata_array) == FAILURE) {
return;
}
zval *tmp = NULL;
if ((tmp = zend_hash_str_find(HASH_OF(metadata_array),
"binary_format_major_version",
sizeof("binary_format_major_version") - 1))) {
zend_update_property(metadata_ce,
PROP_OBJ(object),
"binaryFormatMajorVersion",
sizeof("binaryFormatMajorVersion") - 1,
tmp);
}
if ((tmp = zend_hash_str_find(HASH_OF(metadata_array),
"binary_format_minor_version",
sizeof("binary_format_minor_version") - 1))) {
zend_update_property(metadata_ce,
PROP_OBJ(object),
"binaryFormatMinorVersion",
sizeof("binaryFormatMinorVersion") - 1,
tmp);
}
if ((tmp = zend_hash_str_find(HASH_OF(metadata_array),
"build_epoch",
sizeof("build_epoch") - 1))) {
zend_update_property(metadata_ce,
PROP_OBJ(object),
"buildEpoch",
sizeof("buildEpoch") - 1,
tmp);
}
if ((tmp = zend_hash_str_find(HASH_OF(metadata_array),
"database_type",
sizeof("database_type") - 1))) {
zend_update_property(metadata_ce,
PROP_OBJ(object),
"databaseType",
sizeof("databaseType") - 1,
tmp);
}
if ((tmp = zend_hash_str_find(HASH_OF(metadata_array),
"description",
sizeof("description") - 1))) {
zend_update_property(metadata_ce,
PROP_OBJ(object),
"description",
sizeof("description") - 1,
tmp);
}
if ((tmp = zend_hash_str_find(HASH_OF(metadata_array),
"ip_version",
sizeof("ip_version") - 1))) {
zend_update_property(metadata_ce,
PROP_OBJ(object),
"ipVersion",
sizeof("ipVersion") - 1,
tmp);
}
if ((tmp = zend_hash_str_find(
HASH_OF(metadata_array), "languages", sizeof("languages") - 1))) {
zend_update_property(metadata_ce,
PROP_OBJ(object),
"languages",
sizeof("languages") - 1,
tmp);
}
if ((tmp = zend_hash_str_find(HASH_OF(metadata_array),
"record_size",
sizeof("record_size") - 1))) {
zend_update_property(metadata_ce,
PROP_OBJ(object),
"recordSize",
sizeof("recordSize") - 1,
tmp);
if (Z_TYPE_P(tmp) == IS_LONG) {
record_size = Z_LVAL_P(tmp);
}
}
if (record_size != 0) {
zend_update_property_long(metadata_ce,
PROP_OBJ(object),
"nodeByteSize",
sizeof("nodeByteSize") - 1,
record_size / 4);
}
if ((tmp = zend_hash_str_find(HASH_OF(metadata_array),
"node_count",
sizeof("node_count") - 1))) {
zend_update_property(metadata_ce,
PROP_OBJ(object),
"nodeCount",
sizeof("nodeCount") - 1,
tmp);
if (Z_TYPE_P(tmp) == IS_LONG) {
node_count = Z_LVAL_P(tmp);
}
}
if (record_size != 0) {
zend_update_property_long(metadata_ce,
PROP_OBJ(object),
"searchTreeSize",
sizeof("searchTreeSize") - 1,
record_size * node_count / 4);
}
} }
#endif
// clang-format off // clang-format off
static zend_function_entry metadata_methods[] = { static zend_function_entry maxminddb_methods[] = {
PHP_ME(MaxMind_Db_Reader_Metadata, __construct, arginfo_metadata_construct, ZEND_ACC_PUBLIC | ZEND_ACC_CTOR) PHP_ME(MaxMind_Db_Reader, __construct, arginfo_maxmindbreader_construct,
{NULL, NULL, NULL} 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 // clang-format on
PHP_MINIT_FUNCTION(maxminddb) { PHP_MINIT_FUNCTION(maxminddb) {
zend_class_entry ce; zend_class_entry ce;
INIT_CLASS_ENTRY(ce, PHP_MAXMINDDB_READER_EX_NS, NULL);
maxminddb_exception_ce =
zend_register_internal_class_ex(&ce, zend_ce_exception);
INIT_CLASS_ENTRY(ce, PHP_MAXMINDDB_READER_NS, maxminddb_methods); INIT_CLASS_ENTRY(ce, PHP_MAXMINDDB_READER_NS, maxminddb_methods);
maxminddb_ce = zend_register_internal_class(&ce TSRMLS_CC); maxminddb_ce = zend_register_internal_class(&ce TSRMLS_CC);
maxminddb_ce->create_object = maxminddb_create_handler; maxminddb_ce->create_object = maxminddb_create_handler;
INIT_CLASS_ENTRY(ce, PHP_MAXMINDDB_METADATA_NS, metadata_methods);
metadata_ce = zend_register_internal_class(&ce TSRMLS_CC);
zend_declare_property_null(metadata_ce,
"binaryFormatMajorVersion",
sizeof("binaryFormatMajorVersion") - 1,
ZEND_ACC_PUBLIC);
zend_declare_property_null(metadata_ce,
"binaryFormatMinorVersion",
sizeof("binaryFormatMinorVersion") - 1,
ZEND_ACC_PUBLIC);
zend_declare_property_null(
metadata_ce, "buildEpoch", sizeof("buildEpoch") - 1, ZEND_ACC_PUBLIC);
zend_declare_property_null(metadata_ce,
"databaseType",
sizeof("databaseType") - 1,
ZEND_ACC_PUBLIC);
zend_declare_property_null(
metadata_ce, "description", sizeof("description") - 1, ZEND_ACC_PUBLIC);
zend_declare_property_null(
metadata_ce, "ipVersion", sizeof("ipVersion") - 1, ZEND_ACC_PUBLIC);
zend_declare_property_null(
metadata_ce, "languages", sizeof("languages") - 1, ZEND_ACC_PUBLIC);
zend_declare_property_null(metadata_ce,
"nodeByteSize",
sizeof("nodeByteSize") - 1,
ZEND_ACC_PUBLIC);
zend_declare_property_null(
metadata_ce, "nodeCount", sizeof("nodeCount") - 1, ZEND_ACC_PUBLIC);
zend_declare_property_null(
metadata_ce, "recordSize", sizeof("recordSize") - 1, ZEND_ACC_PUBLIC);
zend_declare_property_null(metadata_ce,
"searchTreeSize",
sizeof("searchTreeSize") - 1,
ZEND_ACC_PUBLIC);
memcpy(&maxminddb_obj_handlers, memcpy(&maxminddb_obj_handlers,
zend_get_std_object_handlers(), zend_get_std_object_handlers(),
sizeof(zend_object_handlers)); sizeof(zend_object_handlers));
maxminddb_obj_handlers.clone_obj = NULL; maxminddb_obj_handlers.clone_obj = NULL;
#ifdef ZEND_ENGINE_3
maxminddb_obj_handlers.offset = XtOffsetOf(maxminddb_obj, std); maxminddb_obj_handlers.offset = XtOffsetOf(maxminddb_obj, std);
maxminddb_obj_handlers.free_obj = maxminddb_free_storage; maxminddb_obj_handlers.free_obj = maxminddb_free_storage;
#endif
zend_declare_class_constant_string(maxminddb_ce, zend_declare_class_constant_string(maxminddb_ce,
"MMDB_LIB_VERSION", "MMDB_LIB_VERSION",
sizeof("MMDB_LIB_VERSION") - 1, sizeof("MMDB_LIB_VERSION") - 1,

View File

@ -15,7 +15,7 @@
#ifndef PHP_MAXMINDDB_H #ifndef PHP_MAXMINDDB_H
#define PHP_MAXMINDDB_H 1 #define PHP_MAXMINDDB_H 1
#define PHP_MAXMINDDB_VERSION "1.10.1" #define PHP_MAXMINDDB_VERSION "1.6.0"
#define PHP_MAXMINDDB_EXTNAME "maxminddb" #define PHP_MAXMINDDB_EXTNAME "maxminddb"
extern zend_module_entry maxminddb_module_entry; extern zend_module_entry maxminddb_module_entry;

View File

@ -1,63 +0,0 @@
<?xml version="1.0"?>
<package version="2.0" xmlns="http://pear.php.net/dtd/package-2.0"
xmlns:tasks="http://pear.php.net/dtd/tasks-1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0 http://pear.php.net/dtd/tasks-1.0.xsd http://pear.php.net/dtd/package-2.0 http://pear.php.net/dtd/package-2.0.xsd">
<name>maxminddb</name>
<channel>pecl.php.net</channel>
<summary>Reader for the MaxMind DB file format</summary>
<description>This is the PHP extension for reading MaxMind DB files. MaxMind DB is a binary file format that stores data indexed by IP address subnets (IPv4 or IPv6).</description>
<lead>
<name>Greg Oschwald</name>
<user>oschwald</user>
<email>goschwald@maxmind.com</email>
<active>yes</active>
</lead>
<date>2021-04-14</date>
<version>
<release>1.10.1</release>
<api>1.10.1</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<license uri="https://github.com/maxmind/MaxMind-DB-Reader-php/blob/main/LICENSE">Apache License 2.0</license>
<notes>* Fix a `TypeError` exception in the pure PHP reader when using large
databases on 32-bit PHP builds with the `bcmath` extension. Reported
by dodo1708. GitHub #124.</notes>
<contents>
<dir name="/">
<file role="doc" name="LICENSE"/>
<file role="doc" name="CHANGELOG.md"/>
<file role="doc" name="README.md"/>
<dir name="ext">
<file role="src" name="config.m4"/>
<file role="src" name="config.w32"/>
<file role="src" name="maxminddb.c"/>
<file role="src" name="php_maxminddb.h"/>
<dir name="tests">
<file role="test" name="001-load.phpt"/>
<file role="test" name="002-final.phpt"/>
<file role="test" name="003-open-basedir.phpt"/>
</dir>
</dir>
</dir>
</contents>
<dependencies>
<required>
<php>
<min>7.2.0</min>
</php>
<pearinstaller>
<min>1.10.0</min>
</pearinstaller>
</required>
</dependencies>
<providesextension>maxminddb</providesextension>
<extsrcrelease />
</package>

View File

@ -1,10 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace MaxMind\Db; namespace MaxMind\Db;
use ArgumentCountError;
use BadMethodCallException; use BadMethodCallException;
use Exception; use Exception;
use InvalidArgumentException; use InvalidArgumentException;
@ -20,42 +17,15 @@ use UnexpectedValueException;
*/ */
class Reader class Reader
{ {
/**
* @var int
*/
private static $DATA_SECTION_SEPARATOR_SIZE = 16; private static $DATA_SECTION_SEPARATOR_SIZE = 16;
/**
* @var string
*/
private static $METADATA_START_MARKER = "\xAB\xCD\xEFMaxMind.com"; private static $METADATA_START_MARKER = "\xAB\xCD\xEFMaxMind.com";
/**
* @var int
*/
private static $METADATA_START_MARKER_LENGTH = 14; private static $METADATA_START_MARKER_LENGTH = 14;
/** private static $METADATA_MAX_SIZE = 131072; // 128 * 1024 = 128KB
* @var int
*/
private static $METADATA_MAX_SIZE = 131072; // 128 * 1024 = 128KiB
/**
* @var Decoder
*/
private $decoder; private $decoder;
/**
* @var resource
*/
private $fileHandle; private $fileHandle;
/**
* @var int
*/
private $fileSize; private $fileSize;
/**
* @var int
*/
private $ipV4Start; private $ipV4Start;
/**
* @var Metadata
*/
private $metadata; private $metadata;
/** /**
@ -65,38 +35,40 @@ class Reader
* @param string $database * @param string $database
* the MaxMind DB file to use * the MaxMind DB file to use
* *
* @throws InvalidArgumentException for invalid database path or unknown arguments * @throws InvalidArgumentException for invalid database path or unknown arguments
* @throws InvalidDatabaseException * @throws \MaxMind\Db\Reader\InvalidDatabaseException
* if the database is invalid or there is an error reading * if the database is invalid or there is an error reading
* from it * from it
*/ */
public function __construct(string $database) public function __construct($database)
{ {
if (\func_num_args() !== 1) { if (\func_num_args() !== 1) {
throw new ArgumentCountError( throw new InvalidArgumentException(
sprintf('%s() expects exactly 1 parameter, %d given', __METHOD__, \func_num_args()) 'The constructor takes exactly one argument.'
); );
} }
$fileHandle = @fopen($database, 'rb'); if (!is_readable($database)) {
if ($fileHandle === false) {
throw new InvalidArgumentException( throw new InvalidArgumentException(
"The file \"$database\" does not exist or is not readable." "The file \"$database\" does not exist or is not readable."
); );
} }
$this->fileHandle = $fileHandle; $this->fileHandle = @fopen($database, 'rb');
if ($this->fileHandle === false) {
$fileSize = @filesize($database); throw new InvalidArgumentException(
if ($fileSize === false) { "Error opening \"$database\"."
);
}
$this->fileSize = @filesize($database);
if ($this->fileSize === false) {
throw new UnexpectedValueException( throw new UnexpectedValueException(
"Error determining the size of \"$database\"." "Error determining the size of \"$database\"."
); );
} }
$this->fileSize = $fileSize;
$start = $this->findMetadataStart($database); $start = $this->findMetadataStart($database);
$metadataDecoder = new Decoder($this->fileHandle, $start); $metadataDecoder = new Decoder($this->fileHandle, $start);
[$metadataArray] = $metadataDecoder->decode($start); list($metadataArray) = $metadataDecoder->decode($start);
$this->metadata = new Metadata($metadataArray); $this->metadata = new Metadata($metadataArray);
$this->decoder = new Decoder( $this->decoder = new Decoder(
$this->fileHandle, $this->fileHandle,
@ -119,14 +91,14 @@ class Reader
* *
* @return mixed the record for the IP address * @return mixed the record for the IP address
*/ */
public function get(string $ipAddress) public function get($ipAddress)
{ {
if (\func_num_args() !== 1) { if (\func_num_args() !== 1) {
throw new ArgumentCountError( throw new InvalidArgumentException(
sprintf('%s() expects exactly 1 parameter, %d given', __METHOD__, \func_num_args()) 'Method takes exactly one argument.'
); );
} }
[$record] = $this->getWithPrefixLen($ipAddress); list($record) = $this->getWithPrefixLen($ipAddress);
return $record; return $record;
} }
@ -146,11 +118,11 @@ class Reader
* @return array an array where the first element is the record and the * @return array an array where the first element is the record and the
* second the network prefix length for the record * second the network prefix length for the record
*/ */
public function getWithPrefixLen(string $ipAddress): array public function getWithPrefixLen($ipAddress)
{ {
if (\func_num_args() !== 1) { if (\func_num_args() !== 1) {
throw new ArgumentCountError( throw new InvalidArgumentException(
sprintf('%s() expects exactly 1 parameter, %d given', __METHOD__, \func_num_args()) 'Method takes exactly one argument.'
); );
} }
@ -160,7 +132,13 @@ class Reader
); );
} }
[$pointer, $prefixLen] = $this->findAddressInTree($ipAddress); 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) { if ($pointer === 0) {
return [null, $prefixLen]; return [null, $prefixLen];
} }
@ -168,16 +146,9 @@ class Reader
return [$this->resolveDataPointer($pointer), $prefixLen]; return [$this->resolveDataPointer($pointer), $prefixLen];
} }
private function findAddressInTree(string $ipAddress): array private function findAddressInTree($ipAddress)
{ {
$packedAddr = @inet_pton($ipAddress); $rawAddress = unpack('C*', inet_pton($ipAddress));
if ($packedAddr === false) {
throw new InvalidArgumentException(
"The value \"$ipAddress\" is not a valid IP address."
);
}
$rawAddress = unpack('C*', $packedAddr);
$bitCount = \count($rawAddress) * 8; $bitCount = \count($rawAddress) * 8;
@ -211,18 +182,14 @@ class Reader
if ($node === $nodeCount) { if ($node === $nodeCount) {
// Record is empty // Record is empty
return [0, $i]; return [0, $i];
} } elseif ($node > $nodeCount) {
if ($node > $nodeCount) {
// Record is a data pointer // Record is a data pointer
return [$node, $i]; return [$node, $i];
} }
throw new InvalidDatabaseException('Something bad happened');
throw new InvalidDatabaseException(
'Invalid or corrupt database. Maximum search depth reached without finding a leaf node'
);
} }
private function ipV4StartNode(): int private function ipV4StartNode()
{ {
// If we have an IPv4 database, the start node is the first node // If we have an IPv4 database, the start node is the first node
if ($this->metadata->ipVersion === 4) { if ($this->metadata->ipVersion === 4) {
@ -238,17 +205,16 @@ class Reader
return $node; return $node;
} }
private function readNode(int $nodeNumber, int $index): int private function readNode($nodeNumber, $index)
{ {
$baseOffset = $nodeNumber * $this->metadata->nodeByteSize; $baseOffset = $nodeNumber * $this->metadata->nodeByteSize;
switch ($this->metadata->recordSize) { switch ($this->metadata->recordSize) {
case 24: case 24:
$bytes = Util::read($this->fileHandle, $baseOffset + $index * 3, 3); $bytes = Util::read($this->fileHandle, $baseOffset + $index * 3, 3);
[, $node] = unpack('N', "\x00" . $bytes); list(, $node) = unpack('N', "\x00" . $bytes);
return $node; return $node;
case 28: case 28:
$bytes = Util::read($this->fileHandle, $baseOffset + 3 * $index, 4); $bytes = Util::read($this->fileHandle, $baseOffset + 3 * $index, 4);
if ($index === 0) { if ($index === 0) {
@ -256,16 +222,14 @@ class Reader
} else { } else {
$middle = 0x0F & \ord($bytes[0]); $middle = 0x0F & \ord($bytes[0]);
} }
[, $node] = unpack('N', \chr($middle) . substr($bytes, $index, 3)); list(, $node) = unpack('N', \chr($middle) . substr($bytes, $index, 3));
return $node; return $node;
case 32: case 32:
$bytes = Util::read($this->fileHandle, $baseOffset + $index * 4, 4); $bytes = Util::read($this->fileHandle, $baseOffset + $index * 4, 4);
[, $node] = unpack('N', $bytes); list(, $node) = unpack('N', $bytes);
return $node; return $node;
default: default:
throw new InvalidDatabaseException( throw new InvalidDatabaseException(
'Unknown record size: ' 'Unknown record size: '
@ -274,10 +238,7 @@ class Reader
} }
} }
/** private function resolveDataPointer($pointer)
* @return mixed
*/
private function resolveDataPointer(int $pointer)
{ {
$resolved = $pointer - $this->metadata->nodeCount $resolved = $pointer - $this->metadata->nodeCount
+ $this->metadata->searchTreeSize; + $this->metadata->searchTreeSize;
@ -287,7 +248,7 @@ class Reader
); );
} }
[$data] = $this->decoder->decode($resolved); list($data) = $this->decoder->decode($resolved);
return $data; return $data;
} }
@ -297,7 +258,7 @@ class Reader
* are much faster algorithms (e.g., Boyer-Moore) for this if speed is ever * are much faster algorithms (e.g., Boyer-Moore) for this if speed is ever
* an issue, but I suspect it won't be. * an issue, but I suspect it won't be.
*/ */
private function findMetadataStart(string $filename): int private function findMetadataStart($filename)
{ {
$handle = $this->fileHandle; $handle = $this->fileHandle;
$fstat = fstat($handle); $fstat = fstat($handle);
@ -317,7 +278,6 @@ class Reader
return $offset + $markerLength; return $offset + $markerLength;
} }
} }
throw new InvalidDatabaseException( throw new InvalidDatabaseException(
"Error opening database file ($filename). " . "Error opening database file ($filename). " .
'Is this a valid MaxMind DB file?' 'Is this a valid MaxMind DB file?'
@ -330,11 +290,11 @@ class Reader
* *
* @return Metadata object for the database * @return Metadata object for the database
*/ */
public function metadata(): Metadata public function metadata()
{ {
if (\func_num_args()) { if (\func_num_args()) {
throw new ArgumentCountError( throw new InvalidArgumentException(
sprintf('%s() expects exactly 0 parameters, %d given', __METHOD__, \func_num_args()) 'Method takes no arguments.'
); );
} }
@ -346,7 +306,7 @@ class Reader
); );
} }
return clone $this->metadata; return $this->metadata;
} }
/** /**
@ -355,14 +315,8 @@ class Reader
* @throws Exception * @throws Exception
* if an I/O error occurs * if an I/O error occurs
*/ */
public function close(): void public function close()
{ {
if (\func_num_args()) {
throw new ArgumentCountError(
sprintf('%s() expects exactly 0 parameters, %d given', __METHOD__, \func_num_args())
);
}
if (!\is_resource($this->fileHandle)) { if (!\is_resource($this->fileHandle)) {
throw new BadMethodCallException( throw new BadMethodCallException(
'Attempt to close a closed MaxMind DB.' 'Attempt to close a closed MaxMind DB.'

View File

@ -1,61 +1,63 @@
<?php <?php
declare(strict_types=1);
namespace MaxMind\Db\Reader; namespace MaxMind\Db\Reader;
// @codingStandardsIgnoreLine // @codingStandardsIgnoreLine
use RuntimeException; 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 class Decoder
{ {
/**
* @var resource
*/
private $fileStream; private $fileStream;
/**
* @var int
*/
private $pointerBase; private $pointerBase;
/**
* @var float
*/
private $pointerBaseByteSize; private $pointerBaseByteSize;
/** // This is only used for unit testing
* This is only used for unit testing.
*
* @var bool
*/
private $pointerTestHack; private $pointerTestHack;
/**
* @var bool
*/
private $switchByteOrder; private $switchByteOrder;
private const _EXTENDED = 0; /** @ignore */
private const _POINTER = 1; const _EXTENDED = 0;
private const _UTF8_STRING = 2; /** @ignore */
private const _DOUBLE = 3; const _POINTER = 1;
private const _BYTES = 4; /** @ignore */
private const _UINT16 = 5; const _UTF8_STRING = 2;
private const _UINT32 = 6; /** @ignore */
private const _MAP = 7; const _DOUBLE = 3;
private const _INT32 = 8; /** @ignore */
private const _UINT64 = 9; const _BYTES = 4;
private const _UINT128 = 10; /** @ignore */
private const _ARRAY = 11; const _UINT16 = 5;
private const _CONTAINER = 12; /** @ignore */
private const _END_MARKER = 13; const _UINT32 = 6;
private const _BOOLEAN = 14; /** @ignore */
private const _FLOAT = 15; 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;
/**
* @param resource $fileStream
*/
public function __construct( public function __construct(
$fileStream, $fileStream,
int $pointerBase = 0, $pointerBase = 0,
bool $pointerTestHack = false $pointerTestHack = false
) { ) {
$this->fileStream = $fileStream; $this->fileStream = $fileStream;
$this->pointerBase = $pointerBase; $this->pointerBase = $pointerBase;
@ -66,7 +68,7 @@ class Decoder
$this->switchByteOrder = $this->isPlatformLittleEndian(); $this->switchByteOrder = $this->isPlatformLittleEndian();
} }
public function decode(int $offset): array public function decode($offset)
{ {
$ctrlByte = \ord(Util::read($this->fileStream, $offset, 1)); $ctrlByte = \ord(Util::read($this->fileStream, $offset, 1));
++$offset; ++$offset;
@ -77,14 +79,14 @@ class Decoder
// use the size to determine the length of the pointer and then follow // use the size to determine the length of the pointer and then follow
// it. // it.
if ($type === self::_POINTER) { if ($type === self::_POINTER) {
[$pointer, $offset] = $this->decodePointer($ctrlByte, $offset); list($pointer, $offset) = $this->decodePointer($ctrlByte, $offset);
// for unit testing // for unit testing
if ($this->pointerTestHack) { if ($this->pointerTestHack) {
return [$pointer]; return [$pointer];
} }
[$result] = $this->decode($pointer); list($result) = $this->decode($pointer);
return [$result, $offset]; return [$result, $offset];
} }
@ -106,51 +108,43 @@ class Decoder
++$offset; ++$offset;
} }
[$size, $offset] = $this->sizeFromCtrlByte($ctrlByte, $offset); list($size, $offset) = $this->sizeFromCtrlByte($ctrlByte, $offset);
return $this->decodeByType($type, $offset, $size); return $this->decodeByType($type, $offset, $size);
} }
private function decodeByType(int $type, int $offset, int $size): array private function decodeByType($type, $offset, $size)
{ {
switch ($type) { switch ($type) {
case self::_MAP: case self::_MAP:
return $this->decodeMap($size, $offset); return $this->decodeMap($size, $offset);
case self::_ARRAY: case self::_ARRAY:
return $this->decodeArray($size, $offset); return $this->decodeArray($size, $offset);
case self::_BOOLEAN: case self::_BOOLEAN:
return [$this->decodeBoolean($size), $offset]; return [$this->decodeBoolean($size), $offset];
} }
$newOffset = $offset + $size; $newOffset = $offset + $size;
$bytes = Util::read($this->fileStream, $offset, $size); $bytes = Util::read($this->fileStream, $offset, $size);
switch ($type) { switch ($type) {
case self::_BYTES: case self::_BYTES:
case self::_UTF8_STRING: case self::_UTF8_STRING:
return [$bytes, $newOffset]; return [$bytes, $newOffset];
case self::_DOUBLE: case self::_DOUBLE:
$this->verifySize(8, $size); $this->verifySize(8, $size);
return [$this->decodeDouble($bytes), $newOffset]; return [$this->decodeDouble($bytes), $newOffset];
case self::_FLOAT: case self::_FLOAT:
$this->verifySize(4, $size); $this->verifySize(4, $size);
return [$this->decodeFloat($bytes), $newOffset]; return [$this->decodeFloat($bytes), $newOffset];
case self::_INT32: case self::_INT32:
return [$this->decodeInt32($bytes, $size), $newOffset]; return [$this->decodeInt32($bytes, $size), $newOffset];
case self::_UINT16: case self::_UINT16:
case self::_UINT32: case self::_UINT32:
case self::_UINT64: case self::_UINT64:
case self::_UINT128: case self::_UINT128:
return [$this->decodeUint($bytes, $size), $newOffset]; return [$this->decodeUint($bytes, $size), $newOffset];
default: default:
throw new InvalidDatabaseException( throw new InvalidDatabaseException(
'Unknown or unexpected type: ' . $type 'Unknown or unexpected type: ' . $type
@ -158,7 +152,7 @@ class Decoder
} }
} }
private function verifySize(int $expected, int $actual): void private function verifySize($expected, $actual)
{ {
if ($expected !== $actual) { if ($expected !== $actual) {
throw new InvalidDatabaseException( throw new InvalidDatabaseException(
@ -167,82 +161,86 @@ class Decoder
} }
} }
private function decodeArray(int $size, int $offset): array private function decodeArray($size, $offset)
{ {
$array = []; $array = [];
for ($i = 0; $i < $size; ++$i) { for ($i = 0; $i < $size; ++$i) {
[$value, $offset] = $this->decode($offset); list($value, $offset) = $this->decode($offset);
$array[] = $value; array_push($array, $value);
} }
return [$array, $offset]; return [$array, $offset];
} }
private function decodeBoolean(int $size): bool private function decodeBoolean($size)
{ {
return $size !== 0; return $size === 0 ? false : true;
} }
private function decodeDouble(string $bytes): float private function decodeDouble($bits)
{ {
// This assumes IEEE 754 doubles, but most (all?) modern platforms // This assumes IEEE 754 doubles, but most (all?) modern platforms
// use them. // use them.
[, $double] = unpack('E', $bytes); //
// 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; return $double;
} }
private function decodeFloat(string $bytes): float private function decodeFloat($bits)
{ {
// This assumes IEEE 754 floats, but most (all?) modern platforms // This assumes IEEE 754 floats, but most (all?) modern platforms
// use them. // use them.
[, $float] = unpack('G', $bytes); //
// 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; return $float;
} }
private function decodeInt32(string $bytes, int $size): int private function decodeInt32($bytes, $size)
{ {
switch ($size) { switch ($size) {
case 0: case 0:
return 0; return 0;
case 1: case 1:
case 2: case 2:
case 3: case 3:
$bytes = str_pad($bytes, 4, "\x00", \STR_PAD_LEFT); $bytes = str_pad($bytes, 4, "\x00", STR_PAD_LEFT);
break; break;
case 4: case 4:
break; break;
default: default:
throw new InvalidDatabaseException( throw new InvalidDatabaseException(
"The MaxMind DB file's data section contains bad data (unknown data type or corrupt data)" "The MaxMind DB file's data section contains bad data (unknown data type or corrupt data)"
); );
} }
[, $int] = unpack('l', $this->maybeSwitchByteOrder($bytes)); list(, $int) = unpack('l', $this->maybeSwitchByteOrder($bytes));
return $int; return $int;
} }
private function decodeMap(int $size, int $offset): array private function decodeMap($size, $offset)
{ {
$map = []; $map = [];
for ($i = 0; $i < $size; ++$i) { for ($i = 0; $i < $size; ++$i) {
[$key, $offset] = $this->decode($offset); list($key, $offset) = $this->decode($offset);
[$value, $offset] = $this->decode($offset); list($value, $offset) = $this->decode($offset);
$map[$key] = $value; $map[$key] = $value;
} }
return [$map, $offset]; return [$map, $offset];
} }
private function decodePointer(int $ctrlByte, int $offset): array private function decodePointer($ctrlByte, $offset)
{ {
$pointerSize = (($ctrlByte >> 3) & 0x3) + 1; $pointerSize = (($ctrlByte >> 3) & 0x3) + 1;
@ -252,56 +250,46 @@ class Decoder
switch ($pointerSize) { switch ($pointerSize) {
case 1: case 1:
$packed = \chr($ctrlByte & 0x7) . $buffer; $packed = \chr($ctrlByte & 0x7) . $buffer;
[, $pointer] = unpack('n', $packed); list(, $pointer) = unpack('n', $packed);
$pointer += $this->pointerBase; $pointer += $this->pointerBase;
break; break;
case 2: case 2:
$packed = "\x00" . \chr($ctrlByte & 0x7) . $buffer; $packed = "\x00" . \chr($ctrlByte & 0x7) . $buffer;
[, $pointer] = unpack('N', $packed); list(, $pointer) = unpack('N', $packed);
$pointer += $this->pointerBase + 2048; $pointer += $this->pointerBase + 2048;
break; break;
case 3: case 3:
$packed = \chr($ctrlByte & 0x7) . $buffer; $packed = \chr($ctrlByte & 0x7) . $buffer;
// It is safe to use 'N' here, even on 32 bit machines as the // It is safe to use 'N' here, even on 32 bit machines as the
// first bit is 0. // first bit is 0.
[, $pointer] = unpack('N', $packed); list(, $pointer) = unpack('N', $packed);
$pointer += $this->pointerBase + 526336; $pointer += $this->pointerBase + 526336;
break; break;
case 4: case 4:
// We cannot use unpack here as we might overflow on 32 bit // We cannot use unpack here as we might overflow on 32 bit
// machines // machines
$pointerOffset = $this->decodeUint($buffer, $pointerSize); $pointerOffset = $this->decodeUint($buffer, $pointerSize);
$pointerBase = $this->pointerBase; $byteLength = $pointerSize + $this->pointerBaseByteSize;
if (\PHP_INT_MAX - $pointerBase >= $pointerOffset) { if ($byteLength <= _MM_MAX_INT_BYTES) {
$pointer = $pointerOffset + $pointerBase; $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 { } else {
throw new RuntimeException( throw new RuntimeException(
'The database offset is too large to be represented on your platform.' 'The gmp or bcmath extension must be installed to read this database.'
); );
} }
break;
default:
throw new InvalidDatabaseException(
'Unexpected pointer size ' . $pointerSize
);
} }
return [$pointer, $offset]; return [$pointer, $offset];
} }
// @phpstan-ignore-next-line private function decodeUint($bytes, $byteLength)
private function decodeUint(string $bytes, int $byteLength)
{ {
if ($byteLength === 0) { if ($byteLength === 0) {
return 0; return 0;
@ -309,22 +297,16 @@ class Decoder
$integer = 0; $integer = 0;
// PHP integers are signed. PHP_INT_SIZE - 1 is the number of
// complete bytes that can be converted to an integer. However,
// we can convert another byte if the leading bit is zero.
$useRealInts = $byteLength <= \PHP_INT_SIZE - 1
|| ($byteLength === \PHP_INT_SIZE && (\ord($bytes[0]) & 0x80) === 0);
for ($i = 0; $i < $byteLength; ++$i) { for ($i = 0; $i < $byteLength; ++$i) {
$part = \ord($bytes[$i]); $part = \ord($bytes[$i]);
// We only use gmp or bcmath if the final value is too big // We only use gmp or bcmath if the final value is too big
if ($useRealInts) { if ($byteLength <= _MM_MAX_INT_BYTES) {
$integer = ($integer << 8) + $part; $integer = ($integer << 8) + $part;
} elseif (\extension_loaded('gmp')) { } elseif (\extension_loaded('gmp')) {
$integer = gmp_strval(gmp_add(gmp_mul((string) $integer, '256'), $part)); $integer = gmp_strval(gmp_add(gmp_mul($integer, 256), $part));
} elseif (\extension_loaded('bcmath')) { } elseif (\extension_loaded('bcmath')) {
$integer = bcadd(bcmul((string) $integer, '256'), (string) $part); $integer = bcadd(bcmul($integer, 256), $part);
} else { } else {
throw new RuntimeException( throw new RuntimeException(
'The gmp or bcmath extension must be installed to read this database.' 'The gmp or bcmath extension must be installed to read this database.'
@ -335,9 +317,9 @@ class Decoder
return $integer; return $integer;
} }
private function sizeFromCtrlByte(int $ctrlByte, int $offset): array private function sizeFromCtrlByte($ctrlByte, $offset)
{ {
$size = $ctrlByte & 0x1F; $size = $ctrlByte & 0x1f;
if ($size < 29) { if ($size < 29) {
return [$size, $offset]; return [$size, $offset];
@ -349,22 +331,22 @@ class Decoder
if ($size === 29) { if ($size === 29) {
$size = 29 + \ord($bytes); $size = 29 + \ord($bytes);
} elseif ($size === 30) { } elseif ($size === 30) {
[, $adjust] = unpack('n', $bytes); list(, $adjust) = unpack('n', $bytes);
$size = 285 + $adjust; $size = 285 + $adjust;
} else { } elseif ($size > 30) {
[, $adjust] = unpack('N', "\x00" . $bytes); list(, $adjust) = unpack('N', "\x00" . $bytes);
$size = $adjust + 65821; $size = $adjust + 65821;
} }
return [$size, $offset + $bytesToRead]; return [$size, $offset + $bytesToRead];
} }
private function maybeSwitchByteOrder(string $bytes): string private function maybeSwitchByteOrder($bytes)
{ {
return $this->switchByteOrder ? strrev($bytes) : $bytes; return $this->switchByteOrder ? strrev($bytes) : $bytes;
} }
private function isPlatformLittleEndian(): bool private function isPlatformLittleEndian()
{ {
$testint = 0x00FF; $testint = 0x00FF;
$packed = pack('S', $testint); $packed = pack('S', $testint);

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace MaxMind\Db\Reader; namespace MaxMind\Db\Reader;
use Exception; use Exception;

View File

@ -1,100 +1,71 @@
<?php <?php
declare(strict_types=1);
namespace MaxMind\Db\Reader; namespace MaxMind\Db\Reader;
use ArgumentCountError;
/** /**
* This class provides the metadata for the MaxMind DB file. * 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 class Metadata
{ {
/** private $binaryFormatMajorVersion;
* This is an unsigned 16-bit integer indicating the major version number private $binaryFormatMinorVersion;
* for the database's binary format. private $buildEpoch;
* private $databaseType;
* @var int private $description;
*/ private $ipVersion;
public $binaryFormatMajorVersion; private $languages;
/** private $nodeByteSize;
* This is an unsigned 16-bit integer indicating the minor version number private $nodeCount;
* for the database's binary format. private $recordSize;
* private $searchTreeSize;
* @var int
*/
public $binaryFormatMinorVersion;
/**
* This is an unsigned 64-bit integer that contains the database build
* timestamp as a Unix epoch value.
*
* @var int
*/
public $buildEpoch;
/**
* 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.
*
* @var string
*/
public $databaseType;
/**
* 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.
*
* @var array
*/
public $description;
/**
* This is an unsigned 16-bit integer which is always 4 or 6. It indicates
* whether the database contains IPv4 or IPv6 address data.
*
* @var int
*/
public $ipVersion;
/**
* 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.
*
* @var array
*/
public $languages;
/**
* @var int
*/
public $nodeByteSize;
/**
* This is an unsigned 32-bit integer indicating the number of nodes in
* the search tree.
*
* @var int
*/
public $nodeCount;
/**
* 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.
*
* @var int
*/
public $recordSize;
/**
* @var int
*/
public $searchTreeSize;
public function __construct(array $metadata) public function __construct($metadata)
{ {
if (\func_num_args() !== 1) {
throw new ArgumentCountError(
sprintf('%s() expects exactly 1 parameter, %d given', __METHOD__, \func_num_args())
);
}
$this->binaryFormatMajorVersion = $this->binaryFormatMajorVersion =
$metadata['binary_format_major_version']; $metadata['binary_format_major_version'];
$this->binaryFormatMinorVersion = $this->binaryFormatMinorVersion =
@ -109,4 +80,9 @@ class Metadata
$this->nodeByteSize = $this->recordSize / 4; $this->nodeByteSize = $this->recordSize / 4;
$this->searchTreeSize = $this->nodeCount * $this->nodeByteSize; $this->searchTreeSize = $this->nodeCount * $this->nodeByteSize;
} }
public function __get($var)
{
return $this->$var;
}
} }

View File

@ -1,15 +1,10 @@
<?php <?php
declare(strict_types=1);
namespace MaxMind\Db\Reader; namespace MaxMind\Db\Reader;
class Util class Util
{ {
/** public static function read($stream, $offset, $numberOfBytes)
* @param resource $stream
*/
public static function read($stream, int $offset, int $numberOfBytes): string
{ {
if ($numberOfBytes === 0) { if ($numberOfBytes === 0) {
return ''; return '';
@ -20,11 +15,10 @@ class Util
// We check that the number of bytes read is equal to the number // 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 // asked for. We use ftell as getting the length of $value is
// much slower. // much slower.
if ($value !== false && ftell($stream) - $offset === $numberOfBytes) { if (ftell($stream) - $offset === $numberOfBytes) {
return $value; return $value;
} }
} }
throw new InvalidDatabaseException( throw new InvalidDatabaseException(
'The MaxMind DB file contains bad data' 'The MaxMind DB file contains bad data'
); );

View File

@ -1,26 +1,6 @@
CHANGELOG CHANGELOG
========= =========
0.9.0 (2022-03-28)
------------------
* Improved internal type hint usage.
0.8.1 (2020-11-02)
------------------
* We now correctly handle responses without a `Content-Type` header. In 0.8.0,
such responses could lead to a type error. In particular, this affected the
minFraud Report Transaction endpoint, which returns a response with no
content. Reported by Dmitry Malashko. GitHub #99 on
`maxmind/minfraud-api-php`.
0.8.0 (2020-10-01)
------------------
* PHP 7.2 or greater is now required.
* Added additional type hints.
0.7.0 (2020-05-06) 0.7.0 (2020-05-06)
------------------ ------------------

View File

@ -5,7 +5,7 @@ shared code between MaxMind's various web service client APIs.
## Requirements ## ## Requirements ##
The library requires PHP 7.2 or greater. The library requires PHP 5.6 or greater.
There are several other dependencies as defined in the `composer.json` file. There are several other dependencies as defined in the `composer.json` file.

View File

@ -12,16 +12,15 @@
} }
], ],
"require": { "require": {
"php": ">=7.2", "php": ">=5.6",
"composer/ca-bundle": "^1.0.3", "composer/ca-bundle": "^1.0.3",
"ext-curl": "*", "ext-curl": "*",
"ext-json": "*" "ext-json": "*"
}, },
"require-dev": { "require-dev": {
"friendsofphp/php-cs-fixer": "3.*", "friendsofphp/php-cs-fixer": "2.*",
"phpunit/phpunit": "^8.0 || ^9.0", "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0",
"squizlabs/php_codesniffer": "3.*", "squizlabs/php_codesniffer": "3.*"
"phpstan/phpstan": "*"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {

View File

@ -51,6 +51,10 @@ fi
git push git push
gh release create --target "$(git branch --show-current)" -t "$version" -n "$notes" "$tag" message="$version
$notes"
hub release create -m "$message" "$tag"
git push --tags git push --tags

View File

@ -1,7 +0,0 @@
parameters:
level: 6
paths:
- src
- tests
checkMissingIterableValueType: false

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace MaxMind\Exception; namespace MaxMind\Exception;
/** /**

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace MaxMind\Exception; namespace MaxMind\Exception;
/** /**
@ -11,8 +9,6 @@ class HttpException extends WebServiceException
{ {
/** /**
* The URI queried. * The URI queried.
*
* @var string
*/ */
private $uri; private $uri;
@ -23,21 +19,21 @@ class HttpException extends WebServiceException
* @param \Exception $previous the previous exception, if any * @param \Exception $previous the previous exception, if any
*/ */
public function __construct( public function __construct(
string $message, $message,
int $httpStatus, $httpStatus,
string $uri, $uri,
\Exception $previous = null \Exception $previous = null
) { ) {
$this->uri = $uri; $this->uri = $uri;
parent::__construct($message, $httpStatus, $previous); parent::__construct($message, $httpStatus, $previous);
} }
public function getUri(): string public function getUri()
{ {
return $this->uri; return $this->uri;
} }
public function getStatusCode(): int public function getStatusCode()
{ {
return $this->getCode(); return $this->getCode();
} }

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace MaxMind\Exception; namespace MaxMind\Exception;
/** /**

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace MaxMind\Exception; namespace MaxMind\Exception;
/** /**

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace MaxMind\Exception; namespace MaxMind\Exception;
/** /**
@ -11,30 +9,28 @@ class InvalidRequestException extends HttpException
{ {
/** /**
* The code returned by the MaxMind web service. * The code returned by the MaxMind web service.
*
* @var string
*/ */
private $error; private $error;
/** /**
* @param string $message the exception message * @param string $message the exception message
* @param string $error the error code returned by the MaxMind web service * @param int $error the error code returned by the MaxMind web service
* @param int $httpStatus the HTTP status code of the response * @param int $httpStatus the HTTP status code of the response
* @param string $uri the URI queries * @param string $uri the URI queries
* @param \Exception $previous the previous exception, if any * @param \Exception $previous the previous exception, if any
*/ */
public function __construct( public function __construct(
string $message, $message,
string $error, $error,
int $httpStatus, $httpStatus,
string $uri, $uri,
\Exception $previous = null \Exception $previous = null
) { ) {
$this->error = $error; $this->error = $error;
parent::__construct($message, $httpStatus, $uri, $previous); parent::__construct($message, $httpStatus, $uri, $previous);
} }
public function getErrorCode(): string public function getErrorCode()
{ {
return $this->error; return $this->error;
} }

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace MaxMind\Exception; namespace MaxMind\Exception;
class IpAddressNotFoundException extends InvalidRequestException class IpAddressNotFoundException extends InvalidRequestException

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace MaxMind\Exception; namespace MaxMind\Exception;
/** /**

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace MaxMind\Exception; namespace MaxMind\Exception;
/** /**

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace MaxMind\WebService; namespace MaxMind\WebService;
use Composer\CaBundle\CaBundle; use Composer\CaBundle\CaBundle;
@ -24,56 +22,16 @@ use MaxMind\WebService\Http\RequestFactory;
*/ */
class Client class Client
{ {
public const VERSION = '0.2.0'; const VERSION = '0.2.0';
/**
* @var string|null
*/
private $caBundle; private $caBundle;
/**
* @var float|null
*/
private $connectTimeout; private $connectTimeout;
/**
* @var string
*/
private $host = 'api.maxmind.com'; private $host = 'api.maxmind.com';
/**
* @var bool
*/
private $useHttps = true;
/**
* @var RequestFactory
*/
private $httpRequestFactory; private $httpRequestFactory;
/**
* @var string
*/
private $licenseKey; private $licenseKey;
/**
* @var string|null
*/
private $proxy; private $proxy;
/**
* @var float|null
*/
private $timeout; private $timeout;
/**
* @var string
*/
private $userAgentPrefix; private $userAgentPrefix;
/**
* @var int
*/
private $accountId; private $accountId;
/** /**
@ -81,7 +39,6 @@ class Client
* @param string $licenseKey your MaxMind license key * @param string $licenseKey your MaxMind license key
* @param array $options an array of options. Possible keys: * @param array $options an array of options. Possible keys:
* * `host` - The host to use when connecting to the web service. * * `host` - The host to use when connecting to the web service.
* * `useHttps` - A boolean flag for sending the request via https.(True by default)
* * `userAgent` - The prefix of the User-Agent to use in the request. * * `userAgent` - The prefix of the User-Agent to use in the request.
* * `caBundle` - The bundle of CA root certificates 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. * * `connectTimeout` - The connect timeout to use for the request.
@ -90,9 +47,9 @@ class Client
* username, and password, e.g., `http://username:password@127.0.0.1:10`. * username, and password, e.g., `http://username:password@127.0.0.1:10`.
*/ */
public function __construct( public function __construct(
int $accountId, $accountId,
string $licenseKey, $licenseKey,
array $options = [] $options = []
) { ) {
$this->accountId = $accountId; $this->accountId = $accountId;
$this->licenseKey = $licenseKey; $this->licenseKey = $licenseKey;
@ -104,9 +61,6 @@ class Client
if (isset($options['host'])) { if (isset($options['host'])) {
$this->host = $options['host']; $this->host = $options['host'];
} }
if (isset($options['useHttps'])) {
$this->useHttps = $options['useHttps'];
}
if (isset($options['userAgent'])) { if (isset($options['userAgent'])) {
$this->userAgentPrefix = $options['userAgent'] . ' '; $this->userAgentPrefix = $options['userAgent'] . ' ';
} }
@ -142,9 +96,9 @@ class Client
* @throws WebServiceException when some other error occurs. This also * @throws WebServiceException when some other error occurs. This also
* serves as the base class for the above exceptions. * serves as the base class for the above exceptions.
* *
* @return array|null The decoded content of a successful response * @return array The decoded content of a successful response
*/ */
public function post(string $service, string $path, array $input): ?array public function post($service, $path, $input)
{ {
$requestBody = json_encode($input); $requestBody = json_encode($input);
if ($requestBody === false) { if ($requestBody === false) {
@ -159,7 +113,7 @@ class Client
['Content-Type: application/json'] ['Content-Type: application/json']
); );
[$statusCode, $contentType, $responseBody] = $request->post($requestBody); list($statusCode, $contentType, $responseBody) = $request->post($requestBody);
return $this->handleResponse( return $this->handleResponse(
$statusCode, $statusCode,
@ -170,13 +124,11 @@ class Client
); );
} }
public function get(string $service, string $path): ?array public function get($service, $path)
{ {
$request = $this->createRequest( $request = $this->createRequest($path);
$path
);
[$statusCode, $contentType, $responseBody] = $request->get(); list($statusCode, $contentType, $responseBody) = $request->get();
return $this->handleResponse( return $this->handleResponse(
$statusCode, $statusCode,
@ -187,15 +139,15 @@ class Client
); );
} }
private function userAgent(): string private function userAgent()
{ {
$curlVersion = curl_version(); $curlVersion = curl_version();
return $this->userAgentPrefix . 'MaxMind-WS-API/' . self::VERSION . ' PHP/' . \PHP_VERSION . return $this->userAgentPrefix . 'MaxMind-WS-API/' . self::VERSION . ' PHP/' . PHP_VERSION .
' curl/' . $curlVersion['version']; ' curl/' . $curlVersion['version'];
} }
private function createRequest(string $path, array $headers = []): Http\Request private function createRequest($path, $headers = [])
{ {
array_push( array_push(
$headers, $headers,
@ -218,11 +170,11 @@ class Client
} }
/** /**
* @param int $statusCode the HTTP status code of the response * @param int $statusCode the HTTP status code of the response
* @param string|null $contentType the Content-Type of the response * @param string $contentType the Content-Type of the response
* @param string|null $responseBody the response body * @param string $responseBody the response body
* @param string $service the name of the service * @param string $service the name of the service
* @param string $path the path used in the request * @param string $path the path used in the request
* *
* @throws AuthenticationException when there is an issue authenticating the * @throws AuthenticationException when there is an issue authenticating the
* request * request
@ -233,15 +185,15 @@ class Client
* @throws WebServiceException when some other error occurs. This also * @throws WebServiceException when some other error occurs. This also
* serves as the base class for the above exceptions * serves as the base class for the above exceptions
* *
* @return array|null The decoded content of a successful response * @return array The decoded content of a successful response
*/ */
private function handleResponse( private function handleResponse(
int $statusCode, $statusCode,
?string $contentType, $contentType,
?string $responseBody, $responseBody,
string $service, $service,
string $path $path
): ?array { ) {
if ($statusCode >= 400 && $statusCode <= 499) { if ($statusCode >= 400 && $statusCode <= 499) {
$this->handle4xx($statusCode, $contentType, $responseBody, $service, $path); $this->handle4xx($statusCode, $contentType, $responseBody, $service, $path);
} elseif ($statusCode >= 500) { } elseif ($statusCode >= 500) {
@ -256,26 +208,20 @@ class Client
/** /**
* @return string describing the JSON error * @return string describing the JSON error
*/ */
private function jsonErrorDescription(): string private function jsonErrorDescription()
{ {
$errno = json_last_error(); $errno = json_last_error();
switch ($errno) { switch ($errno) {
case \JSON_ERROR_DEPTH: case JSON_ERROR_DEPTH:
return 'The maximum stack depth has been exceeded.'; return 'The maximum stack depth has been exceeded.';
case JSON_ERROR_STATE_MISMATCH:
case \JSON_ERROR_STATE_MISMATCH:
return 'Invalid or malformed JSON.'; return 'Invalid or malformed JSON.';
case JSON_ERROR_CTRL_CHAR:
case \JSON_ERROR_CTRL_CHAR:
return 'Control character error.'; return 'Control character error.';
case JSON_ERROR_SYNTAX:
case \JSON_ERROR_SYNTAX:
return 'Syntax error.'; return 'Syntax error.';
case JSON_ERROR_UTF8:
case \JSON_ERROR_UTF8:
return 'Malformed UTF-8 characters.'; return 'Malformed UTF-8 characters.';
default: default:
return "Other JSON error ($errno)."; return "Other JSON error ($errno).";
} }
@ -286,17 +232,17 @@ class Client
* *
* @return string the constructed URL * @return string the constructed URL
*/ */
private function urlFor(string $path): string private function urlFor($path)
{ {
return ($this->useHttps ? 'https://' : 'http://') . $this->host . $path; return 'https://' . $this->host . $path;
} }
/** /**
* @param int $statusCode the HTTP status code * @param int $statusCode the HTTP status code
* @param string|null $contentType the response content-type * @param string $contentType the response content-type
* @param string|null $body the response body * @param string $body the response body
* @param string $service the service name * @param string $service the service name
* @param string $path the path used in the request * @param string $path the path used in the request
* *
* @throws AuthenticationException * @throws AuthenticationException
* @throws HttpException * @throws HttpException
@ -304,20 +250,20 @@ class Client
* @throws InvalidRequestException * @throws InvalidRequestException
*/ */
private function handle4xx( private function handle4xx(
int $statusCode, $statusCode,
?string $contentType, $contentType,
?string $body, $body,
string $service, $service,
string $path $path
): void { ) {
if ($body === null || $body === '') { if (\strlen($body) === 0) {
throw new HttpException( throw new HttpException(
"Received a $statusCode error for $service with no body", "Received a $statusCode error for $service with no body",
$statusCode, $statusCode,
$this->urlFor($path) $this->urlFor($path)
); );
} }
if ($contentType === null || !strstr($contentType, 'json')) { if (!strstr($contentType, 'json')) {
throw new HttpException( throw new HttpException(
"Received a $statusCode error for $service with " . "Received a $statusCode error for $service with " .
'the following body: ' . $body, 'the following body: ' . $body,
@ -365,11 +311,11 @@ class Client
* @throws InsufficientFundsException * @throws InsufficientFundsException
*/ */
private function handleWebServiceError( private function handleWebServiceError(
string $message, $message,
string $code, $code,
int $statusCode, $statusCode,
string $path $path
): void { ) {
switch ($code) { switch ($code) {
case 'IP_ADDRESS_NOT_FOUND': case 'IP_ADDRESS_NOT_FOUND':
case 'IP_ADDRESS_RESERVED': case 'IP_ADDRESS_RESERVED':
@ -379,7 +325,6 @@ class Client
$statusCode, $statusCode,
$this->urlFor($path) $this->urlFor($path)
); );
case 'ACCOUNT_ID_REQUIRED': case 'ACCOUNT_ID_REQUIRED':
case 'ACCOUNT_ID_UNKNOWN': case 'ACCOUNT_ID_UNKNOWN':
case 'AUTHORIZATION_INVALID': case 'AUTHORIZATION_INVALID':
@ -392,7 +337,6 @@ class Client
$statusCode, $statusCode,
$this->urlFor($path) $this->urlFor($path)
); );
case 'OUT_OF_QUERIES': case 'OUT_OF_QUERIES':
case 'INSUFFICIENT_FUNDS': case 'INSUFFICIENT_FUNDS':
throw new InsufficientFundsException( throw new InsufficientFundsException(
@ -401,7 +345,6 @@ class Client
$statusCode, $statusCode,
$this->urlFor($path) $this->urlFor($path)
); );
case 'PERMISSION_REQUIRED': case 'PERMISSION_REQUIRED':
throw new PermissionRequiredException( throw new PermissionRequiredException(
$message, $message,
@ -409,7 +352,6 @@ class Client
$statusCode, $statusCode,
$this->urlFor($path) $this->urlFor($path)
); );
default: default:
throw new InvalidRequestException( throw new InvalidRequestException(
$message, $message,
@ -427,7 +369,7 @@ class Client
* *
* @throws HttpException * @throws HttpException
*/ */
private function handle5xx(int $statusCode, string $service, string $path): void private function handle5xx($statusCode, $service, $path)
{ {
throw new HttpException( throw new HttpException(
"Received a server error ($statusCode) for $service", "Received a server error ($statusCode) for $service",
@ -443,7 +385,7 @@ class Client
* *
* @throws HttpException * @throws HttpException
*/ */
private function handleUnexpectedStatus(int $statusCode, string $service, string $path): void private function handleUnexpectedStatus($statusCode, $service, $path)
{ {
throw new HttpException( throw new HttpException(
'Received an unexpected HTTP status ' . 'Received an unexpected HTTP status ' .
@ -454,22 +396,22 @@ class Client
} }
/** /**
* @param int $statusCode the HTTP status code * @param int $statusCode the HTTP status code
* @param string|null $body the successful request body * @param string $body the successful request body
* @param string $service the service name * @param string $service the service name
* *
* @throws WebServiceException if a response body is included but not * @throws WebServiceException if a response body is included but not
* expected, or is not expected but not * expected, or is not expected but not
* included, or is expected and included * included, or is expected and included
* but cannot be decoded as JSON * but cannot be decoded as JSON
* *
* @return array|null the decoded request body * @return array the decoded request body
*/ */
private function handleSuccess(int $statusCode, ?string $body, string $service): ?array private function handleSuccess($statusCode, $body, $service)
{ {
// A 204 should have no response body // A 204 should have no response body
if ($statusCode === 204) { if ($statusCode === 204) {
if ($body !== null && $body !== '') { if (\strlen($body) !== 0) {
throw new WebServiceException( throw new WebServiceException(
"Received a 204 response for $service along with an " . "Received a 204 response for $service along with an " .
"unexpected HTTP body: $body" "unexpected HTTP body: $body"
@ -480,7 +422,7 @@ class Client
} }
// A 200 should have a valid JSON body // A 200 should have a valid JSON body
if ($body === null || $body === '') { if (\strlen($body) === 0) {
throw new WebServiceException( throw new WebServiceException(
"Received a 200 response for $service but did not " . "Received a 200 response for $service but did not " .
'receive a HTTP body.' 'receive a HTTP body.'
@ -499,7 +441,7 @@ class Client
return $decodedContent; return $decodedContent;
} }
private function getCaBundle(): ?string private function getCaBundle()
{ {
$curlVersion = curl_version(); $curlVersion = curl_version();

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace MaxMind\WebService\Http; namespace MaxMind\WebService\Http;
use MaxMind\Exception\HttpException; use MaxMind\Exception\HttpException;
@ -14,7 +12,7 @@ use MaxMind\Exception\HttpException;
class CurlRequest implements Request class CurlRequest implements Request
{ {
/** /**
* @var \CurlHandle * @var resource
*/ */
private $ch; private $ch;
@ -28,7 +26,11 @@ class CurlRequest implements Request
*/ */
private $options; private $options;
public function __construct(string $url, array $options) /**
* @param string $url
* @param array $options
*/
public function __construct($url, $options)
{ {
$this->url = $url; $this->url = $url;
$this->options = $options; $this->options = $options;
@ -36,65 +38,69 @@ class CurlRequest implements Request
} }
/** /**
* @param string $body
*
* @throws HttpException * @throws HttpException
*
* @return array
*/ */
public function post(string $body): array public function post($body)
{ {
$curl = $this->createCurl(); $curl = $this->createCurl();
curl_setopt($curl, \CURLOPT_POST, true); curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, \CURLOPT_POSTFIELDS, $body); curl_setopt($curl, CURLOPT_POSTFIELDS, $body);
return $this->execute($curl); return $this->execute($curl);
} }
public function get(): array public function get()
{ {
$curl = $this->createCurl(); $curl = $this->createCurl();
curl_setopt($curl, \CURLOPT_HTTPGET, true); curl_setopt($curl, CURLOPT_HTTPGET, true);
return $this->execute($curl); return $this->execute($curl);
} }
/** /**
* @return \CurlHandle * @return resource
*/ */
private function createCurl() private function createCurl()
{ {
curl_reset($this->ch); curl_reset($this->ch);
$opts = []; $opts = [];
$opts[\CURLOPT_URL] = $this->url; $opts[CURLOPT_URL] = $this->url;
if (!empty($this->options['caBundle'])) { if (!empty($this->options['caBundle'])) {
$opts[\CURLOPT_CAINFO] = $this->options['caBundle']; $opts[CURLOPT_CAINFO] = $this->options['caBundle'];
} }
$opts[\CURLOPT_ENCODING] = ''; $opts[CURLOPT_ENCODING] = '';
$opts[\CURLOPT_SSL_VERIFYHOST] = 2; $opts[CURLOPT_SSL_VERIFYHOST] = 2;
$opts[\CURLOPT_FOLLOWLOCATION] = false; $opts[CURLOPT_FOLLOWLOCATION] = false;
$opts[\CURLOPT_SSL_VERIFYPEER] = true; $opts[CURLOPT_SSL_VERIFYPEER] = true;
$opts[\CURLOPT_RETURNTRANSFER] = true; $opts[CURLOPT_RETURNTRANSFER] = true;
$opts[\CURLOPT_HTTPHEADER] = $this->options['headers']; $opts[CURLOPT_HTTPHEADER] = $this->options['headers'];
$opts[\CURLOPT_USERAGENT] = $this->options['userAgent']; $opts[CURLOPT_USERAGENT] = $this->options['userAgent'];
$opts[\CURLOPT_PROXY] = $this->options['proxy']; $opts[CURLOPT_PROXY] = $this->options['proxy'];
// The defined()s are here as the *_MS opts are not available on older // The defined()s are here as the *_MS opts are not available on older
// cURL versions // cURL versions
$connectTimeout = $this->options['connectTimeout']; $connectTimeout = $this->options['connectTimeout'];
if (\defined('CURLOPT_CONNECTTIMEOUT_MS')) { if (\defined('CURLOPT_CONNECTTIMEOUT_MS')) {
$opts[\CURLOPT_CONNECTTIMEOUT_MS] = ceil($connectTimeout * 1000); $opts[CURLOPT_CONNECTTIMEOUT_MS] = ceil($connectTimeout * 1000);
} else { } else {
$opts[\CURLOPT_CONNECTTIMEOUT] = ceil($connectTimeout); $opts[CURLOPT_CONNECTTIMEOUT] = ceil($connectTimeout);
} }
$timeout = $this->options['timeout']; $timeout = $this->options['timeout'];
if (\defined('CURLOPT_TIMEOUT_MS')) { if (\defined('CURLOPT_TIMEOUT_MS')) {
$opts[\CURLOPT_TIMEOUT_MS] = ceil($timeout * 1000); $opts[CURLOPT_TIMEOUT_MS] = ceil($timeout * 1000);
} else { } else {
$opts[\CURLOPT_TIMEOUT] = ceil($timeout); $opts[CURLOPT_TIMEOUT] = ceil($timeout);
} }
curl_setopt_array($this->ch, $opts); curl_setopt_array($this->ch, $opts);
@ -103,11 +109,13 @@ class CurlRequest implements Request
} }
/** /**
* @param \CurlHandle $curl * @param resource $curl
* *
* @throws HttpException * @throws HttpException
*
* @return array
*/ */
private function execute($curl): array private function execute($curl)
{ {
$body = curl_exec($curl); $body = curl_exec($curl);
if ($errno = curl_errno($curl)) { if ($errno = curl_errno($curl)) {
@ -120,17 +128,9 @@ class CurlRequest implements Request
); );
} }
$statusCode = curl_getinfo($curl, \CURLINFO_HTTP_CODE); $statusCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$contentType = curl_getinfo($curl, \CURLINFO_CONTENT_TYPE); $contentType = curl_getinfo($curl, CURLINFO_CONTENT_TYPE);
return [ return [$statusCode, $contentType, $body];
$statusCode,
// The PHP docs say "Content-Type: of the requested document. NULL
// indicates server did not send valid Content-Type: header" for
// CURLINFO_CONTENT_TYPE. However, it will return FALSE if no header
// is set. To keep our types simple, we return null in this case.
($contentType === false ? null : $contentType),
$body,
];
} }
} }

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace MaxMind\WebService\Http; namespace MaxMind\WebService\Http;
/** /**
@ -11,9 +9,21 @@ namespace MaxMind\WebService\Http;
*/ */
interface Request interface Request
{ {
public function __construct(string $url, array $options); /**
* @param string $url
* @param array $options
*/
public function __construct($url, $options);
public function post(string $body): array; /**
* @param string $body
*
* @return mixed
*/
public function post($body);
public function get(): array; /**
* @return mixed
*/
public function get();
} }

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace MaxMind\WebService\Http; namespace MaxMind\WebService\Http;
/** /**
@ -16,7 +14,7 @@ class RequestFactory
* done the connection is kept alive, SSL resumption can be used * done the connection is kept alive, SSL resumption can be used
* etcetera. * etcetera.
* *
* @var \CurlHandle|null * @var resource
*/ */
private $ch; private $ch;
@ -27,9 +25,6 @@ class RequestFactory
} }
} }
/**
* @return \CurlHandle
*/
private function getCurlHandle() private function getCurlHandle()
{ {
if (empty($this->ch)) { if (empty($this->ch)) {
@ -39,7 +34,13 @@ class RequestFactory
return $this->ch; return $this->ch;
} }
public function request(string $url, array $options): Request /**
* @param string $url
* @param array $options
*
* @return Request
*/
public function request($url, $options)
{ {
$options['curlHandle'] = $this->getCurlHandle(); $options['curlHandle'] = $this->getCurlHandle();

View File

@ -0,0 +1,60 @@
# DO NOT EDIT THIS FILE!
#
# It's auto-generated by sonata-project/dev-kit package.
all:
@echo "Please choose a task."
.PHONY: all
lint: lint-composer lint-yaml lint-composer lint-xml lint-php
.PHONY: lint
lint-composer:
composer validate
.PHONY: lint-composer
lint-yaml:
yaml-lint --ignore-non-yaml-files --quiet --exclude vendor .
.PHONY: lint-yaml
lint-xml:
find . \( -name '*.xml' -or -name '*.xliff' \) \
-not -path './vendor/*' \
-not -path './src/Resources/public/vendor/*' \
| while read xmlFile; \
do \
XMLLINT_INDENT=' ' xmllint --encode UTF-8 --format "$$xmlFile"|diff - "$$xmlFile"; \
if [ $$? -ne 0 ] ;then exit 1; fi; \
done
.PHONY: lint-xml
lint-php:
php-cs-fixer fix --ansi --verbose --diff --dry-run
.PHONY: lint-php
cs-fix: cs-fix-php cs-fix-xml
.PHONY: cs-fix
cs-fix-php:
php-cs-fixer fix --verbose
.PHONY: cs-fix-php
cs-fix-xml:
find . \( -name '*.xml' -or -name '*.xliff' \) \
-not -path './vendor/*' \
-not -path './src/Resources/public/vendor/*' \
| while read xmlFile; \
do \
XMLLINT_INDENT=' ' xmllint --encode UTF-8 --format "$$xmlFile" --output "$$xmlFile"; \
done
.PHONY: cs-fix-xml
test:
phpunit -c phpunit.xml.dist --coverage-clover build/logs/clover.xml
.PHONY: test
docs:
cd docs && sphinx-build -W -b html -d _build/doctrees . _build/html
.PHONY: docs

View File

@ -22,10 +22,10 @@
} }
], ],
"require": { "require": {
"php": "^7.3 || ^8.0" "php": "^7.1"
}, },
"require-dev": { "require-dev": {
"symfony/phpunit-bridge": "^5.1.8" "symfony/phpunit-bridge": "^4.0"
}, },
"config": { "config": {
"sort-packages": true "sort-packages": true

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
DO NOT EDIT THIS FILE!
It's auto-generated by sonata-project/dev-kit package.
-->
<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
bootstrap="tests/bootstrap.php"
>
<testsuites>
<testsuite name="Sonata Google Authenticator Test Suite">
<directory suffix="Test.php">./tests/</directory>
</testsuite>
</testsuites>
<listeners>
<listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener" />
</listeners>
<filter>
<whitelist>
<directory suffix=".php">./src/</directory>
</whitelist>
</filter>
<php>
<ini name="precision" value="8"/>
</php>
</phpunit>

View File

@ -82,7 +82,7 @@ class User
public function isOTP() public function isOTP()
{ {
if (isset($_SESSION['OTP']) && true === $_SESSION['OTP']) { if (isset($_SESSION['OTP']) && true == $_SESSION['OTP']) {
return true; return true;
} }
@ -91,8 +91,8 @@ class User
public function isLoggedIn() public function isLoggedIn()
{ {
if (isset($_SESSION['loggedin']) && true === $_SESSION['loggedin'] && if (isset($_SESSION['loggedin']) && true == $_SESSION['loggedin'] &&
isset($_SESSION['ua']) && $_SESSION['ua'] === $_SERVER['HTTP_USER_AGENT'] isset($_SESSION['ua']) && $_SESSION['ua'] == $_SERVER['HTTP_USER_AGENT']
) { ) {
return $_SESSION['username']; return $_SESSION['username'];
} }
@ -143,9 +143,9 @@ class User
$daysUntilInvalid = 0; $daysUntilInvalid = 0;
$time = (string) floor((time() / (3600 * 24))); // get day number $time = (string) floor((time() / (3600 * 24))); // get day number
if (isset($_COOKIE['otp'])) { if (isset($_COOKIE['otp'])) {
[$otpday, $hash] = explode(':', $_COOKIE['otp']); list($otpday, $hash) = explode(':', $_COOKIE['otp']);
if ($otpday >= $time - $daysUntilInvalid && $hash === hash_hmac('sha1', $this->getUsername().':'.$otpday.':'.$_SERVER['HTTP_USER_AGENT'], $this->getSecret())) { if ($otpday >= $time - $daysUntilInvalid && $hash == hash_hmac('sha1', $this->getUsername().':'.$otpday.':'.$_SERVER['HTTP_USER_AGENT'], $this->getSecret())) {
return true; return true;
} }
} }

View File

@ -65,7 +65,7 @@ if ($username = $users->hasSession()) {
include __DIR__.'/../tmpl/login.php'; include __DIR__.'/../tmpl/login.php';
} }
} }
exit(); die();
} }
//if the username is set in _POST, then we assume the user filled in the login form. //if the username is set in _POST, then we assume the user filled in the login form.
@ -101,14 +101,14 @@ if ($username = $users->hasSession()) {
} }
} }
exit(); die();
} }
} }
// if we're here, something went wrong, destroy the session and show a login error // if we're here, something went wrong, destroy the session and show a login error
session_destroy(); session_destroy();
include __DIR__.'/../tmpl/login-error.php'; include __DIR__.'/../tmpl/login-error.php';
exit(); die();
} }
// if neither a session nor tried to submit the login credentials -> login screen // if neither a session nor tried to submit the login credentials -> login screen

View File

@ -66,10 +66,10 @@ final class FixedBitNotation
* @param bool $padFinalGroup Add padding to end of encoded output * @param bool $padFinalGroup Add padding to end of encoded output
* @param string $padCharacter Character to use for padding * @param string $padCharacter Character to use for padding
*/ */
public function __construct(int $bitsPerCharacter, ?string $chars = null, bool $rightPadFinalBits = false, bool $padFinalGroup = false, string $padCharacter = '=') public function __construct(int $bitsPerCharacter, string $chars = null, bool $rightPadFinalBits = false, bool $padFinalGroup = false, string $padCharacter = '=')
{ {
// Ensure validity of $chars // Ensure validity of $chars
if (!\is_string($chars) || ($charLength = \strlen($chars)) < 2) { if (!is_string($chars) || ($charLength = strlen($chars)) < 2) {
$chars = $chars =
'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-,'; '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-,';
$charLength = 64; $charLength = 64;
@ -111,12 +111,14 @@ final class FixedBitNotation
* Encode a string. * Encode a string.
* *
* @param string $rawString Binary data to encode * @param string $rawString Binary data to encode
*
* @return string
*/ */
public function encode($rawString): string public function encode($rawString): string
{ {
// Unpack string into an array of bytes // Unpack string into an array of bytes
$bytes = unpack('C*', $rawString); $bytes = unpack('C*', $rawString);
$byteCount = \count($bytes); $byteCount = count($bytes);
$encodedString = ''; $encodedString = '';
$byte = array_shift($bytes); $byte = array_shift($bytes);
@ -152,9 +154,9 @@ final class FixedBitNotation
// $bitsPerCharacter and 8, divided by 8 // $bitsPerCharacter and 8, divided by 8
$lcmMap = [1 => 1, 2 => 1, 3 => 3, 4 => 1, 5 => 5, 6 => 3, 7 => 7, 8 => 1]; $lcmMap = [1 => 1, 2 => 1, 3 => 3, 4 => 1, 5 => 5, 6 => 3, 7 => 7, 8 => 1];
$bytesPerGroup = $lcmMap[$bitsPerCharacter]; $bytesPerGroup = $lcmMap[$bitsPerCharacter];
$pads = (int) ($bytesPerGroup * 8 / $bitsPerCharacter $pads = $bytesPerGroup * 8 / $bitsPerCharacter
- ceil((\strlen($rawString) % $bytesPerGroup) - ceil((strlen($rawString) % $bytesPerGroup)
* 8 / $bitsPerCharacter)); * 8 / $bitsPerCharacter);
$encodedString .= str_repeat($padCharacter[0], $pads); $encodedString .= str_repeat($padCharacter[0], $pads);
} }
@ -192,10 +194,12 @@ final class FixedBitNotation
* @param bool $caseSensitive * @param bool $caseSensitive
* @param bool $strict Returns null if $encodedString contains * @param bool $strict Returns null if $encodedString contains
* an undecodable character * an undecodable character
*
* @return string
*/ */
public function decode($encodedString, $caseSensitive = true, $strict = false): string public function decode($encodedString, $caseSensitive = true, $strict = false): string
{ {
if (!$encodedString || !\is_string($encodedString)) { if (!$encodedString || !is_string($encodedString)) {
// Empty string, nothing to decode // Empty string, nothing to decode
return ''; return '';
} }
@ -220,10 +224,10 @@ final class FixedBitNotation
} }
// The last encoded character is $encodedString[$lastNotatedIndex] // The last encoded character is $encodedString[$lastNotatedIndex]
$lastNotatedIndex = \strlen($encodedString) - 1; $lastNotatedIndex = strlen($encodedString) - 1;
// Remove trailing padding characters // Remove trailing padding characters
while ($encodedString[$lastNotatedIndex] === $padCharacter[0]) { while ($encodedString[$lastNotatedIndex] == $padCharacter[0]) {
$encodedString = substr($encodedString, 0, $lastNotatedIndex); $encodedString = substr($encodedString, 0, $lastNotatedIndex);
--$lastNotatedIndex; --$lastNotatedIndex;
} }
@ -254,7 +258,7 @@ final class FixedBitNotation
$newBits = $charmap[$encodedString[$c]] << $bitsNeeded $newBits = $charmap[$encodedString[$c]] << $bitsNeeded
- $bitsPerCharacter; - $bitsPerCharacter;
$bitsWritten += $bitsPerCharacter; $bitsWritten += $bitsPerCharacter;
} elseif ($c !== $lastNotatedIndex || $rightPadFinalBits) { } elseif ($c != $lastNotatedIndex || $rightPadFinalBits) {
// Zero or more too many bits to complete a byte; // Zero or more too many bits to complete a byte;
// shift right // shift right
$newBits = $charmap[$encodedString[$c]] >> $unusedBitCount; $newBits = $charmap[$encodedString[$c]] >> $unusedBitCount;
@ -267,11 +271,11 @@ final class FixedBitNotation
$byte |= $newBits; $byte |= $newBits;
if (8 === $bitsWritten || $c === $lastNotatedIndex) { if (8 == $bitsWritten || $c == $lastNotatedIndex) {
// Byte is ready to be written // Byte is ready to be written
$rawString .= pack('C', $byte); $rawString .= pack('C', $byte);
if ($c !== $lastNotatedIndex) { if ($c != $lastNotatedIndex) {
// Start the next byte // Start the next byte
$bitsWritten = $unusedBitCount; $bitsWritten = $unusedBitCount;
$byte = ($charmap[$encodedString[$c]] $byte = ($charmap[$encodedString[$c]]

View File

@ -36,61 +36,52 @@ final class GoogleAuthenticator implements GoogleAuthenticatorInterface
/** /**
* @var \DateTimeInterface * @var \DateTimeInterface
*/ */
private $instanceTime; private $now;
/** /**
* @var int * @var int
*/ */
private $codePeriod; private $codePeriod = 30;
/** /**
* @var int * @param int $passCodeLength
* @param int $secretLength
* @param \DateTimeInterface|null $now
*/ */
private $periodSize = 30; public function __construct(int $passCodeLength = 6, int $secretLength = 10, \DateTimeInterface $now = null)
public function __construct(int $passCodeLength = 6, int $secretLength = 10, ?\DateTimeInterface $instanceTime = null, int $codePeriod = 30)
{ {
/*
* codePeriod is the duration in seconds that the code is valid.
* periodSize is the length of a period to calculate periods since Unix epoch.
* periodSize cannot be larger than the codePeriod.
*/
$this->passCodeLength = $passCodeLength; $this->passCodeLength = $passCodeLength;
$this->secretLength = $secretLength; $this->secretLength = $secretLength;
$this->codePeriod = $codePeriod;
$this->periodSize = $codePeriod < $this->periodSize ? $codePeriod : $this->periodSize;
$this->pinModulo = 10 ** $passCodeLength; $this->pinModulo = 10 ** $passCodeLength;
$this->instanceTime = $instanceTime ?? new \DateTimeImmutable(); $this->now = $now ?? new \DateTimeImmutable();
} }
/** /**
* @param string $secret * @param string $secret
* @param string $code * @param string $code
* @param int $discrepancy
*/ */
public function checkCode($secret, $code, $discrepancy = 1): bool public function checkCode($secret, $code): bool
{ {
/** /**
* Discrepancy is the factor of periodSize ($discrepancy * $periodSize) allowed on either side of the * The result of each comparison is accumulated here instead of using a guard clause
* given codePeriod. For example, if a code with codePeriod = 60 is generated at 10:00:00, a discrepancy
* of 1 will allow a periodSize of 30 seconds on either side of the codePeriod resulting in a valid code
* from 09:59:30 to 10:00:29.
*
* The result of each comparison is stored as a timestamp here instead of using a guard clause
* (https://refactoring.com/catalog/replaceNestedConditionalWithGuardClauses.html). This is to implement * (https://refactoring.com/catalog/replaceNestedConditionalWithGuardClauses.html). This is to implement
* constant time comparison to make side-channel attacks harder. See * constant time comparison to make side-channel attacks harder. See
* https://cryptocoding.net/index.php/Coding_rules#Compare_secret_strings_in_constant_time for details. * https://cryptocoding.net/index.php/Coding_rules#Compare_secret_strings_in_constant_time for details.
* Each comparison uses hash_equals() instead of an operator to implement constant time equality comparison * Each comparison uses hash_equals() instead of an operator to implement constant time equality comparison
* for each code. * for each code.
*/ */
$periods = floor($this->codePeriod / $this->periodSize);
$result = 0; $result = 0;
for ($i = -$discrepancy; $i < $periods + $discrepancy; ++$i) {
$dateTime = new \DateTimeImmutable('@'.($this->instanceTime->getTimestamp() - ($i * $this->periodSize))); // current period
$result = hash_equals($this->getCode($secret, $dateTime), $code) ? $dateTime->getTimestamp() : $result; $result += hash_equals($this->getCode($secret, $this->now), $code);
}
// previous period, happens if the user was slow to enter or it just crossed over
$dateTime = new \DateTimeImmutable('@'.($this->now->getTimestamp() - $this->codePeriod));
$result += hash_equals($this->getCode($secret, $dateTime), $code);
// next period, happens if the user is not completely synced and possibly a few seconds ahead
$dateTime = new \DateTimeImmutable('@'.($this->now->getTimestamp() + $this->codePeriod));
$result += hash_equals($this->getCode($secret, $dateTime), $code);
return $result > 0; return $result > 0;
} }
@ -99,21 +90,21 @@ final class GoogleAuthenticator implements GoogleAuthenticatorInterface
* NEXT_MAJOR: add the interface typehint to $time and remove deprecation. * NEXT_MAJOR: add the interface typehint to $time and remove deprecation.
* *
* @param string $secret * @param string $secret
* @param float|string|int|\DateTimeInterface|null $time * @param float|string|int|null|\DateTimeInterface $time
*/ */
public function getCode($secret, /* \DateTimeInterface */ $time = null): string public function getCode($secret, /* \DateTimeInterface */$time = null): string
{ {
if (null === $time) { if (null === $time) {
$time = $this->instanceTime; $time = $this->now;
} }
if ($time instanceof \DateTimeInterface) { if ($time instanceof \DateTimeInterface) {
$timeForCode = floor($time->getTimestamp() / $this->periodSize); $timeForCode = floor($time->getTimestamp() / $this->codePeriod);
} else { } else {
@trigger_error( @trigger_error(
'Passing anything other than null or a DateTimeInterface to $time is deprecated as of 2.0 '. 'Passing anything other than null or a DateTimeInterface to $time is deprecated as of 2.0 '.
'and will not be possible as of 3.0.', 'and will not be possible as of 3.0.',
\E_USER_DEPRECATED E_USER_DEPRECATED
); );
$timeForCode = $time; $timeForCode = $time;
} }
@ -121,15 +112,15 @@ final class GoogleAuthenticator implements GoogleAuthenticatorInterface
$base32 = new FixedBitNotation(5, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', true, true); $base32 = new FixedBitNotation(5, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', true, true);
$secret = $base32->decode($secret); $secret = $base32->decode($secret);
$timeForCode = str_pad(pack('N', $timeForCode), 8, \chr(0), \STR_PAD_LEFT); $timeForCode = str_pad(pack('N', $timeForCode), 8, chr(0), STR_PAD_LEFT);
$hash = hash_hmac('sha1', $timeForCode, $secret, true); $hash = hash_hmac('sha1', $timeForCode, $secret, true);
$offset = \ord(substr($hash, -1)); $offset = ord(substr($hash, -1));
$offset &= 0xF; $offset &= 0xF;
$truncatedHash = $this->hashToInt($hash, $offset) & 0x7FFFFFFF; $truncatedHash = $this->hashToInt($hash, $offset) & 0x7FFFFFFF;
return str_pad((string) ($truncatedHash % $this->pinModulo), $this->passCodeLength, '0', \STR_PAD_LEFT); return str_pad((string) ($truncatedHash % $this->pinModulo), $this->passCodeLength, '0', STR_PAD_LEFT);
} }
/** /**
@ -147,9 +138,9 @@ final class GoogleAuthenticator implements GoogleAuthenticatorInterface
'Using %s() is deprecated as of 2.1 and will be removed in 3.0. '. 'Using %s() is deprecated as of 2.1 and will be removed in 3.0. '.
'Use Sonata\GoogleAuthenticator\GoogleQrUrl::generate() instead.', 'Use Sonata\GoogleAuthenticator\GoogleQrUrl::generate() instead.',
__METHOD__ __METHOD__
), \E_USER_DEPRECATED); ), E_USER_DEPRECATED);
$issuer = \func_get_args()[3] ?? null; $issuer = func_get_args()[3] ?? null;
$accountName = sprintf('%s@%s', $user, $hostname); $accountName = sprintf('%s@%s', $user, $hostname);
// manually concat the issuer to avoid a change in URL // manually concat the issuer to avoid a change in URL
@ -168,6 +159,10 @@ final class GoogleAuthenticator implements GoogleAuthenticatorInterface
->encode(random_bytes($this->secretLength)); ->encode(random_bytes($this->secretLength));
} }
/**
* @param string $bytes
* @param int $start
*/
private function hashToInt(string $bytes, int $start): int private function hashToInt(string $bytes, int $start): int
{ {
return unpack('N', substr(substr($bytes, $start), 0, 4))[1]; return unpack('N', substr(substr($bytes, $start), 0, 4))[1];

View File

@ -19,15 +19,15 @@ interface GoogleAuthenticatorInterface
* @param string $secret * @param string $secret
* @param string $code * @param string $code
*/ */
public function checkCode($secret, $code, $discrepancy = 1): bool; public function checkCode($secret, $code): bool;
/** /**
* NEXT_MAJOR: add the interface typehint to $time and remove deprecation. * NEXT_MAJOR: add the interface typehint to $time and remove deprecation.
* *
* @param string $secret * @param string $secret
* @param float|string|int|\DateTimeInterface|null $time * @param float|string|int|null|\DateTimeInterface $time
*/ */
public function getCode($secret, /* \DateTimeInterface */ $time = null): string; public function getCode($secret, /* \DateTimeInterface */$time = null): string;
/** /**
* NEXT_MAJOR: Remove this method. * NEXT_MAJOR: Remove this method.

View File

@ -16,8 +16,7 @@ namespace Sonata\GoogleAuthenticator;
/** /**
* Responsible for QR image url generation. * Responsible for QR image url generation.
* *
* @see http://goqr.me/api/ * @see https://developers.google.com/chart/infographics/docs/qr_codes
* @see http://goqr.me/api/doc/
* @see https://github.com/google/google-authenticator/wiki/Key-Uri-Format * @see https://github.com/google/google-authenticator/wiki/Key-Uri-Format
* *
* @author Iltar van der Berg <kjarli@gmail.com> * @author Iltar van der Berg <kjarli@gmail.com>
@ -55,8 +54,10 @@ final class GoogleQrUrl
* @param string $secret The secret is the generated secret unique to that user * @param string $secret The secret is the generated secret unique to that user
* @param string|null $issuer Where you log in to * @param string|null $issuer Where you log in to
* @param int $size Image size in pixels, 200 will make it 200x200 * @param int $size Image size in pixels, 200 will make it 200x200
*
* @return string
*/ */
public static function generate(string $accountName, string $secret, ?string $issuer = null, int $size = 200): string public static function generate(string $accountName, string $secret, string $issuer = null, int $size = 200): string
{ {
if ('' === $accountName || false !== strpos($accountName, ':')) { if ('' === $accountName || false !== strpos($accountName, ':')) {
throw RuntimeException::InvalidAccountName($accountName); throw RuntimeException::InvalidAccountName($accountName);
@ -82,7 +83,7 @@ final class GoogleQrUrl
$otpauthString = rawurlencode(sprintf($otpauthString, $label, $secret, $issuer)); $otpauthString = rawurlencode(sprintf($otpauthString, $label, $secret, $issuer));
return sprintf( return sprintf(
'https://api.qrserver.com/v1/create-qr-code/?size=%1$dx%1$d&data=%2$s&ecc=M', 'https://chart.googleapis.com/chart?chs=%1$dx%1$d&chld=M|0&cht=qr&chl=%2$s',
$size, $size,
$otpauthString $otpauthString
); );