1
0

getIP.php 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. <?php
  2. /*
  3. * This script detects the client's IP address and fetches ISP info from ipinfo.io/
  4. * 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 (will be empty if isp detection is disabled).
  5. * 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.
  6. */
  7. error_reporting(0);
  8. define('API_KEY_FILE', 'getIP_ipInfo_apikey.php');
  9. define('SERVER_LOCATION_CACHE_FILE', 'getIP_serverLocation.php');
  10. require_once 'getIP_util.php';
  11. /**
  12. * @param string $ip
  13. *
  14. * @return string|null
  15. */
  16. function getLocalOrPrivateIpInfo($ip)
  17. {
  18. // ::1/128 is the only localhost ipv6 address. there are no others, no need to strpos this
  19. if ('::1' === $ip) {
  20. return 'localhost IPv6 access';
  21. }
  22. // simplified IPv6 link-local address (should match fe80::/10)
  23. if (stripos($ip, 'fe80:') === 0) {
  24. return 'link-local IPv6 access';
  25. }
  26. // fc00::/7 Unique Local IPv6 Unicast Addresses
  27. if (preg_match('/^(fc|fd)([0-9a-f]{0,4}:){1,7}[0-9a-f]{1,4}$/i', $ip) === 1) {
  28. return 'ULA IPv6 access';
  29. }
  30. // anything within the 127/8 range is localhost ipv4, the ip must start with 127.0
  31. if (strpos($ip, '127.') === 0) {
  32. return 'localhost IPv4 access';
  33. }
  34. // 10/8 private IPv4
  35. if (strpos($ip, '10.') === 0) {
  36. return 'private IPv4 access';
  37. }
  38. // 172.16/12 private IPv4
  39. if (preg_match('/^172\.(1[6-9]|2\d|3[01])\./', $ip) === 1) {
  40. return 'private IPv4 access';
  41. }
  42. // 192.168/16 private IPv4
  43. if (strpos($ip, '192.168.') === 0) {
  44. return 'private IPv4 access';
  45. }
  46. // IPv4 link-local
  47. if (strpos($ip, '169.254.') === 0) {
  48. return 'link-local IPv4 access';
  49. }
  50. return null;
  51. }
  52. /**
  53. * @return string
  54. */
  55. function getIpInfoTokenString()
  56. {
  57. if (
  58. !file_exists(API_KEY_FILE)
  59. || !is_readable(API_KEY_FILE)
  60. ) {
  61. return '';
  62. }
  63. require API_KEY_FILE;
  64. if (empty($IPINFO_APIKEY)) {
  65. return '';
  66. }
  67. return '?token=' . $IPINFO_APIKEY;
  68. }
  69. /**
  70. * @param string $ip
  71. *
  72. * @return array|null
  73. */
  74. function getIspInfo($ip)
  75. {
  76. $json = file_get_contents('https://ipinfo.io/' . $ip . '/json' . getIpInfoTokenString());
  77. if (!is_string($json)) {
  78. return null;
  79. }
  80. $data = json_decode($json, true);
  81. if (!is_array($data)) {
  82. return null;
  83. }
  84. return $data;
  85. }
  86. /**
  87. * @param array|null $rawIspInfo
  88. *
  89. * @return string
  90. */
  91. function getIsp($rawIspInfo)
  92. {
  93. if (is_array($rawIspInfo)) {
  94. /* variant with no token
  95. has json like:
  96. {
  97. "ip": "xxx.xxx.xxx.xxx",
  98. "hostname": "example.com",
  99. "city": "Vienna",
  100. "region": "Vienna",
  101. "country": "AT",
  102. "loc": "48.2085,16.3721",
  103. "org": "ASxxxx T-Mobile Austria GmbH",
  104. "postal": "nnnn",
  105. "timezone": "Europe/Vienna",
  106. "readme": "https://ipinfo.io/missingauth"
  107. }
  108. */
  109. if (
  110. array_key_exists('org', $rawIspInfo)
  111. && is_string($rawIspInfo['org'])
  112. && !empty($rawIspInfo['org'])
  113. ) {
  114. // Remove AS##### from ISP name, if present
  115. return preg_replace('/AS\\d+\\s/', '', $rawIspInfo['org']);
  116. }
  117. /*
  118. variant with valid token has json:
  119. {
  120. "ip": "xxx.xxx.xxx.xxx",
  121. "hostname": "example.com",
  122. "city": "Vienna",
  123. "region": "Vienna",
  124. "country": "AT",
  125. "loc": "48.2085,16.3721",
  126. "postal": "1010",
  127. "timezone": "Europe/Vienna",
  128. "asn": {
  129. "asn": "ASxxxx",
  130. "name": "T-Mobile Austria GmbH",
  131. "domain": "t-mobile.at",
  132. "route": "xxx.xxx.xxx.xxx/xx",
  133. "type": "isp"
  134. },
  135. "company": {
  136. "name": "XX",
  137. "domain": "example.com",
  138. "type": "isp"
  139. },
  140. "privacy": {
  141. "vpn": true,
  142. "proxy": false,
  143. "tor": false,
  144. "relay": false,
  145. "hosting": false,
  146. "service": ""
  147. },
  148. "abuse": {
  149. "address": "...",
  150. "country": "AT",
  151. "email": "[email protected]",
  152. "name": "XXX",
  153. "network": "xxx.xxx.xxx.xxx-xxx.xxx.xxx.xxx",
  154. "phone": ""
  155. },
  156. "domains": {
  157. "total": 0,
  158. "domains": [
  159. ]
  160. }
  161. }
  162. */
  163. if (
  164. array_key_exists('asn', $rawIspInfo)
  165. && is_array($rawIspInfo['asn'])
  166. && !empty($rawIspInfo['asn'])
  167. && array_key_exists('name', $rawIspInfo['asn'])
  168. && is_string($rawIspInfo['asn']['name'])
  169. ) {
  170. // Remove AS##### from ISP name, if present
  171. return $rawIspInfo['asn']['name'];
  172. }
  173. }
  174. return 'Unknown ISP';
  175. }
  176. /**
  177. * @return string|null
  178. */
  179. function getServerLocation()
  180. {
  181. $serverLoc = null;
  182. if (
  183. file_exists(SERVER_LOCATION_CACHE_FILE)
  184. && is_readable(SERVER_LOCATION_CACHE_FILE)
  185. ) {
  186. require SERVER_LOCATION_CACHE_FILE;
  187. }
  188. if (is_string($serverLoc) && !empty($serverLoc)) {
  189. return $serverLoc;
  190. }
  191. $json = file_get_contents('https://ipinfo.io/json' . getIpInfoTokenString());
  192. if (!is_string($json)) {
  193. return null;
  194. }
  195. $details = json_decode($json, true);
  196. if (
  197. !is_array($details)
  198. || !array_key_exists('loc', $details)
  199. || !is_string($details['loc'])
  200. || empty($details['loc'])
  201. ) {
  202. return null;
  203. }
  204. $serverLoc = $details['loc'];
  205. $cacheData = "<?php\n\n\$serverLoc = '" . addslashes($serverLoc) . "';\n";
  206. file_put_contents(SERVER_LOCATION_CACHE_FILE, $cacheData);
  207. return $serverLoc;
  208. }
  209. /**
  210. * Optimized algorithm from http://www.codexworld.com
  211. *
  212. * @param float $latitudeFrom
  213. * @param float $longitudeFrom
  214. * @param float $latitudeTo
  215. * @param float $longitudeTo
  216. *
  217. * @return float [km]
  218. */
  219. function distance(
  220. $latitudeFrom,
  221. $longitudeFrom,
  222. $latitudeTo,
  223. $longitudeTo
  224. ) {
  225. $rad = M_PI / 180;
  226. $theta = $longitudeFrom - $longitudeTo;
  227. $dist = sin($latitudeFrom * $rad)
  228. * sin($latitudeTo * $rad)
  229. + cos($latitudeFrom * $rad)
  230. * cos($latitudeTo * $rad)
  231. * cos($theta * $rad);
  232. return acos($dist) / $rad * 60 * 1.853;
  233. }
  234. /**
  235. * @param array|null $rawIspInfo
  236. *
  237. * @return string|null
  238. */
  239. function getDistance($rawIspInfo)
  240. {
  241. if (
  242. !is_array($rawIspInfo)
  243. || !array_key_exists('loc', $rawIspInfo)
  244. || !isset($_GET['distance'])
  245. || !in_array($_GET['distance'], ['mi', 'km'], true)
  246. ) {
  247. return null;
  248. }
  249. $unit = $_GET['distance'];
  250. $clientLocation = $rawIspInfo['loc'];
  251. $serverLocation = getServerLocation();
  252. if (!is_string($serverLocation)) {
  253. return null;
  254. }
  255. return calculateDistance(
  256. $serverLocation,
  257. $clientLocation,
  258. $unit
  259. );
  260. }
  261. /**
  262. * @param string $clientLocation
  263. * @param string $serverLocation
  264. * @param string $unit
  265. *
  266. * @return string
  267. */
  268. function calculateDistance($clientLocation, $serverLocation, $unit)
  269. {
  270. list($clientLatitude, $clientLongitude) = explode(',', $clientLocation);
  271. list($serverLatitude, $serverLongitude) = explode(',', $serverLocation);
  272. $dist = distance(
  273. $clientLatitude,
  274. $clientLongitude,
  275. $serverLatitude,
  276. $serverLongitude
  277. );
  278. if ('mi' === $unit) {
  279. $dist /= 1.609344;
  280. $dist = round($dist, -1);
  281. if ($dist < 15) {
  282. $dist = '<15';
  283. }
  284. return $dist . ' mi';
  285. }
  286. if ('km' === $unit) {
  287. $dist = round($dist, -1);
  288. if ($dist < 20) {
  289. $dist = '<20';
  290. }
  291. return $dist . ' km';
  292. }
  293. return null;
  294. }
  295. /**
  296. * @return void
  297. */
  298. function sendHeaders()
  299. {
  300. header('Content-Type: application/json; charset=utf-8');
  301. if (isset($_GET['cors'])) {
  302. header('Access-Control-Allow-Origin: *');
  303. header('Access-Control-Allow-Methods: GET, POST');
  304. }
  305. header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0, s-maxage=0');
  306. header('Cache-Control: post-check=0, pre-check=0', false);
  307. header('Pragma: no-cache');
  308. }
  309. /**
  310. * @param string $ip
  311. * @param string|null $ipInfo
  312. * @param string|null $distance
  313. * @param array|null $rawIspInfo
  314. *
  315. * @return void
  316. */
  317. function sendResponse(
  318. $ip,
  319. $ipInfo = null,
  320. $distance = null,
  321. $rawIspInfo = null
  322. ) {
  323. $processedString = $ip;
  324. if (is_string($ipInfo)) {
  325. $processedString .= ' - ' . $ipInfo;
  326. }
  327. if (
  328. is_array($rawIspInfo)
  329. && array_key_exists('country', $rawIspInfo)
  330. ) {
  331. $processedString .= ', ' . $rawIspInfo['country'];
  332. }
  333. if (is_string($distance)) {
  334. $processedString .= ' (' . $distance . ')';
  335. }
  336. sendHeaders();
  337. echo json_encode([
  338. 'processedString' => $processedString,
  339. 'rawIspInfo' => $rawIspInfo ?: '',
  340. ]);
  341. }
  342. $ip = getClientIp();
  343. $localIpInfo = getLocalOrPrivateIpInfo($ip);
  344. // local ip, no need to fetch further information
  345. if (is_string($localIpInfo)) {
  346. sendResponse($ip, $localIpInfo);
  347. exit;
  348. }
  349. if (!isset($_GET['isp'])) {
  350. sendResponse($ip);
  351. exit;
  352. }
  353. $rawIspInfo = getIspInfo($ip);
  354. $isp = getIsp($rawIspInfo);
  355. $distance = getDistance($rawIspInfo);
  356. sendResponse($ip, $isp, $distance, $rawIspInfo);