PageRenderTime 26ms CodeModel.GetById 11ms RepoModel.GetById 1ms app.codeStats 0ms

/wp-content/plugins/wp-statistics/vendor/maxmind-db/reader/src/MaxMind/Db/Reader.php

https://gitlab.com/hop23typhu/faci-vinhomes
PHP | 296 lines | 215 code | 29 blank | 52 comment | 40 complexity | dd648dd18ea4df0c8ff6eabee28ddf57 MD5 | raw file
  1. <?php
  2. namespace MaxMind\Db;
  3. use MaxMind\Db\Reader\Decoder;
  4. use MaxMind\Db\Reader\InvalidDatabaseException;
  5. use MaxMind\Db\Reader\Metadata;
  6. use MaxMind\Db\Reader\Util;
  7. /**
  8. * Instances of this class provide a reader for the MaxMind DB format. IP
  9. * addresses can be looked up using the <code>get</code> method.
  10. */
  11. class Reader
  12. {
  13. private static $DATA_SECTION_SEPARATOR_SIZE = 16;
  14. private static $METADATA_START_MARKER = "\xAB\xCD\xEFMaxMind.com";
  15. private static $METADATA_START_MARKER_LENGTH = 14;
  16. private $decoder;
  17. private $fileHandle;
  18. private $fileSize;
  19. private $ipV4Start;
  20. private $metadata;
  21. /**
  22. * Constructs a Reader for the MaxMind DB format. The file passed to it must
  23. * be a valid MaxMind DB file such as a GeoIp2 database file.
  24. *
  25. * @param string $database
  26. * the MaxMind DB file to use.
  27. * @throws \InvalidArgumentException for invalid database path or unknown arguments
  28. * @throws \MaxMind\Db\Reader\InvalidDatabaseException
  29. * if the database is invalid or there is an error reading
  30. * from it.
  31. */
  32. public function __construct($database)
  33. {
  34. if (func_num_args() != 1) {
  35. throw new \InvalidArgumentException(
  36. 'The constructor takes exactly one argument.'
  37. );
  38. }
  39. if (!is_readable($database)) {
  40. throw new \InvalidArgumentException(
  41. "The file \"$database\" does not exist or is not readable."
  42. );
  43. }
  44. $this->fileHandle = @fopen($database, 'rb');
  45. if ($this->fileHandle === false) {
  46. throw new \InvalidArgumentException(
  47. "Error opening \"$database\"."
  48. );
  49. }
  50. $this->fileSize = @filesize($database);
  51. if ($this->fileSize === false) {
  52. throw new \UnexpectedValueException(
  53. "Error determining the size of \"$database\"."
  54. );
  55. }
  56. $start = $this->findMetadataStart($database);
  57. $metadataDecoder = new Decoder($this->fileHandle, $start);
  58. list($metadataArray) = $metadataDecoder->decode($start);
  59. $this->metadata = new Metadata($metadataArray);
  60. $this->decoder = new Decoder(
  61. $this->fileHandle,
  62. $this->metadata->searchTreeSize + self::$DATA_SECTION_SEPARATOR_SIZE
  63. );
  64. }
  65. /**
  66. * Looks up the <code>address</code> in the MaxMind DB.
  67. *
  68. * @param string $ipAddress
  69. * the IP address to look up.
  70. * @return array the record for the IP address.
  71. * @throws \BadMethodCallException if this method is called on a closed database.
  72. * @throws \InvalidArgumentException if something other than a single IP address is passed to the method.
  73. * @throws InvalidDatabaseException
  74. * if the database is invalid or there is an error reading
  75. * from it.
  76. */
  77. public function get($ipAddress)
  78. {
  79. if (func_num_args() != 1) {
  80. throw new \InvalidArgumentException(
  81. 'Method takes exactly one argument.'
  82. );
  83. }
  84. if (!is_resource($this->fileHandle)) {
  85. throw new \BadMethodCallException(
  86. 'Attempt to read from a closed MaxMind DB.'
  87. );
  88. }
  89. if (!filter_var($ipAddress, FILTER_VALIDATE_IP)) {
  90. throw new \InvalidArgumentException(
  91. "The value \"$ipAddress\" is not a valid IP address."
  92. );
  93. }
  94. if ($this->metadata->ipVersion == 4 && strrpos($ipAddress, ':')) {
  95. throw new \InvalidArgumentException(
  96. "Error looking up $ipAddress. You attempted to look up an"
  97. . " IPv6 address in an IPv4-only database."
  98. );
  99. }
  100. $pointer = $this->findAddressInTree($ipAddress);
  101. if ($pointer == 0) {
  102. return null;
  103. }
  104. return $this->resolveDataPointer($pointer);
  105. }
  106. private function findAddressInTree($ipAddress)
  107. {
  108. // XXX - could simplify. Done as a byte array to ease porting
  109. $rawAddress = array_merge(unpack('C*', inet_pton($ipAddress)));
  110. $bitCount = count($rawAddress) * 8;
  111. // The first node of the tree is always node 0, at the beginning of the
  112. // value
  113. $node = $this->startNode($bitCount);
  114. for ($i = 0; $i < $bitCount; $i++) {
  115. if ($node >= $this->metadata->nodeCount) {
  116. break;
  117. }
  118. $tempBit = 0xFF & $rawAddress[$i >> 3];
  119. $bit = 1 & ($tempBit >> 7 - ($i % 8));
  120. $node = $this->readNode($node, $bit);
  121. }
  122. if ($node == $this->metadata->nodeCount) {
  123. // Record is empty
  124. return 0;
  125. } elseif ($node > $this->metadata->nodeCount) {
  126. // Record is a data pointer
  127. return $node;
  128. }
  129. throw new InvalidDatabaseException("Something bad happened");
  130. }
  131. private function startNode($length)
  132. {
  133. // Check if we are looking up an IPv4 address in an IPv6 tree. If this
  134. // is the case, we can skip over the first 96 nodes.
  135. if ($this->metadata->ipVersion == 6 && $length == 32) {
  136. return $this->ipV4StartNode();
  137. }
  138. // The first node of the tree is always node 0, at the beginning of the
  139. // value
  140. return 0;
  141. }
  142. private function ipV4StartNode()
  143. {
  144. // This is a defensive check. There is no reason to call this when you
  145. // have an IPv4 tree.
  146. if ($this->metadata->ipVersion == 4) {
  147. return 0;
  148. }
  149. if ($this->ipV4Start != 0) {
  150. return $this->ipV4Start;
  151. }
  152. $node = 0;
  153. for ($i = 0; $i < 96 && $node < $this->metadata->nodeCount; $i++) {
  154. $node = $this->readNode($node, 0);
  155. }
  156. $this->ipV4Start = $node;
  157. return $node;
  158. }
  159. private function readNode($nodeNumber, $index)
  160. {
  161. $baseOffset = $nodeNumber * $this->metadata->nodeByteSize;
  162. // XXX - probably could condense this.
  163. switch ($this->metadata->recordSize) {
  164. case 24:
  165. $bytes = Util::read($this->fileHandle, $baseOffset + $index * 3, 3);
  166. list(, $node) = unpack('N', "\x00" . $bytes);
  167. return $node;
  168. case 28:
  169. $middleByte = Util::read($this->fileHandle, $baseOffset + 3, 1);
  170. list(, $middle) = unpack('C', $middleByte);
  171. if ($index == 0) {
  172. $middle = (0xF0 & $middle) >> 4;
  173. } else {
  174. $middle = 0x0F & $middle;
  175. }
  176. $bytes = Util::read($this->fileHandle, $baseOffset + $index * 4, 3);
  177. list(, $node) = unpack('N', chr($middle) . $bytes);
  178. return $node;
  179. case 32:
  180. $bytes = Util::read($this->fileHandle, $baseOffset + $index * 4, 4);
  181. list(, $node) = unpack('N', $bytes);
  182. return $node;
  183. default:
  184. throw new InvalidDatabaseException(
  185. 'Unknown record size: '
  186. . $this->metadata->recordSize
  187. );
  188. }
  189. }
  190. private function resolveDataPointer($pointer)
  191. {
  192. $resolved = $pointer - $this->metadata->nodeCount
  193. + $this->metadata->searchTreeSize;
  194. if ($resolved > $this->fileSize) {
  195. throw new InvalidDatabaseException(
  196. "The MaxMind DB file's search tree is corrupt"
  197. );
  198. }
  199. list($data) = $this->decoder->decode($resolved);
  200. return $data;
  201. }
  202. /*
  203. * This is an extremely naive but reasonably readable implementation. There
  204. * are much faster algorithms (e.g., Boyer-Moore) for this if speed is ever
  205. * an issue, but I suspect it won't be.
  206. */
  207. private function findMetadataStart($filename)
  208. {
  209. $handle = $this->fileHandle;
  210. $fstat = fstat($handle);
  211. $fileSize = $fstat['size'];
  212. $marker = self::$METADATA_START_MARKER;
  213. $markerLength = self::$METADATA_START_MARKER_LENGTH;
  214. for ($i = 0; $i < $fileSize - $markerLength + 1; $i++) {
  215. for ($j = 0; $j < $markerLength; $j++) {
  216. fseek($handle, $fileSize - $i - $j - 1);
  217. $matchBit = fgetc($handle);
  218. if ($matchBit != $marker[$markerLength - $j - 1]) {
  219. continue 2;
  220. }
  221. }
  222. return $fileSize - $i;
  223. }
  224. throw new InvalidDatabaseException(
  225. "Error opening database file ($filename). " .
  226. 'Is this a valid MaxMind DB file?'
  227. );
  228. }
  229. /**
  230. * @throws \InvalidArgumentException if arguments are passed to the method.
  231. * @throws \BadMethodCallException if the database has been closed.
  232. * @return Metadata object for the database.
  233. */
  234. public function metadata()
  235. {
  236. if (func_num_args()) {
  237. throw new \InvalidArgumentException(
  238. 'Method takes no arguments.'
  239. );
  240. }
  241. // Not technically required, but this makes it consistent with
  242. // C extension and it allows us to change our implementation later.
  243. if (!is_resource($this->fileHandle)) {
  244. throw new \BadMethodCallException(
  245. 'Attempt to read from a closed MaxMind DB.'
  246. );
  247. }
  248. return $this->metadata;
  249. }
  250. /**
  251. * Closes the MaxMind DB and returns resources to the system.
  252. *
  253. * @throws \Exception
  254. * if an I/O error occurs.
  255. */
  256. public function close()
  257. {
  258. if (!is_resource($this->fileHandle)) {
  259. throw new \BadMethodCallException(
  260. 'Attempt to close a closed MaxMind DB.'
  261. );
  262. }
  263. fclose($this->fileHandle);
  264. }
  265. }