Merged in feature/280-dev-dev01 (pull request #21)

auto-patch  280-dev-dev01-2024-01-19T16_41_58

* auto-patch  280-dev-dev01-2024-01-19T16_41_58
This commit is contained in:
Tony Volpe
2024-01-19 16:44:43 +00:00
parent 2699b5437a
commit be83910651
2125 changed files with 179300 additions and 35639 deletions

View File

@@ -35,7 +35,7 @@ function mmdb_autoload($class): void
$path = str_replace('\\', '/', $path);
// and finally, add the PHP file extension to the result.
$path = $path . '.php';
$path .= '.php';
// $path should now contain the path to a PHP file defining $class
if (file_exists($path)) {

View File

@@ -3,7 +3,7 @@ PHP_ARG_WITH(maxminddb,
[ --with-maxminddb Enable MaxMind DB Reader extension support])
PHP_ARG_ENABLE(maxminddb-debug, for MaxMind DB debug support,
[ --enable-maxminddb-debug Enable enable MaxMind DB deubg support], no, no)
[ --enable-maxminddb-debug Enable MaxMind DB debug support], no, no)
if test $PHP_MAXMINDDB != "no"; then

View File

@@ -143,7 +143,7 @@ PHP_METHOD(MaxMind_Db_Reader, __construct) {
}
MMDB_s *mmdb = (MMDB_s *)ecalloc(1, sizeof(MMDB_s));
uint16_t status = MMDB_open(db_file, MMDB_MODE_MMAP, mmdb);
int const status = MMDB_open(db_file, MMDB_MODE_MMAP, mmdb);
if (MMDB_SUCCESS != status) {
zend_throw_exception_ex(
@@ -388,12 +388,12 @@ handle_entry_data_list(const MMDB_entry_data_list_s *entry_data_list,
return handle_array(entry_data_list, z_value TSRMLS_CC);
case MMDB_DATA_TYPE_UTF8_STRING:
ZVAL_STRINGL(z_value,
(char *)entry_data_list->entry_data.utf8_string,
entry_data_list->entry_data.utf8_string,
entry_data_list->entry_data.data_size);
break;
case MMDB_DATA_TYPE_BYTES:
ZVAL_STRINGL(z_value,
(char *)entry_data_list->entry_data.bytes,
(char const *)entry_data_list->entry_data.bytes,
entry_data_list->entry_data.data_size);
break;
case MMDB_DATA_TYPE_DOUBLE:
@@ -440,7 +440,7 @@ handle_map(const MMDB_entry_data_list_s *entry_data_list,
for (i = 0; i < map_size && entry_data_list; i++) {
entry_data_list = entry_data_list->next;
char *key = estrndup((char *)entry_data_list->entry_data.utf8_string,
char *key = estrndup(entry_data_list->entry_data.utf8_string,
entry_data_list->entry_data.data_size);
if (NULL == key) {
zend_throw_exception_ex(maxminddb_exception_ce,

View File

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

View File

@@ -14,19 +14,19 @@
<email>goschwald@maxmind.com</email>
<active>yes</active>
</lead>
<date>2021-04-14</date>
<date>2023-12-01</date>
<version>
<release>1.10.1</release>
<api>1.10.1</api>
<release>1.11.1</release>
<api>1.11.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>
<notes>* Resolve warnings when compiling the C extension.
* Fix various type issues detected by PHPStan level. Pull request by
LauraTaylorUK. GitHub #160.</notes>
<contents>
<dir name="/">
<file role="doc" name="LICENSE"/>

View File

@@ -4,15 +4,10 @@ declare(strict_types=1);
namespace MaxMind\Db;
use ArgumentCountError;
use BadMethodCallException;
use Exception;
use InvalidArgumentException;
use MaxMind\Db\Reader\Decoder;
use MaxMind\Db\Reader\InvalidDatabaseException;
use MaxMind\Db\Reader\Metadata;
use MaxMind\Db\Reader\Util;
use UnexpectedValueException;
/**
* Instances of this class provide a reader for the MaxMind DB format. IP
@@ -24,14 +19,17 @@ class Reader
* @var int
*/
private static $DATA_SECTION_SEPARATOR_SIZE = 16;
/**
* @var string
*/
private static $METADATA_START_MARKER = "\xAB\xCD\xEFMaxMind.com";
/**
* @var int
* @var int<0, max>
*/
private static $METADATA_START_MARKER_LENGTH = 14;
/**
* @var int
*/
@@ -41,18 +39,22 @@ class Reader
* @var Decoder
*/
private $decoder;
/**
* @var resource
*/
private $fileHandle;
/**
* @var int
*/
private $fileSize;
/**
* @var int
*/
private $ipV4Start;
/**
* @var Metadata
*/
@@ -65,22 +67,22 @@ class Reader
* @param string $database
* 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
* if the database is invalid or there is an error reading
* from it
* if the database is invalid or there is an error reading
* from it
*/
public function __construct(string $database)
{
if (\func_num_args() !== 1) {
throw new ArgumentCountError(
throw new \ArgumentCountError(
sprintf('%s() expects exactly 1 parameter, %d given', __METHOD__, \func_num_args())
);
}
$fileHandle = @fopen($database, 'rb');
if ($fileHandle === false) {
throw new InvalidArgumentException(
throw new \InvalidArgumentException(
"The file \"$database\" does not exist or is not readable."
);
}
@@ -88,7 +90,7 @@ class Reader
$fileSize = @filesize($database);
if ($fileSize === false) {
throw new UnexpectedValueException(
throw new \UnexpectedValueException(
"Error determining the size of \"$database\"."
);
}
@@ -111,18 +113,18 @@ class Reader
* @param string $ipAddress
* the IP address to look up
*
* @throws BadMethodCallException if this method is called on a closed database
* @throws InvalidArgumentException if something other than a single IP address is passed to the method
* @throws \BadMethodCallException if this method is called on a closed database
* @throws \InvalidArgumentException if something other than a single IP address is passed to the method
* @throws InvalidDatabaseException
* if the database is invalid or there is an error reading
* from it
* if the database is invalid or there is an error reading
* from it
*
* @return mixed the record for the IP address
*/
public function get(string $ipAddress)
{
if (\func_num_args() !== 1) {
throw new ArgumentCountError(
throw new \ArgumentCountError(
sprintf('%s() expects exactly 1 parameter, %d given', __METHOD__, \func_num_args())
);
}
@@ -137,11 +139,11 @@ class Reader
* @param string $ipAddress
* the IP address to look up
*
* @throws BadMethodCallException if this method is called on a closed database
* @throws InvalidArgumentException if something other than a single IP address is passed to the method
* @throws \BadMethodCallException if this method is called on a closed database
* @throws \InvalidArgumentException if something other than a single IP address is passed to the method
* @throws InvalidDatabaseException
* if the database is invalid or there is an error reading
* from it
* if the database is invalid or there is an error reading
* from it
*
* @return array an array where the first element is the record and the
* second the network prefix length for the record
@@ -149,13 +151,13 @@ class Reader
public function getWithPrefixLen(string $ipAddress): array
{
if (\func_num_args() !== 1) {
throw new ArgumentCountError(
throw new \ArgumentCountError(
sprintf('%s() expects exactly 1 parameter, %d given', __METHOD__, \func_num_args())
);
}
if (!\is_resource($this->fileHandle)) {
throw new BadMethodCallException(
throw new \BadMethodCallException(
'Attempt to read from a closed MaxMind DB.'
);
}
@@ -172,12 +174,17 @@ class Reader
{
$packedAddr = @inet_pton($ipAddress);
if ($packedAddr === false) {
throw new InvalidArgumentException(
throw new \InvalidArgumentException(
"The value \"$ipAddress\" is not a valid IP address."
);
}
$rawAddress = unpack('C*', $packedAddr);
if ($rawAddress === false) {
throw new InvalidDatabaseException(
'Could not unpack the unsigned char of the packed in_addr representation.'
);
}
$bitCount = \count($rawAddress) * 8;
@@ -194,7 +201,7 @@ class Reader
$node = $this->ipV4Start;
}
} elseif ($metadata->ipVersion === 4 && $bitCount === 128) {
throw new InvalidArgumentException(
throw new \InvalidArgumentException(
"Error looking up $ipAddress. You attempted to look up an"
. ' IPv6 address in an IPv4-only database.'
);
@@ -245,7 +252,13 @@ class Reader
switch ($this->metadata->recordSize) {
case 24:
$bytes = Util::read($this->fileHandle, $baseOffset + $index * 3, 3);
[, $node] = unpack('N', "\x00" . $bytes);
$rc = unpack('N', "\x00" . $bytes);
if ($rc === false) {
throw new InvalidDatabaseException(
'Could not unpack the unsigned long of the node.'
);
}
[, $node] = $rc;
return $node;
@@ -256,13 +269,25 @@ class Reader
} else {
$middle = 0x0F & \ord($bytes[0]);
}
[, $node] = unpack('N', \chr($middle) . substr($bytes, $index, 3));
$rc = unpack('N', \chr($middle) . substr($bytes, $index, 3));
if ($rc === false) {
throw new InvalidDatabaseException(
'Could not unpack the unsigned long of the node.'
);
}
[, $node] = $rc;
return $node;
case 32:
$bytes = Util::read($this->fileHandle, $baseOffset + $index * 4, 4);
[, $node] = unpack('N', $bytes);
$rc = unpack('N', $bytes);
if ($rc === false) {
throw new InvalidDatabaseException(
'Could not unpack the unsigned long of the node.'
);
}
[, $node] = $rc;
return $node;
@@ -301,6 +326,11 @@ class Reader
{
$handle = $this->fileHandle;
$fstat = fstat($handle);
if ($fstat === false) {
throw new InvalidDatabaseException(
"Error getting file information ($filename)."
);
}
$fileSize = $fstat['size'];
$marker = self::$METADATA_START_MARKER;
$markerLength = self::$METADATA_START_MARKER_LENGTH;
@@ -325,15 +355,15 @@ class Reader
}
/**
* @throws InvalidArgumentException if arguments are passed to the method
* @throws BadMethodCallException if the database has been closed
* @throws \InvalidArgumentException if arguments are passed to the method
* @throws \BadMethodCallException if the database has been closed
*
* @return Metadata object for the database
*/
public function metadata(): Metadata
{
if (\func_num_args()) {
throw new ArgumentCountError(
throw new \ArgumentCountError(
sprintf('%s() expects exactly 0 parameters, %d given', __METHOD__, \func_num_args())
);
}
@@ -341,7 +371,7 @@ class Reader
// Not technically required, but this makes it consistent with
// C extension and it allows us to change our implementation later.
if (!\is_resource($this->fileHandle)) {
throw new BadMethodCallException(
throw new \BadMethodCallException(
'Attempt to read from a closed MaxMind DB.'
);
}
@@ -352,19 +382,19 @@ class Reader
/**
* Closes the MaxMind DB and returns resources to the system.
*
* @throws Exception
* if an I/O error occurs
* @throws \Exception
* if an I/O error occurs
*/
public function close(): void
{
if (\func_num_args()) {
throw new ArgumentCountError(
throw new \ArgumentCountError(
sprintf('%s() expects exactly 0 parameters, %d given', __METHOD__, \func_num_args())
);
}
if (!\is_resource($this->fileHandle)) {
throw new BadMethodCallException(
throw new \BadMethodCallException(
'Attempt to close a closed MaxMind DB.'
);
}

View File

@@ -5,7 +5,6 @@ declare(strict_types=1);
namespace MaxMind\Db\Reader;
// @codingStandardsIgnoreLine
use RuntimeException;
class Decoder
{
@@ -13,20 +12,19 @@ class Decoder
* @var resource
*/
private $fileStream;
/**
* @var int
*/
private $pointerBase;
/**
* @var float
*/
private $pointerBaseByteSize;
/**
* This is only used for unit testing.
*
* @var bool
*/
private $pointerTestHack;
/**
* @var bool
*/
@@ -44,8 +42,8 @@ class Decoder
private const _UINT64 = 9;
private const _UINT128 = 10;
private const _ARRAY = 11;
private const _CONTAINER = 12;
private const _END_MARKER = 13;
// 12 is the container type
// 13 is the end marker type
private const _BOOLEAN = 14;
private const _FLOAT = 15;
@@ -60,7 +58,6 @@ class Decoder
$this->fileStream = $fileStream;
$this->pointerBase = $pointerBase;
$this->pointerBaseByteSize = $pointerBase > 0 ? log($pointerBase, 2) / 8 : 0;
$this->pointerTestHack = $pointerTestHack;
$this->switchByteOrder = $this->isPlatformLittleEndian();
@@ -111,6 +108,9 @@ class Decoder
return $this->decodeByType($type, $offset, $size);
}
/**
* @param int<0, max> $size
*/
private function decodeByType(int $type, int $offset, int $size): array
{
switch ($type) {
@@ -188,7 +188,13 @@ class Decoder
{
// This assumes IEEE 754 doubles, but most (all?) modern platforms
// use them.
[, $double] = unpack('E', $bytes);
$rc = unpack('E', $bytes);
if ($rc === false) {
throw new InvalidDatabaseException(
'Could not unpack a double value from the given bytes.'
);
}
[, $double] = $rc;
return $double;
}
@@ -197,7 +203,13 @@ class Decoder
{
// This assumes IEEE 754 floats, but most (all?) modern platforms
// use them.
[, $float] = unpack('G', $bytes);
$rc = unpack('G', $bytes);
if ($rc === false) {
throw new InvalidDatabaseException(
'Could not unpack a float value from the given bytes.'
);
}
[, $float] = $rc;
return $float;
}
@@ -224,7 +236,13 @@ class Decoder
);
}
[, $int] = unpack('l', $this->maybeSwitchByteOrder($bytes));
$rc = unpack('l', $this->maybeSwitchByteOrder($bytes));
if ($rc === false) {
throw new InvalidDatabaseException(
'Could not unpack a 32bit integer value from the given bytes.'
);
}
[, $int] = $rc;
return $int;
}
@@ -247,19 +265,31 @@ class Decoder
$pointerSize = (($ctrlByte >> 3) & 0x3) + 1;
$buffer = Util::read($this->fileStream, $offset, $pointerSize);
$offset = $offset + $pointerSize;
$offset += $pointerSize;
switch ($pointerSize) {
case 1:
$packed = \chr($ctrlByte & 0x7) . $buffer;
[, $pointer] = unpack('n', $packed);
$rc = unpack('n', $packed);
if ($rc === false) {
throw new InvalidDatabaseException(
'Could not unpack an unsigned short value from the given bytes (pointerSize is 1).'
);
}
[, $pointer] = $rc;
$pointer += $this->pointerBase;
break;
case 2:
$packed = "\x00" . \chr($ctrlByte & 0x7) . $buffer;
[, $pointer] = unpack('N', $packed);
$rc = unpack('N', $packed);
if ($rc === false) {
throw new InvalidDatabaseException(
'Could not unpack an unsigned long value from the given bytes (pointerSize is 2).'
);
}
[, $pointer] = $rc;
$pointer += $this->pointerBase + 2048;
break;
@@ -269,7 +299,13 @@ class Decoder
// It is safe to use 'N' here, even on 32 bit machines as the
// first bit is 0.
[, $pointer] = unpack('N', $packed);
$rc = unpack('N', $packed);
if ($rc === false) {
throw new InvalidDatabaseException(
'Could not unpack an unsigned long value from the given bytes (pointerSize is 3).'
);
}
[, $pointer] = $rc;
$pointer += $this->pointerBase + 526336;
break;
@@ -284,7 +320,7 @@ class Decoder
if (\PHP_INT_MAX - $pointerBase >= $pointerOffset) {
$pointer = $pointerOffset + $pointerBase;
} else {
throw new RuntimeException(
throw new \RuntimeException(
'The database offset is too large to be represented on your platform.'
);
}
@@ -307,32 +343,39 @@ class Decoder
return 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);
if ($useRealInts) {
$integer = 0;
for ($i = 0; $i < $byteLength; ++$i) {
$part = \ord($bytes[$i]);
$integer = ($integer << 8) + $part;
}
return $integer;
}
// We only use gmp or bcmath if the final value is too big
$integerAsString = '0';
for ($i = 0; $i < $byteLength; ++$i) {
$part = \ord($bytes[$i]);
// We only use gmp or bcmath if the final value is too big
if ($useRealInts) {
$integer = ($integer << 8) + $part;
} elseif (\extension_loaded('gmp')) {
$integer = gmp_strval(gmp_add(gmp_mul((string) $integer, '256'), $part));
if (\extension_loaded('gmp')) {
$integerAsString = gmp_strval(gmp_add(gmp_mul($integerAsString, '256'), $part));
} elseif (\extension_loaded('bcmath')) {
$integer = bcadd(bcmul((string) $integer, '256'), (string) $part);
$integerAsString = bcadd(bcmul($integerAsString, '256'), (string) $part);
} else {
throw new RuntimeException(
throw new \RuntimeException(
'The gmp or bcmath extension must be installed to read this database.'
);
}
}
return $integer;
return $integerAsString;
}
private function sizeFromCtrlByte(int $ctrlByte, int $offset): array
@@ -349,10 +392,22 @@ class Decoder
if ($size === 29) {
$size = 29 + \ord($bytes);
} elseif ($size === 30) {
[, $adjust] = unpack('n', $bytes);
$rc = unpack('n', $bytes);
if ($rc === false) {
throw new InvalidDatabaseException(
'Could not unpack an unsigned short value from the given bytes.'
);
}
[, $adjust] = $rc;
$size = 285 + $adjust;
} else {
[, $adjust] = unpack('N', "\x00" . $bytes);
$rc = unpack('N', "\x00" . $bytes);
if ($rc === false) {
throw new InvalidDatabaseException(
'Could not unpack an unsigned long value from the given bytes.'
);
}
[, $adjust] = $rc;
$size = $adjust + 65821;
}
@@ -368,7 +423,13 @@ class Decoder
{
$testint = 0x00FF;
$packed = pack('S', $testint);
$rc = unpack('v', $packed);
if ($rc === false) {
throw new InvalidDatabaseException(
'Could not unpack an unsigned short value from the given bytes.'
);
}
return $testint === current(unpack('v', $packed));
return $testint === current($rc);
}
}

View File

@@ -4,11 +4,8 @@ declare(strict_types=1);
namespace MaxMind\Db\Reader;
use Exception;
/**
* This class should be thrown when unexpected data is found in the database.
*/
class InvalidDatabaseException extends Exception
{
}
// phpcs:disable
class InvalidDatabaseException extends \Exception {}

View File

@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace MaxMind\Db\Reader;
use ArgumentCountError;
/**
* This class provides the metadata for the MaxMind DB file.
*/
@@ -18,6 +16,7 @@ class Metadata
* @var int
*/
public $binaryFormatMajorVersion;
/**
* This is an unsigned 16-bit integer indicating the minor version number
* for the database's binary format.
@@ -25,6 +24,7 @@ class Metadata
* @var int
*/
public $binaryFormatMinorVersion;
/**
* This is an unsigned 64-bit integer that contains the database build
* timestamp as a Unix epoch value.
@@ -32,6 +32,7 @@ class Metadata
* @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
@@ -40,6 +41,7 @@ class Metadata
* @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
@@ -49,6 +51,7 @@ class Metadata
* @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.
@@ -56,6 +59,7 @@ class Metadata
* @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
@@ -64,10 +68,12 @@ class Metadata
* @var array
*/
public $languages;
/**
* @var int
*/
public $nodeByteSize;
/**
* This is an unsigned 32-bit integer indicating the number of nodes in
* the search tree.
@@ -75,6 +81,7 @@ class Metadata
* @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.
@@ -82,6 +89,7 @@ class Metadata
* @var int
*/
public $recordSize;
/**
* @var int
*/
@@ -90,7 +98,7 @@ class Metadata
public function __construct(array $metadata)
{
if (\func_num_args() !== 1) {
throw new ArgumentCountError(
throw new \ArgumentCountError(
sprintf('%s() expects exactly 1 parameter, %d given', __METHOD__, \func_num_args())
);
}

View File

@@ -7,7 +7,8 @@ namespace MaxMind\Db\Reader;
class Util
{
/**
* @param resource $stream
* @param resource $stream
* @param int<0, max> $numberOfBytes
*/
public static function read($stream, int $offset, int $numberOfBytes): string
{