class Reader in Smart IP 7.2
Same name in this branch
- 7.2 includes/vendor/geoip2/geoip2/src/Database/Reader.php \GeoIp2\Database\Reader
- 7.2 includes/vendor/maxmind-db/reader/src/MaxMind/Db/Reader.php \MaxMind\Db\Reader
Same name and namespace in other branches
- 6.2 includes/vendor/maxmind-db/reader/src/MaxMind/Db/Reader.php \MaxMind\Db\Reader
Instances of this class provide a reader for the MaxMind DB format. IP addresses can be looked up using the <code>get</code> method.
Hierarchy
- class \MaxMind\Db\Reader
Expanded class hierarchy of Reader
3 files declare their use of Reader
- benchmark.php in includes/
vendor/ maxmind-db/ reader/ examples/ benchmark.php - Reader.php in includes/
vendor/ geoip2/ geoip2/ src/ Database/ Reader.php - ReaderTest.php in includes/
vendor/ maxmind-db/ reader/ tests/ MaxMind/ Db/ Test/ ReaderTest.php
File
- includes/
vendor/ maxmind-db/ reader/ src/ MaxMind/ Db/ Reader.php, line 14
Namespace
MaxMind\DbView source
class Reader {
private static $DATA_SECTION_SEPARATOR_SIZE = 16;
private static $METADATA_START_MARKER = "";
private static $METADATA_START_MARKER_LENGTH = 14;
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.
* @return array the record for the IP address.
* @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.
*/
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 != 0) {
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', "\0" . $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 = 0xf & $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;
for ($i = 0; $i < $fileSize - $markerLength + 1; $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);
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
Reader:: |
private static | property | ||
Reader:: |
private | property | ||
Reader:: |
private | property | ||
Reader:: |
private | property | ||
Reader:: |
private | property | ||
Reader:: |
private | property | ||
Reader:: |
private static | property | ||
Reader:: |
private static | property | ||
Reader:: |
public | function | Closes the MaxMind DB and returns resources to the system. | |
Reader:: |
private | function | ||
Reader:: |
private | function | ||
Reader:: |
public | function | Looks up the <code>address</code> in the MaxMind DB. | |
Reader:: |
private | function | ||
Reader:: |
public | function | ||
Reader:: |
private | function | ||
Reader:: |
private | function | ||
Reader:: |
private | function | ||
Reader:: |
public | function | 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. |