From 23b681c7b5c39dd5ab52da52e41e98b797a6f3e3 Mon Sep 17 00:00:00 2001 From: Ferran Recio Date: Tue, 22 Dec 2020 17:21:35 +0100 Subject: [PATCH] MDL-70303 libraries: upgrade GeoIP to 2.11.0 and MaxMind DB to 1.9.0 --- lib/maxmind/GeoIp2/Database/Reader.php | 48 ++---- .../Exception/AddressNotFoundException.php | 2 + .../Exception/AuthenticationException.php | 2 + .../GeoIp2/Exception/GeoIp2Exception.php | 2 + .../GeoIp2/Exception/HttpException.php | 8 +- .../Exception/InvalidRequestException.php | 10 +- .../Exception/OutOfQueriesException.php | 2 + lib/maxmind/GeoIp2/Model/AbstractModel.php | 20 +-- lib/maxmind/GeoIp2/Model/AnonymousIp.php | 10 +- lib/maxmind/GeoIp2/Model/Asn.php | 6 +- lib/maxmind/GeoIp2/Model/City.php | 19 +-- lib/maxmind/GeoIp2/Model/ConnectionType.php | 6 +- lib/maxmind/GeoIp2/Model/Country.php | 7 +- lib/maxmind/GeoIp2/Model/Domain.php | 6 +- lib/maxmind/GeoIp2/Model/Enterprise.php | 2 + lib/maxmind/GeoIp2/Model/Insights.php | 2 + lib/maxmind/GeoIp2/Model/Isp.php | 6 +- lib/maxmind/GeoIp2/ProviderInterface.php | 6 +- .../GeoIp2/Record/AbstractPlaceRecord.php | 21 +-- lib/maxmind/GeoIp2/Record/AbstractRecord.php | 18 +- lib/maxmind/GeoIp2/Record/City.php | 2 + lib/maxmind/GeoIp2/Record/Continent.php | 2 + lib/maxmind/GeoIp2/Record/Country.php | 2 + lib/maxmind/GeoIp2/Record/Location.php | 2 + lib/maxmind/GeoIp2/Record/MaxMind.php | 2 + lib/maxmind/GeoIp2/Record/Postal.php | 2 + .../GeoIp2/Record/RepresentedCountry.php | 2 + lib/maxmind/GeoIp2/Record/Subdivision.php | 2 + lib/maxmind/GeoIp2/Record/Traits.php | 8 +- lib/maxmind/GeoIp2/Util.php | 7 +- lib/maxmind/GeoIp2/WebService/Client.php | 28 ++-- lib/maxmind/MaxMind/CHANGELOG.md | 39 +++++ lib/maxmind/MaxMind/Db/Reader.php | 136 +++++++++------ lib/maxmind/MaxMind/Db/Reader/Decoder.php | 158 +++++++++--------- .../Db/Reader/InvalidDatabaseException.php | 2 + lib/maxmind/MaxMind/Db/Reader/Metadata.php | 148 +++++++++------- lib/maxmind/MaxMind/Db/Reader/Util.php | 9 +- lib/maxmind/MaxMind/README.md | 4 +- lib/maxmind/MaxMind/autoload.php | 4 +- lib/maxmind/MaxMind/composer.json | 11 +- lib/maxmind/readme_moodle.txt | 23 ++- lib/thirdpartylibs.xml | 4 +- 42 files changed, 466 insertions(+), 334 deletions(-) diff --git a/lib/maxmind/GeoIp2/Database/Reader.php b/lib/maxmind/GeoIp2/Database/Reader.php index 813654753a1a8..0c370b730fec2 100644 --- a/lib/maxmind/GeoIp2/Database/Reader.php +++ b/lib/maxmind/GeoIp2/Database/Reader.php @@ -1,5 +1,7 @@ dbReader = new DbReader($filename); $this->dbType = $this->dbReader->metadata()->databaseType; @@ -65,10 +67,8 @@ public function __construct( * not in the database * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database * is corrupt or invalid - * - * @return \GeoIp2\Model\City */ - public function city($ipAddress) + public function city(string $ipAddress): \GeoIp2\Model\City { return $this->modelFor('City', 'City', $ipAddress); } @@ -82,10 +82,8 @@ public function city($ipAddress) * not in the database * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database * is corrupt or invalid - * - * @return \GeoIp2\Model\Country */ - public function country($ipAddress) + public function country(string $ipAddress): \GeoIp2\Model\Country { return $this->modelFor('Country', 'Country', $ipAddress); } @@ -99,10 +97,8 @@ public function country($ipAddress) * not in the database * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database * is corrupt or invalid - * - * @return \GeoIp2\Model\AnonymousIp */ - public function anonymousIp($ipAddress) + public function anonymousIp(string $ipAddress): \GeoIp2\Model\AnonymousIp { return $this->flatModelFor( 'AnonymousIp', @@ -120,10 +116,8 @@ public function anonymousIp($ipAddress) * not in the database * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database * is corrupt or invalid - * - * @return \GeoIp2\Model\Asn */ - public function asn($ipAddress) + public function asn(string $ipAddress): \GeoIp2\Model\Asn { return $this->flatModelFor( 'Asn', @@ -141,10 +135,8 @@ public function asn($ipAddress) * not in the database * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database * is corrupt or invalid - * - * @return \GeoIp2\Model\ConnectionType */ - public function connectionType($ipAddress) + public function connectionType(string $ipAddress): \GeoIp2\Model\ConnectionType { return $this->flatModelFor( 'ConnectionType', @@ -162,10 +154,8 @@ public function connectionType($ipAddress) * not in the database * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database * is corrupt or invalid - * - * @return \GeoIp2\Model\Domain */ - public function domain($ipAddress) + public function domain(string $ipAddress): \GeoIp2\Model\Domain { return $this->flatModelFor( 'Domain', @@ -183,10 +173,8 @@ public function domain($ipAddress) * not in the database * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database * is corrupt or invalid - * - * @return \GeoIp2\Model\Enterprise */ - public function enterprise($ipAddress) + public function enterprise(string $ipAddress): \GeoIp2\Model\Enterprise { return $this->modelFor('Enterprise', 'Enterprise', $ipAddress); } @@ -200,10 +188,8 @@ public function enterprise($ipAddress) * not in the database * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database * is corrupt or invalid - * - * @return \GeoIp2\Model\Isp */ - public function isp($ipAddress) + public function isp(string $ipAddress): \GeoIp2\Model\Isp { return $this->flatModelFor( 'Isp', @@ -212,7 +198,7 @@ public function isp($ipAddress) ); } - private function modelFor($class, $type, $ipAddress) + private function modelFor(string $class, string $type, string $ipAddress) { list($record, $prefixLen) = $this->getRecord($class, $type, $ipAddress); @@ -224,7 +210,7 @@ private function modelFor($class, $type, $ipAddress) return new $class($record, $this->locales); } - private function flatModelFor($class, $type, $ipAddress) + private function flatModelFor(string $class, string $type, string $ipAddress) { list($record, $prefixLen) = $this->getRecord($class, $type, $ipAddress); @@ -235,7 +221,7 @@ private function flatModelFor($class, $type, $ipAddress) return new $class($record); } - private function getRecord($class, $type, $ipAddress) + private function getRecord(string $class, string $type, string $ipAddress): array { if (strpos($this->dbType, $type) === false) { $method = lcfirst($class); @@ -272,7 +258,7 @@ private function getRecord($class, $type, $ipAddress) * * @return \MaxMind\Db\Reader\Metadata object for the database */ - public function metadata() + public function metadata(): \MaxMind\Db\Reader\Metadata { return $this->dbReader->metadata(); } @@ -280,7 +266,7 @@ public function metadata() /** * Closes the GeoIP2 database and returns the resources to the system. */ - public function close() + public function close(): void { $this->dbReader->close(); } diff --git a/lib/maxmind/GeoIp2/Exception/AddressNotFoundException.php b/lib/maxmind/GeoIp2/Exception/AddressNotFoundException.php index d5483388b10c1..628fb0672a1b6 100644 --- a/lib/maxmind/GeoIp2/Exception/AddressNotFoundException.php +++ b/lib/maxmind/GeoIp2/Exception/AddressNotFoundException.php @@ -1,5 +1,7 @@ uri = $uri; diff --git a/lib/maxmind/GeoIp2/Exception/InvalidRequestException.php b/lib/maxmind/GeoIp2/Exception/InvalidRequestException.php index 6464bcbb1eb8e..e469f56ccd79e 100644 --- a/lib/maxmind/GeoIp2/Exception/InvalidRequestException.php +++ b/lib/maxmind/GeoIp2/Exception/InvalidRequestException.php @@ -1,5 +1,7 @@ error = $error; diff --git a/lib/maxmind/GeoIp2/Exception/OutOfQueriesException.php b/lib/maxmind/GeoIp2/Exception/OutOfQueriesException.php index 87a6ade412186..9734c8ce403bc 100644 --- a/lib/maxmind/GeoIp2/Exception/OutOfQueriesException.php +++ b/lib/maxmind/GeoIp2/Exception/OutOfQueriesException.php @@ -1,5 +1,7 @@ raw = $raw; } /** * @ignore - * - * @param mixed $field */ - protected function get($field) + protected function get(string $field) { if (isset($this->raw[$field])) { return $this->raw[$field]; @@ -38,10 +36,8 @@ protected function get($field) /** * @ignore - * - * @param mixed $attr */ - public function __get($attr) + public function __get(string $attr) { if ($attr !== 'instance' && property_exists($this, $attr)) { return $this->$attr; @@ -52,15 +48,13 @@ public function __get($attr) /** * @ignore - * - * @param mixed $attr */ - public function __isset($attr) + public function __isset(string $attr): bool { return $attr !== 'instance' && isset($this->$attr); } - public function jsonSerialize() + public function jsonSerialize(): array { return $this->raw; } diff --git a/lib/maxmind/GeoIp2/Model/AnonymousIp.php b/lib/maxmind/GeoIp2/Model/AnonymousIp.php index a8f946ee8655c..bdfbc258a3374 100644 --- a/lib/maxmind/GeoIp2/Model/AnonymousIp.php +++ b/lib/maxmind/GeoIp2/Model/AnonymousIp.php @@ -1,5 +1,7 @@ isAnonymousVpn = $this->get('is_anonymous_vpn'); $this->isHostingProvider = $this->get('is_hosting_provider'); $this->isPublicProxy = $this->get('is_public_proxy'); + $this->isResidentialProxy = $this->get('is_residential_proxy'); $this->isTorExitNode = $this->get('is_tor_exit_node'); $ipAddress = $this->get('ip_address'); $this->ipAddress = $ipAddress; diff --git a/lib/maxmind/GeoIp2/Model/Asn.php b/lib/maxmind/GeoIp2/Model/Asn.php index 8e7c802eebab0..38a0f750c6414 100644 --- a/lib/maxmind/GeoIp2/Model/Asn.php +++ b/lib/maxmind/GeoIp2/Model/Asn.php @@ -1,5 +1,7 @@ autonomousSystemNumber = $this->get('autonomous_system_number'); diff --git a/lib/maxmind/GeoIp2/Model/City.php b/lib/maxmind/GeoIp2/Model/City.php index 3e78c4f14c48e..1840e778f21d1 100644 --- a/lib/maxmind/GeoIp2/Model/City.php +++ b/lib/maxmind/GeoIp2/Model/City.php @@ -1,5 +1,7 @@ createSubdivisions($raw, $locales); } - private function createSubdivisions($raw, $locales) + private function createSubdivisions(array $raw, array $locales): void { if (!isset($raw['subdivisions'])) { return; @@ -79,10 +78,8 @@ private function createSubdivisions($raw, $locales) /** * @ignore - * - * @param mixed $attr */ - public function __get($attr) + public function __get(string $attr) { if ($attr === 'mostSpecificSubdivision') { return $this->$attr(); @@ -93,10 +90,8 @@ public function __get($attr) /** * @ignore - * - * @param mixed $attr */ - public function __isset($attr) + public function __isset(string $attr): bool { if ($attr === 'mostSpecificSubdivision') { // We always return a mostSpecificSubdivision, even if it is the @@ -107,7 +102,7 @@ public function __isset($attr) return parent::__isset($attr); } - private function mostSpecificSubdivision() + private function mostSpecificSubdivision(): \GeoIp2\Record\Subdivision { return empty($this->subdivisions) ? new \GeoIp2\Record\Subdivision([], $this->locales) : diff --git a/lib/maxmind/GeoIp2/Model/ConnectionType.php b/lib/maxmind/GeoIp2/Model/ConnectionType.php index 8091e26ce50ae..5f7747fe527d1 100644 --- a/lib/maxmind/GeoIp2/Model/ConnectionType.php +++ b/lib/maxmind/GeoIp2/Model/ConnectionType.php @@ -1,5 +1,7 @@ autonomousSystemNumber = $this->get('autonomous_system_number'); diff --git a/lib/maxmind/GeoIp2/ProviderInterface.php b/lib/maxmind/GeoIp2/ProviderInterface.php index 44851b07e1218..4e28eff1bd058 100644 --- a/lib/maxmind/GeoIp2/ProviderInterface.php +++ b/lib/maxmind/GeoIp2/ProviderInterface.php @@ -1,5 +1,7 @@ locales = $locales; parent::__construct($record); @@ -20,10 +19,8 @@ public function __construct($record, $locales = ['en']) /** * @ignore - * - * @param mixed $attr */ - public function __get($attr) + public function __get(string $attr) { if ($attr === 'name') { return $this->name(); @@ -34,26 +31,24 @@ public function __get($attr) /** * @ignore - * - * @param mixed $attr */ - public function __isset($attr) + public function __isset(string $attr): bool { if ($attr === 'name') { - return $this->firstSetNameLocale() === null ? false : true; + return $this->firstSetNameLocale() !== null; } return parent::__isset($attr); } - private function name() + private function name(): ?string { $locale = $this->firstSetNameLocale(); return $locale === null ? null : $this->names[$locale]; } - private function firstSetNameLocale() + private function firstSetNameLocale(): ?string { foreach ($this->locales as $locale) { if (isset($this->names[$locale])) { diff --git a/lib/maxmind/GeoIp2/Record/AbstractRecord.php b/lib/maxmind/GeoIp2/Record/AbstractRecord.php index dad2fd3f37614..e2abcf16c4c00 100644 --- a/lib/maxmind/GeoIp2/Record/AbstractRecord.php +++ b/lib/maxmind/GeoIp2/Record/AbstractRecord.php @@ -1,5 +1,7 @@ record = isset($record) ? $record : []; } /** * @ignore - * - * @param mixed $attr */ - public function __get($attr) + public function __get(string $attr) { // XXX - kind of ugly but greatly reduces boilerplate code $key = $this->attributeToKey($attr); @@ -38,23 +36,23 @@ public function __get($attr) throw new \RuntimeException("Unknown attribute: $attr"); } - public function __isset($attr) + public function __isset(string $attr): bool { return $this->validAttribute($attr) && isset($this->record[$this->attributeToKey($attr)]); } - private function attributeToKey($attr) + private function attributeToKey(string $attr): string { return strtolower(preg_replace('/([A-Z])/', '_\1', $attr)); } - private function validAttribute($attr) + private function validAttribute(string $attr): bool { return \in_array($attr, $this->validAttributes, true); } - public function jsonSerialize() + public function jsonSerialize(): ?array { return $this->record; } diff --git a/lib/maxmind/GeoIp2/Record/City.php b/lib/maxmind/GeoIp2/Record/City.php index 7f495ad7cc02d..ac62bea6e2dc8 100644 --- a/lib/maxmind/GeoIp2/Record/City.php +++ b/lib/maxmind/GeoIp2/Record/City.php @@ -1,5 +1,7 @@ locales = $locales; @@ -87,7 +89,7 @@ public function __construct( $this->client = new WsClient($accountId, $licenseKey, $options); } - private function userAgent() + private function userAgent(): string { return 'GeoIP2-API/' . self::VERSION; } @@ -115,10 +117,8 @@ private function userAgent() * @throws \GeoIp2\Exception\GeoIp2Exception This serves as the parent * class to the above exceptions. It will be thrown directly * if a 200 status code is returned but the body is invalid. - * - * @return \GeoIp2\Model\City */ - public function city($ipAddress = 'me') + public function city(string $ipAddress = 'me'): \GeoIp2\Model\City { return $this->responseFor('city', 'City', $ipAddress); } @@ -146,10 +146,8 @@ public function city($ipAddress = 'me') * @throws \GeoIp2\Exception\GeoIp2Exception This serves as the parent class to the above exceptions. It * will be thrown directly if a 200 status code is returned but * the body is invalid. - * - * @return \GeoIp2\Model\Country */ - public function country($ipAddress = 'me') + public function country(string $ipAddress = 'me'): \GeoIp2\Model\Country { return $this->responseFor('country', 'Country', $ipAddress); } @@ -177,15 +175,13 @@ public function country($ipAddress = 'me') * @throws \GeoIp2\Exception\GeoIp2Exception This serves as the parent * class to the above exceptions. It will be thrown directly * if a 200 status code is returned but the body is invalid. - * - * @return \GeoIp2\Model\Insights */ - public function insights($ipAddress = 'me') + public function insights(string $ipAddress = 'me'): \GeoIp2\Model\Insights { return $this->responseFor('insights', 'Insights', $ipAddress); } - private function responseFor($endpoint, $class, $ipAddress) + private function responseFor(string $endpoint, string $class, string $ipAddress) { $path = implode('/', [self::$basePath, $endpoint, $ipAddress]); diff --git a/lib/maxmind/MaxMind/CHANGELOG.md b/lib/maxmind/MaxMind/CHANGELOG.md index 9ed49bd416d6d..91e2aa4603580 100644 --- a/lib/maxmind/MaxMind/CHANGELOG.md +++ b/lib/maxmind/MaxMind/CHANGELOG.md @@ -1,6 +1,45 @@ CHANGELOG ========= +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.5.0 and 1.5.1 contained a possible memory corruptions when using + `getWithPrefixLen`. This has been fixed. Reported by proton-ab. + GitHub #96. +* The `composer.json` file now conflicts with all versions of the + `maxminddb` C extension less than the Composer version. This is to + reduce the chance of having an older, conflicting version of the + extension installed. You will need to upgrade the extension before + running `composer update`. Pull request by BenoƮt Burnichon. GitHub + #97. + 1.5.1 (2019-12-12) ------------------ diff --git a/lib/maxmind/MaxMind/Db/Reader.php b/lib/maxmind/MaxMind/Db/Reader.php index 3d5a8291d9589..ee444f8f18c66 100644 --- a/lib/maxmind/MaxMind/Db/Reader.php +++ b/lib/maxmind/MaxMind/Db/Reader.php @@ -1,7 +1,10 @@ fileHandle = @fopen($database, 'rb'); - if ($this->fileHandle === false) { - throw new InvalidArgumentException( - "Error opening \"$database\"." - ); - } - $this->fileSize = @filesize($database); - if ($this->fileSize === false) { + $this->fileHandle = $fileHandle; + + $fileSize = @filesize($database); + if ($fileSize === false) { throw new UnexpectedValueException( "Error determining the size of \"$database\"." ); } + $this->fileSize = $fileSize; $start = $this->findMetadataStart($database); $metadataDecoder = new Decoder($this->fileHandle, $start); - list($metadataArray) = $metadataDecoder->decode($start); + [$metadataArray] = $metadataDecoder->decode($start); $this->metadata = new Metadata($metadataArray); $this->decoder = new Decoder( $this->fileHandle, @@ -91,14 +119,14 @@ public function __construct($database) * * @return mixed the record for the IP address */ - public function get($ipAddress) + public function get(string $ipAddress) { if (\func_num_args() !== 1) { - throw new InvalidArgumentException( - 'Method takes exactly one argument.' + throw new ArgumentCountError( + sprintf('%s() expects exactly 1 parameter, %d given', __METHOD__, \func_num_args()) ); } - list($record) = $this->getWithPrefixLen($ipAddress); + [$record] = $this->getWithPrefixLen($ipAddress); return $record; } @@ -118,11 +146,11 @@ public function get($ipAddress) * @return array an array where the first element is the record and the * second the network prefix length for the record */ - public function getWithPrefixLen($ipAddress) + public function getWithPrefixLen(string $ipAddress): array { if (\func_num_args() !== 1) { - throw new InvalidArgumentException( - 'Method takes exactly one argument.' + throw new ArgumentCountError( + sprintf('%s() expects exactly 1 parameter, %d given', __METHOD__, \func_num_args()) ); } @@ -132,13 +160,7 @@ public function getWithPrefixLen($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); + [$pointer, $prefixLen] = $this->findAddressInTree($ipAddress); if ($pointer === 0) { return [null, $prefixLen]; } @@ -146,9 +168,16 @@ public function getWithPrefixLen($ipAddress) return [$this->resolveDataPointer($pointer), $prefixLen]; } - private function findAddressInTree($ipAddress) + private function findAddressInTree(string $ipAddress): array { - $rawAddress = unpack('C*', inet_pton($ipAddress)); + $packedAddr = @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; @@ -186,10 +215,12 @@ private function findAddressInTree($ipAddress) // Record is a data pointer 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() + private function ipV4StartNode(): int { // If we have an IPv4 database, the start node is the first node if ($this->metadata->ipVersion === 4) { @@ -205,14 +236,14 @@ private function ipV4StartNode() return $node; } - private function readNode($nodeNumber, $index) + private function readNode(int $nodeNumber, int $index): int { $baseOffset = $nodeNumber * $this->metadata->nodeByteSize; switch ($this->metadata->recordSize) { case 24: $bytes = Util::read($this->fileHandle, $baseOffset + $index * 3, 3); - list(, $node) = unpack('N', "\x00" . $bytes); + [, $node] = unpack('N', "\x00" . $bytes); return $node; case 28: @@ -222,12 +253,12 @@ private function readNode($nodeNumber, $index) } else { $middle = 0x0F & \ord($bytes[0]); } - list(, $node) = unpack('N', \chr($middle) . substr($bytes, $index, 3)); + [, $node] = unpack('N', \chr($middle) . substr($bytes, $index, 3)); return $node; case 32: $bytes = Util::read($this->fileHandle, $baseOffset + $index * 4, 4); - list(, $node) = unpack('N', $bytes); + [, $node] = unpack('N', $bytes); return $node; default: @@ -238,7 +269,10 @@ private function readNode($nodeNumber, $index) } } - private function resolveDataPointer($pointer) + /** + * @return mixed + */ + private function resolveDataPointer(int $pointer) { $resolved = $pointer - $this->metadata->nodeCount + $this->metadata->searchTreeSize; @@ -248,7 +282,7 @@ private function resolveDataPointer($pointer) ); } - list($data) = $this->decoder->decode($resolved); + [$data] = $this->decoder->decode($resolved); return $data; } @@ -258,7 +292,7 @@ private function resolveDataPointer($pointer) * are much faster algorithms (e.g., Boyer-Moore) for this if speed is ever * an issue, but I suspect it won't be. */ - private function findMetadataStart($filename) + private function findMetadataStart(string $filename): int { $handle = $this->fileHandle; $fstat = fstat($handle); @@ -290,11 +324,11 @@ private function findMetadataStart($filename) * * @return Metadata object for the database */ - public function metadata() + public function metadata(): Metadata { if (\func_num_args()) { - throw new InvalidArgumentException( - 'Method takes no arguments.' + throw new ArgumentCountError( + sprintf('%s() expects exactly 0 parameters, %d given', __METHOD__, \func_num_args()) ); } @@ -306,7 +340,7 @@ public function metadata() ); } - return $this->metadata; + return clone $this->metadata; } /** @@ -315,8 +349,14 @@ public function metadata() * @throws Exception * if an I/O error occurs */ - public function close() + public function close(): void { + if (\func_num_args()) { + throw new ArgumentCountError( + sprintf('%s() expects exactly 0 parameters, %d given', __METHOD__, \func_num_args()) + ); + } + if (!\is_resource($this->fileHandle)) { throw new BadMethodCallException( 'Attempt to close a closed MaxMind DB.' diff --git a/lib/maxmind/MaxMind/Db/Reader/Decoder.php b/lib/maxmind/MaxMind/Db/Reader/Decoder.php index 132dae82c22c3..9816befa66edd 100644 --- a/lib/maxmind/MaxMind/Db/Reader/Decoder.php +++ b/lib/maxmind/MaxMind/Db/Reader/Decoder.php @@ -1,5 +1,7 @@ fileStream = $fileStream; $this->pointerBase = $pointerBase; @@ -68,7 +73,7 @@ public function __construct( $this->switchByteOrder = $this->isPlatformLittleEndian(); } - public function decode($offset) + public function decode(int $offset): array { $ctrlByte = \ord(Util::read($this->fileStream, $offset, 1)); ++$offset; @@ -79,14 +84,14 @@ public function decode($offset) // use the size to determine the length of the pointer and then follow // it. if ($type === self::_POINTER) { - list($pointer, $offset) = $this->decodePointer($ctrlByte, $offset); + [$pointer, $offset] = $this->decodePointer($ctrlByte, $offset); // for unit testing if ($this->pointerTestHack) { return [$pointer]; } - list($result) = $this->decode($pointer); + [$result] = $this->decode($pointer); return [$result, $offset]; } @@ -108,12 +113,12 @@ public function decode($offset) ++$offset; } - list($size, $offset) = $this->sizeFromCtrlByte($ctrlByte, $offset); + [$size, $offset] = $this->sizeFromCtrlByte($ctrlByte, $offset); return $this->decodeByType($type, $offset, $size); } - private function decodeByType($type, $offset, $size) + private function decodeByType(int $type, int $offset, int $size): array { switch ($type) { case self::_MAP: @@ -152,7 +157,7 @@ private function decodeByType($type, $offset, $size) } } - private function verifySize($expected, $actual) + private function verifySize(int $expected, int $actual): void { if ($expected !== $actual) { throw new InvalidDatabaseException( @@ -161,50 +166,42 @@ private function verifySize($expected, $actual) } } - private function decodeArray($size, $offset) + private function decodeArray(int $size, int $offset): array { $array = []; for ($i = 0; $i < $size; ++$i) { - list($value, $offset) = $this->decode($offset); - array_push($array, $value); + [$value, $offset] = $this->decode($offset); + $array[] = $value; } return [$array, $offset]; } - private function decodeBoolean($size) + private function decodeBoolean(int $size): bool { - return $size === 0 ? false : true; + return $size !== 0; } - private function decodeDouble($bits) + private function decodeDouble(string $bytes): float { // This assumes IEEE 754 doubles, but most (all?) modern platforms // use them. - // - // We are not using the "E" format as that was only added in - // 7.0.15 and 7.1.1. As such, we must switch byte order on - // little endian machines. - list(, $double) = unpack('d', $this->maybeSwitchByteOrder($bits)); + [, $double] = unpack('E', $bytes); return $double; } - private function decodeFloat($bits) + private function decodeFloat(string $bytes): float { // This assumes IEEE 754 floats, but most (all?) modern platforms // use them. - // - // We are not using the "G" format as that was only added in - // 7.0.15 and 7.1.1. As such, we must switch byte order on - // little endian machines. - list(, $float) = unpack('f', $this->maybeSwitchByteOrder($bits)); + [, $float] = unpack('G', $bytes); return $float; } - private function decodeInt32($bytes, $size) + private function decodeInt32(string $bytes, int $size): int { switch ($size) { case 0: @@ -222,25 +219,25 @@ private function decodeInt32($bytes, $size) ); } - list(, $int) = unpack('l', $this->maybeSwitchByteOrder($bytes)); + [, $int] = unpack('l', $this->maybeSwitchByteOrder($bytes)); return $int; } - private function decodeMap($size, $offset) + private function decodeMap(int $size, int $offset): array { $map = []; for ($i = 0; $i < $size; ++$i) { - list($key, $offset) = $this->decode($offset); - list($value, $offset) = $this->decode($offset); + [$key, $offset] = $this->decode($offset); + [$value, $offset] = $this->decode($offset); $map[$key] = $value; } return [$map, $offset]; } - private function decodePointer($ctrlByte, $offset) + private function decodePointer(int $ctrlByte, int $offset): array { $pointerSize = (($ctrlByte >> 3) & 0x3) + 1; @@ -250,12 +247,12 @@ private function decodePointer($ctrlByte, $offset) switch ($pointerSize) { case 1: $packed = \chr($ctrlByte & 0x7) . $buffer; - list(, $pointer) = unpack('n', $packed); + [, $pointer] = unpack('n', $packed); $pointer += $this->pointerBase; break; case 2: $packed = "\x00" . \chr($ctrlByte & 0x7) . $buffer; - list(, $pointer) = unpack('N', $packed); + [, $pointer] = unpack('N', $packed); $pointer += $this->pointerBase + 2048; break; case 3: @@ -263,7 +260,7 @@ private function decodePointer($ctrlByte, $offset) // It is safe to use 'N' here, even on 32 bit machines as the // first bit is 0. - list(, $pointer) = unpack('N', $packed); + [, $pointer] = unpack('N', $packed); $pointer += $this->pointerBase + 526336; break; case 4: @@ -278,18 +275,23 @@ private function decodePointer($ctrlByte, $offset) } elseif (\extension_loaded('gmp')) { $pointer = gmp_strval(gmp_add($pointerOffset, $this->pointerBase)); } elseif (\extension_loaded('bcmath')) { - $pointer = bcadd($pointerOffset, $this->pointerBase); + $pointer = bcadd($pointerOffset, (string) $this->pointerBase); } else { throw new RuntimeException( 'The gmp or bcmath extension must be installed to read this database.' ); } + break; + default: + throw new InvalidDatabaseException( + 'Unexpected pointer size ' . $pointerSize + ); } return [$pointer, $offset]; } - private function decodeUint($bytes, $byteLength) + private function decodeUint(string $bytes, int $byteLength) { if ($byteLength === 0) { return 0; @@ -304,9 +306,9 @@ private function decodeUint($bytes, $byteLength) if ($byteLength <= _MM_MAX_INT_BYTES) { $integer = ($integer << 8) + $part; } elseif (\extension_loaded('gmp')) { - $integer = gmp_strval(gmp_add(gmp_mul($integer, 256), $part)); + $integer = gmp_strval(gmp_add(gmp_mul((string) $integer, '256'), $part)); } elseif (\extension_loaded('bcmath')) { - $integer = bcadd(bcmul($integer, 256), $part); + $integer = bcadd(bcmul((string) $integer, '256'), (string) $part); } else { throw new RuntimeException( 'The gmp or bcmath extension must be installed to read this database.' @@ -317,7 +319,7 @@ private function decodeUint($bytes, $byteLength) return $integer; } - private function sizeFromCtrlByte($ctrlByte, $offset) + private function sizeFromCtrlByte(int $ctrlByte, int $offset): array { $size = $ctrlByte & 0x1f; @@ -331,22 +333,22 @@ private function sizeFromCtrlByte($ctrlByte, $offset) if ($size === 29) { $size = 29 + \ord($bytes); } elseif ($size === 30) { - list(, $adjust) = unpack('n', $bytes); + [, $adjust] = unpack('n', $bytes); $size = 285 + $adjust; - } elseif ($size > 30) { - list(, $adjust) = unpack('N', "\x00" . $bytes); + } else { + [, $adjust] = unpack('N', "\x00" . $bytes); $size = $adjust + 65821; } return [$size, $offset + $bytesToRead]; } - private function maybeSwitchByteOrder($bytes) + private function maybeSwitchByteOrder(string $bytes): string { return $this->switchByteOrder ? strrev($bytes) : $bytes; } - private function isPlatformLittleEndian() + private function isPlatformLittleEndian(): bool { $testint = 0x00FF; $packed = pack('S', $testint); diff --git a/lib/maxmind/MaxMind/Db/Reader/InvalidDatabaseException.php b/lib/maxmind/MaxMind/Db/Reader/InvalidDatabaseException.php index 478a22c556300..5323107032944 100644 --- a/lib/maxmind/MaxMind/Db/Reader/InvalidDatabaseException.php +++ b/lib/maxmind/MaxMind/Db/Reader/InvalidDatabaseException.php @@ -1,5 +1,7 @@ binaryFormatMajorVersion = $metadata['binary_format_major_version']; $this->binaryFormatMinorVersion = @@ -80,9 +109,4 @@ public function __construct($metadata) $this->nodeByteSize = $this->recordSize / 4; $this->searchTreeSize = $this->nodeCount * $this->nodeByteSize; } - - public function __get($var) - { - return $this->$var; - } } diff --git a/lib/maxmind/MaxMind/Db/Reader/Util.php b/lib/maxmind/MaxMind/Db/Reader/Util.php index 87ebbf133f3a5..15e6bc4d4d6d2 100644 --- a/lib/maxmind/MaxMind/Db/Reader/Util.php +++ b/lib/maxmind/MaxMind/Db/Reader/Util.php @@ -1,10 +1,15 @@ =5.6" + "php": ">=7.2" }, "suggest": { "ext-bcmath": "bcmath or gmp is required for decoding larger integers with the pure PHP decoder", "ext-gmp": "bcmath or gmp is required for decoding larger integers with the pure PHP decoder", "ext-maxminddb": "A C-based database decoder that provides significantly faster lookups" }, + "conflict": { + "ext-maxminddb": "<1.9.0,>=2.0.0" + }, "require-dev": { - "friendsofphp/php-cs-fixer": "2.*", - "phpunit/phpunit": "5.*", + "friendsofphp/php-cs-fixer": "*", + "phpunit/phpunit": ">=8.0.0,<10.0.0", "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpcov": "^3.0", + "phpunit/phpcov": ">=6.0.0", "squizlabs/php_codesniffer": "3.*" }, "autoload": { diff --git a/lib/maxmind/readme_moodle.txt b/lib/maxmind/readme_moodle.txt index ad9b9039da2fe..ac76edcb850fb 100644 --- a/lib/maxmind/readme_moodle.txt +++ b/lib/maxmind/readme_moodle.txt @@ -16,15 +16,24 @@ Installation ------------ 1) Download the latest versions of GeoIP2-php and MaxMind-DB-Reader-php -wget https://github.com/maxmind/GeoIP2-php/archive/v2.10.0.zip -wget https://github.com/maxmind/MaxMind-DB-Reader-php/archive/v1.5.1.zip +wget https://github.com/maxmind/GeoIP2-php/archive/vX.Y.Z.zip +wget https://github.com/maxmind/MaxMind-DB-Reader-php/archive/vX.Y.Z.zip 2) Unzip the archives -unzip v2.10.0.zip -unzip v1.5.1.zip +unzip vX.Y.Z.zip +unzip vX.Y.Z.zip 3) Move the source code directories into place -mv GeoIP2-php-2.10.0/src/ /path/to/moodle/lib/maxmind/GeoIp2/ -mv MaxMind-DB-Reader-php-1.5.1/src/MaxMind/ /path/to/moodle/lib/maxmind/MaxMind/ +mv GeoIP2-php-X.Y.Z/src/ /path/to/moodle/lib/maxmind/GeoIp2/ +mv MaxMind-DB-Reader-php-X.Y.Z/src/MaxMind/ /path/to/moodle/lib/maxmind/MaxMind/ -4) Run unit tests on iplookup/tests/geoip_test.php. +4) Update other MaxMind related files: +mv MaxMind-DB-Reader-php-X.Y.Z/LICENSE /path/to/moodle/lib/maxmind/MaxMind/ +mv MaxMind-DB-Reader-php-X.Y.Z/CHANGELOG.md /path/to/moodle/lib/maxmind/MaxMind/ +mv MaxMind-DB-Reader-php-X.Y.Z/README.md /path/to/moodle/lib/maxmind/MaxMind/ +mv MaxMind-DB-Reader-php-X.Y.Z/composer.json /path/to/moodle/lib/maxmind/MaxMind/ +mv MaxMind-DB-Reader-php-X.Y.Z/autoload.php /path/to/moodle/lib/maxmind/MaxMind/ + +5) Run unit tests on iplookup/tests/geoip_test.php. + +6) Update maxmind/GeoIp2 and maxmind/MaxMin versions in lib/thirdpartylibs.xml diff --git a/lib/thirdpartylibs.xml b/lib/thirdpartylibs.xml index 768a7af3d57bf..fa4dcc0f73875 100644 --- a/lib/thirdpartylibs.xml +++ b/lib/thirdpartylibs.xml @@ -248,13 +248,13 @@ maxmind/GeoIp2 GeoIP2 PHP API Apache 2.0 - 2.10.0 + 2.11.0 maxmind/MaxMind MaxMind DB Reader API Apache 2.0 - 1.5.1 + 1.9.0 ltiprovider