Skip to content

Commit 2e26a8b

Browse files
committed
Added new "provider" field and error handling for invalid BIN database.
1 parent 092a2e5 commit 2e26a8b

File tree

3 files changed

+153
-33
lines changed

3 files changed

+153
-33
lines changed

examples/example.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
require 'vendor/autoload.php';
44

55
// Lookup by local BIN database
6-
$db = new \IP2Proxy\Database('./data/PX10.SAMPLE.BIN', \IP2PROXY\Database::FILE_IO);
6+
$db = new \IP2Proxy\Database('./data/PX11.SAMPLE.BIN', \IP2PROXY\Database::FILE_IO);
77

88
echo 'Module Version: ' . $db->getModuleVersion() . PHP_EOL. PHP_EOL;
99
echo 'Package: PX'. $db->getPackageVersion() . PHP_EOL;
@@ -17,7 +17,7 @@
1717
echo 'Web Service' . PHP_EOL;
1818

1919
// Lookup by Web API
20-
$ws = new \IP2Proxy\WebService('demo', 'PX10', false);
20+
$ws = new \IP2Proxy\WebService('demo', 'PX11', false);
2121

2222
$results = $ws->lookup('1.0.0.8');
2323
print_r($results);

src/Database.php

Lines changed: 151 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class Database
1212
*
1313
* @var string
1414
*/
15-
private const VERSION = '4.0.1';
15+
private const VERSION = '4.1.0';
1616

1717
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1818
// Error field constants ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -23,26 +23,40 @@ class Database
2323
*
2424
* @var string
2525
*/
26-
private const FIELD_NOT_SUPPORTED = 'NOT SUPPORTED';
26+
public const FIELD_NOT_SUPPORTED = 'This parameter is unavailable in selected .BIN data file. Please upgrade data file.';
2727

2828
/**
2929
* Unknown field message.
3030
*
3131
* @var string
3232
*/
33-
private const FIELD_NOT_KNOWN = 'This parameter does not exists. Please verify.';
33+
public const FIELD_NOT_KNOWN = 'This parameter does not exists. Please verify.';
3434

3535
/**
3636
* Invalid IP address message.
3737
*
3838
* @var string
3939
*/
40-
private const INVALID_IP_ADDRESS = 'INVALID IP ADDRESS';
40+
public const INVALID_IP_ADDRESS = 'INVALID IP ADDRESS';
4141

4242
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
4343
// Field selection constants ///////////////////////////////////////////////////////////////////////////////////////////////////////////
4444
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
4545

46+
/**
47+
* Maximum IPv4 number.
48+
*
49+
* @var int
50+
*/
51+
public const MAX_IPV4_RANGE = 4294967295;
52+
53+
/**
54+
* MAximum IPv6 number.
55+
*
56+
* @var int
57+
*/
58+
public const MAX_IPV6_RANGE = 340282366920938463463374607431768211455;
59+
4660
/**
4761
* Country code (ISO 3166-1 Alpha 2).
4862
*
@@ -134,6 +148,13 @@ class Database
134148
*/
135149
public const THREAT = 13;
136150

151+
/**
152+
* Provider.
153+
*
154+
* @var int
155+
*/
156+
public const PROVIDER = 14;
157+
137158
/**
138159
* Country name and code.
139160
*
@@ -243,6 +264,13 @@ class Database
243264
*/
244265
private const EXCEPTION_NO_PATH = 10009;
245266

