Files
GridTV/proxy.php
T

138 lines
5.1 KiB
PHP

<?php
/**
* GridTV — proxy.php
* Proxy HTTP->HTTPS restreint aux hotes autorises dans config.json
*/
// ── Charger la whitelist depuis config.json ────────────────────────────────────
$config_path = __DIR__ . '/config.json';
$allowed_hosts = [];
if (file_exists($config_path)) {
$config = json_decode(file_get_contents($config_path), true);
foreach ($config['epg_sources'] ?? [] as $src) {
foreach (['epg_url', 'm3u_url'] as $key) {
if (!empty($src[$key])) {
$host = parse_url($src[$key], PHP_URL_HOST);
if ($host) $allowed_hosts[] = strtolower($host);
}
}
}
}
// ── Valider l'URL demandee ─────────────────────────────────────────────────────
$url = $_GET['url'] ?? '';
if (empty($url) || !preg_match('#^https?://#i', $url)) {
http_response_code(400); die('Invalid URL');
}
$parsed = parse_url($url);
$host = strtolower($parsed['host'] ?? '');
// Bloquer si hote absent de la whitelist
if (empty($allowed_hosts) || !in_array($host, $allowed_hosts, true)) {
http_response_code(403); die('Host not allowed');
}
// Bloquer les IPs privees, loopback, metadata cloud
function is_private_host(string $host): bool {
// Loopback / localhost
if ($host === 'localhost' || $host === '::1') return true;
// Metadata AWS/GCP/Azure
if ($host === '169.254.169.254' || $host === 'metadata.google.internal') return true;
// Resoudre et verifier si IP privee
$ip = filter_var($host, FILTER_VALIDATE_IP) ? $host : gethostbyname($host);
if (!filter_var($ip, FILTER_VALIDATE_IP)) return true; // echec resolution
return !filter_var($ip, FILTER_VALIDATE_IP, [
'flags' => FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
]);
}
// Note : on autorise les IPs privees si elles sont explicitement dans config.json
// (cas Tunarr/Jellyfin sur le reseau local) — on bloque seulement les hotes
// qui ne sont PAS dans la whitelist, ce qui couvre deja le SSRF.
// ── Proxy ─────────────────────────────────────────────────────────────────────
$base = preg_replace('#[^/]*(\?.*)?$#', '', $url);
$origin = $parsed['scheme'] . '://' . $parsed['host'];
$port = $parsed['port'] ?? null;
if ($port) $origin .= ':' . $port;
$path = $parsed['path'] ?? '';
$is_segment = preg_match('#\.(ts|aac|mp4|m4s|fmp4)(\?|$)#i', $path);
header('Access-Control-Allow-Origin: *');
header('Cache-Control: no-cache');
$ua = $_SERVER['HTTP_USER_AGENT'] ?? 'Mozilla/5.0';
if ($is_segment) {
header('Content-Type: video/MP2T');
header('X-Content-Type-Options: nosniff');
if (ob_get_level()) ob_end_clean();
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_USERAGENT => $ua,
CURLOPT_HTTPHEADER => ['Accept: */*'],
CURLOPT_RETURNTRANSFER => false,
CURLOPT_WRITEFUNCTION => function($ch, $data) {
echo $data; flush(); return strlen($data);
},
CURLOPT_HEADERFUNCTION => function($ch, $header) {
$h = trim($header);
if (preg_match('/^Content-Type:/i', $h)) header($h);
return strlen($header);
},
]);
$ok = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if (!$ok || $code >= 400) http_response_code($code ?: 502);
curl_close($ch);
} else {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_TIMEOUT => 15,
CURLOPT_USERAGENT => $ua,
CURLOPT_HTTPHEADER => ['Accept: */*'],
CURLOPT_RETURNTRANSFER => true,
]);
$body = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($body === false || $code >= 400) {
http_response_code($code ?: 502); die("Upstream error $code");
}
header('Content-Type: application/vnd.apple.mpegurl');
$proxy_base = (isset($_SERVER['HTTPS']) ? 'https' : 'http')
. '://' . $_SERVER['HTTP_HOST']
. strtok($_SERVER['REQUEST_URI'], '?')
. '?url=';
$out = [];
foreach (explode("\n", $body) as $line) {
$line = rtrim($line);
if ($line === '' || $line[0] === '#') {
$line = preg_replace_callback('/URI="([^"]+)"/', function($m) use ($base, $origin, $proxy_base) {
$seg = strpos($m[1], 'http') === 0 ? $m[1]
: ($m[1][0] === '/' ? $origin . $m[1] : $base . $m[1]);
return 'URI="' . $proxy_base . urlencode($seg) . '"';
}, $line);
$out[] = $line;
} else {
$seg = strpos($line, 'http') === 0 ? $line
: ($line[0] === '/' ? $origin . $line : $base . $line);
$out[] = $proxy_base . urlencode($seg);
}
}
echo implode("\n", $out);
}