• 使用说明

    proxy.php?url=
  • 适用于苹果CMS超级播放器

    <?php
    // 设置CORS头,允许跨域访问
    header('Access-Control-Allow-Origin: *');
    header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
    header('Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Authorization');
    
    // 处理预检请求
    if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
      http_response_code(200);
      exit();
    }
    
    // 获取参数
    $url = isset($_GET['url']) ? trim($_GET['url']) : '';
    $type = isset($_GET['type']) ? trim($_GET['type']) : 'auto';
    $referer = isset($_GET['referer']) ? trim($_GET['referer']) : '';
    $ua = isset($_GET['ua']) ? trim($_GET['ua']) : '';
    
    // 验证URL
    if (empty($url)) {
      http_response_code(400);
      echo json_encode(['error' => '缺少URL参数']);
      exit();
    }
    
    // URL解码处理 - 这是关键修复
    $url = urldecode($url);
    
    // 验证URL格式 - 使用更宽松的验证
    if (!filter_var($url, FILTER_VALIDATE_URL) && !preg_match('/^https?:\/\/[a-zA-Z0-9.-]+/', $url)) {
      http_response_code(400);
      echo json_encode(['error' => '无效的URL格式', 'url' => $url]);
      exit();
    }
    
    // 安全检查 - 防止SSRF攻击
    $parsed_url = parse_url($url);
    if (!$parsed_url || !isset($parsed_url['host'])) {
      http_response_code(400);
      echo json_encode(['error' => '无效的URL', 'parsed_url' => $parsed_url]);
      exit();
    }
    
    // 检查是否为内网IP - 增加错误处理
    $ip = gethostbyname($parsed_url['host']);
    if ($ip === $parsed_url['host']) {
      // DNS解析失败,但不一定是错误,可能是有效域名
      // 继续执行,让curl去处理
    } else if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) {
      http_response_code(403);
      echo json_encode(['error' => '禁止访问内网资源', 'ip' => $ip]);
      exit();
    }
    
    /**
     * 发送HTTP请求 - 简化版本
     * @param string $url 请求URL
     * @param string $referer 来源
     * @param string $userAgent 用户代理
     * @return array
     */
    function fetchContent($url, $referer = '', $userAgent = '') {
      $ch = curl_init();
      
      // 基础配置
      curl_setopt_array($ch, [
          CURLOPT_URL => $url,
          CURLOPT_RETURNTRANSFER => true,
          CURLOPT_FOLLOWLOCATION => true,
          CURLOPT_MAXREDIRS => 3,
          CURLOPT_TIMEOUT => 30,
          CURLOPT_CONNECTTIMEOUT => 15,
          CURLOPT_SSL_VERIFYPEER => false,
          CURLOPT_SSL_VERIFYHOST => false,
          CURLOPT_USERAGENT => $userAgent ?: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0'
      ]);
      
      // 设置请求头
      $headers = [
          'Accept: */*',
          'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8',
          'Cache-Control: no-cache',
          'Connection: keep-alive'
      ];
      
      if ($referer) {
          $headers[] = 'Referer: ' . $referer;
      }
      
      curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
      
      // 获取响应头
      curl_setopt($ch, CURLOPT_HEADER, true);
      
      $response = curl_exec($ch);
      $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
      $contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
      $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
      $error = curl_error($ch);
      
      curl_close($ch);
      
      if ($response === false) {
          return [
              'success' => false,
              'error' => $error ?: '请求失败',
              'code' => 500
          ];
      }
      
      $headers = substr($response, 0, $headerSize);
      $body = substr($response, $headerSize);
      
      return [
          'success' => true,
          'code' => $httpCode,
          'headers' => $headers,
          'body' => $body,
          'contentType' => $contentType
      ];
    }
    
    /**
     * 增强版M3U8文件内容处理
     * 确保播放器从第一个片段开始播放
     */
    function processM3U8Content($content, $baseUrl) {
      $lines = explode("\n", $content);
      $baseUrlInfo = parse_url($baseUrl);
      $basePath = dirname($baseUrlInfo['path']);
      $baseScheme = $baseUrlInfo['scheme'] ?? 'http';
      $baseHost = $baseUrlInfo['host'];
      $basePort = isset($baseUrlInfo['port']) ? ':' . $baseUrlInfo['port'] : '';
      
      // 获取当前proxy.php的基本URL
      $proxyBase = $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'] . dirname($_SERVER['SCRIPT_NAME']);
      
      foreach ($lines as &$line) {
          $line = trim($line);
          
          // 核心修复:强制从第一个片段开始
          if (strpos($line, '#EXT-X-MEDIA-SEQUENCE:') === 0) {
              $line = '#EXT-X-MEDIA-SEQUENCE:0';  // 强制序号从0开始
              continue;
          }
          
          // 处理EXT-X-KEY标签
          if (strpos($line, '#EXT-X-KEY:') === 0) {
              if (preg_match('/URI="([^"]+)"/', $line, $matches)) {
                  $keyUri = $matches[1];
                  
                  // 处理密钥URI的相对路径
                  if (!preg_match('/^https?:\/\//', $keyUri)) {
                      if (strpos($keyUri, '/') === 0) {
                          $keyUri = $baseScheme . '://' . $baseHost . $basePort . $keyUri;
                      } else {
                          $keyUri = $baseScheme . '://' . $baseHost . $basePort . rtrim($basePath, '/') . '/' . ltrim($keyUri, '/');
                      }
                  }
                  
                  // 将密钥URI也通过代理
                  $proxyKeyUri = $proxyBase . '/proxy.php?url=' . urlencode($keyUri);
                  $line = str_replace($matches[1], $proxyKeyUri, $line);
              }
              continue;
          }
          
          // 跳过空行和注释行(除了EXT-X-KEY)
          if (empty($line) || strpos($line, '#') === 0) {
              continue;
          }
          
          // 处理普通TS文件或播放列表的相对路径
          if (!preg_match('/^https?:\/\//', $line)) {
              if (strpos($line, '/') === 0) {
                  $line = $baseScheme . '://' . $baseHost . $basePort . $line;
              } else {
                  $line = $baseScheme . '://' . $baseHost . $basePort . rtrim($basePath, '/') . '/' . ltrim($line, '/');
              }
              
              $line = $proxyBase . '/proxy.php?url=' . urlencode($line);
          } else {
              $line = $proxyBase . '/proxy.php?url=' . urlencode($line);
          }
      }
      
      return implode("\n", $lines);
    }
    
    /**
     * 获取文件扩展名
     * @param string $url
     * @return string
     */
    function getFileExtension($url) {
      $path = parse_url($url, PHP_URL_PATH);
      $extension = pathinfo($path, PATHINFO_EXTENSION);
      return strtolower($extension);
    }
    
    // 发送请求
    $result = fetchContent($url, $referer, $ua);
    
    if (!$result['success']) {
      http_response_code($result['code']);
      echo json_encode(['error' => $result['error']]);
      exit();
    }
    
    // 检查HTTP状态码
    if ($result['code'] >= 400) {
      http_response_code($result['code']);
      echo json_encode(['error' => 'HTTP错误: ' . $result['code']]);
      exit();
    }
    
    // 获取文件类型
    $extension = getFileExtension($url);
    $contentType = $result['contentType'] ?: 'application/octet-stream';
    
    // 检查是否是.key文件的请求(密钥文件)
    if ($extension === 'key') {
      // 直接返回密钥内容
      header('Content-Type: application/octet-stream');
      header('Content-Length: ' . strlen($result['body']));
      header('Cache-Control: no-cache');
      header('Access-Control-Allow-Origin: *');
      echo $result['body'];
      exit();
    }
    
    // 根据文件类型设置响应头
    switch ($extension) {
      case 'm3u8':
          $contentType = 'application/vnd.apple.mpegurl';
          break;
      case 'ts':
          $contentType = 'video/mp2t';
          break;
      case 'mp4':
          $contentType = 'video/mp4';
          break;
      case 'flv':
          $contentType = 'video/x-flv';
          break;
    }
    
    // 设置响应头
    header('Content-Type: ' . $contentType);
    header('Content-Length: ' . strlen($result['body']));
    header('Cache-Control: public, max-age=300'); // 缓存5分钟
    header('Access-Control-Allow-Origin: *');
    
    // 如果是M3U8文件,处理其中的URL
    if ($extension === 'm3u8' || strpos($contentType, 'mpegurl') !== false) {
      $result['body'] = processM3U8Content($result['body'], $url);
      // 重新计算内容长度
      header('Content-Length: ' . strlen($result['body']));
    }
    
    // 输出内容
    echo $result['body'];
    
    ?>