267+
/**
268+
* Invalid BIN database file.
269+
*
270+
* @var int
271+
*/
272+
public const EXCEPTION_INVALID_BIN_DATABASE = 10010;
273+
246274
/**
247275
* BCMath extension not installed.
248276
*
@@ -306,18 +334,19 @@ class Database
306334
* @var array
307335
*/
308336
private $columns = [
309-
self::COUNTRY_CODE => [8, 12, 12, 12, 12, 12, 12, 12, 12, 12],
310-
self::COUNTRY_NAME => [8, 12, 12, 12, 12, 12, 12, 12, 12, 12],
311-
self::REGION_NAME => [0, 0, 16, 16, 16, 16, 16, 16, 16, 16],
312-
self::CITY_NAME => [0, 0, 20, 20, 20, 20, 20, 20, 20, 20],
313-
self::ISP => [0, 0, 0, 24, 24, 24, 24, 24, 24, 24],
314-
self::PROXY_TYPE => [0, 8, 8, 8, 8, 8, 8, 8, 8, 8],
315-
self::DOMAIN => [0, 0, 0, 0, 28, 28, 28, 28, 28, 28],
316-
self::USAGE_TYPE => [0, 0, 0, 0, 0, 32, 32, 32, 32, 32],
317-
self::ASN => [0, 0, 0, 0, 0, 0, 36, 36, 36, 36],
318-
self::_AS => [0, 0, 0, 0, 0, 0, 40, 40, 40, 40],
319-
self::LAST_SEEN => [0, 0, 0, 0, 0, 0, 0, 44, 44, 44],
320-
self::THREAT => [0, 0, 0, 0, 0, 0, 0, 0, 48, 48],
337+
self::COUNTRY_CODE => [8, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12],
338+
self::COUNTRY_NAME => [8, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12],
339+
self::REGION_NAME => [0, 0, 16, 16, 16, 16, 16, 16, 16, 16, 16],
340+
self::CITY_NAME => [0, 0, 20, 20, 20, 20, 20, 20, 20, 20, 20],
341+
self::ISP => [0, 0, 0, 24, 24, 24, 24, 24, 24, 24, 24],
342+
self::PROXY_TYPE => [0, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8],
343+
self::DOMAIN => [0, 0, 0, 0, 28, 28, 28, 28, 28, 28, 28],
344+
self::USAGE_TYPE => [0, 0, 0, 0, 0, 32, 32, 32, 32, 32, 32],
345+
self::ASN => [0, 0, 0, 0, 0, 0, 36, 36, 36, 36, 36],
346+
self::_AS => [0, 0, 0, 0, 0, 0, 40, 40, 40, 40, 40],
347+
self::LAST_SEEN => [0, 0, 0, 0, 0, 0, 0, 44, 44, 44, 44],
348+
self::THREAT => [0, 0, 0, 0, 0, 0, 0, 0, 48, 48, 48],
349+
self::PROVIDER => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52],
321350
];
322351

