php-useragent/src/IpLocator.php

115 lines
3.6 KiB
PHP
Raw Normal View History

2025-11-02 19:02:01 +01:00
<?php
/**
* (c) 2025 Sakuragasaki46
*
* This is an interface to the IP2Location Lite country IP database, CC BY-SA licensed
* and accessible free of charge at <https://lite.ip2location.com> (registration required).
* IP2Location Lite® is a registered trademark of Hexasoft Development Sdn Bhd.
*/
namespace Yusurko\UserAgent;
use Yusurko\UserAgent\IpAddress;
class IpLocator {
protected $conn, $source_file;
const SOURCE_FILE = '.err/data/IP2LOCATION-LITE-DB1.IPV6.CSV';
const LOCAL_RANGES = [
["0", "1", '@'],
["281470681743360", "281470698520575", '+'],
["281470849515520", "281470866292735", "+"],
["281472812449792", "281472829227007", "@"],
["281473533739008", "281473533804543", "+"],
["281473568473088", "281473569521663", "+"],
["281473913978880", "281473914044415", "+"],
["338288524927261089654018896841347694592", "338288524927261089672465640915057246207", "+"]
];
public function __construct($conn, $source_file = self::SOURCE_FILE) {
$this->conn = $conn;
$this->source_file = $source_file;
}
public function locate($addr){
$addr = new IpAddress($addr);
if (!$this->isDbLoaded()) {
$up_to_range = $this->getDbLoadedUpto();
$this->loadCsvIntoDb($up_to_range);
}
$country = $this->lookupDb($addr);
return new Country($country);
}
private function isDbLoaded() {
$ex = $this->conn->prepare("SELECT 1 FROM af_ip2location WHERE range_end = x'ffffffffffffffffffffffffffffffff'");
$ex->execute();
$res = $ex->get_result();
return ($res->num_rows > 0);
}
private function getDbLoadedUpto(){
$ex = $this->conn->prepare("SELECT hex(range_end) FROM af_ip2location ORDER BY id DESC LIMIT 1");
$ex->execute();
$ex->store_result();
$ex->bind_result($range_end);
$ex->fetch();
return IpAddress::fromHex($range_end);
}
private function loadCsvIntoDb($start = false) {
$f = fopen($this->source_file, 'r');
while (($line = fgetcsv($f)) !== false) {
$rs = new IpAddress($line[0]);
$re = new IpAddress($line[1]);
if ($rs->compare($start) <= 0) continue;
try {
$this->storeRangeIntoDb($rs, $re, $line[2]);
} catch (\mysqli_sql_exception $ee) {
return false;
}
}
return true;
}
private function storeRangeIntoDb($range_start, $range_end, $country) {
$range_start_x = $range_start->toFullHex();
$range_end_x = $range_end->toFullHex();
$ins = $this->conn->prepare('INSERT INTO af_ip2location (range_start, range_end, country) VALUES (unhex(?), unhex(?), ?)');
$ins->bind_param('sss', $range_start_x, $range_end_x, $country);
$ins->execute();
$ins->store_result();
}
private function lookupDb($addr) {
// look up forbidden ranges first
foreach (self::LOCAL_RANGES as $rang) {
[ $rs, $re, $co ] = $rang;
if ($addr->compare(new IpAddress($rs)) >= 0 && $addr->compare(new IpAddress($re)) <= 0) {
return $co;
}
}
$addr_x = $addr->toFullHex();
$st = $this->conn->prepare('SELECT country FROM af_ip2location WHERE range_end >= unhex(?) AND range_start <= unhex(?)');
$st->bind_param('ss', $addr_x, $addr_x);
$st->execute();
$res = $st->get_result();
if ($res->num_rows < 1) {
return false;
}
return $res->fetch_assoc()['country'];
}
}