<?php
/**
 * secure-stream.php
 * Gateway for secure video streaming from lets-fit.ir (MP4 + HLS)
 *
 * Place this file in public_html/ on lets-fit.ir
 *
 * IMPORTANT:
 * - Update LETSFIT_VIDEO_ROOT to where your media lives
 * - Keep $shared_secret in sync with your WordPress signer (letsfit_video_secret)
 */

// ======== CONFIG ========

// اگر ویدیوها OUTSIDE webroot هستند (توصیه می‌شود):
// define('LETSFIT_VIDEO_ROOT', '/home2/letsfiti/public_html/videos'); // <- مسیر واقعی را تنظیم کنید

// اگر ویدیوها زیر /public_html/videos هستند، می‌توانید این را استفاده کنید:
define('LETSFIT_VIDEO_ROOT', __DIR__ . '/videos');

// فقط دامنه‌هایی که می‌خواهید اجازه embed بدهید (اختیاری)
// نکته: اگر ویدیو و پلیر روی یک دامنه هستند، CORS لازم نیست؛ ولی این لیست را کامل‌تر کردیم.
$allowed_origins = [
    'https://lets-fit.ir',
    'https://www.lets-fit.ir',
    'https://letsfit.net',
    'https://www.letsfit.net',
    'https://app.letsfit.net',
	'https://tv.letsfit.net',
];

// کلید مخفی مشترک با WordPress (باید با letsfit_video_secret هماهنگ باشد)
$shared_secret = 'vahid1370';

// ======== CORS / Headers ========
if (isset($_SERVER['HTTP_ORIGIN']) && in_array($_SERVER['HTTP_ORIGIN'], $allowed_origins, true)) {
    header('Access-Control-Allow-Origin: ' . $_SERVER['HTTP_ORIGIN']);
    header('Vary: Origin');
}
// برای درخواست‌های media گاهی Origin ست می‌شود؛ این هدرها کمک می‌کنند
header('Access-Control-Allow-Methods: GET, HEAD, OPTIONS');
header('Access-Control-Allow-Headers: Range, Origin, Accept, Content-Type, Authorization');

if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'OPTIONS') {
    http_response_code(204);
    exit;
}

// ======== Helper Functions ========
function bad_request($code = 400, $msg = 'Bad Request') {
    http_response_code($code);
    header('Content-Type: application/json; charset=utf-8');
    echo json_encode(['error' => $msg]);
    exit;
}

function get_client_ip() {
    // اگر پشت Cloudflare هستید، این هدر دقیق‌ترین است
    if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])) {
        return trim($_SERVER['HTTP_CF_CONNECTING_IP']);
    }
    // در صورت وجود، اولین IP در X-Forwarded-For را بگیریم
    if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        $parts = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
        return trim($parts[0]);
    }
    return $_SERVER['REMOTE_ADDR'] ?? '';
}

function hmac_sign($path, $exp, $uid, $ip, $secret) {
    // Path must be canonical (e.g., "/demo/video.mp4")
    $base = implode('|', [$path, $exp, $uid, $ip]);
    return hash_hmac('sha256', $base, $secret);
}

function build_signed_url($path, $exp, $uid, $bindToIp, $secret) {
    $path = '/' . ltrim($path, '/');
    $ip = $bindToIp ? get_client_ip() : '';
    $sig = hmac_sign($path, $exp, $uid, $ip, $secret);

    $params = [
        'path' => $path,
        'exp'  => (int)$exp,
        'uid'  => (int)$uid,
        'sig'  => $sig,
    ];
    if ($bindToIp) {
        $params['bind'] = 1;
    }

    $script = $_SERVER['SCRIPT_NAME'] ?? '/secure-stream.php';
    return $script . '?' . http_build_query($params);
}

function normalize_join_path($base_dir, $uri) {
    // اگر uri با / شروع شود یعنی از ریشه‌ی LETSFIT_VIDEO_ROOT
    if (strlen($uri) > 0 && $uri[0] === '/') {
        $full = $uri;
    } else {
        $base = rtrim($base_dir, '/');
        $full = $base . '/' . ltrim($uri, '/');
    }

    // canonicalize: /a/b/./c => /a/b/c (و جلوی .. را می‌گیریم)
    $full = '/' . ltrim($full, '/');
    $parts = explode('/', $full);
    $stack = [];
    foreach ($parts as $p) {
        if ($p === '' || $p === '.') continue;
        if ($p === '..') {
            // جلوگیری از traversal
            continue;
        }
        $stack[] = $p;
    }
    return '/' . implode('/', $stack);
}

// ======== Validate Query ========
// Expected: ?path=/demo/video.mp4&exp=1730131200&uid=123&sig=<hash>[&bind=1]
$path = isset($_GET['path']) ? $_GET['path'] : null;
$exp  = isset($_GET['exp'])  ? (int) $_GET['exp'] : 0;
$uid  = isset($_GET['uid'])  ? (int) $_GET['uid'] : 0;
$sig  = isset($_GET['sig'])  ? $_GET['sig'] : '';

if (!$path || !$exp || !$sig) {
    bad_request(400, 'Missing parameters');
}
if ($exp < time()) {
    bad_request(401, 'URL expired');
}

// Optional: bind to IP (set to true to enforce)
$bindToIp = isset($_GET['bind']) ? (bool) $_GET['bind'] : false;
$ip = $bindToIp ? get_client_ip() : '';

// Normalize and prevent path traversal
$path = '/' . ltrim($path, '/');
if (strpos($path, '..') !== false) {
    bad_request(400, 'Invalid path');
}

// Recompute signature
$expected = hmac_sign($path, $exp, $uid, $ip, $shared_secret);
if (!hash_equals($expected, $sig)) {
    bad_request(403, 'Invalid signature');
}

