From 4598c37f1327cc71fdca864d1742541013821df3 Mon Sep 17 00:00:00 2001 From: rad168 <86744795+rad168@users.noreply.github.com> Date: Sun, 27 Jul 2025 05:00:13 +0800 Subject: [PATCH] Update mytv.php --- mytv/mytv.php | 135 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 89 insertions(+), 46 deletions(-) diff --git a/mytv/mytv.php b/mytv/mytv.php index d5d31d2..94d991a 100644 --- a/mytv/mytv.php +++ b/mytv/mytv.php @@ -1,11 +1,13 @@ $value) { - if (substr($name, 0, 5) == 'HTTP_') { - $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; + if (strpos($name, 'HTTP_') === 0) { + $key = str_replace('_', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5))))); + $headers[$key] = $value; } } return $headers; } } -// 处理 HTTP 头信息 +// 兼容 str_starts_with() 函数 +if (!function_exists('str_starts_with')) { + function str_starts_with($haystack, $needle) { + return substr($haystack, 0, strlen($needle)) === $needle; + } +} + +// 兼容 str_contains 函数 +if (!function_exists('str_contains')) { + function str_contains($haystack, $needle) { + return $needle !== '' && strpos($haystack, $needle) !== false; + } +} + +// 构造请求头 $headers = []; foreach (getallheaders() as $name => $value) { if (strtolower($name) !== 'host') { $headers[] = "$name: $value"; } } -$headers[] = "Host: {$parsed_url['host']}"; +$headers[] = "Host: $host"; $headers[] = "User-Agent: AppleCoreMedia/1.0.0.7B367 (iPad; U; CPU OS 4_3_3 like Mac OS X)"; -$headers[] = "Referer: https://{$parsed_url['host']}/"; +$headers[] = "Referer: https://$host/"; $headers[] = "Accept-Encoding: gzip, deflate"; -// 发送请求 +// 发起请求 $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $request_url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); -curl_setopt($ch, CURLOPT_HEADER, true); // 获取完整响应头 -curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); // 禁用自动跳转 +curl_setopt($ch, CURLOPT_HEADER, true); +curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_ENCODING, ""); - -// 禁用 HTTP/2,强制使用 HTTP/1.1 curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); if ($_SERVER['REQUEST_METHOD'] === 'POST') { @@ -78,29 +95,26 @@ $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); curl_close($ch); -// 分离响应头和响应体 -$headers = substr($response, 0, $header_size); +// 拆分头和主体 +$headers_raw = substr($response, 0, $header_size); $body = substr($response, $header_size); -// 解析响应头 +// 解析头 $response_headers = []; -foreach (explode("\r\n", $headers) as $line) { - if (strpos($line, 'HTTP/') === 0) { +foreach (explode("\r\n", $headers_raw) as $line) { + if (stripos($line, 'HTTP/') === 0) { $response_headers[] = $line; continue; } $parts = explode(': ', $line, 2); if (count($parts) === 2) { - $name = strtolower($parts[0]); - $response_headers[$name] = $parts[1]; + $response_headers[strtolower($parts[0])] = $parts[1]; } } -// 处理重定向 +// 重定向处理 if (in_array($http_code, [301, 302, 303, 307, 308]) && isset($response_headers['location'])) { $location = $response_headers['location']; - - // 处理相对路径 if (!parse_url($location, PHP_URL_SCHEME)) { $base = $parsed_url['scheme'] . '://' . $parsed_url['host']; if (isset($parsed_url['port'])) { @@ -108,44 +122,73 @@ if (in_array($http_code, [301, 302, 303, 307, 308]) && isset($response_headers[' } $location = $base . '/' . ltrim($location, '/'); } - - // 生成代理地址并跳转 header("Location: mytv.php?url=" . urlencode($location), true, $http_code); exit(); } -// 保留原始 Content-Type +// 设置 content-type if (isset($response_headers['content-type'])) { header('Content-Type: ' . $response_headers['content-type']); } -// 设置 HTTP 响应状态码 +// 设置状态码 http_response_code($http_code); +// 出错输出 if ($response === false) { die("CURL ERROR: " . $curl_error); } -// 处理 m3u8 文件 -if (strpos($request_url, '.m3u8') !== false || (isset($response_headers['content-type']) && strpos($response_headers['content-type'], 'application/vnd.apple.mpegurl') !== false)) { - $base_url = dirname($request_url) . '/'; - $allowed_domains_regex = implode('|', array_map(function($domain) { - return preg_quote($domain, '/'); - }, $allowed_domains)); - +// ========== m3u8 替换逻辑 ========== +$is_m3u8 = false; +$content_type = $response_headers['content-type'] ?? ''; + +if ( + strpos($request_url, '.m3u8') !== false || + stripos($content_type, 'mpegurl') !== false || + stripos($content_type, 'application/x-mpegurl') !== false || + stripos($content_type, 'text/plain') !== false || + strpos(ltrim($body), '#EXTM3U') === 0 +) { + $is_m3u8 = true; +} + +if ($is_m3u8) { + $base_root = $parsed_url['scheme'] . '://' . $parsed_url['host'] . + (isset($parsed_url['port']) ? ':' . $parsed_url['port'] : ''); + $base_dir = $base_root . dirname($parsed_url['path']) . '/'; + $body = preg_replace_callback( - '/(https?:\/\/(?:' . $allowed_domains_regex . ')\/[^\s"\']+\.ts)|([^\s"\']+\.ts)/', - function ($matches) use ($base_url) { - if (!empty($matches[1])) { - return 'mytv.php?url=' . urlencode($matches[1]); - } elseif (!empty($matches[2])) { - $ts_url = $base_url . ltrim($matches[2], '/'); - return 'mytv.php?url=' . urlencode($ts_url); + // 更稳健的正则表达式:匹配 URL、绝对路径、相对路径,允许 query 参数,允许无后缀 + '/(?P(https?:\/\/[^\s"\']+)|((\/|\.\.?\/)?[^\s"\']+))/i', + function ($matches) use ($base_root, $base_dir) { + $url = trim($matches['url']); + + // 跳过 m3u8 语法行 + if (str_starts_with($url, '#')) return $url; + + // 跳过 data uri 等 + if (str_starts_with($url, 'data:')) return $url; + + // 跳过已经被代理过的 + if (str_contains($url, 'mytv.php?url=')) return $url; + + // 完整 URL + if (preg_match('/^https?:\/\//i', $url)) { + return 'mytv.php?url=' . urlencode($url); } - return $matches[0]; + + // 以 / 开头(绝对路径) + if (str_starts_with($url, '/')) { + return 'mytv.php?url=' . urlencode($base_root . $url); + } + + // 处理 ../ 或 ./ 等相对路径 + return 'mytv.php?url=' . urlencode($base_dir . $url); }, $body ); + header('Content-Disposition: inline; filename=index.m3u8'); }