<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
ini_set('log_errors', 0);
//ini_set('error_log', __DIR__ . '/proxy_error.log');

$url = isset($_GET['url']) ? $_GET['url'] : '';
if ($url === '') {
    header('HTTP/1.1 400 Bad Request');
    exit('no url');
}

header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, HEAD, OPTIONS');
header('Access-Control-Allow-Headers: Range, Content-Type');

if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    header('HTTP/1.1 204 No Content');
    exit;
}

$hasRange = isset($_SERVER['HTTP_RANGE']) && $_SERVER['HTTP_RANGE'] !== '';
$debug = isset($_GET['debug']) ? true : false;

$debugLog = array();
function proxyLog($msg) {
    global $debugLog;
    $debugLog[] = $msg;
 //   @file_put_contents(__DIR__ . '/proxy_error.log', date('Y-m-d H:i:s') . ' ' . $msg . "\n", FILE_APPEND);
}

function debugDump($content, $httpCode, $errMsg) {
    global $debugLog, $url;
    header('Content-Type: text/html; charset=utf-8');
    echo '<pre style="font-family:Consolas,monospace;font-size:13px;color:#eee;background:#1a1a2e;padding:20px;white-space:pre-wrap;word-break:break-all;">';
    echo '<b style="color:#ffcc66;">=== LMP34 Proxy Debug ===</b>' . "\n\n";
    echo '<b>INPUT URL:</b> ' . htmlspecialchars($url) . "\n\n";
    echo '<b>LOG:</b>' . "\n";
    foreach ($debugLog as $line) echo '  ' . htmlspecialchars($line) . "\n";
    echo "\n" . '<b>HTTP CODE:</b> ' . $httpCode . "\n";
    echo '<b>CURL ERROR:</b> ' . ($errMsg ? htmlspecialchars($errMsg) : '(none)') . "\n";
    echo '<b>CONTENT LEN:</b> ' . strlen($content) . "\n";
    if (strlen($content) > 0) {
        echo "\n<b>CONTENT PREVIEW (first 2000 bytes):</b>\n";
        echo htmlspecialchars(substr($content, 0, 2000));
        if (strlen($content) > 2000) echo "\n\n... (truncated, total " . strlen($content) . " bytes)";
    }
    echo '</pre>';
    exit;
}

/**
 * 绕过服务器 DNS 污染：hosts 硬编码优先 + 公共 DNS 兜底
 * 多个 IP 自动降级尝试
 */
function resolveWithPublicDns($host) {
    if (filter_var($host, FILTER_VALIDATE_IP)) return false;

    $hostsMap = array(
        'www.panurl.cn' => array('104.16.36.185', '104.20.61.142'),
    );
    if (isset($hostsMap[$host])) {
        $list = $hostsMap[$host];
        proxyLog("DNS-resolve via hosts-map: {$host} => " . implode(', ', $list));
        return $list;
    }

    $dnsServers = array('8.8.8.8', '223.5.5.5', '119.29.29.29');

    if (function_exists('shell_exec')) {
        foreach ($dnsServers as $dns) {
            $out = @shell_exec("dig +short @{$dns} {$host} A 2>/dev/null");
            if ($out) {
                $ips = array_filter(array_map('trim', explode("\n", $out)), function($ip) {
                    return filter_var($ip, FILTER_VALIDATE_IP);
                });
                if (!empty($ips)) {
                    proxyLog("DNS-resolve via dig @{$dns}: {$host} => " . implode(', ', $ips));
                    return array_values($ips);
                }
            }
        }
        foreach ($dnsServers as $dns) {
            $out = @shell_exec("nslookup {$host} {$dns} 2>/dev/null");
            if ($out && preg_match_all('/Address:\s*(\d+\.\d+\.\d+\.\d+)/', $out, $m)) {
                $ips = array_unique($m[1]);
                proxyLog("DNS-resolve via nslookup @{$dns}: {$host} => " . implode(', ', $ips));
                return array_values($ips);
            }
        }
    }

    $ip = @gethostbyname($host);
    if ($ip && $ip !== $host && filter_var($ip, FILTER_VALIDATE_IP)) {
        proxyLog("DNS-resolve via gethostbyname: {$host} -> {$ip}");
        return array($ip);
    }

    proxyLog("DNS-resolve FAILED for {$host}");
    return false;
}

