From 7556d649578dfedb1e82c01195a52d268f8fb9e1 Mon Sep 17 00:00:00 2001 From: "hugh.li" Date: Tue, 8 Oct 2019 20:02:37 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81php=E5=8F=98=E9=87=8F?= =?UTF-8?q?=E4=BD=9C=E4=B8=BA=E6=95=B0=E6=8D=AE=E6=9D=A5=E6=BA=90,=20?= =?UTF-8?q?=E5=8A=A0=E9=80=9Fcli=E6=88=96=E8=80=85=E5=85=B6=E4=BB=96?= =?UTF-8?q?=E7=89=B9=E6=AE=8A=E7=8E=AF=E5=A2=83=E7=9A=84=E9=80=9F=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 + composer.json | 48 +++--- src/ipip/db/BaseStation.php | 20 +-- src/ipip/db/BaseStationInfo.php | 5 +- src/ipip/db/City.php | 25 ++- src/ipip/db/District.php | 20 +-- src/ipip/db/IDC.php | 20 +-- src/ipip/db/IpDbReader.php | 67 ++++++++ src/ipip/db/IpUtils.php | 66 ++++++++ src/ipip/db/PHPReader.php | 42 +++++ src/ipip/db/Proxy.php | 39 +++++ src/ipip/db/Reader.php | 289 ++++++++++++++++---------------- 12 files changed, 422 insertions(+), 223 deletions(-) create mode 100644 .gitignore create mode 100644 src/ipip/db/IpDbReader.php create mode 100644 src/ipip/db/IpUtils.php create mode 100644 src/ipip/db/PHPReader.php create mode 100644 src/ipip/db/Proxy.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cb88a55 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.idea +composer.lock +ipipfree.ipdb +vendor diff --git a/composer.json b/composer.json index 75492c9..d1c7ced 100644 --- a/composer.json +++ b/composer.json @@ -1,23 +1,31 @@ { - "name": "ipip/db", - "type": "library", - "description": "IPIP.net officially supported IP database ipdb format parsing library", - "keywords": ["ipip.net","geoip","geolocation", "geo","ip","ipdb"], - "homepage": "https://www.ipip.net", - "license": "Apache-2.0", - "authors": [ - { - "name": "IPIP.net", - "email": "frk@ipip.net", - "homepage": "https://www.ipip.net" - } - ], - "require": { - "php": ">=5.4.0" - }, - "autoload": { - "psr-4": { - "ipip\\db\\": "src/ipip/db/" - } + "name": "ipip/db", + "type": "library", + "description": "IPIP.net officially supported IP database ipdb format parsing library", + "keywords": [ + "ipip.net", + "geoip", + "geolocation", + "geo", + "ip", + "ipdb" + ], + "homepage": "https://www.ipip.net", + "license": "Apache-2.0", + "authors": [ + { + "name": "IPIP.net", + "email": "frk@ipip.net", + "homepage": "https://www.ipip.net" } + ], + "require": { + "php": ">=5.4.0", + "ext-json": "*" + }, + "autoload": { + "psr-4": { + "ipip\\db\\": "src/ipip/db/" + } + } } diff --git a/src/ipip/db/BaseStation.php b/src/ipip/db/BaseStation.php index a2e7aef..dcd9856 100644 --- a/src/ipip/db/BaseStation.php +++ b/src/ipip/db/BaseStation.php @@ -8,33 +8,25 @@ namespace ipip\db; -class BaseStation +class BaseStation extends Proxy { - public $reader = NULL; - - public function __construct($db) - { - $this->reader = new Reader($db); - } - public function find($ip, $language) { - return $this->reader->find($ip, $language); + return $this->getReader()->find($ip, $language); } public function findMap($ip, $language) { - return $this->reader->findMap($ip, $language); + return $this->getReader()->findMap($ip, $language); } public function findInfo($ip, $language) { $map = $this->findMap($ip, $language); - if (NULL === $map) - { - return NULL; + if (null === $map){ + return null; } return new BaseStationInfo($map); } -} \ No newline at end of file +} diff --git a/src/ipip/db/BaseStationInfo.php b/src/ipip/db/BaseStationInfo.php index 164c460..f24a4ab 100644 --- a/src/ipip/db/BaseStationInfo.php +++ b/src/ipip/db/BaseStationInfo.php @@ -19,8 +19,7 @@ class BaseStationInfo public function __construct(array $data) { - foreach ($data AS $field => $value) - { + foreach($data AS $field => $value){ $this->{$field} = $value; } } @@ -29,4 +28,4 @@ public function __get($name) { return $this->{$name}; } -} \ No newline at end of file +} diff --git a/src/ipip/db/City.php b/src/ipip/db/City.php index d6afa0e..bdc250b 100644 --- a/src/ipip/db/City.php +++ b/src/ipip/db/City.php @@ -8,33 +8,30 @@ namespace ipip\db; -class City +class City extends Proxy { - public $reader = NULL; - - public function __construct($db) - { - $this->reader = new Reader($db); - } - + /** + * @param string $ip + * @param string $language + * @return array|null + */ public function find($ip, $language) { - return $this->reader->find($ip, $language); + return $this->getReader()->find($ip, $language); } public function findMap($ip, $language) { - return $this->reader->findMap($ip, $language); + return $this->getReader()->findMap($ip, $language); } public function findInfo($ip, $language) { $map = $this->findMap($ip, $language); - if (NULL === $map) - { - return NULL; + if (null === $map){ + return null; } return new CityInfo($map); } -} \ No newline at end of file +} diff --git a/src/ipip/db/District.php b/src/ipip/db/District.php index e867f1e..e502ee9 100644 --- a/src/ipip/db/District.php +++ b/src/ipip/db/District.php @@ -8,33 +8,25 @@ namespace ipip\db; -class District +class District extends Proxy { - public $reader = NULL; - - public function __construct($db) - { - $this->reader = new Reader($db); - } - public function find($ip, $language) { - return $this->reader->find($ip, $language); + return $this->getReader()->find($ip, $language); } public function findMap($ip, $language) { - return $this->reader->findMap($ip, $language); + return $this->getReader()->findMap($ip, $language); } public function findInfo($ip, $language) { $map = $this->findMap($ip, $language); - if (NULL === $map) - { - return NULL; + if (null === $map){ + return null; } return new DistrictInfo($map); } -} \ No newline at end of file +} diff --git a/src/ipip/db/IDC.php b/src/ipip/db/IDC.php index 16afb66..b6a0d96 100644 --- a/src/ipip/db/IDC.php +++ b/src/ipip/db/IDC.php @@ -8,33 +8,25 @@ namespace ipip\db; -class IDC +class IDC extends Proxy { - public $reader = NULL; - - public function __construct($db) - { - $this->reader = new Reader($db); - } - public function find($ip, $language) { - return $this->reader->find($ip, $language); + return $this->getReader()->find($ip, $language); } public function findMap($ip, $language) { - return $this->reader->findMap($ip, $language); + return $this->getReader()->findMap($ip, $language); } public function findInfo($ip, $language) { $map = $this->findMap($ip, $language); - if (NULL === $map) - { - return NULL; + if (null === $map){ + return null; } return new IDCInfo($map); } -} \ No newline at end of file +} diff --git a/src/ipip/db/IpDbReader.php b/src/ipip/db/IpDbReader.php new file mode 100644 index 0000000..d5c4134 --- /dev/null +++ b/src/ipip/db/IpDbReader.php @@ -0,0 +1,67 @@ +database = $database; + + if (is_readable($this->database) === false){ + throw new \InvalidArgumentException("The IP Database file \"{$this->database}\" does not exist or is not readable."); + } + + $this->file = @fopen($this->database, 'rb'); + if ($this->file === false){ + throw new \InvalidArgumentException("IP Database File opening \"{$this->database}\"."); + } + + $this->init(); + } + + /** + * @inheritDoc + */ + protected function computeFileSize() + { + return @filesize($this->database); + } + + /** + * @inheritDoc + */ + protected function read($offset, $length) + { + if (0 !== fseek($this->file, $offset)){ + return false; + } + + return fread($this->file, $length); + } + + /** + * @inheritDoc + */ + public function close() + { + if (is_resource($this->file)){ + fclose($this->file); + } + } +} diff --git a/src/ipip/db/IpUtils.php b/src/ipip/db/IpUtils.php new file mode 100644 index 0000000..5284e7c --- /dev/null +++ b/src/ipip/db/IpUtils.php @@ -0,0 +1,66 @@ +data = $data; + + $this->init(); + } + + /** + * @inheritDoc + */ + protected function computeFileSize() + { + return strlen($this->data); + } + + /** + * @inheritDoc + */ + protected function read($offset, $length) + { + return substr($this->data, $offset, $length); + } +} diff --git a/src/ipip/db/Proxy.php b/src/ipip/db/Proxy.php new file mode 100644 index 0000000..6f8e744 --- /dev/null +++ b/src/ipip/db/Proxy.php @@ -0,0 +1,39 @@ +reader = $reader; + } + + /** + * @return Reader|null + */ + public function getReader() + { + return $this->reader; + } +} diff --git a/src/ipip/db/Reader.php b/src/ipip/db/Reader.php index bc90120..46d37e6 100644 --- a/src/ipip/db/Reader.php +++ b/src/ipip/db/Reader.php @@ -8,62 +8,118 @@ namespace ipip\db; -class Reader +abstract class Reader { const IPV4 = 1; const IPV6 = 2; - private $file = NULL; - private $fileSize = 0; + /** + * @var int ipDB文件大小 + */ + private $fileSize; + private $nodeCount = 0; private $nodeOffset = 0; - private $meta = []; + /** + * @var array + */ + private $meta; - private $database = ''; + /** + * 计算文件大小 + * + * @return integer + */ + abstract protected function computeFileSize(); /** - * Reader constructor. - * @param $database - * @throws \Exception + * 读取文件内容 + * + * @param integer $offset 指针偏移 + * @param integer $length 读取长度 + * @return string|false + */ + abstract protected function read($offset, $length); + + /** + * 是否支持IP V6 + * + * @return bool */ - public function __construct($database) + public function supportV6() { - $this->database = $database; + return ($this->meta['ip_version'] & static::IPV6) === static::IPV6; + } - $this->init(); + /** + * 是否支持IP V4 + * + * @return bool + */ + public function supportV4() + { + return ($this->meta['ip_version'] & static::IPV4) === static::IPV4; } - private function init() + /** + * 是否支持指定语言 + * + * @param string $language + * @return bool + */ + public function supportLanguage($language) { - if (is_readable($this->database) === FALSE) - { - throw new \InvalidArgumentException("The IP Database file \"{$this->database}\" does not exist or is not readable."); - } - $this->file = @fopen($this->database, 'rb'); - if ($this->file === FALSE) - { - throw new \InvalidArgumentException("IP Database File opening \"{$this->database}\"."); - } - $this->fileSize = @filesize($this->database); - if ($this->fileSize === FALSE) - { - throw new \UnexpectedValueException("Error determining the size of \"{$this->database}\"."); - } + return in_array($language, $this->getSupportLanguages(), true); + } - $metaLength = unpack('N', fread($this->file, 4))[1]; - $text = fread($this->file, $metaLength); + /** + * 支持的语言 + * @return array + */ + public function getSupportLanguages() + { + return (isset($this->meta['languages']) && is_array($this->meta['languages'])) ? array_keys($this->meta['languages']) : []; + } + + /** + * @return int UTC Timestamp + */ + public function getBuildTime() + { + return $this->meta['build']; + } - $this->meta = json_decode($text, 1); + /** + * 获取mete数据 + * + * @return array + */ + public function getMeta() + { + return $this->meta; + } - if (isset($this->meta['fields']) === FALSE || isset($this->meta['languages']) === FALSE) - { + /** + * @throws \Exception + */ + protected function init() + { + $this->fileSize = $this->computeFileSize(); + if ($this->fileSize === false){ + throw new \UnexpectedValueException("Error determining the size of data."); + } + + $metaLength = unpack('N', $this->read(0, 4))[1]; + $text = $this->read(4, $metaLength); + + $this->meta = json_decode($text, true); + if (isset($this->meta['fields']) === false || isset($this->meta['languages']) === false){ throw new \Exception('IP Database metadata error.'); } $fileSize = 4 + $metaLength + $this->meta['total_size']; - if ($fileSize != $this->fileSize) - { + if ($fileSize != $this->fileSize){ throw new \Exception('IP Database size error.'); } @@ -72,69 +128,58 @@ private function init() } /** - * @param $ip + * @param string $ip * @param string $language * @return array|NULL */ public function find($ip, $language) { - if (is_resource($this->file) === FALSE) - { - throw new \BadMethodCallException('IPIP DB closed.'); - } - - if (isset($this->meta['languages'][$language]) === FALSE) - { + if (!$this->supportLanguage($language)){ throw new \InvalidArgumentException("language : {$language} not support."); } - if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6) === FALSE) - { + if (!IpUtils::isIp($ip)){ throw new \InvalidArgumentException("The value \"$ip\" is not a valid IP address."); } - if (strpos($ip, '.') !== FALSE && !$this->supportV4()) - { + if (IpUtils::isIp4($ip) && !$this->supportV4()){ throw new \InvalidArgumentException("The Database not support IPv4 address."); - } - elseif (strpos($ip, ':') !== FALSE && !$this->supportV6()) - { + }// + elseif (IpUtils::isIp6($ip) && !$this->supportV6()){ throw new \InvalidArgumentException("The Database not support IPv6 address."); } - try - { + try{ $node = $this->findNode($ip); - if ($node > 0) - { + if ($node > 0){ $data = $this->resolve($node); - $values = explode("\t", $data); return array_slice($values, $this->meta['languages'][$language], count($this->meta['fields'])); } - } - catch (\Exception $e) - { - return NULL; + }catch(\Exception $e){ } - return NULL; + return null; } + /** + * @param string $ip + * @param string $language + * @return array|false|null + */ public function findMap($ip, $language) { $array = $this->find($ip, $language); - if (NULL === $array) - { - return NULL; + if (null === $array){ + return null; } return array_combine($this->meta['fields'], $array); } - private $v4offset = 0; + private $v4offset = 0; private $v6offsetCache = []; /** @@ -149,63 +194,45 @@ private function findNode($ip) $key = substr($binary, 0, 2); $node = 0; $index = 0; - if ($bitCount === 32) - { - if ($this->v4offset === 0) - { - for ($i = 0; $i < 96 && $node < $this->nodeCount; $i++) - { - if ($i >= 80) - { + if ($bitCount === 32){ + if ($this->v4offset === 0){ + for($i = 0; $i < 96 && $node < $this->nodeCount; $i++){ + if ($i >= 80){ $idx = 1; - } - else - { + }else{ $idx = 0; } $node = $this->readNode($node, $idx); - if ($node > $this->nodeCount) - { + if ($node > $this->nodeCount){ return 0; } } $this->v4offset = $node; - } - else - { + }else{ $node = $this->v4offset; } - } - else - { - if (isset($this->v6offsetCache[$key])) - { + }else{ + if (isset($this->v6offsetCache[$key])){ $index = 16; $node = $this->v6offsetCache[$key]; } } - for ($i = $index; $i < $bitCount; $i++) - { - if ($node >= $this->nodeCount) - { + for($i = $index; $i < $bitCount; $i++){ + if ($node >= $this->nodeCount){ break; } $node = $this->readNode($node, 1 & ((0xFF & ord($binary[$i >> 3])) >> 7 - ($i % 8))); - if ($i == 15) - { + if ($i == 15){ $this->v6offsetCache[$key] = $node; } } - if ($node === $this->nodeCount) - { + if ($node === $this->nodeCount){ return 0; - } - elseif ($node > $this->nodeCount) - { + }elseif ($node > $this->nodeCount){ return $node; } @@ -220,7 +247,7 @@ private function findNode($ip) */ private function readNode($node, $index) { - return unpack('N', $this->read($this->file, $node * 8 + $index * 4, 4))[1]; + return unpack('N', $this->readNodeData(($node * 8 + $index * 4), 4))[1]; } /** @@ -231,73 +258,47 @@ private function readNode($node, $index) private function resolve($node) { $resolved = $node - $this->nodeCount + $this->nodeCount * 8; - if ($resolved >= $this->fileSize) - { - return NULL; + if ($resolved >= $this->fileSize){ + return null; } - $bytes = $this->read($this->file, $resolved, 2); + $bytes = $this->readNodeData($resolved, 2); $size = unpack('N', str_pad($bytes, 4, "\x00", STR_PAD_LEFT))[1]; $resolved += 2; - return $this->read($this->file, $resolved, $size); - } - - public function close() - { - if (is_resource($this->file) === TRUE) - { - fclose($this->file); - } + return $this->readNodeData($resolved, $size); } /** - * @param $stream - * @param $offset - * @param $length - * @return bool|string + * 读取节点数据 + * + * @param integer $offset + * @param integer $length + * @return string * @throws \Exception */ - private function read($stream, $offset, $length) + private function readNodeData($offset, $length) { - if ($length > 0) - { - if (fseek($stream, $offset + $this->nodeOffset) === 0) - { - $value = fread($stream, $length); - if (strlen($value) === $length) - { - return $value; - } - } - - throw new \Exception("The Database file read bad data."); + if (0 >= $length){ + return ''; } - return ''; - } - - public function supportV6() - { - return ($this->meta['ip_version'] & self::IPV6) === self::IPV6; - } - - public function supportV4() - { - return ($this->meta['ip_version'] & self::IPV4) === self::IPV4; - } + $value = $this->read(($offset + $this->nodeOffset), $length); + if (strlen($value) === $length){ + return $value; + } - public function getMeta() - { - return $this->meta; + throw new \Exception("The Database file read bad data."); } /** - * @return int UTC Timestamp + * 回收资源 + * + * @return bool */ - public function getBuildTime() + public function close() { - return $this->meta['build']; + return true; } -} \ No newline at end of file +}