Fixed Broken Shit
This commit is contained in:
185
includes/vendor/maxmind-db/reader/README.md
vendored
Normal file
185
includes/vendor/maxmind-db/reader/README.md
vendored
Normal file
@ -0,0 +1,185 @@
|
||||
# MaxMind DB Reader PHP API #
|
||||
|
||||
## Description ##
|
||||
|
||||
This is the PHP API for reading MaxMind DB files. MaxMind DB is a binary file
|
||||
format that stores data indexed by IP address subnets (IPv4 or IPv6).
|
||||
|
||||
## Installation (Composer) ##
|
||||
|
||||
We recommend installing this package with [Composer](https://getcomposer.org/).
|
||||
|
||||
### Download Composer ###
|
||||
|
||||
To download Composer, run in the root directory of your project:
|
||||
|
||||
```bash
|
||||
curl -sS https://getcomposer.org/installer | php
|
||||
```
|
||||
|
||||
You should now have the file `composer.phar` in your project directory.
|
||||
|
||||
### Install Dependencies ###
|
||||
|
||||
Run in your project root:
|
||||
|
||||
```
|
||||
php composer.phar require maxmind-db/reader:~1.0
|
||||
```
|
||||
|
||||
You should now have the files `composer.json` and `composer.lock` as well as
|
||||
the directory `vendor` in your project directory. If you use a version control
|
||||
system, `composer.json` should be added to it.
|
||||
|
||||
### Require Autoloader ###
|
||||
|
||||
After installing the dependencies, you need to require the Composer autoloader
|
||||
from your code:
|
||||
|
||||
```php
|
||||
require 'vendor/autoload.php';
|
||||
```
|
||||
|
||||
## Installation (Standalone) ##
|
||||
|
||||
If you don't want to use Composer for some reason, a custom
|
||||
`autoload.php` is provided for you in the project root. To use the
|
||||
library, simply include that file,
|
||||
|
||||
```php
|
||||
require('/path/to/MaxMind-DB-Reader-php/autoload.php');
|
||||
```
|
||||
|
||||
and then instantiate the reader class normally:
|
||||
|
||||
```php
|
||||
use MaxMind\Db\Reader;
|
||||
$reader = new Reader('example.mmdb');
|
||||
```
|
||||
|
||||
## Installation (RPM)
|
||||
|
||||
RPMs are available in the [official Fedora repository](https://apps.fedoraproject.org/packages/php-maxminddb).
|
||||
|
||||
To install on Fedora, run:
|
||||
|
||||
```bash
|
||||
dnf install php-maxminddb
|
||||
```
|
||||
|
||||
To install on CentOS or RHEL 7, first [enable the EPEL repository](https://fedoraproject.org/wiki/EPEL)
|
||||
and then run:
|
||||
|
||||
```bash
|
||||
yum install php-maxminddb
|
||||
```
|
||||
|
||||
Please note that these packages are *not* maintained by MaxMind.
|
||||
|
||||
## Usage ##
|
||||
|
||||
## Example ##
|
||||
|
||||
```php
|
||||
<?php
|
||||
require_once 'vendor/autoload.php';
|
||||
|
||||
use MaxMind\Db\Reader;
|
||||
|
||||
$ipAddress = '24.24.24.24';
|
||||
$databaseFile = 'GeoIP2-City.mmdb';
|
||||
|
||||
$reader = new Reader($databaseFile);
|
||||
|
||||
// get returns just the record for the IP address
|
||||
print_r($reader->get($ipAddress));
|
||||
|
||||
// getWithPrefixLen returns an array containing the record and the
|
||||
// associated prefix length for that record.
|
||||
print_r($reader->getWithPrefixLen($ipAddress));
|
||||
|
||||
$reader->close();
|
||||
```
|
||||
|
||||
## Optional PHP C Extension ##
|
||||
|
||||
MaxMind provides an optional C extension that is a drop-in replacement for
|
||||
`MaxMind\Db\Reader`. In order to use this extension, you must install the
|
||||
Reader API as described above and install the extension as described below. If
|
||||
you are using an autoloader, no changes to your code should be necessary.
|
||||
|
||||
### Installing Extension ###
|
||||
|
||||
First install [libmaxminddb](https://github.com/maxmind/libmaxminddb) as
|
||||
described in its [README.md
|
||||
file](https://github.com/maxmind/libmaxminddb/blob/main/README.md#installing-from-a-tarball).
|
||||
After successfully installing libmaxmindb, you may install the extension
|
||||
from [pecl](https://pecl.php.net/package/maxminddb):
|
||||
|
||||
```
|
||||
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
|
||||
phpize
|
||||
./configure
|
||||
make
|
||||
make test
|
||||
sudo make install
|
||||
```
|
||||
|
||||
You then must load your extension. The recommend method is to add the
|
||||
following to your `php.ini` file:
|
||||
|
||||
```
|
||||
extension=maxminddb.so
|
||||
```
|
||||
|
||||
Note: You may need to install the PHP development package on your OS such as
|
||||
php5-dev for Debian-based systems or php-devel for RedHat/Fedora-based ones.
|
||||
|
||||
## 128-bit Integer Support ##
|
||||
|
||||
The MaxMind DB format includes 128-bit unsigned integer as a type. Although
|
||||
no MaxMind-distributed database currently makes use of this type, both the
|
||||
pure PHP reader and the C extension support this type. The pure PHP reader
|
||||
requires gmp or bcmath to read databases with 128-bit unsigned integers.
|
||||
|
||||
The integer is currently returned as a hexadecimal string (prefixed with "0x")
|
||||
by the C extension and a decimal string (no prefix) by the pure PHP reader.
|
||||
Any change to make the reader implementations always return either a
|
||||
hexadecimal or decimal representation of the integer will NOT be considered a
|
||||
breaking change.
|
||||
|
||||
## Support ##
|
||||
|
||||
Please report all issues with this code using the [GitHub issue tracker](https://github.com/maxmind/MaxMind-DB-Reader-php/issues).
|
||||
|
||||
If you are having an issue with a MaxMind service that is not specific to the
|
||||
client API, please see [our support page](https://www.maxmind.com/en/support).
|
||||
|
||||
## Requirements ##
|
||||
|
||||
This library requires PHP 7.2 or greater.
|
||||
|
||||
The GMP or BCMath extension may be required to read some databases
|
||||
using the pure PHP API.
|
||||
|
||||
## Contributing ##
|
||||
|
||||
Patches and pull requests are encouraged. All code should follow the PSR-1 and
|
||||
PSR-2 style guidelines. Please include unit tests whenever possible.
|
||||
|
||||
## Versioning ##
|
||||
|
||||
The MaxMind DB Reader PHP API uses [Semantic Versioning](https://semver.org/).
|
||||
|
||||
## Copyright and License ##
|
||||
|
||||
This software is Copyright (c) 2014-2020 by MaxMind, Inc.
|
||||
|
||||
This is free software, licensed under the Apache License, Version 2.0.
|
373
includes/vendor/maxmind-db/reader/src/MaxMind/Db/Reader.php
vendored
Normal file
373
includes/vendor/maxmind-db/reader/src/MaxMind/Db/Reader.php
vendored
Normal file
@ -0,0 +1,373 @@
|
||||
<?php
|
||||
|
||||
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
|
||||
* addresses can be looked up using the get method.
|
||||
*/
|
||||
class Reader
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private static $DATA_SECTION_SEPARATOR_SIZE = 16;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private static $METADATA_START_MARKER = "\xAB\xCD\xEFMaxMind.com";
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private static $METADATA_START_MARKER_LENGTH = 14;
|
||||
/**
|
||||
* @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;
|
||||
|
||||
/**
|
||||
* Constructs a Reader for the MaxMind DB format. The file passed to it must
|
||||
* be a valid MaxMind DB file such as a GeoIp2 database file.
|
||||
*
|
||||
* @param string $database
|
||||
* the MaxMind DB file to use
|
||||
*
|
||||
* @throws InvalidArgumentException for invalid database path or unknown arguments
|
||||
* @throws InvalidDatabaseException
|
||||
* 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(
|
||||
sprintf('%s() expects exactly 1 parameter, %d given', __METHOD__, \func_num_args())
|
||||
);
|
||||
}
|
||||
|
||||
$fileHandle = @fopen($database, 'rb');
|
||||
if ($fileHandle === false) {
|
||||
throw new InvalidArgumentException(
|
||||
"The file \"$database\" does not exist or is not readable."
|
||||
);
|
||||
}
|
||||
$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);
|
||||
[$metadataArray] = $metadataDecoder->decode($start);
|
||||
$this->metadata = new Metadata($metadataArray);
|
||||
$this->decoder = new Decoder(
|
||||
$this->fileHandle,
|
||||
$this->metadata->searchTreeSize + self::$DATA_SECTION_SEPARATOR_SIZE
|
||||
);
|
||||
$this->ipV4Start = $this->ipV4StartNode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the record for the IP address.
|
||||
*
|
||||
* @param string $ipAddress
|
||||
* the IP address to look up
|
||||
*
|
||||
* @throws BadMethodCallException if this method is called on a closed database
|
||||
* @throws InvalidArgumentException if something other than a single IP address is passed to the method
|
||||
* @throws InvalidDatabaseException
|
||||
* if the database is invalid or there is an error reading
|
||||
* from it
|
||||
*
|
||||
* @return mixed the record for the IP address
|
||||
*/
|
||||
public function get(string $ipAddress)
|
||||
{
|
||||
if (\func_num_args() !== 1) {
|
||||
throw new ArgumentCountError(
|
||||
sprintf('%s() expects exactly 1 parameter, %d given', __METHOD__, \func_num_args())
|
||||
);
|
||||
}
|
||||
[$record] = $this->getWithPrefixLen($ipAddress);
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the record for the IP address and its associated network prefix length.
|
||||
*
|
||||
* @param string $ipAddress
|
||||
* the IP address to look up
|
||||
*
|
||||
* @throws BadMethodCallException if this method is called on a closed database
|
||||
* @throws InvalidArgumentException if something other than a single IP address is passed to the method
|
||||
* @throws InvalidDatabaseException
|
||||
* if the database is invalid or there is an error reading
|
||||
* from it
|
||||
*
|
||||
* @return array an array where the first element is the record and the
|
||||
* second the network prefix length for the record
|
||||
*/
|
||||
public function getWithPrefixLen(string $ipAddress): array
|
||||
{
|
||||
if (\func_num_args() !== 1) {
|
||||
throw new ArgumentCountError(
|
||||
sprintf('%s() expects exactly 1 parameter, %d given', __METHOD__, \func_num_args())
|
||||
);
|
||||
}
|
||||
|
||||
if (!\is_resource($this->fileHandle)) {
|
||||
throw new BadMethodCallException(
|
||||
'Attempt to read from a closed MaxMind DB.'
|
||||
);
|
||||
}
|
||||
|
||||
[$pointer, $prefixLen] = $this->findAddressInTree($ipAddress);
|
||||
if ($pointer === 0) {
|
||||
return [null, $prefixLen];
|
||||
}
|
||||
|
||||
return [$this->resolveDataPointer($pointer), $prefixLen];
|
||||
}
|
||||
|
||||
private function findAddressInTree(string $ipAddress): array
|
||||
{
|
||||
$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;
|
||||
|
||||
// The first node of the tree is always node 0, at the beginning of the
|
||||
// value
|
||||
$node = 0;
|
||||
|
||||
$metadata = $this->metadata;
|
||||
|
||||
// Check if we are looking up an IPv4 address in an IPv6 tree. If this
|
||||
// is the case, we can skip over the first 96 nodes.
|
||||
if ($metadata->ipVersion === 6) {
|
||||
if ($bitCount === 32) {
|
||||
$node = $this->ipV4Start;
|
||||
}
|
||||
} elseif ($metadata->ipVersion === 4 && $bitCount === 128) {
|
||||
throw new InvalidArgumentException(
|
||||
"Error looking up $ipAddress. You attempted to look up an"
|
||||
. ' IPv6 address in an IPv4-only database.'
|
||||
);
|
||||
}
|
||||
|
||||
$nodeCount = $metadata->nodeCount;
|
||||
|
||||
for ($i = 0; $i < $bitCount && $node < $nodeCount; ++$i) {
|
||||
$tempBit = 0xFF & $rawAddress[($i >> 3) + 1];
|
||||
$bit = 1 & ($tempBit >> 7 - ($i % 8));
|
||||
|
||||
$node = $this->readNode($node, $bit);
|
||||
}
|
||||
if ($node === $nodeCount) {
|
||||
// Record is empty
|
||||
return [0, $i];
|
||||
}
|
||||
if ($node > $nodeCount) {
|
||||
// Record is a data pointer
|
||||
return [$node, $i];
|
||||
}
|
||||
|
||||
throw new InvalidDatabaseException(
|
||||
'Invalid or corrupt database. Maximum search depth reached without finding a leaf node'
|
||||
);
|
||||
}
|
||||
|
||||
private function ipV4StartNode(): int
|
||||
{
|
||||
// If we have an IPv4 database, the start node is the first node
|
||||
if ($this->metadata->ipVersion === 4) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$node = 0;
|
||||
|
||||
for ($i = 0; $i < 96 && $node < $this->metadata->nodeCount; ++$i) {
|
||||
$node = $this->readNode($node, 0);
|
||||
}
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
private function readNode(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);
|
||||
[, $node] = unpack('N', "\x00" . $bytes);
|
||||
|
||||
return $node;
|
||||
|
||||
case 28:
|
||||
$bytes = Util::read($this->fileHandle, $baseOffset + 3 * $index, 4);
|
||||
if ($index === 0) {
|
||||
$middle = (0xF0 & \ord($bytes[3])) >> 4;
|
||||
} else {
|
||||
$middle = 0x0F & \ord($bytes[0]);
|
||||
}
|
||||
[, $node] = unpack('N', \chr($middle) . substr($bytes, $index, 3));
|
||||
|
||||
return $node;
|
||||
|
||||
case 32:
|
||||
$bytes = Util::read($this->fileHandle, $baseOffset + $index * 4, 4);
|
||||
[, $node] = unpack('N', $bytes);
|
||||
|
||||
return $node;
|
||||
|
||||
default:
|
||||
throw new InvalidDatabaseException(
|
||||
'Unknown record size: '
|
||||
. $this->metadata->recordSize
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
private function resolveDataPointer(int $pointer)
|
||||
{
|
||||
$resolved = $pointer - $this->metadata->nodeCount
|
||||
+ $this->metadata->searchTreeSize;
|
||||
if ($resolved >= $this->fileSize) {
|
||||
throw new InvalidDatabaseException(
|
||||
"The MaxMind DB file's search tree is corrupt"
|
||||
);
|
||||
}
|
||||
|
||||
[$data] = $this->decoder->decode($resolved);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is an extremely naive but reasonably readable implementation. There
|
||||
* are much faster algorithms (e.g., Boyer-Moore) for this if speed is ever
|
||||
* an issue, but I suspect it won't be.
|
||||
*/
|
||||
private function findMetadataStart(string $filename): int
|
||||
{
|
||||
$handle = $this->fileHandle;
|
||||
$fstat = fstat($handle);
|
||||
$fileSize = $fstat['size'];
|
||||
$marker = self::$METADATA_START_MARKER;
|
||||
$markerLength = self::$METADATA_START_MARKER_LENGTH;
|
||||
|
||||
$minStart = $fileSize - min(self::$METADATA_MAX_SIZE, $fileSize);
|
||||
|
||||
for ($offset = $fileSize - $markerLength; $offset >= $minStart; --$offset) {
|
||||
if (fseek($handle, $offset) !== 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
$value = fread($handle, $markerLength);
|
||||
if ($value === $marker) {
|
||||
return $offset + $markerLength;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidDatabaseException(
|
||||
"Error opening database file ($filename). " .
|
||||
'Is this a valid MaxMind DB file?'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException if arguments are passed to the method
|
||||
* @throws BadMethodCallException if the database has been closed
|
||||
*
|
||||
* @return Metadata object for the database
|
||||
*/
|
||||
public function metadata(): Metadata
|
||||
{
|
||||
if (\func_num_args()) {
|
||||
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(
|
||||
'Attempt to read from a closed MaxMind DB.'
|
||||
);
|
||||
}
|
||||
|
||||
return clone $this->metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the MaxMind DB and returns resources to the system.
|
||||
*
|
||||
* @throws Exception
|
||||
* if an I/O error occurs
|
||||
*/
|
||||
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.'
|
||||
);
|
||||
}
|
||||
fclose($this->fileHandle);
|
||||
}
|
||||
}
|
374
includes/vendor/maxmind-db/reader/src/MaxMind/Db/Reader/Decoder.php
vendored
Normal file
374
includes/vendor/maxmind-db/reader/src/MaxMind/Db/Reader/Decoder.php
vendored
Normal file
@ -0,0 +1,374 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace MaxMind\Db\Reader;
|
||||
|
||||
// @codingStandardsIgnoreLine
|
||||
use RuntimeException;
|
||||
|
||||
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
|
||||
*/
|
||||
private $switchByteOrder;
|
||||
|
||||
private const _EXTENDED = 0;
|
||||
private const _POINTER = 1;
|
||||
private const _UTF8_STRING = 2;
|
||||
private const _DOUBLE = 3;
|
||||
private const _BYTES = 4;
|
||||
private const _UINT16 = 5;
|
||||
private const _UINT32 = 6;
|
||||
private const _MAP = 7;
|
||||
private const _INT32 = 8;
|
||||
private const _UINT64 = 9;
|
||||
private const _UINT128 = 10;
|
||||
private const _ARRAY = 11;
|
||||
private const _CONTAINER = 12;
|
||||
private const _END_MARKER = 13;
|
||||
private const _BOOLEAN = 14;
|
||||
private const _FLOAT = 15;
|
||||
|
||||
/**
|
||||
* @param resource $fileStream
|
||||
*/
|
||||
public function __construct(
|
||||
$fileStream,
|
||||
int $pointerBase = 0,
|
||||
bool $pointerTestHack = false
|
||||
) {
|
||||
$this->fileStream = $fileStream;
|
||||
$this->pointerBase = $pointerBase;
|
||||
|
||||
$this->pointerBaseByteSize = $pointerBase > 0 ? log($pointerBase, 2) / 8 : 0;
|
||||
$this->pointerTestHack = $pointerTestHack;
|
||||
|
||||
$this->switchByteOrder = $this->isPlatformLittleEndian();
|
||||
}
|
||||
|
||||
public function decode(int $offset): array
|
||||
{
|
||||
$ctrlByte = \ord(Util::read($this->fileStream, $offset, 1));
|
||||
++$offset;
|
||||
|
||||
$type = $ctrlByte >> 5;
|
||||
|
||||
// Pointers are a special case, we don't read the next $size bytes, we
|
||||
// use the size to determine the length of the pointer and then follow
|
||||
// it.
|
||||
if ($type === self::_POINTER) {
|
||||
[$pointer, $offset] = $this->decodePointer($ctrlByte, $offset);
|
||||
|
||||
// for unit testing
|
||||
if ($this->pointerTestHack) {
|
||||
return [$pointer];
|
||||
}
|
||||
|
||||
[$result] = $this->decode($pointer);
|
||||
|
||||
return [$result, $offset];
|
||||
}
|
||||
|
||||
if ($type === self::_EXTENDED) {
|
||||
$nextByte = \ord(Util::read($this->fileStream, $offset, 1));
|
||||
|
||||
$type = $nextByte + 7;
|
||||
|
||||
if ($type < 8) {
|
||||
throw new InvalidDatabaseException(
|
||||
'Something went horribly wrong in the decoder. An extended type '
|
||||
. 'resolved to a type number < 8 ('
|
||||
. $type
|
||||
. ')'
|
||||
);
|
||||
}
|
||||
|
||||
++$offset;
|
||||
}
|
||||
|
||||
[$size, $offset] = $this->sizeFromCtrlByte($ctrlByte, $offset);
|
||||
|
||||
return $this->decodeByType($type, $offset, $size);
|
||||
}
|
||||
|
||||
private function decodeByType(int $type, int $offset, int $size): array
|
||||
{
|
||||
switch ($type) {
|
||||
case self::_MAP:
|
||||
return $this->decodeMap($size, $offset);
|
||||
|
||||
case self::_ARRAY:
|
||||
return $this->decodeArray($size, $offset);
|
||||
|
||||
case self::_BOOLEAN:
|
||||
return [$this->decodeBoolean($size), $offset];
|
||||
}
|
||||
|
||||
$newOffset = $offset + $size;
|
||||
$bytes = Util::read($this->fileStream, $offset, $size);
|
||||
|
||||
switch ($type) {
|
||||
case self::_BYTES:
|
||||
case self::_UTF8_STRING:
|
||||
return [$bytes, $newOffset];
|
||||
|
||||
case self::_DOUBLE:
|
||||
$this->verifySize(8, $size);
|
||||
|
||||
return [$this->decodeDouble($bytes), $newOffset];
|
||||
|
||||
case self::_FLOAT:
|
||||
$this->verifySize(4, $size);
|
||||
|
||||
return [$this->decodeFloat($bytes), $newOffset];
|
||||
|
||||
case self::_INT32:
|
||||
return [$this->decodeInt32($bytes, $size), $newOffset];
|
||||
|
||||
case self::_UINT16:
|
||||
case self::_UINT32:
|
||||
case self::_UINT64:
|
||||
case self::_UINT128:
|
||||
return [$this->decodeUint($bytes, $size), $newOffset];
|
||||
|
||||
default:
|
||||
throw new InvalidDatabaseException(
|
||||
'Unknown or unexpected type: ' . $type
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function verifySize(int $expected, int $actual): void
|
||||
{
|
||||
if ($expected !== $actual) {
|
||||
throw new InvalidDatabaseException(
|
||||
"The MaxMind DB file's data section contains bad data (unknown data type or corrupt data)"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function decodeArray(int $size, int $offset): array
|
||||
{
|
||||
$array = [];
|
||||
|
||||
for ($i = 0; $i < $size; ++$i) {
|
||||
[$value, $offset] = $this->decode($offset);
|
||||
$array[] = $value;
|
||||
}
|
||||
|
||||
return [$array, $offset];
|
||||
}
|
||||
|
||||
private function decodeBoolean(int $size): bool
|
||||
{
|
||||
return $size !== 0;
|
||||
}
|
||||
|
||||
private function decodeDouble(string $bytes): float
|
||||
{
|
||||
// This assumes IEEE 754 doubles, but most (all?) modern platforms
|
||||
// use them.
|
||||
[, $double] = unpack('E', $bytes);
|
||||
|
||||
return $double;
|
||||
}
|
||||
|
||||
private function decodeFloat(string $bytes): float
|
||||
{
|
||||
// This assumes IEEE 754 floats, but most (all?) modern platforms
|
||||
// use them.
|
||||
[, $float] = unpack('G', $bytes);
|
||||
|
||||
return $float;
|
||||
}
|
||||
|
||||
private function decodeInt32(string $bytes, int $size): int
|
||||
{
|
||||
switch ($size) {
|
||||
case 0:
|
||||
return 0;
|
||||
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
$bytes = str_pad($bytes, 4, "\x00", \STR_PAD_LEFT);
|
||||
|
||||
break;
|
||||
|
||||
case 4:
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidDatabaseException(
|
||||
"The MaxMind DB file's data section contains bad data (unknown data type or corrupt data)"
|
||||
);
|
||||
}
|
||||
|
||||
[, $int] = unpack('l', $this->maybeSwitchByteOrder($bytes));
|
||||
|
||||
return $int;
|
||||
}
|
||||
|
||||
private function decodeMap(int $size, int $offset): array
|
||||
{
|
||||
$map = [];
|
||||
|
||||
for ($i = 0; $i < $size; ++$i) {
|
||||
[$key, $offset] = $this->decode($offset);
|
||||
[$value, $offset] = $this->decode($offset);
|
||||
$map[$key] = $value;
|
||||
}
|
||||
|
||||
return [$map, $offset];
|
||||
}
|
||||
|
||||
private function decodePointer(int $ctrlByte, int $offset): array
|
||||
{
|
||||
$pointerSize = (($ctrlByte >> 3) & 0x3) + 1;
|
||||
|
||||
$buffer = Util::read($this->fileStream, $offset, $pointerSize);
|
||||
$offset = $offset + $pointerSize;
|
||||
|
||||
switch ($pointerSize) {
|
||||
case 1:
|
||||
$packed = \chr($ctrlByte & 0x7) . $buffer;
|
||||
[, $pointer] = unpack('n', $packed);
|
||||
$pointer += $this->pointerBase;
|
||||
|
||||
break;
|
||||
|
||||
case 2:
|
||||
$packed = "\x00" . \chr($ctrlByte & 0x7) . $buffer;
|
||||
[, $pointer] = unpack('N', $packed);
|
||||
$pointer += $this->pointerBase + 2048;
|
||||
|
||||
break;
|
||||
|
||||
case 3:
|
||||
$packed = \chr($ctrlByte & 0x7) . $buffer;
|
||||
|
||||
// It is safe to use 'N' here, even on 32 bit machines as the
|
||||
// first bit is 0.
|
||||
[, $pointer] = unpack('N', $packed);
|
||||
$pointer += $this->pointerBase + 526336;
|
||||
|
||||
break;
|
||||
|
||||
case 4:
|
||||
// We cannot use unpack here as we might overflow on 32 bit
|
||||
// machines
|
||||
$pointerOffset = $this->decodeUint($buffer, $pointerSize);
|
||||
|
||||
$pointerBase = $this->pointerBase;
|
||||
|
||||
if (\PHP_INT_MAX - $pointerBase >= $pointerOffset) {
|
||||
$pointer = $pointerOffset + $pointerBase;
|
||||
} else {
|
||||
throw new RuntimeException(
|
||||
'The database offset is too large to be represented on your platform.'
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidDatabaseException(
|
||||
'Unexpected pointer size ' . $pointerSize
|
||||
);
|
||||
}
|
||||
|
||||
return [$pointer, $offset];
|
||||
}
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
private function decodeUint(string $bytes, int $byteLength)
|
||||
{
|
||||
if ($byteLength === 0) {
|
||||
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);
|
||||
|
||||
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));
|
||||
} elseif (\extension_loaded('bcmath')) {
|
||||
$integer = bcadd(bcmul((string) $integer, '256'), (string) $part);
|
||||
} else {
|
||||
throw new RuntimeException(
|
||||
'The gmp or bcmath extension must be installed to read this database.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $integer;
|
||||
}
|
||||
|
||||
private function sizeFromCtrlByte(int $ctrlByte, int $offset): array
|
||||
{
|
||||
$size = $ctrlByte & 0x1F;
|
||||
|
||||
if ($size < 29) {
|
||||
return [$size, $offset];
|
||||
}
|
||||
|
||||
$bytesToRead = $size - 28;
|
||||
$bytes = Util::read($this->fileStream, $offset, $bytesToRead);
|
||||
|
||||
if ($size === 29) {
|
||||
$size = 29 + \ord($bytes);
|
||||
} elseif ($size === 30) {
|
||||
[, $adjust] = unpack('n', $bytes);
|
||||
$size = 285 + $adjust;
|
||||
} else {
|
||||
[, $adjust] = unpack('N', "\x00" . $bytes);
|
||||
$size = $adjust + 65821;
|
||||
}
|
||||
|
||||
return [$size, $offset + $bytesToRead];
|
||||
}
|
||||
|
||||
private function maybeSwitchByteOrder(string $bytes): string
|
||||
{
|
||||
return $this->switchByteOrder ? strrev($bytes) : $bytes;
|
||||
}
|
||||
|
||||
private function isPlatformLittleEndian(): bool
|
||||
{
|
||||
$testint = 0x00FF;
|
||||
$packed = pack('S', $testint);
|
||||
|
||||
return $testint === current(unpack('v', $packed));
|
||||
}
|
||||
}
|
14
includes/vendor/maxmind-db/reader/src/MaxMind/Db/Reader/InvalidDatabaseException.php
vendored
Normal file
14
includes/vendor/maxmind-db/reader/src/MaxMind/Db/Reader/InvalidDatabaseException.php
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
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
|
||||
{
|
||||
}
|
112
includes/vendor/maxmind-db/reader/src/MaxMind/Db/Reader/Metadata.php
vendored
Normal file
112
includes/vendor/maxmind-db/reader/src/MaxMind/Db/Reader/Metadata.php
vendored
Normal file
@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace MaxMind\Db\Reader;
|
||||
|
||||
use ArgumentCountError;
|
||||
|
||||
/**
|
||||
* This class provides the metadata for the MaxMind DB file.
|
||||
*/
|
||||
class Metadata
|
||||
{
|
||||
/**
|
||||
* This is an unsigned 16-bit integer indicating the major version number
|
||||
* for the database's binary format.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $binaryFormatMajorVersion;
|
||||
/**
|
||||
* This is an unsigned 16-bit integer indicating the minor version number
|
||||
* for the database's binary format.
|
||||
*
|
||||
* @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)
|
||||
{
|
||||
if (\func_num_args() !== 1) {
|
||||
throw new ArgumentCountError(
|
||||
sprintf('%s() expects exactly 1 parameter, %d given', __METHOD__, \func_num_args())
|
||||
);
|
||||
}
|
||||
|
||||
$this->binaryFormatMajorVersion =
|
||||
$metadata['binary_format_major_version'];
|
||||
$this->binaryFormatMinorVersion =
|
||||
$metadata['binary_format_minor_version'];
|
||||
$this->buildEpoch = $metadata['build_epoch'];
|
||||
$this->databaseType = $metadata['database_type'];
|
||||
$this->languages = $metadata['languages'];
|
||||
$this->description = $metadata['description'];
|
||||
$this->ipVersion = $metadata['ip_version'];
|
||||
$this->nodeCount = $metadata['node_count'];
|
||||
$this->recordSize = $metadata['record_size'];
|
||||
$this->nodeByteSize = $this->recordSize / 4;
|
||||
$this->searchTreeSize = $this->nodeCount * $this->nodeByteSize;
|
||||
}
|
||||
}
|
32
includes/vendor/maxmind-db/reader/src/MaxMind/Db/Reader/Util.php
vendored
Normal file
32
includes/vendor/maxmind-db/reader/src/MaxMind/Db/Reader/Util.php
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace MaxMind\Db\Reader;
|
||||
|
||||
class Util
|
||||
{
|
||||
/**
|
||||
* @param resource $stream
|
||||
*/
|
||||
public static function read($stream, int $offset, int $numberOfBytes): string
|
||||
{
|
||||
if ($numberOfBytes === 0) {
|
||||
return '';
|
||||
}
|
||||
if (fseek($stream, $offset) === 0) {
|
||||
$value = fread($stream, $numberOfBytes);
|
||||
|
||||
// We check that the number of bytes read is equal to the number
|
||||
// asked for. We use ftell as getting the length of $value is
|
||||
// much slower.
|
||||
if ($value !== false && ftell($stream) - $offset === $numberOfBytes) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidDatabaseException(
|
||||
'The MaxMind DB file contains bad data'
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user