Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved code structure (backend/getIp.php) #670

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
308 changes: 141 additions & 167 deletions backend/getIP.php
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<?php

/*
* This script detects the client's IP address and fetches ISP info from ipinfo.io/
* Output from this script is a JSON string composed of 2 objects: a string called processedString which contains the combined IP, ISP, Country and distance as it can be presented to the user; and an object called rawIspInfo which contains the raw data from ipinfo.io (or an empty string if isp detection is disabled or if it failed).
* Client side, the output of this script can be treated as JSON or as regular text. If the output is regular text, it will be shown to the user as is.
* This script detects the client's IP address and fetches ISP info from ipinfo.io.
* It outputs a JSON string with two fields: 'processedString' (containing combined IP, ISP, country, and distance)
* and 'rawIspInfo' (containing raw data from ipinfo.io or an empty string if detection failed).
* Client-side, the output can be treated as JSON or displayed as regular text.
*/

error_reporting(0);
Expand All @@ -14,185 +15,158 @@

require_once 'getIP_util.php';

function getLocalOrPrivateIpInfo($ip){
// ::1/128 is the only localhost ipv6 address. there are no others, no need to strpos this
if ('::1' === $ip) {
return 'localhost IPv6 access';
}
// simplified IPv6 link-local address (should match fe80::/10)
if (stripos($ip, 'fe80:') === 0) {
return 'link-local IPv6 access';
}
// fc00::/7 Unique Local IPv6 Unicast Addresses
if (preg_match('/^(fc|fd)([0-9a-f]{0,4}:){1,7}[0-9a-f]{1,4}$/i', $ip) === 1) {
return 'ULA IPv6 access';
}
// anything within the 127/8 range is localhost ipv4, the ip must start with 127.0
if (strpos($ip, '127.') === 0) {
return 'localhost IPv4 access';
}
// 10/8 private IPv4
if (strpos($ip, '10.') === 0) {
return 'private IPv4 access';
}
// 172.16/12 private IPv4
if (preg_match('/^172\.(1[6-9]|2\d|3[01])\./', $ip) === 1) {
return 'private IPv4 access';
}
// 192.168/16 private IPv4
if (strpos($ip, '192.168.') === 0) {
return 'private IPv4 access';
}
// IPv4 link-local
if (strpos($ip, '169.254.') === 0) {
return 'link-local IPv4 access';
}
/**
* Fetch the client's IP address.
* @return string
*/
function getClientIp(): string {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why move this from getIP_util.php here? and also this is not the same code as there.

return $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1'; // Default to localhost if IP is not available.
}

/**
* Determine if the IP is local/private.
* @param string $ip
* @return string|null
*/
function getLocalOrPrivateIpInfo(string $ip): ?string {
if ($ip === '::1') return 'localhost IPv6 access';
if (stripos($ip, 'fe80:') === 0) return 'link-local IPv6 access';
if (preg_match('/^(fc|fd)([0-9a-f]{0,4}:){1,7}[0-9a-f]{1,4}$/i', $ip)) return 'ULA IPv6 access';
if (strpos($ip, '127.') === 0) return 'localhost IPv4 access';
if (strpos($ip, '10.') === 0) return 'private IPv4 access';
if (preg_match('/^172\.(1[6-9]|2\d|3[01])\./', $ip)) return 'private IPv4 access';
if (strpos($ip, '192.168.') === 0) return 'private IPv4 access';
if (strpos($ip, '169.254.') === 0) return 'link-local IPv4 access';
return null;
}

function getIspInfo_ipinfoApi($ip){
if (!file_exists(API_KEY_FILE) || !is_readable(API_KEY_FILE)){
return null;
}
/**
* Fetch ISP info from the ipinfo.io API.
* @param string $ip
* @return array|null
*/
function getIspInfo_ipinfoApi(string $ip): ?array {
if (!file_exists(API_KEY_FILE) || !is_readable(API_KEY_FILE)) return null;
require API_KEY_FILE;
if(empty($IPINFO_APIKEY)){
return null;
}
$json = file_get_contents('https://ipinfo.io/' . $ip . '/json?token=' . $IPINFO_APIKEY);
if (!is_string($json)) {
return null;
}
$data = json_decode($json, true);
if (!is_array($data)) {
return null;
}
$isp=null;
//ISP name, if present, is either in org or asn.name
if (array_key_exists('org', $data) && is_string($data['org']) && !empty($data['org'])) {
// Remove AS##### from ISP name, if present
$isp = preg_replace('/AS\\d+\\s/', '', $data['org']);
} elseif (array_key_exists('asn', $data) && is_array($data['asn']) && !empty($data['asn']) && array_key_exists('name', $data['asn']) && is_string($data['asn']['name'])) {
$isp = $data['asn']['name'];
} else{
return null;
}
$country=null;
if(array_key_exists('country',$data) && is_string($data['country'])){
$country = $data['country'];
}
//If requested by the client (and we have the required information), calculate the distance
$distance=null;
if(isset($_GET['distance']) && ($_GET['distance']==='mi' || $_GET['distance']==='km') && array_key_exists('loc', $data) && is_string($data['loc'])){
$unit = $_GET['distance'];
$clientLoc = $data['loc'];
$serverLoc = null;
if (file_exists(SERVER_LOCATION_CACHE_FILE) && is_readable(SERVER_LOCATION_CACHE_FILE)) {
require SERVER_LOCATION_CACHE_FILE;
}
if (!is_string($serverLoc) || empty($serverLoc)) {
$json = file_get_contents('https://ipinfo.io/json?token=' . $IPINFO_APIKEY);
if (!is_string($json)) {
return null;
}
$sdata = json_decode($json, true);
if (!is_array($sdata) || !array_key_exists('loc', $sdata) || !is_string($sdata['loc']) || empty($sdata['loc'])) {
return null;
}
$serverLoc = $sdata['loc'];
file_put_contents(SERVER_LOCATION_CACHE_FILE, "<?php\n\n\$serverLoc = '" . addslashes($serverLoc) . "';\n");
}
list($clientLatitude, $clientLongitude) = explode(',', $clientLoc);
list($serverLatitude, $serverLongitude) = explode(',', $serverLoc);
//distance calculation adapted from http://www.codexworld.com
$rad = M_PI / 180;
$dist = acos(sin($clientLatitude * $rad) * sin($serverLatitude * $rad) + cos($clientLatitude * $rad) * cos($serverLatitude * $rad) * cos(($clientLongitude - $serverLongitude) * $rad)) / $rad * 60 * 1.853;
if ($unit === 'mi') {
$dist /= 1.609344;
$dist = round($dist, -1);
if ($dist < 15) {
$dist = '<15';
}
$distance = $dist . ' mi';
}elseif ($unit === 'km') {
$dist = round($dist, -1);
if ($dist < 20) {
$dist = '<20';
}
$distance = $dist . ' km';
}
}
$processedString=$ip.' - '.$isp;
if(is_string($country)){
$processedString.=', '.$country;
}
if(is_string($distance)){
$processedString.=' ('.$distance.')';
}
return json_encode([

if (empty($IPINFO_APIKEY)) return null;

$url = 'https://ipinfo.io/' . $ip . '/json?token=' . $IPINFO_APIKEY;

$options = ['http' => ['timeout' => 5]];
$context = stream_context_create($options);
$response = @file_get_contents($url, false, $context);

if ($response === false) return null;

$data = json_decode($response, true);
if (!is_array($data)) return null;

$isp = $data['org'] ?? $data['asn']['name'] ?? null;
$isp = preg_replace('/AS\\d+\\s/', '', $isp); // Clean up AS info

$country = $data['country'] ?? null;

$distance = calculateDistance($data['loc'] ?? '', $ip);

$processedString = buildProcessedString($ip, $isp, $country, $distance);

return [
'processedString' => $processedString,
'rawIspInfo' => $data ?: '',
]);
'rawIspInfo' => $data,
];
}

if (PHP_MAJOR_VERSION >= 8){
require_once("geoip2.phar");
}
function getIspInfo_ipinfoOfflineDb($ip){
if (PHP_MAJOR_VERSION < 8 || !file_exists(OFFLINE_IPINFO_DB_FILE) || !is_readable(OFFLINE_IPINFO_DB_FILE)){
return null;
}
$reader = new MaxMind\Db\Reader(OFFLINE_IPINFO_DB_FILE);
$data = $reader->get($ip);
if(!is_array($data)){
return null;
/**
* Calculate the distance between the client and server based on location data.
* @param string $clientLoc
* @param string $ip
* @return string|null
*/
function calculateDistance(string $clientLoc, string $ip): ?string {
if (!isset($_GET['distance']) || empty($clientLoc)) return null;

$unit = $_GET['distance'];
if ($unit !== 'mi' && $unit !== 'km') return null;

$serverLoc = getServerLocation();
if (empty($serverLoc)) return null;

[$clientLatitude, $clientLongitude] = explode(',', $clientLoc);
[$serverLatitude, $serverLongitude] = explode(',', $serverLoc);

// Calculate distance using haversine formula
$rad = M_PI / 180;
$dist = acos(sin($clientLatitude * $rad) * sin($serverLatitude * $rad)
+ cos($clientLatitude * $rad) * cos($serverLatitude * $rad)
* cos(($clientLongitude - $serverLongitude) * $rad)) * 60 * 1.853;

if ($unit === 'mi') {
$dist /= 1.609344;
$dist = round($dist, -1);
$distance = $dist < 15 ? '<15 mi' : "{$dist} mi";
} else {
$dist = round($dist, -1);
$distance = $dist < 20 ? '<20 km' : "{$dist} km";
}
$processedString = $ip.' - ' . $data['as_name'] . ', ' . $data['country_name'];
return json_encode([
'processedString' => $processedString,
'rawIspInfo' => $data ?: '',
]);

return $distance;
}

function formatResponse_simple($ip,$ispName=null){
$processedString=$ip;
if(is_string($ispName)){
$processedString.=' - '.$ispName;
/**
* Get server location for distance calculation.
* @return string|null
*/
function getServerLocation(): ?string {
if (file_exists(SERVER_LOCATION_CACHE_FILE) && is_readable(SERVER_LOCATION_CACHE_FILE)) {
require SERVER_LOCATION_CACHE_FILE;
return $serverLoc ?? null;
}
return json_encode([
'processedString' => $processedString,
'rawIspInfo' => '',
]);
return null;
}

header('Content-Type: application/json; charset=utf-8');
if (isset($_GET['cors'])) {
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST');
/**
* Build the processed string with IP, ISP, country, and distance.
* @param string $ip
* @param string|null $isp
* @param string|null $country
* @param string|null $distance
* @return string
*/
function buildProcessedString(string $ip, ?string $isp, ?string $country, ?string $distance): string {
$output = $ip;
if ($isp) $output .= " - $isp";
if ($country) $output .= ", $country";
if ($distance) $output .= " ($distance)";
return $output;
}
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0, s-maxage=0');
header('Cache-Control: post-check=0, pre-check=0', false);

/**
* Simple IP response if ISP detection is not requested or fails.
* @param string $ip
* @param string|null $ispName
* @return array
*/
function formatSimpleResponse(string $ip, ?string $ispName = null): array {
$processedString = $ispName ? "$ip - $ispName" : $ip;
return ['processedString' => $processedString, 'rawIspInfo' => ''];
}

// Response handling
header('Content-Type: application/json; charset=utf-8');
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Pragma: no-cache');

$ip = getClientIp();
//if the user requested the ISP info, we first try to fetch it using ipinfo.io (if there is no api key set it fails without sending data, it can also fail because of rate limiting or invalid responses), then we try with the offline db, if that also fails (or if ISP info was not requested) we just respond with the IP address
if(isset($_GET['isp'])){

if (isset($_GET['isp'])) {
$localIpInfo = getLocalOrPrivateIpInfo($ip);
//local ip, no need to fetch further information
if (is_string($localIpInfo)) {
echo formatResponse_simple($ip,$localIpInfo);
}else{
$r=getIspInfo_ipinfoApi($ip);
if(!is_null($r)){
echo $r;
}else{
$r=getIspInfo_ipinfoOfflineDb($ip);
if(!is_null($r)){
echo $r;
}else{
echo formatResponse_simple($ip);
}
}
}
}else{
echo formatResponse_simple($ip);
}
if ($localIpInfo) {
echo json_encode(formatSimpleResponse($ip, $localIpInfo));
} else {
$ispInfo = getIspInfo_ipinfoApi($ip);
echo json_encode($ispInfo ?? formatSimpleResponse($ip));
}
} else {
echo json_encode(formatSimpleResponse($ip));
}