proxyLog("REQUEST: " . $url . " range=" . ($hasRange ? $_SERVER['HTTP_RANGE'] : 'none'));

$urlParts = parse_url($url);
$targetHost = isset($urlParts['host']) ? $urlParts['host'] : '';
$targetPort = isset($urlParts['scheme']) && $urlParts['scheme'] === 'https' ? 443 : 80;
$resolvedIps = false;
if ($targetHost) {
    $resolvedIps = resolveWithPublicDns($targetHost);
}

// 构造 curl 的 CURLOPT_RESOLVE 映射（多 IP 供 curl 自动 fallback）
$resolveMap = array();
if (is_array($resolvedIps)) {
    foreach ($resolvedIps as $ip) {
        $resolveMap[] = "{$targetHost}:{$targetPort}:{$ip}";
    }
}

$ext = strtolower(pathinfo(parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION));
$audioExts = array('mp3'=>'audio/mpeg','wav'=>'audio/wav','ogg'=>'audio/ogg','m4a'=>'audio/mp4','aac'=>'audio/aac','flac'=>'audio/flac','wma'=>'audio/x-ms-wma');
$videoExts = array('mp4'=>'video/mp4','webm'=>'video/webm','ogv'=>'video/ogg','mov'=>'video/quicktime','m4v'=>'video/x-m4v');
$imageExts = array('jpg'=>'image/jpeg','jpeg'=>'image/jpeg','png'=>'image/png','gif'=>'image/gif','webp'=>'image/webp','svg'=>'image/svg+xml','bmp'=>'image/bmp','ico'=>'image/x-icon','tiff'=>'image/tiff');
$defaultCt = 'application/octet-stream';
$isImage = false;
if (isset($audioExts[$ext])) $defaultCt = $audioExts[$ext];
elseif (isset($videoExts[$ext])) $defaultCt = $videoExts[$ext];
elseif (isset($imageExts[$ext])) { $defaultCt = $imageExts[$ext]; $isImage = true; }

$content = false;
$respHeaders = array();
$httpCode = 0;
$errMsg = '';

if (function_exists('curl_init')) {
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($ch, CURLOPT_MAXREDIRS, 10);
    curl_setopt($ch, CURLOPT_ENCODING, 'gzip, deflate');
    curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
    curl_setopt($ch, CURLOPT_REFERER, dirname($url) . '/');
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    curl_setopt($ch, CURLOPT_TIMEOUT, 60);
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15);
    $extraHeaders = array(
        'Accept: */*',
        'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8'
    );
    if ($hasRange) {
        $extraHeaders[] = 'Range: ' . $_SERVER['HTTP_RANGE'];
    }
    curl_setopt($ch, CURLOPT_HTTPHEADER, $extraHeaders);
    curl_setopt($ch, CURLOPT_HEADERFUNCTION, function($ch, $header) use (&$respHeaders) {
        $trimmed = trim($header);
        if ($trimmed !== '' && strpos($trimmed, ':') !== false) {
            list($k, $v) = explode(':', $trimmed, 2);
            $respHeaders[strtolower(trim($k))] = trim($v);
        }
        return strlen($header);
    });
    if (!empty($resolveMap)) {
        curl_setopt($ch, CURLOPT_RESOLVE, $resolveMap);
        proxyLog("CURLOPT_RESOLVE: " . count($resolveMap) . " IP(s) set");
    }
    $content = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $errMsg = curl_error($ch);
    $effectiveUrl = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
    curl_close($ch);
    proxyLog("CURL: code=$httpCode effective=$effectiveUrl err=$errMsg len=" . ($content === false ? 'false' : strlen($content)));
}

