ÿØÿà JFIF    ÿÛ „  ( %!1"%)+...383-7(-.+  -%%--------+------------/----------------------------ÿÀ  ¶" ÿÄ     ÿÄ @    !1AQaq‘¡"±ÁÑð2’BRráS‚ñ3b¢CT²Ò #cÿÄ    ÿÄ '   1!AQ2a"‘±#ÿÚ   ? ò(/p‘—–¢·,M|¯G»R£üešT. ôŸEoCFñ‡ 4ÔkåóxR|µÁ¨ÓDà!Ç´µ˜‡p ÍQ‹§!þž^õWsŒy  åÁ§$á«%ºq˜™JyyË0y«^Ϗ -3ÃÏËÈ£Æ\P’ä„s&•‹Üs üô'àWt¹pñCp$bÚF`·7.^)¾!)ÒÙ˜–ÅÏš^h¾ø³·ìe±ŒaeÖ—Í,e=B}Jéߦ$$½—šÙÜCC¯¶è8β–Åkèwx LÉngT±æ¹£Ócœ¿Í/©Éÿ ‰ÚCÒ­mq;Tt¶–}0"zÖPcd1š­¤tˆ„ÐÓâqú[™öUCŠ2޿د­É(¸ßúçšmW,Ò ºf‹Ån™ÄLêÁ8üÁkÁ€L¥ÍuJ1œôrÂSŒÓöt!7EUíïb)AËi¬â-ÙHk8òÁiXYwÅ)O^"ÇØriÛô{»wRUKÙÏÚlÓ¬€‚’שT ] ªb—ÊîKC^×M“xùäñz¬zJ× {4[¹M'IÆeH1LCWÕ]œûº WDæà°OÅ‚¹c²ÞuA–rL`HšpCez‡V’— Ž‚Ë„–ñ­'µR0ÕÖ=À:±G»C\nÆÉ5*¢ †3šhQ4ýÒi$Õ1"Ü]¢p-q3zcQ$ªö¨¥æf›‘»´ÝÚHá„e²JÇ–iÊ:·à¬Øc5Åk»Laª*wi [0Ówk ¦a¦0Õ²ÅÅ‚T¸’³q:Æ<¹ú Ò˜Æ}2VE•Ѐp¡Ô3Úº …h³^LJºçì¥Ánó|˜ÖˆÎˆ+« ¦dø‘Mº–d\ªDðÛ„Ì×LÝ——Ð9Ç|•FèçVm™¨€™3õS–9Äå¢<̉Ìm¢±`·º˜&Z²ž Ë®D«¶»6™‰©­HÈ Àá«h­i³04æ’hxœi3ï¨(ÓL·†‹¶ËpŒ ƒÜÒ? :õdsÀ –3æÓ·Üjâ¬9—]8n¼&]I F£¼…ì–—Œ0v°LǐÅfÛ2TÖ’aµ˜N@ÖBSÏ^à´4-±°b×nÈÈœÆD=KdŒø…ÄÈK,›ÖR§fjü$^ÔÎ!„6^ s:Üun\ôkCÞò÷<—Lúl Í›G¹ÀPá¼Nc¡šÒ…ÙWͳœ±vÊáЄ×9‹P‰cEØ‘™›µŸP<×g1 º©Ø¬wiÁ^l5× I/';Ë+´Z‡ÓÄFÕp[Èð’ HÄ ó 9¬Dl4“é±Ï”V=^Hð;žLädÉa‚°Ë,#/Ž~²BÔÛ QãñQtIeórVB,ÏÂf‹4#Μc‹É3ZIÖ Œ¢¥·üxa‡Ãçè„jŠ[5 Ä!U?,3žÎ×…ðdÓ÷háŠA‰ìÄ4÷ÃÜZÃE{‰®+W\ZÍE[‰»µk»MÝ­`¢©†˜ÃV‹-f¢¡†¢a«eŠ%«Y¨©q%jâd,Ôq`) ¤¦…šˆ ¦E>0Ùb²t–Ÿ  ÷jsςҒKÈcø(é›;k(’#ôŒQ ™C> õ³M9óºÆµ¤çâ;uÀ)5>RÔ¸fÓ~Ȧ—’-$fe«"FâUˆÊwµPÏu w+"åÉó¨6s™Û9(À¨ù©hE°½†n`m~“@©›p!Vkhµ Á1šbEqqÊ@ºèl›2 –Yã%‘‚hÛ{a¶‡ÅÞM²¬çL34®Çl]®‹ŽØŒm'RÜe35æöˆ­Á­ cP9šeÁköo´L³Ì=p8]”‡B¶,šº|ɏdzXˆØk*ÇÚ{#ñ‰pã(€¶‡9ý'šÜ³½¯kƒ†Â’ëSOƒ•Å®H6+a¢µˆ‹Y¨b˜b+XˆÖ!°u!¢"µˆˆn2ˆ Å0Ä`Å Ô®c(ÓÜGº•Ä»Œ âWî%q.áÐÄ÷î%umÍ£+ÜQ,Vn¦-GphU¸¢X­&,Gt ·K¢ÅÅ·¥RÄÊÉbKn N­R2g$VµÕó Qr#–ÓZUÏð0ÎtÙÀœz ëöo“Îk¯n„T°"SÞ+³/…€½"ižãO%Í%&Δâ‘ËØ´IˆÓ°Ë‰ÞM3.™yçÁzit›`4C`k¢¼á!álÄï sÂ[×!¤l¾æo"ƒIÔ'@ªu)3›ô®“@Z à¾] üœ'‡4¸PÞkÁ-qÄH‰ÓÍBd´ÿ —¹°ÄA&ÊUC„öz‚‚RæŠtf¬>˜ÑidšEçÑL÷áý¡V±h§­c¿¥¨ƒ@g¼…Ýö^ÞËc.ž„æ“I[ ª+·zÐÒÚ,½Ð»¶€C›7jk$๖€Ž¾ÅÙpp¶ÍþéΤ ½ØÎŒq ñpæžÅ¥#Øâ49ÃÂK^MYV“,¶Éz_òæ:…;Õ«¦I3âJ£¤û5$7Èxün\â]Êe¯(Ÿ&Ž‹¶2<6Ćf× æ¢ƱqWı[(ÙÂŽç‚T7¨@ÈÉÍß‚ïÙ"&3TYlGށ†)†#5ŠA©^T2ÆÁ†)©†©†¤yGXÁ†©"†)©¼ÅV WS†#©]R–r‹q5ÕbâWþBh¯u+ªÅÄ×ü„nÑ^êkªÁj‰jeœWˆÕÕ`µDµ2Ì+Ä µDµµ5Ô{¤Þ0RF’H÷AÛ<ÊÁ§ ƈæ0Ñ£ê4 öÚµ[¬/1²€â<-¬šö²r×0o×w ´xÐÒË„ g01¬„åEM蛇Ã\6µE›‘fÙO k÷CºnÓ1`iîÒ² áÂ!ÑLÄÿ ³¬\õ*:M=Ç»c‹2[‰ØsÕLò\ŒK L^™üÄN\¶#» Ç\……ßïIœÉ$¸ÎñÍ®ÔHÇ Ûí/´>Li”„†g^$ 7NÍÑÑ2M2ªNg=k½Ð}Ÿl6‡ —JWˆ”ÞéT·+µ¦üÂFè¢Vs6ýÆ4»2+2IÝ9ô9®ÇGÙÇ1+Ä8;*Õ§ž½D*îÑWÚçá3Ma¤·ˆ¥t}š²Ü…Q+Æüµ8HíS”üxGéÁˆ¦Ël„éÈ 5 µÄƒ6äÜ(kIš¯S†fÖ&¸®×v|Äyˆ*禌Éù@7-®Ä[]ÎÖ¿ê`º…=:…žJVnݳvðÖKE¼‚ »mà ÷f®:Ë ™–4rð¡4` ܹçž~‹C=œ…¾Ïj|VDtK.ƒ"9 Ï ­–éXá³6WÒïIL-Ї Ç PKٶ\aF99GeoIp2/README.mdnu[# GeoIP2 PHP API # ## Description ## This package provides an API for the GeoIP2 and GeoLite2 [web services](https://dev.maxmind.com/geoip/docs/web-services?lang=en) and [databases](https://dev.maxmind.com/geoip/docs/databases?lang=en). ## Install via 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: ```sh php composer.phar require geoip2/geoip2:~2.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'; ``` ## Install via Phar ## Although we strongly recommend using Composer, we also provide a [phar archive](https://php.net/manual/en/book.phar.php) containing most of the dependencies for GeoIP2. Our latest phar archive is available on [our releases page](https://github.com/maxmind/GeoIP2-php/releases). ### Install Dependencies ### In order to use the phar archive, you must have the PHP [Phar extension](https://php.net/manual/en/book.phar.php) installed and enabled. If you will be making web service requests, you must have the PHP [cURL extension](https://php.net/manual/en/book.curl.php) installed to use this archive. For Debian based distributions, this can typically be found in the the `php-curl` package. For other operating systems, please consult the relevant documentation. After installing the extension you may need to restart your web server. If you are missing this extension, you will see errors like the following: ``` PHP Fatal error: Uncaught Error: Call to undefined function MaxMind\WebService\curl_version() ``` ### Require Package ### To use the archive, just require it from your script: ```php require 'geoip2.phar'; ``` ## Optional C Extension ## The [MaxMind DB API](https://github.com/maxmind/MaxMind-DB-Reader-php) includes an optional C extension that you may install to dramatically increase the performance of lookups in GeoIP2 or GeoLite2 databases. To install, please follow the instructions included with that API. The extension has no effect on web-service lookups. ## IP Geolocation Usage ## IP geolocation is inherently imprecise. Locations are often near the center of the population. Any location provided by a GeoIP2 database or web service should not be used to identify a particular address or household. ## Database Reader ## ### Usage ### To use this API, you must create a new `\GeoIp2\Database\Reader` object with the path to the database file as the first argument to the constructor. You may then call the method corresponding to the database you are using. If the lookup succeeds, the method call will return a model class for the record in the database. This model in turn contains multiple container classes for the different parts of the data such as the city in which the IP address is located. If the record is not found, a `\GeoIp2\Exception\AddressNotFoundException` is thrown. If the database is invalid or corrupt, a `\MaxMind\Db\InvalidDatabaseException` will be thrown. See the [API documentation](https://maxmind.github.io/GeoIP2-php/) for more details. ### City Example ### ```php city('128.101.101.101'); print($record->country->isoCode . "\n"); // 'US' print($record->country->name . "\n"); // 'United States' print($record->country->names['zh-CN'] . "\n"); // '美国' print($record->mostSpecificSubdivision->name . "\n"); // 'Minnesota' print($record->mostSpecificSubdivision->isoCode . "\n"); // 'MN' print($record->city->name . "\n"); // 'Minneapolis' print($record->postal->code . "\n"); // '55455' print($record->location->latitude . "\n"); // 44.9733 print($record->location->longitude . "\n"); // -93.2323 print($record->traits->network . "\n"); // '128.101.101.101/32' ``` ### Anonymous IP Example ### ```php anonymousIp('128.101.101.101'); if ($record->isAnonymous) { print "anon\n"; } print($record->ipAddress . "\n"); // '128.101.101.101' print($record->network . "\n"); // '128.101.101.101/32' ``` ### Connection-Type Example ### ```php connectionType('128.101.101.101'); print($record->connectionType . "\n"); // 'Corporate' print($record->ipAddress . "\n"); // '128.101.101.101' print($record->network . "\n"); // '128.101.101.101/32' ``` ### Domain Example ### ```php domain('128.101.101.101'); print($record->domain . "\n"); // 'umn.edu' print($record->ipAddress . "\n"); // '128.101.101.101' print($record->network . "\n"); // '128.101.101.101/32' ``` ### Enterprise Example ### ```php enterprise method to do a lookup in the Enterprise database $record = $enterpriseDbReader->enterprise('128.101.101.101'); print($record->country->confidence . "\n"); // 99 print($record->country->isoCode . "\n"); // 'US' print($record->country->name . "\n"); // 'United States' print($record->country->names['zh-CN'] . "\n"); // '美国' print($record->mostSpecificSubdivision->confidence . "\n"); // 77 print($record->mostSpecificSubdivision->name . "\n"); // 'Minnesota' print($record->mostSpecificSubdivision->isoCode . "\n"); // 'MN' print($record->city->confidence . "\n"); // 60 print($record->city->name . "\n"); // 'Minneapolis' print($record->postal->code . "\n"); // '55455' print($record->location->accuracyRadius . "\n"); // 50 print($record->location->latitude . "\n"); // 44.9733 print($record->location->longitude . "\n"); // -93.2323 print($record->traits->network . "\n"); // '128.101.101.101/32' ``` ### ISP Example ### ```php isp('128.101.101.101'); print($record->autonomousSystemNumber . "\n"); // 217 print($record->autonomousSystemOrganization . "\n"); // 'University of Minnesota' print($record->isp . "\n"); // 'University of Minnesota' print($record->organization . "\n"); // 'University of Minnesota' print($record->ipAddress . "\n"); // '128.101.101.101' print($record->network . "\n"); // '128.101.101.101/32' ``` ## Database Updates ## You can keep your databases up to date with our [GeoIP Update program](https://github.com/maxmind/geoipupdate/releases). [Learn more about GeoIP Update on our developer portal.](https://dev.maxmind.com/geoip/updating-databases?lang=en) ## Web Service Client ## ### Usage ### To use this API, you must create a new `\GeoIp2\WebService\Client` object with your `$accountId` and `$licenseKey`: ```php $client = new Client(42, 'abcdef123456'); ``` You may also call the constructor with additional arguments. The third argument specifies the language preferences when using the `->name` method on the model classes that this client creates. The fourth argument is additional options such as `host` and `timeout`. For instance, to call the GeoLite2 web service instead of the GeoIP2 web service: ```php $client = new Client(42, 'abcdef123456', ['en'], ['host' => 'geolite.info']); ``` To call the Sandbox GeoIP2 web service instead of the production GeoIP2 web service: ```php $client = new Client(42, 'abcdef123456', ['en'], ['host' => 'sandbox.maxmind.com']); ``` After creating the client, you may now call the method corresponding to a specific endpoint with the IP address to look up, e.g.: ```php $record = $client->city('128.101.101.101'); ``` If the request succeeds, the method call will return a model class for the endpoint you called. This model in turn contains multiple record classes, each of which represents part of the data returned by the web service. If there is an error, a structured exception is thrown. See the [API documentation](https://maxmind.github.io/GeoIP2-php/) for more details. ### Example ### ```php city('128.101.101.101'); print($record->country->isoCode . "\n"); // 'US' print($record->country->name . "\n"); // 'United States' print($record->country->names['zh-CN'] . "\n"); // '美国' print($record->mostSpecificSubdivision->name . "\n"); // 'Minnesota' print($record->mostSpecificSubdivision->isoCode . "\n"); // 'MN' print($record->city->name . "\n"); // 'Minneapolis' print($record->postal->code . "\n"); // '55455' print($record->location->latitude . "\n"); // 44.9733 print($record->location->longitude . "\n"); // -93.2323 print($record->traits->network . "\n"); // '128.101.101.101/32' ``` ## Values to use for Database or Array Keys ## **We strongly discourage you from using a value from any `names` property as a key in a database or array.** These names may change between releases. Instead we recommend using one of the following: * `GeoIp2\Record\City` - `$city->geonameId` * `GeoIp2\Record\Continent` - `$continent->code` or `$continent->geonameId` * `GeoIp2\Record\Country` and `GeoIp2\Record\RepresentedCountry` - `$country->isoCode` or `$country->geonameId` * `GeoIp2\Record\Subdivision` - `$subdivision->isoCode` or `$subdivision->geonameId` ### What data is returned? ### While many of the end points return the same basic records, the attributes which can be populated vary between end points. In addition, while an end point may offer a particular piece of data, MaxMind does not always have every piece of data for any given IP address. Because of these factors, it is possible for any end point to return a record where some or all of the attributes are unpopulated. See the [GeoIP2 web service docs](https://dev.maxmind.com/geoip/docs/web-services?lang=en) for details on what data each end point may return. The only piece of data which is always returned is the `ipAddress` attribute in the `GeoIp2\Record\Traits` record. ## Integration with GeoNames ## [GeoNames](https://www.geonames.org/) offers web services and downloadable databases with data on geographical features around the world, including populated places. They offer both free and paid premium data. Each feature is unique identified by a `geonameId`, which is an integer. Many of the records returned by the GeoIP2 web services and databases include a `geonameId` property. This is the ID of a geographical feature (city, region, country, etc.) in the GeoNames database. Some of the data that MaxMind provides is also sourced from GeoNames. We source things like place names, ISO codes, and other similar data from the GeoNames premium data set. ## Reporting data problems ## If the problem you find is that an IP address is incorrectly mapped, please [submit your correction to MaxMind](https://www.maxmind.com/en/correction). If you find some other sort of mistake, like an incorrect spelling, please check the [GeoNames site](https://www.geonames.org/) first. Once you've searched for a place and found it on the GeoNames map view, there are a number of links you can use to correct data ("move", "edit", "alternate names", etc.). Once the correction is part of the GeoNames data set, it will be automatically incorporated into future MaxMind releases. If you are a paying MaxMind customer and you're not sure where to submit a correction, please [contact MaxMind support](https://www.maxmind.com/en/support) for help. ## Other Support ## Please report all issues with this code using the [GitHub issue tracker](https://github.com/maxmind/GeoIP2-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 8.1 or greater. This library also relies on the [MaxMind DB Reader](https://github.com/maxmind/MaxMind-DB-Reader-php). ## Contributing ## Patches and pull requests are encouraged. All code should follow the PSR-2 style guidelines. Please include unit tests whenever possible. You may obtain the test data for the maxmind-db folder by running `git submodule update --init --recursive` or adding `--recursive` to your initial clone, or from https://github.com/maxmind/MaxMind-DB ## Versioning ## The GeoIP2 PHP API uses [Semantic Versioning](https://semver.org/). ## Copyright and License ## This software is Copyright (c) 2013-2024 by MaxMind, Inc. This is free software, licensed under the Apache License, Version 2.0. PKٶ\|DjaaGeoIp2/composer.jsonnu[{ "name": "geoip2/geoip2", "description": "MaxMind GeoIP2 PHP API", "keywords": ["geoip", "geoip2", "geolocation", "ip", "maxmind"], "homepage": "https://github.com/maxmind/GeoIP2-php", "type": "library", "license": "Apache-2.0", "authors": [ { "name": "Gregory J. Oschwald", "email": "goschwald@maxmind.com", "homepage": "https://www.maxmind.com/" } ], "require": { "maxmind-db/reader": "^1.12.0", "maxmind/web-service-common": "~0.10", "php": ">=8.1", "ext-json": "*" }, "require-dev": { "friendsofphp/php-cs-fixer": "3.*", "phpunit/phpunit": "^10.0", "squizlabs/php_codesniffer": "3.*", "phpstan/phpstan": "*" }, "autoload": { "psr-4": { "GeoIp2\\": "src" } } } PKٶ\Y 7GeoIp2/src/Util.phpnu[ 0; $i++) { $b = $ipBytes[$i]; if ($curPrefix < 8) { $shiftN = 8 - $curPrefix; $b = \chr(0xFF & (\ord($b) >> $shiftN) << $shiftN); } $networkBytes[$i] = $b; $curPrefix -= 8; } $network = inet_ntop($networkBytes); return "$network/$prefixLen"; } } PKٶ\kfGeoIp2/src/Model/Domain.phpnu[ $raw */ public function __construct(array $raw) { $this->domain = $raw['domain'] ?? null; $ipAddress = $raw['ip_address']; $this->ipAddress = $ipAddress; $this->network = Util::cidr($ipAddress, $raw['prefix_len']); } /** * @return array|null */ public function jsonSerialize(): ?array { $js = []; if ($this->domain !== null) { $js['domain'] = $this->domain; } $js['ip_address'] = $this->ipAddress; $js['network'] = $this->network; return $js; } } PKٶ\ۣ  GeoIp2/src/Model/AnonymousIp.phpnu[ $raw */ public function __construct(array $raw) { $this->isAnonymous = $raw['is_anonymous'] ?? false; $this->isAnonymousVpn = $raw['is_anonymous_vpn'] ?? false; $this->isHostingProvider = $raw['is_hosting_provider'] ?? false; $this->isPublicProxy = $raw['is_public_proxy'] ?? false; $this->isResidentialProxy = $raw['is_residential_proxy'] ?? false; $this->isTorExitNode = $raw['is_tor_exit_node'] ?? false; $ipAddress = $raw['ip_address']; $this->ipAddress = $ipAddress; $this->network = Util::cidr($ipAddress, $raw['prefix_len']); } /** * @return array|null */ public function jsonSerialize(): ?array { $js = []; $js['is_anonymous'] = $this->isAnonymous; $js['is_anonymous_vpn'] = $this->isAnonymousVpn; $js['is_hosting_provider'] = $this->isHostingProvider; $js['is_public_proxy'] = $this->isPublicProxy; $js['is_residential_proxy'] = $this->isResidentialProxy; $js['is_tor_exit_node'] = $this->isTorExitNode; $js['ip_address'] = $this->ipAddress; $js['network'] = $this->network; return $js; } } PKٶ\GeoIp2/src/Model/City.phpnu[ An array of \GeoIp2\Record\Subdivision * objects representing the country * subdivisions for the requested IP * address. The number and type of * subdivisions varies by country, * but a subdivision is typically a * state, province, county, etc. * Subdivisions are ordered from most * general (largest) to most specific * (smallest). If the response did * not contain any subdivisions, this * method returns an empty array. */ public readonly array $subdivisions; /** * @ignore * * @param array $raw * @param list $locales */ public function __construct(array $raw, array $locales = ['en']) { parent::__construct($raw, $locales); $this->city = new CityRecord($raw['city'] ?? [], $locales); $this->location = new Location($raw['location'] ?? []); $this->postal = new Postal($raw['postal'] ?? []); if (!isset($raw['subdivisions'])) { $this->subdivisions = []; $this->mostSpecificSubdivision = new Subdivision([], $locales); return; } $subdivisions = []; foreach ($raw['subdivisions'] as $sub) { $subdivisions[] = new Subdivision($sub, $locales) ; } // Not using end as we don't want to modify internal pointer. $this->mostSpecificSubdivision = $subdivisions[\count($subdivisions) - 1]; $this->subdivisions = $subdivisions; } /** * @return array|null */ public function jsonSerialize(): ?array { $js = parent::jsonSerialize(); $city = $this->city->jsonSerialize(); if (!empty($city)) { $js['city'] = $city; } $location = $this->location->jsonSerialize(); if (!empty($location)) { $js['location'] = $location; } $postal = $this->postal->jsonSerialize(); if (!empty($postal)) { $js['postal'] = $postal; } $subdivisions = []; foreach ($this->subdivisions as $sub) { $subdivisions[] = $sub->jsonSerialize(); } if (!empty($subdivisions)) { $js['subdivisions'] = $subdivisions; } return $js; } } PKٶ\_GeoIp2/src/Model/Enterprise.phpnu[ $raw * @param list $locales */ public function __construct(array $raw, array $locales = ['en']) { $this->continent = new Continent( $raw['continent'] ?? [], $locales ); $this->country = new CountryRecord( $raw['country'] ?? [], $locales ); $this->maxmind = new MaxMind($raw['maxmind'] ?? []); $this->registeredCountry = new CountryRecord( $raw['registered_country'] ?? [], $locales ); $this->representedCountry = new RepresentedCountry( $raw['represented_country'] ?? [], $locales ); $this->traits = new Traits($raw['traits'] ?? []); } /** * @return array|null */ public function jsonSerialize(): ?array { $js = []; $continent = $this->continent->jsonSerialize(); if (!empty($continent)) { $js['continent'] = $continent; } $country = $this->country->jsonSerialize(); if (!empty($country)) { $js['country'] = $country; } $maxmind = $this->maxmind->jsonSerialize(); if (!empty($maxmind)) { $js['maxmind'] = $maxmind; } $registeredCountry = $this->registeredCountry->jsonSerialize(); if (!empty($registeredCountry)) { $js['registered_country'] = $registeredCountry; } $representedCountry = $this->representedCountry->jsonSerialize(); if (!empty($representedCountry)) { $js['represented_country'] = $representedCountry; } $traits = $this->traits->jsonSerialize(); if (!empty($traits)) { $js['traits'] = $traits; } return $js; } } PKٶ\GeoIp2/src/Model/Asn.phpnu[ $raw */ public function __construct(array $raw) { $this->autonomousSystemNumber = $raw['autonomous_system_number'] ?? null; $this->autonomousSystemOrganization = $raw['autonomous_system_organization'] ?? null; $ipAddress = $raw['ip_address']; $this->ipAddress = $ipAddress; $this->network = Util::cidr($ipAddress, $raw['prefix_len']); } /** * @return array|null */ public function jsonSerialize(): ?array { $js = []; if ($this->autonomousSystemNumber !== null) { $js['autonomous_system_number'] = $this->autonomousSystemNumber; } if ($this->autonomousSystemOrganization !== null) { $js['autonomous_system_organization'] = $this->autonomousSystemOrganization; } $js['ip_address'] = $this->ipAddress; $js['network'] = $this->network; return $js; } } PKٶ\!nMM#GeoIp2/src/Model/ConnectionType.phpnu[ $raw */ public function __construct(array $raw) { $this->connectionType = $raw['connection_type'] ?? null; $ipAddress = $raw['ip_address']; $this->ipAddress = $ipAddress; $this->network = Util::cidr($ipAddress, $raw['prefix_len']); } /** * @return array|null */ public function jsonSerialize(): ?array { $js = []; if ($this->connectionType !== null) { $js['connection_type'] = $this->connectionType; } $js['ip_address'] = $this->ipAddress; $js['network'] = $this->network; return $js; } } PKٶ\ߞHGeoIp2/src/Model/Insights.phpnu[ $raw */ public function __construct(array $raw) { $this->autonomousSystemNumber = $raw['autonomous_system_number'] ?? null; $this->autonomousSystemOrganization = $raw['autonomous_system_organization'] ?? null; $this->isp = $raw['isp'] ?? null; $this->mobileCountryCode = $raw['mobile_country_code'] ?? null; $this->mobileNetworkCode = $raw['mobile_network_code'] ?? null; $this->organization = $raw['organization'] ?? null; $ipAddress = $raw['ip_address']; $this->ipAddress = $ipAddress; $this->network = Util::cidr($ipAddress, $raw['prefix_len']); } /** * @return array|null */ public function jsonSerialize(): ?array { $js = []; if ($this->autonomousSystemNumber !== null) { $js['autonomous_system_number'] = $this->autonomousSystemNumber; } if ($this->autonomousSystemOrganization !== null) { $js['autonomous_system_organization'] = $this->autonomousSystemOrganization; } if ($this->isp !== null) { $js['isp'] = $this->isp; } if ($this->mobileCountryCode !== null) { $js['mobile_country_code'] = $this->mobileCountryCode; } if ($this->mobileNetworkCode !== null) { $js['mobile_network_code'] = $this->mobileNetworkCode; } if ($this->organization !== null) { $js['organization'] = $this->organization; } $js['ip_address'] = $this->ipAddress; $js['network'] = $this->network; return $js; } } PKٶ\LiY.GeoIp2/src/Exception/OutOfQueriesException.phpnu[uri = $uri; parent::__construct($message, $httpStatus, $previous); } } PKٶ\i(ͭ(GeoIp2/src/Exception/GeoIp2Exception.phpnu[CC0GeoIp2/src/Exception/InvalidRequestException.phpnu[error = $error; parent::__construct($message, $httpStatus, $uri, $previous); } } PKٶ\|ɝ0GeoIp2/src/Exception/AuthenticationException.phpnu[ $record * @param list $locales */ public function __construct(array $record, array $locales = ['en']) { parent::__construct($record, $locales); $this->code = $record['code'] ?? null; $this->geonameId = $record['geoname_id'] ?? null; } /** * @return array */ public function jsonSerialize(): array { $js = parent::jsonSerialize(); if ($this->code !== null) { $js['code'] = $this->code; } if ($this->geonameId !== null) { $js['geoname_id'] = $this->geonameId; } return $js; } } PKٶ\ 5H/H/GeoIp2/src/Record/Traits.phpnu[The user type associated with the IP * address. This can be one of the following values:

*
    *
  • business *
  • cafe *
  • cellular *
  • college *
  • consumer_privacy_network *
  • content_delivery_network *
  • dialup *
  • government *
  • hosting *
  • library *
  • military *
  • residential *
  • router *
  • school *
  • search_engine_spider *
  • traveler *
*

* This attribute is only available from the Insights web service and the * GeoIP2 Enterprise database. *

*/ public readonly ?string $userType; /** * @ignore * * @param array $record */ public function __construct(array $record) { $this->autonomousSystemNumber = $record['autonomous_system_number'] ?? null; $this->autonomousSystemOrganization = $record['autonomous_system_organization'] ?? null; $this->connectionType = $record['connection_type'] ?? null; $this->domain = $record['domain'] ?? null; $this->ipAddress = $record['ip_address'] ?? null; $this->isAnonymous = $record['is_anonymous'] ?? false; $this->isAnonymousVpn = $record['is_anonymous_vpn'] ?? false; $this->isAnycast = $record['is_anycast'] ?? false; $this->isHostingProvider = $record['is_hosting_provider'] ?? false; $this->isLegitimateProxy = $record['is_legitimate_proxy'] ?? false; $this->isp = $record['isp'] ?? null; $this->isPublicProxy = $record['is_public_proxy'] ?? false; $this->isResidentialProxy = $record['is_residential_proxy'] ?? false; $this->isTorExitNode = $record['is_tor_exit_node'] ?? false; $this->mobileCountryCode = $record['mobile_country_code'] ?? null; $this->mobileNetworkCode = $record['mobile_network_code'] ?? null; $this->organization = $record['organization'] ?? null; $this->staticIpScore = $record['static_ip_score'] ?? null; $this->userCount = $record['user_count'] ?? null; $this->userType = $record['user_type'] ?? null; if (isset($record['network'])) { $this->network = $record['network']; } else { $this->network = isset($record['prefix_len']) ? Util::cidr($this->ipAddress, $record['prefix_len']) : null; } } /** * @return array */ public function jsonSerialize(): array { $js = []; if ($this->autonomousSystemNumber !== null) { $js['autonomous_system_number'] = $this->autonomousSystemNumber; } if ($this->autonomousSystemOrganization !== null) { $js['autonomous_system_organization'] = $this->autonomousSystemOrganization; } if ($this->connectionType !== null) { $js['connection_type'] = $this->connectionType; } if ($this->domain !== null) { $js['domain'] = $this->domain; } if ($this->ipAddress !== null) { $js['ip_address'] = $this->ipAddress; } if ($this->isAnonymous !== false) { $js['is_anonymous'] = $this->isAnonymous; } if ($this->isAnonymousVpn !== false) { $js['is_anonymous_vpn'] = $this->isAnonymousVpn; } if ($this->isAnycast !== false) { $js['is_anycast'] = $this->isAnycast; } if ($this->isHostingProvider !== false) { $js['is_hosting_provider'] = $this->isHostingProvider; } if ($this->isLegitimateProxy !== false) { $js['is_legitimate_proxy'] = $this->isLegitimateProxy; } if ($this->isPublicProxy !== false) { $js['is_public_proxy'] = $this->isPublicProxy; } if ($this->isResidentialProxy !== false) { $js['is_residential_proxy'] = $this->isResidentialProxy; } if ($this->isTorExitNode !== false) { $js['is_tor_exit_node'] = $this->isTorExitNode; } if ($this->isp !== null) { $js['isp'] = $this->isp; } if ($this->mobileCountryCode !== null) { $js['mobile_country_code'] = $this->mobileCountryCode; } if ($this->mobileNetworkCode !== null) { $js['mobile_network_code'] = $this->mobileNetworkCode; } if ($this->network !== null) { $js['network'] = $this->network; } if ($this->organization !== null) { $js['organization'] = $this->organization; } if ($this->staticIpScore !== null) { $js['static_ip_score'] = $this->staticIpScore; } if ($this->userCount !== null) { $js['user_count'] = $this->userCount; } if ($this->userType !== null) { $js['user_type'] = $this->userType; } return $js; } } PKٶ\p!GeoIp2/src/Record/Subdivision.phpnu[ $record * @param list $locales */ public function __construct(array $record, array $locales = ['en']) { parent::__construct($record, $locales); $this->isoCode = $record['iso_code'] ?? null; } /** * @return array */ public function jsonSerialize(): array { $js = parent::jsonSerialize(); if ($this->isoCode !== null) { $js['iso_code'] = $this->isoCode; } return $js; } } PKٶ\Wm(GeoIp2/src/Record/RepresentedCountry.phpnu[military * but this could expand to include other types in the future. */ public readonly ?string $type; /** * @ignore * * @param array $record * @param list $locales */ public function __construct(array $record, array $locales = ['en']) { parent::__construct($record, $locales); $this->type = $record['type'] ?? null; } /** * @return array */ public function jsonSerialize(): array { $js = parent::jsonSerialize(); if ($this->type !== null) { $js['type'] = $this->type; } return $js; } } PKٶ\~íGeoIp2/src/Record/Postal.phpnu[ $record */ public function __construct(array $record) { $this->code = $record['code'] ?? null; $this->confidence = $record['confidence'] ?? null; } /** * @return array */ public function jsonSerialize(): array { $js = []; if ($this->code !== null) { $js['code'] = $this->code; } if ($this->confidence !== null) { $js['confidence'] = $this->confidence; } return $js; } } PKٶ\+%  GeoIp2/src/Record/City.phpnu[ $record */ public function __construct(array $record) { $this->averageIncome = $record['average_income'] ?? null; $this->accuracyRadius = $record['accuracy_radius'] ?? null; $this->latitude = $record['latitude'] ?? null; $this->longitude = $record['longitude'] ?? null; $this->metroCode = $record['metro_code'] ?? null; $this->populationDensity = $record['population_density'] ?? null; $this->timeZone = $record['time_zone'] ?? null; } /** * @return array */ public function jsonSerialize(): array { $js = []; if ($this->averageIncome !== null) { $js['average_income'] = $this->averageIncome; } if ($this->accuracyRadius !== null) { $js['accuracy_radius'] = $this->accuracyRadius; } if ($this->latitude !== null) { $js['latitude'] = $this->latitude; } if ($this->longitude !== null) { $js['longitude'] = $this->longitude; } if ($this->metroCode !== null) { $js['metro_code'] = $this->metroCode; } if ($this->populationDensity !== null) { $js['population_density'] = $this->populationDensity; } if ($this->timeZone !== null) { $js['time_zone'] = $this->timeZone; } return $js; } } PKٶ\*$b,,GeoIp2/src/Record/Country.phpnu[ $record * @param list $locales */ public function __construct(array $record, array $locales = ['en']) { parent::__construct($record, $locales); $this->isInEuropeanUnion = $record['is_in_european_union'] ?? false; $this->isoCode = $record['iso_code'] ?? null; } /** * @return array */ public function jsonSerialize(): array { $js = parent::jsonSerialize(); if ($this->isInEuropeanUnion !== false) { $js['is_in_european_union'] = $this->isInEuropeanUnion; } if ($this->isoCode !== null) { $js['iso_code'] = $this->isoCode; } return $js; } } PKٶ\t@dd)GeoIp2/src/Record/AbstractNamedRecord.phpnu[ An array map where the keys are locale codes * and the values are names. This attribute is returned by all location * services and databases. */ public readonly array $names; /** * @ignore * * @param array $record * @param list $locales */ public function __construct(array $record, array $locales = ['en']) { $this->names = $record['names'] ?? []; foreach ($locales as $locale) { if (isset($this->names[$locale])) { $this->name = $this->names[$locale]; return; } } $this->name = null; } /** * @return array */ public function jsonSerialize(): array { $js = []; if (!empty($this->names)) { $js['names'] = $this->names; } return $js; } } PKٶ\ 5``)GeoIp2/src/Record/AbstractPlaceRecord.phpnu[ $record * @param list $locales */ public function __construct(array $record, array $locales = ['en']) { parent::__construct($record, $locales); $this->confidence = $record['confidence'] ?? null; $this->geonameId = $record['geoname_id'] ?? null; } /** * @return array */ public function jsonSerialize(): array { $js = parent::jsonSerialize(); if ($this->confidence !== null) { $js['confidence'] = $this->confidence; } if ($this->geonameId !== null) { $js['geoname_id'] = $this->geonameId; } return $js; } } PKٶ\,>nnGeoIp2/src/Record/MaxMind.phpnu[ $record */ public function __construct(array $record) { $this->queriesRemaining = $record['queries_remaining'] ?? null; } /** * @return array */ public function jsonSerialize(): array { $js = []; if ($this->queriesRemaining !== null) { $js['queries_remaining'] = $this->queriesRemaining; } return $js; } } PKٶ\iw   GeoIp2/src/ProviderInterface.phpnu[ */ private array $locales; /** * Constructor. * * @param string $filename the path to the GeoIP2 database file * @param array $locales list of locale codes to use in name property * from most preferred to least preferred * * @throws InvalidDatabaseException if the database is corrupt or invalid */ public function __construct( string $filename, array $locales = ['en'] ) { $this->dbReader = new DbReader($filename); $this->dbType = $this->dbReader->metadata()->databaseType; $this->locales = $locales; } /** * This method returns a GeoIP2 City model. * * @param string $ipAddress an IPv4 or IPv6 address as a string * * @throws AddressNotFoundException if the address is not in the database * @throws InvalidDatabaseException if the database is corrupt or invalid */ public function city(string $ipAddress): City { return $this->modelFor(City::class, 'City', $ipAddress); } /** * This method returns a GeoIP2 Country model. * * @param string $ipAddress an IPv4 or IPv6 address as a string * * @throws AddressNotFoundException if the address is not in the database * @throws InvalidDatabaseException if the database is corrupt or invalid */ public function country(string $ipAddress): Country { return $this->modelFor(Country::class, 'Country', $ipAddress); } /** * This method returns a GeoIP2 Anonymous IP model. * * @param string $ipAddress an IPv4 or IPv6 address as a string * * @throws AddressNotFoundException if the address is not in the database * @throws InvalidDatabaseException if the database is corrupt or invalid */ public function anonymousIp(string $ipAddress): AnonymousIp { return $this->flatModelFor( AnonymousIp::class, 'GeoIP2-Anonymous-IP', $ipAddress ); } /** * This method returns a GeoLite2 ASN model. * * @param string $ipAddress an IPv4 or IPv6 address as a string * * @throws AddressNotFoundException if the address is not in the database * @throws InvalidDatabaseException if the database is corrupt or invalid */ public function asn(string $ipAddress): Asn { return $this->flatModelFor( Asn::class, 'GeoLite2-ASN', $ipAddress ); } /** * This method returns a GeoIP2 Connection Type model. * * @param string $ipAddress an IPv4 or IPv6 address as a string * * @throws AddressNotFoundException if the address is not in the database * @throws InvalidDatabaseException if the database is corrupt or invalid */ public function connectionType(string $ipAddress): ConnectionType { return $this->flatModelFor( ConnectionType::class, 'GeoIP2-Connection-Type', $ipAddress ); } /** * This method returns a GeoIP2 Domain model. * * @param string $ipAddress an IPv4 or IPv6 address as a string * * @throws AddressNotFoundException if the address is not in the database * @throws InvalidDatabaseException if the database is corrupt or invalid */ public function domain(string $ipAddress): Domain { return $this->flatModelFor( Domain::class, 'GeoIP2-Domain', $ipAddress ); } /** * This method returns a GeoIP2 Enterprise model. * * @param string $ipAddress an IPv4 or IPv6 address as a string * * @throws AddressNotFoundException if the address is not in the database * @throws InvalidDatabaseException if the database is corrupt or invalid */ public function enterprise(string $ipAddress): Enterprise { return $this->modelFor(Enterprise::class, 'Enterprise', $ipAddress); } /** * This method returns a GeoIP2 ISP model. * * @param string $ipAddress an IPv4 or IPv6 address as a string * * @throws AddressNotFoundException if the address is not in the database * @throws InvalidDatabaseException if the database is corrupt or invalid */ public function isp(string $ipAddress): Isp { return $this->flatModelFor( Isp::class, 'GeoIP2-ISP', $ipAddress ); } private function modelFor(string $class, string $type, string $ipAddress): object { [$record, $prefixLen] = $this->getRecord($class, $type, $ipAddress); $record['traits']['ip_address'] = $ipAddress; $record['traits']['prefix_len'] = $prefixLen; return new $class($record, $this->locales); } private function flatModelFor(string $class, string $type, string $ipAddress): object { [$record, $prefixLen] = $this->getRecord($class, $type, $ipAddress); $record['ip_address'] = $ipAddress; $record['prefix_len'] = $prefixLen; return new $class($record); } /** * @return array{0:array, 1:int} */ private function getRecord(string $class, string $type, string $ipAddress): array { if (!str_contains($this->dbType, $type)) { $method = lcfirst((new \ReflectionClass($class))->getShortName()); throw new \BadMethodCallException( "The $method method cannot be used to open a {$this->dbType} database" ); } [$record, $prefixLen] = $this->dbReader->getWithPrefixLen($ipAddress); if ($record === null) { throw new AddressNotFoundException( "The address $ipAddress is not in the database." ); } if (!\is_array($record)) { // This can happen on corrupt databases. Generally, // MaxMind\Db\Reader will throw a // MaxMind\Db\Reader\InvalidDatabaseException, but occasionally // the lookup may result in a record that looks valid but is not // an array. This mostly happens when the user is ignoring all // exceptions and the more frequent InvalidDatabaseException // exceptions go unnoticed. throw new InvalidDatabaseException( "Expected an array when looking up $ipAddress but received: " . \gettype($record) ); } return [$record, $prefixLen]; } /** * @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 { return $this->dbReader->metadata(); } /** * Closes the GeoIP2 database and returns the resources to the system. */ public function close(): void { $this->dbReader->close(); } } PKٶ\Y#U1U1 GeoIp2/src/WebService/Client.phpnu[ */ private array $locales; private WsClient $client; private static string $basePath = '/geoip/v2.1'; public const VERSION = 'v3.1.0'; /** * Constructor. * * @param int $accountId your MaxMind account ID * @param string $licenseKey your MaxMind license key * @param list $locales list of locale codes to use in name property * from most preferred to least preferred * @param array $options array of options. Valid options include: * * `host` - The host to use when querying the web * service. To query the GeoLite2 web service * instead of the GeoIP2 web service, set the * host to `geolite.info`. To query the Sandbox * GeoIP2 web service instead of the production * GeoIP2 web service, set the host to * `sandbox.maxmind.com`. The sandbox allows you to * experiment with the API without affecting your * production data. * * `timeout` - Timeout in seconds. * * `connectTimeout` - Initial connection timeout in seconds. * * `proxy` - The HTTP proxy to use. May include a schema, port, * username, and password, e.g., * `http://username:password@127.0.0.1:10`. */ public function __construct( int $accountId, string $licenseKey, array $locales = ['en'], array $options = [] ) { $this->locales = $locales; // This is for backwards compatibility. Do not remove except for a // major version bump. // @phpstan-ignore-next-line if (\is_string($options)) { $options = ['host' => $options]; } if (!isset($options['host'])) { $options['host'] = 'geoip.maxmind.com'; } $options['userAgent'] = $this->userAgent(); $this->client = new WsClient($accountId, $licenseKey, $options); } private function userAgent(): string { return 'GeoIP2-API/' . self::VERSION; } /** * This method calls the City Plus service. * * @param string $ipAddress IPv4 or IPv6 address as a string. If no * address is provided, the address that the web service is called * from will be used. * * @throws AddressNotFoundException if the address you provided is not in our database (e.g., * a private address). * @throws AuthenticationException if there is a problem with the account ID or license key * that you provided * @throws OutOfQueriesException if your account is out of queries * @throws InvalidRequestException if your request was received by the web service but is * invalid for some other reason. This may indicate an issue * with this API. Please report the error to MaxMind. * @throws HttpException if an unexpected HTTP error code or message was returned. * This could indicate a problem with the connection between * your server and the web service or that the web service * returned an invalid document or 500 error code * @throws GeoIp2Exception This serves as the parent * class to the above exceptions. It will be thrown directly * if a 200 status code is returned but the body is invalid. * @throws \InvalidArgumentException if something other than a single IP address or "me" is * passed to the method */ public function city(string $ipAddress = 'me'): City { // @phpstan-ignore-next-line return $this->responseFor('city', City::class, $ipAddress); } /** * This method calls the Country service. * * @param string $ipAddress IPv4 or IPv6 address as a string. If no * address is provided, the address that the web service is called * from will be used. * * @throws AddressNotFoundException if the address you provided is not in our database (e.g., * a private address). * @throws AuthenticationException if there is a problem with the account ID or license key that you provided * @throws OutOfQueriesException if your account is out of queries * @throws InvalidRequestException if your request was received by the web service but is * invalid for some other reason. This may indicate an * issue with this API. Please report the error to MaxMind. * @throws HttpException if an unexpected HTTP error * code or message was returned. This could indicate a problem * with the connection between your server and the web service * or that the web service returned an invalid document or 500 * error code. * @throws GeoIp2Exception This serves as the parent class to the above exceptions. It * will be thrown directly if a 200 status code is returned but * the body is invalid. * @throws \InvalidArgumentException if something other than a single IP address or "me" is * passed to the method */ public function country(string $ipAddress = 'me'): Country { return $this->responseFor('country', Country::class, $ipAddress); } /** * This method calls the Insights service. Insights is only supported by * the GeoIP2 web service. The GeoLite2 web service does not support it. * * @param string $ipAddress IPv4 or IPv6 address as a string. If no * address is provided, the address that the web service is called * from will be used. * * @throws AddressNotFoundException if the address you provided is not in our database (e.g., * a private address). * @throws AuthenticationException if there is a problem with the account ID or license key * that you provided * @throws OutOfQueriesException if your account is out of queries * @throws InvalidRequestException if your request was received by the web service but is * invalid for some other reason. This may indicate an * issue with this API. Please report the error to MaxMind. * @throws HttpException if an unexpected HTTP error code or message was returned. * This could indicate a problem with the connection between * your server and the web service or that the web service * returned an invalid document or 500 error code * @throws GeoIp2Exception This serves as the parent * class to the above exceptions. It will be thrown directly * if a 200 status code is returned but the body is invalid. * @throws \InvalidArgumentException if something other than a single IP address or "me" is * passed to the method */ public function insights(string $ipAddress = 'me'): Insights { // @phpstan-ignore-next-line return $this->responseFor('insights', Insights::class, $ipAddress); } private function responseFor(string $endpoint, string $class, string $ipAddress): Country { if ($ipAddress !== 'me' && !filter_var($ipAddress, \FILTER_VALIDATE_IP)) { throw new \InvalidArgumentException( "The value \"$ipAddress\" is not a valid IP address." ); } $path = implode('/', [self::$basePath, $endpoint, $ipAddress]); try { $service = (new \ReflectionClass($class))->getShortName(); $body = $this->client->get('GeoIP2 ' . $service, $path); } catch (IpAddressNotFoundException $ex) { throw new AddressNotFoundException( $ex->getMessage(), $ex->getStatusCode(), $ex ); } catch (\MaxMind\Exception\AuthenticationException $ex) { throw new AuthenticationException( $ex->getMessage(), $ex->getStatusCode(), $ex ); } catch (InsufficientFundsException $ex) { throw new OutOfQueriesException( $ex->getMessage(), $ex->getStatusCode(), $ex ); } catch (\MaxMind\Exception\InvalidRequestException $ex) { throw new InvalidRequestException( $ex->getMessage(), $ex->getErrorCode(), $ex->getStatusCode(), $ex->getUri(), $ex ); } catch (\MaxMind\Exception\HttpException $ex) { throw new HttpException( $ex->getMessage(), $ex->getStatusCode(), $ex->getUri(), $ex ); } catch (WebServiceException $ex) { throw new GeoIp2Exception( $ex->getMessage(), $ex->getCode(), $ex ); } return new $class($body, $this->locales); } } PKٶ\@//GeoIp2/CHANGELOG.mdnu[CHANGELOG ========= 3.1.0 (2024-11-15) ------------------ * This library no longer uses implicitly nullable parameter types. This will fix deprecation warning in PHP 8.4. Reported by Steven Lewis. GitHub #230. * The PHPDoc type hints have been improved for use with PHPStan. 3.0.0 (2023-12-04) ------------------ * IMPORTANT: PHP 8.1 or greater is now required. * BREAKING: Read-only properties are now used for the model and record classes rather than magic methods. This significantly improves performance. * BREAKING: The `raw` property on model classess and the `record` property on record classes have been removed. * BREAKING: On `GeoIp2\Record\Traits`, the deprecated `isAnonymousProxy` and `isSatelliteProvider` properties have been removed. * BREAKING: The `jsonSerialize` output has changed. * `GeoIp2\WebService\Client` methods now throw an `InvalidArgumentException` if an invalid IP address is passed to them. Previously, they would make a request to the web service and throw a `GeoIp2\Exception\InvalidRequestException`. * The `isAnycast` property was added to `GeoIp2\Record\Traits`. This returns `true` if the IP address belongs to an [anycast network](https://en.wikipedia.org/wiki/Anycast). This is available for the GeoIP2 Country, City Plus, and Insights web services and the GeoIP2 Country, City, and Enterprise databases. 2.13.0 (2022-08-05) ------------------- * The model class names are no longer constructed by concatenating strings. This change was made to improve support for tools like PHP-Scoper. Reported by Andrew Mead. GitHub #194. * Box 4.0.1 is now used to generate the `geoip2.phar` file. 2.12.2 (2021-11-30) ------------------- * The `geoip2.phar` now works when included from another directory. Reported by Eduardo Ruiz. GitHub #179. 2.12.1 (2021-11-23) ------------------- * The `geoip2.phar` included in 2.12.0 would only work in CLI applications. This was due to a change in Box 3.x. The Phar should now work in all applications. This release only affects users of the Phar file. 2.12.0 (2021-11-18) ------------------- * Support for mobile country code (MCC) and mobile network codes (MNC) was added for the GeoIP2 ISP and Enterprise databases as well as the GeoIP2 City and Insights web services. `$mobileCountryCode` and `$mobileNetworkCode` properties were added to `GeoIp2\Model\Isp` for the GeoIP2 ISP database and `GeoIp2\Record\Traits` for the Enterprise database and the GeoIP2 City and Insights web services. We expect this data to be available by late January, 2022. * `geoip2.phar` is now generated with Box 3.x. 2.11.0 (2020-10-01) ------------------- * IMPORTANT: PHP 7.2 or greater is now required. * Added the `isResidentialProxy` property to `GeoIp2\Model\AnonymousIP` and `GeoIp2\Record\Traits`. * Additional type hints have been added. 2.10.0 (2019-12-12) ------------------- * PHP 5.6 or greater is now required. * The `network` property was added to `GeoIp2\Record\Traits`, `GeoIp2\Model\AnonymousIp`, `GeoIp2\Model\Asn`, `GeoIp2\Model\ConnectionType`, `Geoip2\Model\Domain`, and `GeoIp2\Model\Isp`. This is a string in CIDR format representing the largest network where all of the properties besides `ipAddress` have the same value. * Updated documentation of anonymizer properties - `isAnonymousVpn` and `isHostingProvider` - to be more descriptive. * The `userCount` property was added to `GeoIp2\Record\Traits`. This is an integer which indicates the estimated number of users sharing the IP/network during the past 24 hours. This output is available from GeoIP2 Precision Insights. * The `staticIpScore` property was added to `GeoIp2\Record\Traits`. This is a float which indicates how static or dynamic an IP address is. This output is available from GeoIP2 Precision Insights. 2.9.0 (2018-04-10) ------------------ * Refer to account IDs using the terminology "account" rather than "user". 2.8.0 (2018-01-18) ------------------ * The `isInEuropeanUnion` property was added to `GeoIp2\Record\Country` and `GeoIp2\Record\RepresentedCountry`. This property is `true` if the country is a member state of the European Union. 2.7.0 (2017-10-27) ------------------ * The following new anonymizer properties were added to `GeoIp2\Record\Traits` for use with GeoIP2 Precision Insights: `isAnonymous`, `isAnonymousVpn`, `isHostingProvider`, `isPublicProxy`, and `isTorExitNode`. 2.6.0 (2017-07-10) ----------------- * Code clean-up and tidying. * Set minimum required PHP version to 5.4 in `composer.json`. Previously, 5.3 would work but was not tested. Now 5.4 is hard minimum version. 2.5.0 (2017-05-08) ------------------ * Support for PHP 5.3 was dropped. * Added support for GeoLite2 ASN database. 2.4.5 (2017-01-31) ------------------ * Additional error checking on the data returned from `MaxMind\Db\Reader` was added to help detect corrupt databases. GitHub #83. 2.4.4 (2016-10-11) ------------------ * `isset()` on `mostSpecificSubdivision` attribute now returns the correct value. Reported by Juan Francisco Giordana. GitHub #81. 2.4.3 (2016-10-11) ------------------ * `isset()` on `name` attribute now returns the correct value. Reported by Juan Francisco Giordana. GitHub #79. 2.4.2 (2016-08-17) ------------------ * Updated documentation to clarify what the accuracy radius refers to. * Upgraded `maxmind/web-service-common` to 0.3.0. This version uses `composer/ca-bundle` rather than our own CA bundle. GitHub #75. * Improved PHP documentation generation. 2.4.1 (2016-06-10) ------------------ * Corrected type annotations in documentation. GitHub #66. * Updated documentation to reflect that the accuracy radius is now included in City. * Upgraded web service client, which supports setting a proxy. GitHub #59. 2.4.0 (2016-04-15) ------------------ * Added support for the GeoIP2 Enterprise database. 2.3.3 (2015-09-24) ------------------ * Corrected case on `JsonSerializable` interface. Reported by Axel Etcheverry. GitHub #56. 2.3.2 (2015-09-23) ------------------ * `JsonSerializable` compatibility interface was moved to `GeoIp2\Compat` rather than the global namespace to prevent autoloading issues. Reported by Tomas Buteler. GitHub #54. * Missing documentation for the `$postal` property was added to the `GeoIp2\Model\City` class. Fix by Roy Sindre Norangshol. GitHub #51. * In the Phar distribution, source files for this module no longer have their documentation stripped, allowing IDE introspection to work properly. Reported by Dominic Black. GitHub #52. 2.3.1 (2015-06-30) ------------------ * Updated `maxmind/web-service-common` to version with fixes for PHP 5.3 and 5.4. 2.3.0 (2015-06-29) ------------------ * Support for demographics fields `averageIncome` and `populationDensity` in the `Location` record, returned by the Insights endpoint. * The `isAnonymousProxy` and `isSatelliteProvider` properties on `GeoIP2\Record\Traits` have been deprecated. Please use our [GeoIP2 Anonymous IP database](https://www.maxmind.com/en/geoip2-anonymous-ip-database) to determine whether an IP address is used by an anonymizing service. 2.2.0-beta1 (2015-06-09) ------------------------ * Typo fix in documentation. 2.2.0-alpha2 (2015-06-01) ------------------------- * `maxmind-ws/web-service-common` was renamed to `maxmind/web-service-common`. 2.2.0-alpha1 (2015-05-22) ------------------------- * The library no longer uses Guzzle and instead uses curl directly. * Support for `timeout` and `connectTimout` were added to the `$options` array passed to the `GeoIp2\WebService\Client` constructor. Pull request by Will Bradley. GitHub #36. 2.1.1 (2014-12-03) ------------------ * The 2.1.0 Phar builds included a shebang line, causing issues when loading it as a library. This has been corrected. GitHub #33. 2.1.0 (2014-10-29) ------------------ * Update ApiGen dependency to version that isn't broken on case sensitive file systems. * Added support for the GeoIP2 Anonymous IP database. The `GeoIP2\Database\Reader` class now has an `anonymousIp` method which returns a `GeoIP2\Model\AnonymousIp` object. * Boolean attributes like those in the `GeoIP2\Record\Traits` class now return `false` instead of `null` when they were not true. 2.0.0 (2014-09-22) ------------------ * First production release. 0.9.0 (2014-09-15) ------------------ * IMPORTANT: The deprecated `omni()` and `cityIspOrg()` methods have been removed from `GeoIp2\WebService\Client`. 0.8.1 (2014-09-12) ------------------ * The check added to the `GeoIP2\Database\Reader` lookup methods in 0.8.0 did not work with the GeoIP2 City Database Subset by Continent with World Countries. This has been fixed. Fixes GitHub issue #23. 0.8.0 (2014-09-10) ------------------ * The `GeoIp2\Database\Reader` lookup methods (e.g., `city()`, `isp()`) now throw a `BadMethodCallException` if they are used with a database that does not match the method. In particular, doing a `city()` lookup on a GeoIP2 Country database will result in an exception, and vice versa. * A `metadata()` method has been added to the `GeoIP2\Database\Reader` class. This returns a `MaxMind\Db\Reader\Metadata` class with information about the database. * The name attribute was missing from the RepresentedCountry class. 0.7.0 (2014-07-22) ------------------ * The web service client API has been updated for the v2.1 release of the web service. In particular, the `cityIspOrg` and `omni` methods on `GeoIp2\WebService\Client` should be considered deprecated. The `city` method now provides all of the data formerly provided by `cityIspOrg`, and the `omni` method has been replaced by the `insights` method. * Support was added for GeoIP2 Connection Type, Domain and ISP databases. 0.6.3 (2014-05-12) ------------------ * With the previous Phar builds, some users received `phar error: invalid url or non-existent phar` errors. The correct alias is now used for the Phar, and this should no longer be an issue. 0.6.2 (2014-05-08) ------------------ * The Phar build was broken with Guzzle 3.9.0+. This has been fixed. 0.6.1 (2014-05-01) ------------------ * This API now officially supports HHVM. * The `maxmind-db/reader` dependency was updated to a version that does not require BC Math. * The Composer compatibility autoload rules are now targeted more narrowly. * A `box.json` file is included to build a Phar package. 0.6.0 (2014-02-19) ------------------ * This API is now licensed under the Apache License, Version 2.0. * Model and record classes now implement `JsonSerializable`. * `isset` now works with model and record classes. 0.5.0 (2013-10-21) ------------------ * Renamed $languages constructor parameters to $locales for both the Client and Reader classes. * Documentation and code clean-up (Ben Morel). * Added the interface `GeoIp2\ProviderInterface`, which is implemented by both `\GeoIp2\Database\Reader` and `\GeoIp2\WebService\Client`. 0.4.0 (2013-07-16) ------------------ * This is the first release with the GeoIP2 database reader. Please see the `README.md` file and the `\GeoIp2\Database\Reader` class. * The general exception classes were replaced with specific exception classes representing particular types of errors, such as an authentication error. 0.3.0 (2013-07-12) ------------------ * In namespaces and class names, "GeoIP2" was renamed to "GeoIp2" to improve consistency. 0.2.1 (2013-06-10) ------------------ * First official beta release. * Documentation updates and corrections. 0.2.0 (2013-05-29) ------------------ * `GenericException` was renamed to `GeoIP2Exception`. * We now support more languages. The new languages are de, es, fr, and pt-BR. * The REST API now returns a record with data about your account. There is a new `GeoIP\Records\MaxMind` class for this data. * The `continentCode` attribute on `Continent` was renamed to `code`. * Documentation updates. 0.1.1 (2013-05-14) ------------------ * Updated Guzzle version requirement. * Fixed Composer example in README.md. 0.1.0 (2013-05-13) ------------------ * Initial release. PKٶ\^,^,GeoIp2/LICENSEnu[ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. PKٶ\&XMaxMind/README.mdnu[# 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 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 recommended 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-2024 by MaxMind, Inc. This is free software, licensed under the Apache License, Version 2.0. PKٶ\pH44MaxMind/composer.jsonnu[{ "name": "maxmind-db/reader", "description": "MaxMind DB Reader API", "keywords": ["database", "geoip", "geoip2", "geolocation", "maxmind"], "homepage": "https://github.com/maxmind/MaxMind-DB-Reader-php", "type": "library", "license": "Apache-2.0", "authors": [ { "name": "Gregory J. Oschwald", "email": "goschwald@maxmind.com", "homepage": "https://www.maxmind.com/" } ], "require": { "php": ">=7.2" }, "suggest": { "ext-bcmath": "bcmath or gmp is required for decoding larger integers with the pure PHP decoder", "ext-gmp": "bcmath or gmp is required for decoding larger integers with the pure PHP decoder", "ext-maxminddb": "A C-based database decoder that provides significantly faster lookups" }, "conflict": { "ext-maxminddb": "<1.11.1 || >=2.0.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "3.*", "phpunit/phpunit": ">=8.0.0,<10.0.0", "squizlabs/php_codesniffer": "3.*", "phpstan/phpstan": "*" }, "autoload": { "psr-4": { "MaxMind\\Db\\": "src/MaxMind/Db" } }, "autoload-dev": { "psr-4": { "MaxMind\\Db\\Test\\Reader\\": "tests/MaxMind/Db/Test/Reader" } } } PKٶ\W2W2!MaxMind/src/MaxMind/Db/Reader.phpnu[ */ 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()) ); } if (is_dir($database)) { // This matches the error that the C extension throws. throw new InvalidDatabaseException( "Error opening database file ($database). Is this a valid MaxMind DB file?" ); } $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{0:mixed, 1:int} 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]; } /** * @return array{0:int, 1:int} */ 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); if ($rawAddress === false) { throw new InvalidDatabaseException( 'Could not unpack the unsigned char of the packed in_addr representation.' ); } $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); $rc = unpack('N', "\x00" . $bytes); if ($rc === false) { throw new InvalidDatabaseException( 'Could not unpack the unsigned long of the node.' ); } [, $node] = $rc; 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]); } $rc = unpack('N', \chr($middle) . substr($bytes, $index, 3)); if ($rc === false) { throw new InvalidDatabaseException( 'Could not unpack the unsigned long of the node.' ); } [, $node] = $rc; return $node; case 32: $bytes = Util::read($this->fileHandle, $baseOffset + $index * 4, 4); $rc = unpack('N', $bytes); if ($rc === false) { throw new InvalidDatabaseException( 'Could not unpack the unsigned long of the node.' ); } [, $node] = $rc; 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); if ($fstat === false) { throw new InvalidDatabaseException( "Error getting file information ($filename)." ); } $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); } } PKٶ\XX&MaxMind/src/MaxMind/Db/Reader/Util.phpnu[ $numberOfBytes */ 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' ); } } PKٶ\' *MaxMind/src/MaxMind/Db/Reader/Metadata.phpnu[ */ 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; /** * @param array $metadata */ 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; } } PKٶ\yD2222)MaxMind/src/MaxMind/Db/Reader/Decoder.phpnu[fileStream = $fileStream; $this->pointerBase = $pointerBase; $this->pointerTestHack = $pointerTestHack; $this->switchByteOrder = $this->isPlatformLittleEndian(); } /** * @return array */ 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); } /** * @param int<0, max> $size * * @return array{0:mixed, 1:int} */ 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)" ); } } /** * @return array{0:array, 1:int} */ 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. $rc = unpack('E', $bytes); if ($rc === false) { throw new InvalidDatabaseException( 'Could not unpack a double value from the given bytes.' ); } [, $double] = $rc; return $double; } private function decodeFloat(string $bytes): float { // This assumes IEEE 754 floats, but most (all?) modern platforms // use them. $rc = unpack('G', $bytes); if ($rc === false) { throw new InvalidDatabaseException( 'Could not unpack a float value from the given bytes.' ); } [, $float] = $rc; 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)" ); } $rc = unpack('l', $this->maybeSwitchByteOrder($bytes)); if ($rc === false) { throw new InvalidDatabaseException( 'Could not unpack a 32bit integer value from the given bytes.' ); } [, $int] = $rc; return $int; } /** * @return array{0:array, 1: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]; } /** * @return array{0:int, 1:int} */ private function decodePointer(int $ctrlByte, int $offset): array { $pointerSize = (($ctrlByte >> 3) & 0x3) + 1; $buffer = Util::read($this->fileStream, $offset, $pointerSize); $offset += $pointerSize; switch ($pointerSize) { case 1: $packed = \chr($ctrlByte & 0x7) . $buffer; $rc = unpack('n', $packed); if ($rc === false) { throw new InvalidDatabaseException( 'Could not unpack an unsigned short value from the given bytes (pointerSize is 1).' ); } [, $pointer] = $rc; $pointer += $this->pointerBase; break; case 2: $packed = "\x00" . \chr($ctrlByte & 0x7) . $buffer; $rc = unpack('N', $packed); if ($rc === false) { throw new InvalidDatabaseException( 'Could not unpack an unsigned long value from the given bytes (pointerSize is 2).' ); } [, $pointer] = $rc; $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. $rc = unpack('N', $packed); if ($rc === false) { throw new InvalidDatabaseException( 'Could not unpack an unsigned long value from the given bytes (pointerSize is 3).' ); } [, $pointer] = $rc; $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; } // 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); if ($useRealInts) { $integer = 0; for ($i = 0; $i < $byteLength; ++$i) { $part = \ord($bytes[$i]); $integer = ($integer << 8) + $part; } return $integer; } // We only use gmp or bcmath if the final value is too big $integerAsString = '0'; for ($i = 0; $i < $byteLength; ++$i) { $part = \ord($bytes[$i]); if (\extension_loaded('gmp')) { $integerAsString = gmp_strval(gmp_add(gmp_mul($integerAsString, '256'), $part)); } elseif (\extension_loaded('bcmath')) { $integerAsString = bcadd(bcmul($integerAsString, '256'), (string) $part); } else { throw new \RuntimeException( 'The gmp or bcmath extension must be installed to read this database.' ); } } return $integerAsString; } /** * @return array{0:int, 1:int} */ 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) { $rc = unpack('n', $bytes); if ($rc === false) { throw new InvalidDatabaseException( 'Could not unpack an unsigned short value from the given bytes.' ); } [, $adjust] = $rc; $size = 285 + $adjust; } else { $rc = unpack('N', "\x00" . $bytes); if ($rc === false) { throw new InvalidDatabaseException( 'Could not unpack an unsigned long value from the given bytes.' ); } [, $adjust] = $rc; $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); $rc = unpack('v', $packed); if ($rc === false) { throw new InvalidDatabaseException( 'Could not unpack an unsigned short value from the given bytes.' ); } return $testint === current($rc); } } PKٶ\sqe:MaxMind/src/MaxMind/Db/Reader/InvalidDatabaseException.phpnu[CC0GeoIp2/src/Exception/InvalidRequestException.phpnu[PKٶ\|ɝ03GeoIp2/src/Exception/AuthenticationException.phpnu[PKٶ\<ޟMGeoIp2/src/Record/Continent.phpnu[PKٶ\ 5H/H/;GeoIp2/src/Record/Traits.phpnu[PKٶ\p!GeoIp2/src/Record/Subdivision.phpnu[PKٶ\Wm(GeoIp2/src/Record/RepresentedCountry.phpnu[PKٶ\~í-GeoIp2/src/Record/Postal.phpnu[PKٶ\+%  GeoIp2/src/Record/City.phpnu[PKٶ\5JJGeoIp2/src/Record/Location.phpnu[PKٶ\*$b,,tGeoIp2/src/Record/Country.phpnu[PKٶ\t@dd)GeoIp2/src/Record/AbstractNamedRecord.phpnu[PKٶ\ 5``)GeoIp2/src/Record/AbstractPlaceRecord.phpnu[PKٶ\,>nncGeoIp2/src/Record/MaxMind.phpnu[PKٶ\iw   GeoIp2/src/ProviderInterface.phpnu[PKٶ\4+lr"r"{ GeoIp2/src/Database/Reader.phpnu[PKٶ\Y#U1U1 ;-GeoIp2/src/WebService/Client.phpnu[PKٶ\@//^GeoIp2/CHANGELOG.mdnu[PKٶ\^,^,GeoIp2/LICENSEnu[PKٶ\&XBMaxMind/README.mdnu[PKٶ\pH44RMaxMind/composer.jsonnu[PKٶ\W2W2!MaxMind/src/MaxMind/Db/Reader.phpnu[PKٶ\XX&sMaxMind/src/MaxMind/Db/Reader/Util.phpnu[PKٶ\' *! MaxMind/src/MaxMind/Db/Reader/Metadata.phpnu[PKٶ\yD2222)qMaxMind/src/MaxMind/Db/Reader/Decoder.phpnu[PKٶ\sqe:JMaxMind/src/MaxMind/Db/Reader/InvalidDatabaseException.phpnu[PKٶ\aaALMaxMind/CHANGELOG.mdnu[PKٶ\^,^,kMaxMind/LICENSEnu[PKٶ\BLreadme_moodle.txtnu[PK,,T