Skip to content

Commit 18a101f

Browse files
committed
add
1 parent 03e8113 commit 18a101f

File tree

2 files changed

+352
-0
lines changed

2 files changed

+352
-0
lines changed

src/ipip/db/Info.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
namespace ipip\db;
4+
5+
class Info
6+
{
7+
public $country_name = '';
8+
public $region_name = '';
9+
public $city_name = '';
10+
public $owner_domain = '';
11+
public $isp_domain = '';
12+
public $latitude = '';
13+
public $longitude = '';
14+
public $timezone = '';
15+
public $utc_offset = '';
16+
public $china_admin_code = '';
17+
public $idd_code = '';
18+
public $country_code = '';
19+
public $continent_code = '';
20+
public $idc = '';
21+
public $base_station = '';
22+
public $country_code3 = '';
23+
public $european_union = '';
24+
public $currency_code = '';
25+
public $currency_name = '';
26+
public $anycast = '';
27+
28+
public function __construct(array $data)
29+
{
30+
foreach ($data AS $field => $value)
31+
{
32+
$this->{$field} = $value;
33+
}
34+
}
35+
36+
public function __get($name)
37+
{
38+
return $this->{$name};
39+
}
40+
}

src/ipip/db/Reader.php

Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
<?php
2+
3+
namespace ipip\db;
4+
5+
class Reader
6+
{
7+
const IPV4 = 1;
8+
const IPV6 = 2;
9+
10+
private $file = NULL;
11+
private $fileSize = 0;
12+
private $nodeCount = 0;
13+
private $nodeOffset = 0;
14+
15+
private $meta = [];
16+
17+
private $database = '';
18+
19+
/**
20+
* Reader constructor.
21+
* @param $database
22+
* @throws \Exception
23+
*/
24+
public function __construct($database)
25+
{
26+
$this->database = $database;
27+
28+
$this->init();
29+
}
30+
31+
private function init()
32+
{
33+
if (is_readable($this->database) === FALSE)
34+
{
35+
throw new \InvalidArgumentException("The IP Database file \"{$this->database}\" does not exist or is not readable.");
36+
}
37+
$this->file = @fopen($this->database, 'rb');
38+
if ($this->file === FALSE)
39+
{
40+
throw new \InvalidArgumentException("IP Database File opening \"{$this->database}\".");
41+
}
42+
$this->fileSize = @filesize($this->database);
43+
if ($this->fileSize === FALSE)
44+
{
45+
throw new \UnexpectedValueException("Error determining the size of \"{$this->database}\".");
46+
}
47+
48+
$metaLength = unpack('N', fread($this->file, 4))[1];
49+
$text = fread($this->file, $metaLength);
50+
51+
$this->meta = json_decode($text, 1);
52+
if (!isset($this->meta['fields']) || !isset($this->meta['languages']))
53+
{
54+
throw new \Exception('IP Database metadata error.');
55+
}
56+
57+
$fileSize = 4 + $metaLength + $this->meta['total_size'];
58+
if ($fileSize != $this->fileSize)
59+
{
60+
throw new \Exception('IP Database size error.');
61+
}
62+
63+
$this->nodeCount = $this->meta['node_count'];
64+
$this->nodeOffset = 4 + $metaLength;
65+
}
66+
67+
/**
68+
* @param $ip
69+
* @param string $language
70+
* @return array|NULL
71+
*/
72+
public function find($ip, $language = 'CN')
73+
{
74+
if (is_resource($this->file) === FALSE)
75+
{
76+
throw new \BadMethodCallException('closed IPIP DB.');
77+
}
78+
79+
if (!isset($this->meta['languages'][$language]))
80+
{
81+
throw new \InvalidArgumentException("language : {$language} not support");
82+
}
83+
84+
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6) === FALSE)
85+
{
86+
throw new \InvalidArgumentException("The value \"$ip\" is not a valid IP address.");
87+
}
88+
89+
if (strpos($ip, '.') !== FALSE)
90+
{
91+
if (!$this->supportV4())
92+
{
93+
94+
}
95+
}
96+
elseif (strpos($ip, ':') !== FALSE)
97+
{
98+
if (!$this->supportV6())
99+
{
100+
101+
}
102+
}
103+
104+
try
105+
{
106+
$node = $this->findNode($ip);
107+
if ($node > 0)
108+
{
109+
$data = $this->resolve($node);
110+
111+
$values = explode("\t", $data);
112+
113+
return array_slice($values, $this->meta['languages'][$language], count($this->meta['fields']));
114+
}
115+
}
116+
catch (\Exception $e)
117+
{
118+
return NULL;
119+
}
120+
121+
return NULL;
122+
}
123+
124+
public function findMap($ip, $language = 'CN')
125+
{
126+
$array = $this->find($ip, $language);
127+
if (NULL == $array)
128+
{
129+
return NULL;
130+
}
131+
132+
return array_combine($this->meta['fields'], $array);
133+
}
134+
135+
public function findInfo($ip, $language = 'CN')
136+
{
137+
$map = $this->findMap($ip, $language);
138+
if (NULL == $map)
139+
{
140+
return NULL;
141+
}
142+
143+
return new Info($map);
144+
}
145+
146+
/**
147+
* @param $ip
148+
* @return int
149+
* @throws \Exception
150+
*/
151+
private function findNode($ip)
152+
{
153+
static $v4offset = 0;
154+
static $v6offsetCache = [];
155+
156+
$binary = inet_pton($ip);
157+
$bitCount = strlen($binary) * 8; // 32 | 128
158+
$key = substr($binary, 0, 2);
159+
$node = 0;
160+
$index = 0;
161+
if ($bitCount === 32)
162+
{
163+
if ($v4offset === 0)
164+
{
165+
for ($i = 0; $i < 96 && $node < $this->nodeCount; $i++)
166+
{
167+
if ($i >= 80)
168+
{
169+
$idx = 1;
170+
}
171+
else
172+
{
173+
$idx = 0;
174+
}
175+
$node = $this->readNode($node, $idx);
176+
if ($node > $this->nodeCount)
177+
{
178+
return 0;
179+
}
180+
}
181+
$v4offset = $node;
182+
}
183+
else
184+
{
185+
$node = $v4offset;
186+
}
187+
}
188+
else
189+
{
190+
if (isset($v6offsetCache[$key]))
191+
{
192+
$index = 16;
193+
$node = $v6offsetCache[$key];
194+
}
195+
}
196+
197+
for ($i = $index; $i < $bitCount; $i++)
198+
{
199+
if ($node >= $this->nodeCount)
200+
{
201+
break;
202+
}
203+
204+
$node = $this->readNode($node, 1 & ((0xFF & ord($binary[$i >> 3])) >> 7 - ($i % 8)));
205+
206+
if ($i == 15)
207+
{
208+
$v6offsetCache[$key] = $node;
209+
}
210+
}
211+
212+
if ($node === $this->nodeCount)
213+
{
214+
return 0;
215+
}
216+
elseif ($node > $this->nodeCount)
217+
{
218+
return $node;
219+
}
220+
221+
throw new \Exception("find node failed");
222+
}
223+
224+
/**
225+
* @param $node
226+
* @param $index
227+
* @return mixed
228+
* @throws \Exception
229+
*/
230+
private function readNode($node, $index)
231+
{
232+
return unpack('N', $this->read($this->file, $node * 8 + $index * 4, 4))[1];
233+
}
234+
235+
/**
236+
* @param $node
237+
* @return mixed
238+
* @throws \Exception
239+
*/
240+
private function resolve($node)
241+
{
242+
$resolved = $node - $this->nodeCount + $this->nodeCount * 8;
243+
if ($resolved >= $this->fileSize)
244+
{
245+
return NULL;
246+
}
247+
248+
$bytes = $this->read($this->file, $resolved, 2);
249+
$size = unpack('N', str_pad($bytes, 4, "\x00", STR_PAD_LEFT))[1];
250+
251+
$resolved += 2;
252+
253+
return $this->read($this->file, $resolved, $size);
254+
}
255+
256+
public function close()
257+
{
258+
if (is_resource($this->file) === TRUE)
259+
{
260+
fclose($this->file);
261+
}
262+
}
263+
264+
/**
265+
* @param $stream
266+
* @param $offset
267+
* @param $length
268+
* @return bool|string
269+
* @throws \Exception
270+
*/
271+
private function read($stream, $offset, $length)
272+
{
273+
if ($length > 0)
274+
{
275+
if (fseek($stream, $offset + $this->nodeOffset) === 0)
276+
{
277+
$value = fread($stream, $length);
278+
if (strlen($value) === $length)
279+
{
280+
return $value;
281+
}
282+
}
283+
284+
throw new \Exception("The DB file read bad data");
285+
}
286+
287+
return '';
288+
}
289+
290+
public function supportV6()
291+
{
292+
return ($this->meta['ip_version'] & self::IPV6) === self::IPV6;
293+
}
294+
295+
public function supportV4()
296+
{
297+
return ($this->meta['ip_version'] & self::IPV4) === self::IPV4;
298+
}
299+
300+
public function getMeta()
301+
{
302+
return $this->meta;
303+
}
304+
305+
/**
306+
* @return int UTC Timestamp
307+
*/
308+
public function getBuildTime()
309+
{
310+
return $this->meta['build'];
311+
}
312+
}

0 commit comments

Comments
 (0)