diff options
Diffstat (limited to 'vendor/maxmind-db/reader/src/MaxMind/Db/Reader.php')
-rw-r--r-- | vendor/maxmind-db/reader/src/MaxMind/Db/Reader.php | 218 |
1 files changed, 147 insertions, 71 deletions
diff --git a/vendor/maxmind-db/reader/src/MaxMind/Db/Reader.php b/vendor/maxmind-db/reader/src/MaxMind/Db/Reader.php index 85457c5..807fe62 100644 --- a/vendor/maxmind-db/reader/src/MaxMind/Db/Reader.php +++ b/vendor/maxmind-db/reader/src/MaxMind/Db/Reader.php @@ -1,15 +1,13 @@ <?php
+declare(strict_types=1);
+
namespace MaxMind\Db;
-use BadMethodCallException;
-use Exception;
-use InvalidArgumentException;
use MaxMind\Db\Reader\Decoder;
use MaxMind\Db\Reader\InvalidDatabaseException;
use MaxMind\Db\Reader\Metadata;
use MaxMind\Db\Reader\Util;
-use UnexpectedValueException;
/**
* Instances of this class provide a reader for the MaxMind DB format. IP
@@ -17,15 +15,49 @@ use UnexpectedValueException; */
class Reader
{
+ /**
+ * @var int
+ */
private static $DATA_SECTION_SEPARATOR_SIZE = 16;
+
+ /**
+ * @var string
+ */
private static $METADATA_START_MARKER = "\xAB\xCD\xEFMaxMind.com";
+
+ /**
+ * @var int<0, max>
+ */
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;
+
+ /**
+ * @var resource
+ */
private $fileHandle;
+
+ /**
+ * @var int
+ */
private $fileSize;
+
+ /**
+ * @var int
+ */
private $ipV4Start;
+
+ /**
+ * @var Metadata
+ */
private $metadata;
/**
@@ -35,40 +67,38 @@ class Reader * @param string $database
* the MaxMind DB file to use
*
- * @throws InvalidArgumentException for invalid database path or unknown arguments
- * @throws \MaxMind\Db\Reader\InvalidDatabaseException
- * if the database is invalid or there is an error reading
- * from it
+ * @throws \InvalidArgumentException for invalid database path or unknown arguments
+ * @throws InvalidDatabaseException
+ * if the database is invalid or there is an error reading
+ * from it
*/
- public function __construct($database)
+ public function __construct(string $database)
{
if (\func_num_args() !== 1) {
- throw new InvalidArgumentException(
- 'The constructor takes exactly one argument.'
+ throw new \ArgumentCountError(
+ sprintf('%s() expects exactly 1 parameter, %d given', __METHOD__, \func_num_args())
);
}
- if (!is_readable($database)) {
- throw new InvalidArgumentException(
+ $fileHandle = @fopen($database, 'rb');
+ if ($fileHandle === false) {
+ throw new \InvalidArgumentException(
"The file \"$database\" does not exist or is not readable."
);
}
- $this->fileHandle = @fopen($database, 'rb');
- if ($this->fileHandle === false) {
- throw new InvalidArgumentException(
- "Error opening \"$database\"."
- );
- }
- $this->fileSize = @filesize($database);
- if ($this->fileSize === false) {
- throw new UnexpectedValueException(
+ $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,
@@ -83,22 +113,22 @@ 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($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;
}
@@ -109,36 +139,30 @@ 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
*/
- 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())
);
}
if (!\is_resource($this->fileHandle)) {
- throw new BadMethodCallException(
+ throw new \BadMethodCallException(
'Attempt to read from a closed MaxMind DB.'
);
}
- if (!filter_var($ipAddress, FILTER_VALIDATE_IP)) {
- throw new InvalidArgumentException(
- "The value \"$ipAddress\" is not a valid IP address."
- );
- }
-
- list($pointer, $prefixLen) = $this->findAddressInTree($ipAddress);
+ [$pointer, $prefixLen] = $this->findAddressInTree($ipAddress);
if ($pointer === 0) {
return [null, $prefixLen];
}
@@ -146,9 +170,21 @@ class Reader 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);
+ if ($rawAddress === false) {
+ throw new InvalidDatabaseException(
+ 'Could not unpack the unsigned char of the packed in_addr representation.'
+ );
+ }
$bitCount = \count($rawAddress) * 8;
@@ -165,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.'
);
@@ -182,14 +218,18 @@ class Reader if ($node === $nodeCount) {
// Record is empty
return [0, $i];
- } elseif ($node > $nodeCount) {
+ }
+ if ($node > $nodeCount) {
// 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,16 +245,23 @@ class Reader 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);
+ $rc = unpack('N', "\x00" . $bytes);
+ if ($rc === false) {
+ throw new InvalidDatabaseException(
+ 'Could not unpack the unsigned long of the node.'
+ );
+ }
+ [, $node] = $rc;
return $node;
+
case 28:
$bytes = Util::read($this->fileHandle, $baseOffset + 3 * $index, 4);
if ($index === 0) {
@@ -222,14 +269,28 @@ class Reader } else {
$middle = 0x0F & \ord($bytes[0]);
}
- list(, $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);
- list(, $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;
+
default:
throw new InvalidDatabaseException(
'Unknown record size: '
@@ -238,7 +299,10 @@ class Reader }
}
- private function resolveDataPointer($pointer)
+ /**
+ * @return mixed
+ */
+ private function resolveDataPointer(int $pointer)
{
$resolved = $pointer - $this->metadata->nodeCount
+ $this->metadata->searchTreeSize;
@@ -248,7 +312,7 @@ class Reader );
}
- list($data) = $this->decoder->decode($resolved);
+ [$data] = $this->decoder->decode($resolved);
return $data;
}
@@ -258,10 +322,15 @@ class Reader * 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);
+ 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;
@@ -278,6 +347,7 @@ class Reader return $offset + $markerLength;
}
}
+
throw new InvalidDatabaseException(
"Error opening database file ($filename). " .
'Is this a valid MaxMind DB file?'
@@ -285,40 +355,46 @@ 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()
+ 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())
);
}
// 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.'
);
}
- return $this->metadata;
+ return clone $this->metadata;
}
/**
* 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()
+ 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(
+ throw new \BadMethodCallException(
'Attempt to close a closed MaxMind DB.'
);
}
|