<?php
// =============================================================================
// /api/webview/img.php?u=<url>
//
// Image proxy + on-disk cache. Used by wv_event_view() (see _wv_common.php's
// wv_img_proxy helper) so team badges, league logos, event thumbs render
// through the panel's own host. End-user devices on networks that block or
// throttle thesportsdb.com still get the assets cleanly, and we don't pay
// the round-trip on every hit (cache TTL = 30 days).
//
// Strict allow-list of upstream hosts mirrors wv_img_proxy() — anything
// else returns 403.
// =============================================================================

$url = isset($_GET['u']) ? trim((string)$_GET['u']) : '';
if ($url === '' || !preg_match('#^https?://#i', $url) || strlen($url) > 2048) {
    header('Content-Type: text/plain; charset=utf-8', true, 400);
    echo "missing/invalid u"; exit;
}

$host = parse_url($url, PHP_URL_HOST);
$ALLOWED = [
    'thesportsdb.com', 'www.thesportsdb.com',
    'r2.thesportsdb.com',                       // Cloudflare R2 — current CDN
    'cdn.sportsdb.com',
    'image.tmdb.org',
    'www.thecocktaildb.com',
];
$ok = false;
if ($host) {
    foreach ($ALLOWED as $h) {
        if (strcasecmp($host, $h) === 0) { $ok = true; break; }
    }
}
if (!$ok) {
    header('Content-Type: text/plain; charset=utf-8', true, 403);
    echo "host not allowed"; exit;
}

// On-disk cache. data/img_cache/<sha1[0:2]>/<sha1>.bin. The leading 2-char
// directory shard keeps any single dir under ~1k files for buyers with
// big catalogs.
require_once __DIR__ . '/../../includes/config.php';
$cache_root = PANEL_ROOT . '/data/img_cache';
$h = sha1($url);
$dir = $cache_root . '/' . substr($h, 0, 2);
$cache_path = $dir . '/' . $h . '.bin';

$serve = function (string $path) {
    // Sniff content-type from the file bytes. JPEG/PNG/WebP/GIF cover everything
    // we'd actually see from sportsdb/tmdb/etc.
    $f = fopen($path, 'rb');
    $head = fread($f, 32);
    fclose($f);
    $ct = 'application/octet-stream';
    if (strncmp($head, "\x89PNG", 4) === 0)              $ct = 'image/png';
    elseif (strncmp($head, "\xFF\xD8", 2) === 0)         $ct = 'image/jpeg';
    elseif (strncmp($head, "RIFF", 4) === 0 && substr($head, 8, 4) === 'WEBP')
                                                         $ct = 'image/webp';
    elseif (strncmp($head, "GIF8", 4) === 0)             $ct = 'image/gif';
    header('Content-Type: ' . $ct);
    header('Cache-Control: public, max-age=2592000, immutable');
    header('Content-Length: ' . filesize($path));
    readfile($path);
    exit;
};

if (is_file($cache_path) && filesize($cache_path) > 0) {
    $serve($cache_path);
}

// Cache miss — fetch upstream, save, serve.
if (!is_dir($dir)) @mkdir($dir, 0775, true);

$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER  => true,
    CURLOPT_FOLLOWLOCATION  => true,
    CURLOPT_MAXREDIRS       => 3,
    CURLOPT_CONNECTTIMEOUT  => 5,
    CURLOPT_TIMEOUT         => 12,
    CURLOPT_USERAGENT       => 'Mozilla/5.0 (panel image proxy)',
    // Block file://, gopher://, etc. via redirect chains
    CURLOPT_PROTOCOLS       => (defined('CURLPROTO_HTTPS') ? CURLPROTO_HTTPS : 0)
                             | (defined('CURLPROTO_HTTP')  ? CURLPROTO_HTTP  : 0),
    CURLOPT_REDIR_PROTOCOLS => (defined('CURLPROTO_HTTPS') ? CURLPROTO_HTTPS : 0)
                             | (defined('CURLPROTO_HTTP')  ? CURLPROTO_HTTP  : 0),
]);
$body = curl_exec($ch);
$code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($code !== 200 || $body === false || $body === '') {
    header('Content-Type: text/plain; charset=utf-8', true, 502);
    echo "upstream $code"; exit;
}
// Hard size cap so a misbehaving CDN can't fill the disk.
if (strlen($body) > 5 * 1024 * 1024) {
    header('Content-Type: text/plain; charset=utf-8', true, 502);
    echo "image too large"; exit;
}
// Atomic write so a half-written cache file can't be served on a race.
$tmp = $cache_path . '.tmp.' . bin2hex(random_bytes(4));
if (file_put_contents($tmp, $body) === strlen($body)) {
    @rename($tmp, $cache_path);
} else {
    @unlink($tmp);
    header('Content-Type: text/plain; charset=utf-8', true, 500);
    echo "cache write failed"; exit;
}
$serve($cache_path);
