diff options
author | Anton Luka Šijanec <anton@sijanec.eu> | 2022-01-11 12:35:47 +0100 |
---|---|---|
committer | Anton Luka Šijanec <anton@sijanec.eu> | 2022-01-11 12:35:47 +0100 |
commit | 19985dbb8c0aa66dc4bf7905abc1148de909097d (patch) | |
tree | 2cd5a5d20d7e80fc2a51adf60d838d8a2c40999e /vendor/maxmind-db/reader/src/MaxMind/Db/Reader | |
download | 1ka-19985dbb8c0aa66dc4bf7905abc1148de909097d.tar 1ka-19985dbb8c0aa66dc4bf7905abc1148de909097d.tar.gz 1ka-19985dbb8c0aa66dc4bf7905abc1148de909097d.tar.bz2 1ka-19985dbb8c0aa66dc4bf7905abc1148de909097d.tar.lz 1ka-19985dbb8c0aa66dc4bf7905abc1148de909097d.tar.xz 1ka-19985dbb8c0aa66dc4bf7905abc1148de909097d.tar.zst 1ka-19985dbb8c0aa66dc4bf7905abc1148de909097d.zip |
Diffstat (limited to 'vendor/maxmind-db/reader/src/MaxMind/Db/Reader')
4 files changed, 507 insertions, 0 deletions
diff --git a/vendor/maxmind-db/reader/src/MaxMind/Db/Reader/Decoder.php b/vendor/maxmind-db/reader/src/MaxMind/Db/Reader/Decoder.php new file mode 100644 index 0000000..8f451b8 --- /dev/null +++ b/vendor/maxmind-db/reader/src/MaxMind/Db/Reader/Decoder.php @@ -0,0 +1,381 @@ +<?php
+
+declare(strict_types=1);
+
+namespace MaxMind\Db\Reader;
+
+// @codingStandardsIgnoreLine
+use RuntimeException;
+
+/*
+ * @ignore
+ *
+ * We subtract 1 from the log to protect against precision loss.
+ */
+\define(__NAMESPACE__ . '\_MM_MAX_INT_BYTES', (int) ((log(\PHP_INT_MAX, 2) - 1) / 8));
+
+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. _MM_MAX_INT_BYTES 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 <= _MM_MAX_INT_BYTES
+ || ($byteLength === _MM_MAX_INT_BYTES + 1 && (\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));
+ }
+}
diff --git a/vendor/maxmind-db/reader/src/MaxMind/Db/Reader/InvalidDatabaseException.php b/vendor/maxmind-db/reader/src/MaxMind/Db/Reader/InvalidDatabaseException.php new file mode 100644 index 0000000..543fde4 --- /dev/null +++ b/vendor/maxmind-db/reader/src/MaxMind/Db/Reader/InvalidDatabaseException.php @@ -0,0 +1,12 @@ +<?php
+
+namespace MaxMind\Db\Reader;
+
+use Exception;
+
+/**
+ * This class should be thrown when unexpected data is found in the database.
+ */
+class InvalidDatabaseException extends Exception
+{
+}
diff --git a/vendor/maxmind-db/reader/src/MaxMind/Db/Reader/Metadata.php b/vendor/maxmind-db/reader/src/MaxMind/Db/Reader/Metadata.php new file mode 100644 index 0000000..94a5592 --- /dev/null +++ b/vendor/maxmind-db/reader/src/MaxMind/Db/Reader/Metadata.php @@ -0,0 +1,88 @@ +<?php
+
+namespace MaxMind\Db\Reader;
+
+/**
+ * This class provides the metadata for the MaxMind DB file.
+ *
+ * @property int $nodeCount This is an unsigned 32-bit
+ * integer indicating the number of
+ * nodes in the search tree.
+ * @property int $recordSize This is an unsigned 16-bit
+ * integer. It indicates the number
+ * of bits in a record in the search
+ * tree. Note that each node
+ * consists of two records.
+ * @property int $ipVersion This is an unsigned 16-bit
+ * integer which is always 4 or 6.
+ * It indicates whether the database
+ * contains IPv4 or IPv6 address
+ * data.
+ * @property string $databaseType This is a string that indicates
+ * the structure of each data record
+ * associated with an IP address.
+ * The actual definition of these
+ * structures is left up to the
+ * database creator.
+ * @property array $languages An array of strings, each of
+ * which is a language code. A given
+ * record may contain data items
+ * that have been localized to some
+ * or all of these languages. This
+ * may be undefined.
+ * @property int $binaryFormatMajorVersion This is an unsigned 16-bit
+ * integer indicating the major
+ * version number for the database's
+ * binary format.
+ * @property int $binaryFormatMinorVersion This is an unsigned 16-bit
+ * integer indicating the minor
+ * version number for the database's
+ * binary format.
+ * @property int $buildEpoch This is an unsigned 64-bit
+ * integer that contains the
+ * database build timestamp as a
+ * Unix epoch value.
+ * @property array $description This key will always point to a
+ * map (associative array). The keys
+ * of that map will be language
+ * codes, and the values will be a
+ * description in that language as a
+ * UTF-8 string. May be undefined
+ * for some databases.
+ */
+class Metadata
+{
+ private $binaryFormatMajorVersion;
+ private $binaryFormatMinorVersion;
+ private $buildEpoch;
+ private $databaseType;
+ private $description;
+ private $ipVersion;
+ private $languages;
+ private $nodeByteSize;
+ private $nodeCount;
+ private $recordSize;
+ private $searchTreeSize;
+
+ public function __construct($metadata)
+ {
+ $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;
+ }
+
+ public function __get($var)
+ {
+ return $this->$var;
+ }
+}
diff --git a/vendor/maxmind-db/reader/src/MaxMind/Db/Reader/Util.php b/vendor/maxmind-db/reader/src/MaxMind/Db/Reader/Util.php new file mode 100644 index 0000000..149a5c4 --- /dev/null +++ b/vendor/maxmind-db/reader/src/MaxMind/Db/Reader/Util.php @@ -0,0 +1,26 @@ +<?php
+
+namespace MaxMind\Db\Reader;
+
+class Util
+{
+ public static function read($stream, $offset, $numberOfBytes)
+ {
+ 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 (ftell($stream) - $offset === $numberOfBytes) {
+ return $value;
+ }
+ }
+ throw new InvalidDatabaseException(
+ 'The MaxMind DB file contains bad data'
+ );
+ }
+}
|