323352
/**
@@ -339,6 +368,7 @@ class Database
339368
self::_AS => 'as',
340369
self::LAST_SEEN => 'lastSeen',
341370
self::THREAT => 'threat',
371+
self::PROVIDER => 'provider',
342372
self::IP_ADDRESS => 'ipAddress',
343373
self::IP_VERSION => 'ipVersion',
344374
self::IP_NUMBER => 'ipNumber',
@@ -351,6 +381,7 @@ class Database
351381
*/
352382
private $databases = [
353383
// IPv4 databases
384+
'IP2PROXY-IP-PROXYTYPE-COUNTRY-REGION-CITY-ISP-DOMAIN-USAGETYPE-ASN-LASTSEEN-THREAT-RESIDENTIAL-PROVIDER',
354385
'IP2PROXY-IP-PROXYTYPE-COUNTRY-REGION-CITY-ISP-DOMAIN-USAGETYPE-ASN-LASTSEEN-THREAT-RESIDENTIAL',
355386
'IP2PROXY-IP-PROXYTYPE-COUNTRY-REGION-CITY-ISP-DOMAIN-USAGETYPE-ASN-LASTSEEN-THREAT',
356387
'IP2PROXY-IP-PROXYTYPE-COUNTRY-REGION-CITY-ISP-DOMAIN-USAGETYPE-ASN-LASTSEEN',
@@ -363,6 +394,7 @@ class Database
363394
'IP2PROXY-IP-COUNTRY',
364395

365396
// IPv6 databases
397+
'IPV6-PROXYTYPE-COUNTRY-REGION-CITY-ISP-DOMAIN-USAGETYPE-ASN-LASTSEEN-THREAT-RESIDENTIAL-PROVIDER',
366398
'IPV6-PROXYTYPE-COUNTRY-REGION-CITY-ISP-DOMAIN-USAGETYPE-ASN-LASTSEEN-THREAT-RESIDENTIAL',
367399
'IPV6-PROXYTYPE-COUNTRY-REGION-CITY-ISP-DOMAIN-USAGETYPE-ASN-LASTSEEN-THREAT',
368400
'IPV6-PROXYTYPE-COUNTRY-REGION-CITY-ISP-DOMAIN-USAGETYPE-ASN-LASTSEEN',
@@ -466,6 +498,27 @@ class Database
466498
private $month;
467499
private $day;
468500

501+
/**
502+
* Product code.
503+
*
504+
* @var string
505+
*/
506+
private $productCode;
507+
508+
/**
509+
* License code.
510+
*
511+
* @var string
512+
*/
513+
private $licenseCode;
514+
515+
/**
516+
* Database size.
517+
*
518+
* @var int
519+
*/
520+
private $databaseSize;
521+
469522
// This variable will be used to hold the raw row of columns's positions
470523
private $rawPositionsRow;
471524

@@ -587,12 +640,23 @@ public function __construct($file = null, $mode = self::FILE_IO)
587640
$this->month = $this->readByte(4);
588641
$this->day = $this->readByte(5);
589642
$this->date = date('Y-m-d', strtotime("{$this->year}-{$this->month}-{$this->day}"));
643+
$this->productCode = $this->readByte(30);
644+
$this->licenseCode = $this->readByte(31);
645+
$this->databaseSize = $this->readByte(32);
590646
$this->ipCount[4] = $this->readWord(6);
591647
$this->ipBase[4] = $this->readWord(10);
592648
$this->ipCount[6] = $this->readWord(14);
593649
$this->ipBase[6] = $this->readWord(18);
594650
$this->indexBaseAddr[4] = $this->readWord(22);
595651
$this->indexBaseAddr[6] = $this->readWord(26);
652+
653+
if ($this->productCode == 2) {
654+
} else {
655+
if ($this->year <= 20 && $this->productCode == 0) {
656+
} else {
657+
throw new \Exception(__CLASS__ . ': Incorrect IP2Proxy BIN file format. Please make sure that you are using the latest IP2Proxy BIN file.', self::EXCEPTION_INVALID_BIN_DATABASE);
658+
}
659+
}
596660
}
597661

598662
/**
@@ -705,6 +769,7 @@ public function lookup($ip, $fields = null, $asNamed = true)
705769
$ifields[] = self::_AS;
706770
$ifields[] = self::LAST_SEEN;
707771
$ifields[] = self::THREAT;
772+
$ifields[] = self::PROVIDER;
708773
$ifields[] = self::COUNTRY;
709774
$ifields[] = self::IP_ADDRESS;
710775
$ifields[] = self::IP_VERSION;
@@ -728,6 +793,7 @@ public function lookup($ip, $fields = null, $asNamed = true)
728793
self::_AS => false,
729794
self::LAST_SEEN => false,
730795
self::THREAT => false,
796+
self::PROVIDER => false,
731797
self::COUNTRY => false,
732798
self::IP_ADDRESS => false,
733799
self::IP_VERSION => false,
@@ -859,6 +925,13 @@ public function lookup($ip, $fields = null, $asNamed = true)
859925
}
860926
break;
861927

928+
case self::PROVIDER:
929+
if (!$done[self::PROVIDER]) {
930+
$results[self::PROVIDER] = $this->readProvider($pointer);
931+
$done[self::PROVIDER] = true;
932+
}
933+
break;
934+
862935
case self::IP_ADDRESS:
863936
if (!$done[self::IP_ADDRESS]) {
864937
$results[self::IP_ADDRESS] = $ip;
@@ -1137,39 +1210,43 @@ private function ipBetween($version, $ip, $low, $high)
11371210
*
11381211
* @return array
11391212
*/
1140-
private function ipVersionAndNumber($ip)
1213+
private static function ipVersionAndNumber($ip)
11411214
{
11421215
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
1143-
return [4, sprintf('%u', ip2long($ip))];
1216+
$number = sprintf('%u', ip2long($ip));
1217+
1218+
return [4, ($number == self::MAX_IPV4_RANGE) ? ($number - 1) : $number];
11441219
} elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
1145-
// Expand IPv6 address
1146-
$ip = implode(':', str_split(unpack('H*0', inet_pton($ip))[0], 4));
1220+
$result = 0;
1221+
$ip = self::expand($ip);
11471222