if ($content === false) {
    $opts = array(
        'http' => array(
            'method' => 'GET',
            'header' => "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36\r\nReferer: " . dirname($url) . "/\r\n",
            'timeout' => 60
        ),
        'ssl' => array('verify_peer' => false, 'verify_peer_name' => false)
    );
    if ($hasRange) {
        $opts['http']['header'] .= 'Range: ' . $_SERVER['HTTP_RANGE'] . "\r\n";
    }
    $headerHost = ($resolvedIps && $targetHost) ? "Host: {$targetHost}\r\n" : '';
    $ctx = stream_context_create($opts);
    if ($resolvedIps && $targetHost) {
        foreach ($resolvedIps as $ip) {
            $fgcUrl = preg_replace('/^(' . preg_quote($urlParts['scheme'], '/') . ':\/\/)' . preg_quote($targetHost, '/') . '/', '$1' . $ip, $url, 1);
            proxyLog("FGC try: {$fgcUrl}");
            $tryCtx = stream_context_create(array(
                'http' => array(
                    'method' => 'GET',
                    'header' => $headerHost . $opts['http']['header'],
                    'timeout' => 60
                ),
                'ssl' => $opts['ssl']
            ));
            $content = @file_get_contents($fgcUrl, false, $tryCtx);
            if ($content !== false) {
                $httpCode = 200;
                $errMsg = '';
                proxyLog("FGC success via {$ip} len=" . strlen($content));
                break;
            }
            proxyLog("FGC failed via {$ip}");
        }
    } else {
        $content = @file_get_contents($url, false, $ctx);
        if ($content !== false) {
            $httpCode = 200;
            proxyLog("FGC: success len=" . strlen($content));
        } else {
            proxyLog("FGC: failed");
        }
    }
}

if ($content === false) {
    proxyLog("FINAL: 502 error");
    if ($debug) debugDump('', 0, $errMsg ? $errMsg : 'fetch failed');
    header('HTTP/1.1 502 Bad Gateway');
    header('Content-Type: text/plain');
    echo 'proxy error: ' . ($errMsg ? $errMsg : 'fetch failed');
    exit;
}

if (strlen($content) === 0) {
    proxyLog("FINAL: empty response");
    if ($debug) debugDump('', $httpCode, 'empty response from remote');
    header('HTTP/1.1 502 Bad Gateway');
    header('Content-Type: text/plain');
    echo 'proxy error: empty response from remote';
    exit;
}

$contentInfo = isset($respHeaders['content-type']) ? $respHeaders['content-type'] : 'unknown';
proxyLog("CONTENT: type=$contentInfo len=" . strlen($content) . " firstbytes=" . bin2hex(substr($content, 0, 16)));

if ($httpCode >= 400) {
    proxyLog("FINAL: remote error $httpCode");
    if ($debug) debugDump($content, $httpCode, 'remote returned ' . $httpCode);
    header('HTTP/1.1 ' . $httpCode);
    header('Content-Type: text/plain');
    echo 'remote error ' . $httpCode;
    exit;
}

$ct = isset($respHeaders['content-type']) ? $respHeaders['content-type'] : $defaultCt;
header('Content-Type: ' . $ct);
header('Accept-Ranges: bytes');

$contentLength = strlen($content);
header('Content-Length: ' . $contentLength);

if ($isImage) {
    header('Cache-Control: public, max-age=86400');
}

if ($hasRange && $httpCode === 206) {
    header('HTTP/1.1 206 Partial Content');
    if (isset($respHeaders['content-range'])) {
        header('Content-Range: ' . $respHeaders['content-range']);
    }
}

proxyLog("FINAL: ct=$ct len=$contentLength code=$httpCode");
if ($debug) debugDump($content, $httpCode, 'success');
echo $content;