// Resolve file
$full = rtrim(LETSFIT_VIDEO_ROOT, '/') . $path;
if (!is_file($full) || !is_readable($full)) {
    // Try parent directory of LETSFIT_VIDEO_ROOT (which is public_html) as a fallback
    $parent_root = dirname(LETSFIT_VIDEO_ROOT);
    $fallback_full = rtrim($parent_root, '/') . $path;
    if (is_file($fallback_full) && is_readable($fallback_full)) {
        $full = $fallback_full;
    } else {
        bad_request(404, 'File not found');
    }
}

/**
 * ======== HLS (.m3u8) support ========
 * مشکل شما این بود که فقط خود playlist ساین می‌شد، اما سگمنت‌ها نه.
 * این بخش playlist را بازنویسی می‌کند تا هر URI (seg.ts / variant.m3u8 / key) به secure-stream.php با امضای معتبر تبدیل شود.
 */
if (preg_match('~\.m3u8$~i', $path)) {
    $playlist = file_get_contents($full);
    if ($playlist === false) {
        bad_request(500, 'Read playlist failed');
    }

    $lines = preg_split("/\r\n|\n|\r/", $playlist);
    $base_dir = rtrim(dirname($path), '/'); // مسیر پوشه‌ی پلی‌لیست

    header('Content-Type: application/vnd.apple.mpegurl; charset=utf-8');
    header('Cache-Control: no-cache, no-store, must-revalidate');
    header('Pragma: no-cache');
    header('Expires: 0');

    foreach ($lines as $line) {
        $line = trim($line);

        // خالی
        if ($line === '') {
            echo "\n";
            continue;
        }

        // خطوط کامنت/متادیتا
        if (isset($line[0]) && $line[0] === '#') {
            // اگر KEY یا MAP داشت، URI داخلش را هم rewrite کن
            if (stripos($line, '#EXT-X-KEY:') === 0 || stripos($line, '#EXT-X-MAP:') === 0) {
                $line = preg_replace_callback('/URI="([^"]+)"/i', function ($m) use ($base_dir, $exp, $uid, $bindToIp, $shared_secret) {
                    $uri = $m[1];

                    // اگر absolute URL است، دست نزن (می‌توانید در صورت نیاز اینجا هم signed کنید)
                    if (preg_match('~^https?://~i', $uri)) {
                        return 'URI="' . $uri . '"';
                    }

                    $seg_path = normalize_join_path($base_dir, $uri);
                    $signed = build_signed_url($seg_path, $exp, $uid, $bindToIp, $shared_secret);
                    return 'URI="' . $signed . '"';
                }, $line);
            }

            echo $line . "\n";
            continue;
        }

        // خط‌های URL (segment / variant playlist)
        if (preg_match('~^https?://~i', $line)) {
            // absolute URL را همانطور نگه می‌داریم
            echo $line . "\n";
        } else {
            $seg_path = normalize_join_path($base_dir, $line);
            $signed = build_signed_url($seg_path, $exp, $uid, $bindToIp, $shared_secret);
            echo $signed . "\n";
        }
    }

    exit;
}

// ======== Stream with Range support (MP4 / TS / M4S / etc.) ========
$size = filesize($full);
$fp   = fopen($full, 'rb');
if (!$fp) {
    bad_request(500, 'Open failed');
}

// Auto-detect MIME type
$mime = 'application/octet-stream';
if (function_exists('mime_content_type')) {
    $tmp = @mime_content_type($full);
    if ($tmp) $mime = $tmp;
}
// اصلاح MIME برای HLS سگمنت‌ها اگر سرور اشتباه تشخیص داد
$ext = strtolower(pathinfo($full, PATHINFO_EXTENSION));
if ($ext === 'ts')   $mime = 'video/mp2t';
if ($ext === 'm4s')  $mime = 'video/iso.segment';
if ($ext === 'mp4')  $mime = 'video/mp4';

$start = 0;
$length = $size;
$httpRange = $_SERVER['HTTP_RANGE'] ?? '';

header('Content-Type: ' . $mime);
header('Accept-Ranges: bytes');

// Handle Range requests for seeking
if ($httpRange && preg_match('/bytes=(\d+)-(\d*)/i', $httpRange, $m)) {
    $start = (int)$m[1];
    $end   = ($m[2] !== '') ? (int)$m[2] : ($size - 1);
    if ($start > $end || $end >= $size) {
        // Invalid range
        header('HTTP/1.1 416 Range Not Satisfiable');
        header("Content-Range: bytes */$size");
        fclose($fp);
        exit;
    }

    $length = $end - $start + 1;
    header('HTTP/1.1 206 Partial Content');
    header("Content-Range: bytes $start-$end/$size");
} else {
    $start = 0;
    $length = $size;
}

header('Content-Length: ' . $length);
// Optional hardening
// ------ header('Content-Disposition: inline; filename="' . basename($full) . '"');

// To Download Pdf 
$disposition = 'inline';
$ext = strtolower(pathinfo($full, PATHINFO_EXTENSION));
if ($ext === 'pdf') {
    $disposition = 'attachment';
}
header('Content-Disposition: ' . $disposition . '; filename="' . basename($full) . '"');
header('Cache-Control: private, max-age=60');
//  End To Download Pdf 


// Stream the file
fseek($fp, $start);
$buffer = 8192;
$sent = 0;
while (!feof($fp) && $sent < $length) {
    $read = min($buffer, $length - $sent);
    $chunk = fread($fp, $read);
    if ($chunk === false) break;
    echo $chunk;
    $sent += strlen($chunk);
    @ob_flush();
    flush();
}
fclose($fp);
exit;