11481223
// 6to4 Address - 2002::/16
11491224
if (substr($ip, 0, 4) == '2002') {
1150-
return [4, (int) (gmp_import(inet_pton($ip)) >> 80) & 4294967295];
1225+
foreach (str_split(bin2hex(inet_pton($ip)), 8) as $word) {
1226+
$result = bcadd(bcmul($result, '4294967296', 0), self::wrap32(hexdec($word)), 0);
1227+
}
1228+
1229+
return [4, bcmod(bcdiv($result, bcpow(2, 80)), '4294967296')];
11511230
}
11521231

11531232
// Teredo Address - 2001:0::/32
11541233
if (substr($ip, 0, 9) == '2001:0000') {
1155-
return [4, (~hexdec(str_replace(':', '', substr($ip, -9))) & 4294967295)];
1234+
return [4, hexdec(substr($ip, 10, 4) . substr($ip, 15, 4))];
11561235
}
11571236

1158-
// IPv4 Address
1159-
if (substr($ip, 0, 9) == '0000:0000') {
1160-
return [4, hexdec(substr($ip, -9))];
1161-
}
1162-
1163-
// Common IPv6 Address
1164-
$result = 0;
1165-
11661237
foreach (str_split(bin2hex(inet_pton($ip)), 8) as $word) {
11671238
$result = bcadd(bcmul($result, '4294967296', 0), self::wrap32(hexdec($word)), 0);
11681239
}
11691240

1241+
// IPv4 address in IPv6
1242+
if (bccomp($result, '281470681743360') >= 0 && bccomp($result, '281474976710655') <= 0) {
1243+
return [4, bcsub($result, '281470681743360')];
1244+
}
1245+
11701246
return [6, $result];
11711247
}
1172-
// Invalid IP address, return falses
1248+
1249+
// Invalid IP address, return false
11731250
return [false, false];
11741251
}
11751252

@@ -1182,6 +1259,10 @@ private function ipVersionAndNumber($ip)
11821259
*/
11831260
private function bcBin2Dec($data)
11841261
{
1262+
if (!$data) {
1263+
return;
1264+
}
1265+
11851266
$parts = [
11861267
unpack('V', substr($data, 12, 4)),
11871268
unpack('V', substr($data, 8, 4)),
@@ -1200,6 +1281,22 @@ private function bcBin2Dec($data)
12001281
return $result;
12011282
}
12021283

1284+
/**
1285+
* Return the decimal string representing the binary data given.
1286+
*
1287+
* @static
1288+
*
1289+
* @param mixed $ipv6
1290+
*
1291+
* @return string
1292+
*/
1293+
private static function expand($ipv6)
1294+
{
1295+
$hex = unpack('H*hex', inet_pton($ipv6));
1296+
1297+
return substr(preg_replace('/([A-f0-9]{4})/', '$1:', $hex['hex']), 0, -1);
1298+
}
1299+
12031300
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
12041301
// Caching backend abstraction /////////////////////////////////////////////////////////////////////////////////////////////////////////
12051302
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -1563,6 +1660,29 @@ private function readThreat($pointer)
15631660
return $threat;
15641661
}
15651662

1663+
/**
1664+
* High level function to fetch the Provider.
1665+
*
1666+
* @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS
1667+
*
1668+
* @return string
1669+
*/
1670+
private function readProvider($pointer)
1671+
{
1672+
if ($pointer === false) {
1673+
// Deal with invalid IPs
1674+
$provider = self::INVALID_IP_ADDRESS;
1675+
} elseif ($this->columns[self::PROVIDER][$this->type] === 0) {
1676+
// If the field is not suported, return accordingly
1677+
$provider = self::FIELD_NOT_SUPPORTED;
1678+
} else {
1679+
// Read the domain
1680+
$provider = $this->readString($this->columns[self::PROVIDER][$this->type]);
1681+
}
1682+
1683+
return $provider;
1684+
}
1685+
15661686
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
15671687
// Binary search and support functions /////////////////////////////////////////////////////////////////////////////////////////////////
15681688
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

0 commit comments

Comments
 (0)