IPQuery.php 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. <?php
  2. /**
  3. * 纯真 IP 数据库查询
  4. *
  5. * 参考资料:
  6. * - 纯真 IP 数据库 http://www.cz88.net/ip/
  7. * 使用示例:
  8. * $ip = new IPQuery();
  9. * $addr = $ip->query('IP地址');
  10. * print_r($addr);
  11. */
  12. class IPQuery {
  13. private $fh; // IP数据库文件句柄
  14. private $first; // 第一条索引
  15. private $last; // 最后一条索引
  16. private $total; // 索引总数
  17. private $dbFile = __DIR__ . DIRECTORY_SEPARATOR . 'qqwry.dat'; // 纯真 IP 数据库文件存放路径
  18. private $dbExpires = 86400 * 10; // 数据库文件有效期(10天)如无需自动更新 IP 数据库,请将此值改为 0
  19. // 构造函数
  20. function __construct() {
  21. // IP 数据库文件不存在或已过期,则自动获取
  22. if (!file_exists($this->dbFile) || ($this->dbExpires && ((time() - filemtime($this->dbFile)) > $this->dbExpires))) {
  23. $this->update();
  24. }
  25. }
  26. // 忽略超时
  27. private function ignore_timeout() {
  28. @ignore_user_abort(true);
  29. @ini_set('max_execution_time', 48 * 60 * 60);
  30. @set_time_limit(48 * 60 * 60); // set_time_limit(0) 2day
  31. @ini_set('memory_limit', '4000M'); // 4G;
  32. }
  33. // 读取little-endian编码的4个字节转化为长整型数
  34. private function getLong4() {
  35. $result = unpack('Vlong', fread($this->fh, 4));
  36. return $result['long'];
  37. }
  38. // 读取little-endian编码的3个字节转化为长整型数
  39. private function getLong3() {
  40. $result = unpack('Vlong', fread($this->fh, 3) . chr(0));
  41. return $result['long'];
  42. }
  43. // 查询位置信息
  44. private function getPos($data = '') {
  45. $char = fread($this->fh, 1);
  46. while (ord($char) != 0) { // 地区信息以 0 结束
  47. $data .= $char;
  48. $char = fread($this->fh, 1);
  49. }
  50. return $data;
  51. }
  52. // 查询运营商
  53. private function getISP() {
  54. $byte = fread($this->fh, 1); // 标志字节
  55. switch (ord($byte)) {
  56. case 0: $area = '';
  57. break; // 没有相关信息
  58. case 1: // 被重定向
  59. fseek($this->fh, $this->getLong3());
  60. $area = $this->getPos();
  61. break;
  62. case 2: // 被重定向
  63. fseek($this->fh, $this->getLong3());
  64. $area = $this->getPos();
  65. break;
  66. default: $area = $this->getPos($byte);
  67. break; // 没有被重定向
  68. }
  69. return $area;
  70. }
  71. // 检查 IP 格式是否正确
  72. public function checkIp($ip) {
  73. $arr = explode('.', $ip);
  74. if (count($arr) != 4)
  75. return false;
  76. for ($i = 0; $i < 4; $i++) {
  77. if ($arr[$i] < '0' || $arr[$i] > '255') {
  78. return false;
  79. }
  80. }
  81. return true;
  82. }
  83. // 查询 IP 地址
  84. public function query($ip) {
  85. if (!$this->checkIp($ip)) {
  86. return false;
  87. }
  88. $this->fh = fopen($this->dbFile, 'rb');
  89. $this->first = $this->getLong4();
  90. $this->last = $this->getLong4();
  91. $this->total = ($this->last - $this->first) / 7; // 每条索引7字节
  92. $ip = pack('N', intval(ip2long($ip)));
  93. // 二分查找 IP 位置
  94. $l = 0;
  95. $r = $this->total;
  96. while ($l <= $r) {
  97. $m = floor(($l + $r) / 2); // 计算中间索引
  98. fseek($this->fh, $this->first + $m * 7);
  99. $beginip = strrev(fread($this->fh, 4)); // 中间索引的开始IP地址
  100. fseek($this->fh, $this->getLong3());
  101. $endip = strrev(fread($this->fh, 4)); // 中间索引的结束IP地址
  102. if ($ip < $beginip) { // 用户的IP小于中间索引的开始IP地址时
  103. $r = $m - 1;
  104. } else {
  105. if ($ip > $endip) { // 用户的IP大于中间索引的结束IP地址时
  106. $l = $m + 1;
  107. } else { // 用户IP在中间索引的IP范围内时
  108. $findip = $this->first + $m * 7;
  109. break;
  110. }
  111. }
  112. }
  113. // 查找 IP 地址段
  114. fseek($this->fh, $findip);
  115. $location['beginip'] = long2ip($this->getLong4()); // 用户IP所在范围的开始地址
  116. $offset = $this->getlong3();
  117. fseek($this->fh, $offset);
  118. $location['endip'] = long2ip($this->getLong4()); // 用户IP所在范围的结束地址
  119. // 查找 IP 信息
  120. $byte = fread($this->fh, 1); // 标志字节
  121. switch (ord($byte)) {
  122. case 1: // 都被重定向
  123. $countryOffset = $this->getLong3(); // 重定向地址
  124. fseek($this->fh, $countryOffset);
  125. $byte = fread($this->fh, 1); // 标志字节
  126. switch (ord($byte)) {
  127. case 2: // 信息被二次重定向
  128. fseek($this->fh, $this->getLong3());
  129. $location['pos'] = $this->getPos();
  130. fseek($this->fh, $countryOffset + 4);
  131. $location['isp'] = $this->getISP();
  132. break;
  133. default: // 信息没有被二次重定向
  134. $location['pos'] = $this->getPos($byte);
  135. $location['isp'] = $this->getISP();
  136. break;
  137. }
  138. break;
  139. case 2: // 信息被重定向
  140. fseek($this->fh, $this->getLong3());
  141. $location['pos'] = $this->getPos();
  142. fseek($this->fh, $offset + 8);
  143. $location['isp'] = $this->getISP();
  144. break;
  145. default: // 信息没有被重定向
  146. $location['pos'] = $this->getPos($byte);
  147. $location['isp'] = $this->getISP();
  148. break;
  149. }
  150. // 信息转码处理
  151. foreach ($location as $k => $v) {
  152. $location[$k] = iconv('gb2312', 'utf-8', $v);
  153. $location[$k] = preg_replace(array('/^.*CZ88\.NET.*$/isU', '/^.*纯真.*$/isU', '/^.*日IP数据/'), '', $location[$k]);
  154. $location[$k] = htmlspecialchars($location[$k]);
  155. }
  156. return $location;
  157. }
  158. // 更新数据库 https://www.22vd.com/40035.html
  159. public function update() {
  160. $this->ignore_timeout();
  161. $copywrite = file_get_contents('http://update.cz88.net/ip/copywrite.rar');
  162. $qqwry = file_get_contents('http://update.cz88.net/ip/qqwry.rar');
  163. $key = unpack('V6', $copywrite)[6];
  164. for ($i = 0; $i < 0x200; $i++) {
  165. $key *= 0x805;
  166. $key++;
  167. $key = $key & 0xFF;
  168. $qqwry[$i] = chr(ord($qqwry[$i]) ^ $key);
  169. }
  170. $qqwry = gzuncompress($qqwry);
  171. file_put_contents($this->dbFile, $qqwry);
  172. }
  173. // 析构函数
  174. function __destruct() {
  175. if ($this->fh) {
  176. fclose($this->fh);
  177. }
  178. $this->fp = null;
  179. }
  180. }