<?php
/**
 * 受控下载：白名单相对路径（相对 downloads/）+ 仅允许 .pdf / .zip / .md，并写入 data/download_log.csv。
 * 用法：GET php/download.php?f=<相对路径>（见 RL_DOWNLOAD_ALLOWED）；禁止路径穿越。
 * 其他扩展名以后需另行约定后再放开。
 */
declare(strict_types=1);

/** 允许下载的相对路径（相对 downloads/，可用子目录；禁止 ..）；类型目前仅 pdf、zip、md */
const RL_DOWNLOAD_ALLOWED = [
    'TV-Magic-Box-User-Manual-v03.pdf',
    'Remote-Test-Box-User-Manual-v03.pdf',
    'TV-Stick-Test-Box-User-Manual-v02.pdf',
    'GD-FW-DWN-Tool-User-Manual-v02.pdf',
    'TTLAB-GD-FW-DWN.zip',
    'Awakeno/Materials.zip',
    'Awakeno/Awakeno-Quick-Bringup-cn.md',
    'Awakeno/Awakeno-Quick-Bringup-en.md',
];

/**
 * 规范化并校验相对路径：正斜杠、无空段、无 ..、无绝对路径。
 *
 * @return non-empty-string|null
 */
function rl_download_normalize_rel(string $raw): ?string
{
    $raw = str_replace('\\', '/', trim($raw));
    $raw = trim($raw, '/');
    if ($raw === '' || str_contains($raw, "\0")) {
        return null;
    }
    $parts = explode('/', $raw);
    foreach ($parts as $seg) {
        if ($seg === '' || $seg === '.' || $seg === '..') {
            return null;
        }
    }

    return $raw;
}

/** @return non-empty-string|null */
function rl_download_allowed_ext(string $relPath): ?string
{
    $ext = strtolower(pathinfo($relPath, PATHINFO_EXTENSION));

    return in_array($ext, ['pdf', 'zip', 'md'], true) ? $ext : null;
}

/** @param non-empty-string $ext */
function rl_download_content_type(string $ext): string
{
    return match ($ext) {
        'pdf' => 'application/pdf',
        'zip' => 'application/zip',
        'md' => 'text/markdown; charset=utf-8',
        default => 'application/octet-stream',
    };
}

if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'GET') {
    http_response_code(405);
    header('Allow: GET');
    header('Content-Type: text/plain; charset=utf-8');
    echo 'Method not allowed';
    exit;
}

$raw = isset($_GET['f']) ? (string) $_GET['f'] : '';
$f = rl_download_normalize_rel($raw);
$ext = $f !== null ? rl_download_allowed_ext($f) : null;
if ($f === null || $ext === null || !in_array($f, RL_DOWNLOAD_ALLOWED, true)) {
    http_response_code(404);
    header('Content-Type: text/plain; charset=utf-8');
    echo 'Not found';
    exit;
}

$root = dirname(__DIR__);
$downloads = $root . DIRECTORY_SEPARATOR . 'downloads';
$path = $downloads . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $f);

$realDir = realpath($downloads);
$realFile = is_file($path) ? realpath($path) : false;
if ($realDir === false || $realFile === false) {
    http_response_code(404);
    header('Content-Type: text/plain; charset=utf-8');
    echo 'Not found';
    exit;
}
$dirPrefix = $realDir . DIRECTORY_SEPARATOR;
if (strncmp($realFile, $dirPrefix, strlen($dirPrefix)) !== 0) {
    http_response_code(404);
    header('Content-Type: text/plain; charset=utf-8');
    echo 'Not found';
    exit;
}
if (!is_readable($realFile)) {
    http_response_code(404);
    header('Content-Type: text/plain; charset=utf-8');
    echo 'Not found';
    exit;
}

require_once __DIR__ . DIRECTORY_SEPARATOR . 'inc' . DIRECTORY_SEPARATOR . 'download-log.php';
rl_download_log_append($root, $f);

$downloadName = basename(str_replace('\\', '/', $f));
$asciiName = preg_replace('/[^\x20-\x7E]/', '_', $downloadName) ?? $downloadName;

header('Content-Type: ' . rl_download_content_type($ext));
header('Content-Disposition: attachment; filename="' . str_replace(['"', '\\'], '', $asciiName) . '"');
header('X-Content-Type-Options: nosniff');
header('Cache-Control: private, max-age=0, must-revalidate');

readfile($realFile);
