diff options
| author | s1n <[email protected]> | 2020-03-28 10:36:41 -0700 |
|---|---|---|
| committer | s1n <[email protected]> | 2020-03-28 10:36:41 -0700 |
| commit | 25b7d2aab61ae6421398d3abae5da6ffe590333d (patch) | |
| tree | 611985ec78bb2d94099c9fd5dd687f5c9cee6f3e /includes/vendor/maxmind-db/reader/src/MaxMind/Db/Reader.php | |
| parent | Initial commit (diff) | |
| download | crack.cf-backup-25b7d2aab61ae6421398d3abae5da6ffe590333d.tar.xz crack.cf-backup-25b7d2aab61ae6421398d3abae5da6ffe590333d.zip | |
Diffstat (limited to 'includes/vendor/maxmind-db/reader/src/MaxMind/Db/Reader.php')
| -rw-r--r-- | includes/vendor/maxmind-db/reader/src/MaxMind/Db/Reader.php | 309 |
1 files changed, 309 insertions, 0 deletions
diff --git a/includes/vendor/maxmind-db/reader/src/MaxMind/Db/Reader.php b/includes/vendor/maxmind-db/reader/src/MaxMind/Db/Reader.php new file mode 100644 index 0000000..745f7bb --- /dev/null +++ b/includes/vendor/maxmind-db/reader/src/MaxMind/Db/Reader.php @@ -0,0 +1,309 @@ +<?php + +namespace MaxMind\Db; + +use MaxMind\Db\Reader\Decoder; +use MaxMind\Db\Reader\InvalidDatabaseException; +use MaxMind\Db\Reader\Metadata; +use MaxMind\Db\Reader\Util; + +/** + * Instances of this class provide a reader for the MaxMind DB format. IP + * addresses can be looked up using the <code>get</code> method. + */ +class Reader +{ + private static $DATA_SECTION_SEPARATOR_SIZE = 16; + private static $METADATA_START_MARKER = "\xAB\xCD\xEFMaxMind.com"; + private static $METADATA_START_MARKER_LENGTH = 14; + private static $METADATA_MAX_SIZE = 131072; // 128 * 1024 = 128KB + + private $decoder; + private $fileHandle; + private $fileSize; + private $ipV4Start; + 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 \MaxMind\Db\Reader\InvalidDatabaseException + * if the database is invalid or there is an error reading + * from it + */ + public function __construct($database) + { + if (\func_num_args() !== 1) { + throw new \InvalidArgumentException( + 'The constructor takes exactly one argument.' + ); + } + + if (!is_readable($database)) { + 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( + "Error determining the size of \"$database\"." + ); + } + + $start = $this->findMetadataStart($database); + $metadataDecoder = new Decoder($this->fileHandle, $start); + list($metadataArray) = $metadataDecoder->decode($start); + $this->metadata = new Metadata($metadataArray); + $this->decoder = new Decoder( + $this->fileHandle, + $this->metadata->searchTreeSize + self::$DATA_SECTION_SEPARATOR_SIZE + ); + } + + /** + * Looks up the <code>address</code> in the MaxMind DB. + * + * @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 the record for the IP address + */ + public function get($ipAddress) + { + if (\func_num_args() !== 1) { + throw new \InvalidArgumentException( + 'Method takes exactly one argument.' + ); + } + + if (!\is_resource($this->fileHandle)) { + 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." + ); + } + + if ($this->metadata->ipVersion === 4 && strrpos($ipAddress, ':')) { + throw new \InvalidArgumentException( + "Error looking up $ipAddress. You attempted to look up an" + . ' IPv6 address in an IPv4-only database.' + ); + } + $pointer = $this->findAddressInTree($ipAddress); + if ($pointer === 0) { + return null; + } + + return $this->resolveDataPointer($pointer); + } + + private function findAddressInTree($ipAddress) + { + // XXX - could simplify. Done as a byte array to ease porting + $rawAddress = array_merge(unpack('C*', inet_pton($ipAddress))); + + $bitCount = \count($rawAddress) * 8; + + // The first node of the tree is always node 0, at the beginning of the + // value + $node = $this->startNode($bitCount); + + for ($i = 0; $i < $bitCount; ++$i) { + if ($node >= $this->metadata->nodeCount) { + break; + } + $tempBit = 0xFF & $rawAddress[$i >> 3]; + $bit = 1 & ($tempBit >> 7 - ($i % 8)); + + $node = $this->readNode($node, $bit); + } + if ($node === $this->metadata->nodeCount) { + // Record is empty + return 0; + } elseif ($node > $this->metadata->nodeCount) { + // Record is a data pointer + return $node; + } + throw new InvalidDatabaseException('Something bad happened'); + } + + private function startNode($length) + { + // 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 ($this->metadata->ipVersion === 6 && $length === 32) { + return $this->ipV4StartNode(); + } + // The first node of the tree is always node 0, at the beginning of the + // value + return 0; + } + + private function ipV4StartNode() + { + // This is a defensive check. There is no reason to call this when you + // have an IPv4 tree. + if ($this->metadata->ipVersion === 4) { + return 0; + } + + if ($this->ipV4Start) { + return $this->ipV4Start; + } + $node = 0; + + for ($i = 0; $i < 96 && $node < $this->metadata->nodeCount; ++$i) { + $node = $this->readNode($node, 0); + } + $this->ipV4Start = $node; + + return $node; + } + + private function readNode($nodeNumber, $index) + { + $baseOffset = $nodeNumber * $this->metadata->nodeByteSize; + + // XXX - probably could condense this. + switch ($this->metadata->recordSize) { + case 24: + $bytes = Util::read($this->fileHandle, $baseOffset + $index * 3, 3); + list(, $node) = unpack('N', "\x00" . $bytes); + + return $node; + case 28: + $middleByte = Util::read($this->fileHandle, $baseOffset + 3, 1); + list(, $middle) = unpack('C', $middleByte); + if ($index === 0) { + $middle = (0xF0 & $middle) >> 4; + } else { + $middle = 0x0F & $middle; + } + $bytes = Util::read($this->fileHandle, $baseOffset + $index * 4, 3); + list(, $node) = unpack('N', \chr($middle) . $bytes); + + return $node; + case 32: + $bytes = Util::read($this->fileHandle, $baseOffset + $index * 4, 4); + list(, $node) = unpack('N', $bytes); + + return $node; + default: + throw new InvalidDatabaseException( + 'Unknown record size: ' + . $this->metadata->recordSize + ); + } + } + + private function resolveDataPointer($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" + ); + } + + list($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($filename) + { + $handle = $this->fileHandle; + $fstat = fstat($handle); + $fileSize = $fstat['size']; + $marker = self::$METADATA_START_MARKER; + $markerLength = self::$METADATA_START_MARKER_LENGTH; + $metadataMaxLengthExcludingMarker + = min(self::$METADATA_MAX_SIZE, $fileSize) - $markerLength; + + for ($i = 0; $i <= $metadataMaxLengthExcludingMarker; ++$i) { + for ($j = 0; $j < $markerLength; ++$j) { + fseek($handle, $fileSize - $i - $j - 1); + $matchBit = fgetc($handle); + if ($matchBit !== $marker[$markerLength - $j - 1]) { + continue 2; + } + } + + return $fileSize - $i; + } + 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() + { + if (\func_num_args()) { + throw new \InvalidArgumentException( + 'Method takes no arguments.' + ); + } + + // 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 $this->metadata; + } + + /** + * Closes the MaxMind DB and returns resources to the system. + * + * @throws \Exception + * if an I/O error occurs + */ + public function close() + { + if (!\is_resource($this->fileHandle)) { + throw new \BadMethodCallException( + 'Attempt to close a closed MaxMind DB.' + ); + } + fclose($this->fileHandle); + } +} |