<?php /** * Simple File Manager PRO * Único arquivo PHP - Gerenciador de arquivos seguro e moderno. * * @author Melhorias aplicadas sobre código base * @version 2.0 * @license MIT */ // ==================== CONFIGURAÇÕES ==================== $config = [ 'use_auth' => true, // Exige login 'auth_users' => [ // Usuário => hash de senha (password_hash) 'admin' => '$2y$10$N9qo8uLOickgx2ZMRZoMy.MrqT4JqXzPxQN5h6ZRJ4nQyJ9Z/3Mou', // senha: admin ], 'readonly_users' => [], // Usuários apenas leitura 'root_path' => $_SERVER['DOCUMENT_ROOT'], // Pasta raiz (recomendado usar um subdiretório) 'max_upload_size' => 50 * 1024 * 1024, // 50MB 'show_hidden' => false, // Mostrar arquivos .* 'allowed_extensions'=> [], // [] = todos; ex: ['jpg','png','txt'] 'allow_zip' => true, // Habilitar compactar/extrair ZIP 'max_search_depth' => 3, // Profundidade máxima da busca recursiva 'login_attempts' => 5, // Tentativas de login antes de bloquear (15min) ]; // ==================== INICIALIZAÇÃO ==================== if (session_status() === PHP_SESSION_NONE) session_start(); // Funções de segurança e utilitários function safe_path($path, $root) { $path = str_replace('\\', '/', $path); $path = preg_replace('/\.\.\/|\.\.\\\\/', '', $path); $full = realpath($root . '/' . ltrim($path, '/')); if ($full === false || strpos($full, realpath($root)) !== 0) { return ''; } return ltrim(substr($full, strlen(realpath($root))), '/'); } function generate_csrf_token() { if (empty($_SESSION['csrf_token'])) { $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); } return $_SESSION['csrf_token']; } function verify_csrf_token($token) { return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token); } function format_size($size) { $units = ['B', 'KB', 'MB', 'GB', 'TB']; $i = 0; while ($size >= 1024 && $i < count($units)-1) { $size /= 1024; $i++; } return round($size, 2) . ' ' . $units[$i]; } function get_file_icon($ext) { $icons = [ 'folder' => '📁', 'php' => '🐘', 'html' => '🌐', 'css' => '🎨', 'js' => '⚡', 'jpg' => '🖼️', 'png' => '🖼️', 'gif' => '🖼️', 'svg' => '🖼️', 'pdf' => '📄', 'txt' => '📃', 'md' => '📃', 'zip' => '📦', 'rar' => '📦', 'tar' => '📦', 'mp3' => '🎵', 'mp4' => '🎬', 'sql' => '🗄️', 'json' => '📊', 'xml' => '📊', ]; return $icons[$ext] ?? '📄'; } function is_text_file($file) { $text_exts = ['php','html','htm','css','js','txt','md','json','xml','sql','csv','ini','conf','log','sh','py','rb']; return in_array(strtolower(pathinfo($file, PATHINFO_EXTENSION)), $text_exts); } function is_image_file($file) { return in_array(strtolower(pathinfo($file, PATHINFO_EXTENSION)), ['jpg','jpeg','png','gif','bmp','svg','webp']); } // Autenticação com bloqueio temporário function is_logged_in() { global $config; if (!$config['use_auth']) return true; return !empty($_SESSION['logged_in']) && $_SESSION['logged_in'] === true; } function login($user, $pass) { global $config; if (isset($_SESSION['login_attempts']) && $_SESSION['login_attempts'] >= $config['login_attempts']) { if (time() - $_SESSION['last_attempt'] < 900) return false; // 15 min $_SESSION['login_attempts'] = 0; } if (isset($config['auth_users'][$user]) && password_verify($pass, $config['auth_users'][$user])) { $_SESSION['logged_in'] = true; $_SESSION['username'] = $user; $_SESSION['login_attempts'] = 0; return true; } $_SESSION['login_attempts'] = ($_SESSION['login_attempts'] ?? 0) + 1; $_SESSION['last_attempt'] = time(); return false; } function logout() { unset($_SESSION['logged_in'], $_SESSION['username'], $_SESSION['csrf_token']); session_destroy(); } function is_readonly() { global $config; if (!is_logged_in()) return true; return in_array($_SESSION['username'], $config['readonly_users']); } // Processar login/logout if (isset($_GET['logout'])) { logout(); header('Location: ' . $_SERVER['PHP_SELF']); exit; } if (isset($_POST['login'])) { if (login($_POST['username'], $_POST['password'])) { header('Location: ' . $_SERVER['PHP_SELF']); exit; } $login_error = 'Usuário ou senha incorretos!'; } // ==================== PROCESSAMENTO DE AÇÕES ==================== $current_path = isset($_GET['p']) ? safe_path($_GET['p'], $config['root_path']) : ''; $full_path = $config['root_path'] . '/' . $current_path; $full_path = realpath($full_path) ?: $config['root_path']; $message = ''; $error = ''; // CSRF check para ações POST if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && !verify_csrf_token($_POST['csrf_token'] ?? '')) { $error = 'Token de segurança inválido. Recarregue a página e tente novamente.'; } elseif ($_SERVER['REQUEST_METHOD'] === 'POST' && is_logged_in() && !is_readonly()) { // Upload múltiplo if (isset($_FILES['uploads']) && $_POST['action'] === 'upload') { foreach ($_FILES['uploads']['error'] as $key => $err) { if ($err === UPLOAD_ERR_OK) { $name = basename($_FILES['uploads']['name'][$key]); $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION)); if (!empty($config['allowed_extensions']) && !in_array($ext, $config['allowed_extensions'])) { $error .= "Extensão não permitida: $name<br>"; continue; } $target = $full_path . '/' . $name; if (move_uploaded_file($_FILES['uploads']['tmp_name'][$key], $target)) { $message .= "Arquivo $name enviado com sucesso.<br>"; } else { $error .= "Falha ao enviar $name.<br>"; } } } } // Criar pasta elseif (isset($_POST['new_folder']) && $_POST['action'] === 'mkdir') { $folder = safe_path($_POST['folder_name'], $config['root_path']); if ($folder && !file_exists($full_path . '/' . $folder)) { mkdir($full_path . '/' . $folder, 0755, true) ? $message = 'Pasta criada.' : $error = 'Erro ao criar pasta.'; } else $error = 'Nome inválido ou já existe.'; } // Criar arquivo elseif (isset($_POST['new_file']) && $_POST['action'] === 'touch') { $file = safe_path($_POST['file_name'], $config['root_path']); if ($file && !file_exists($full_path . '/' . $file)) { file_put_contents($full_path . '/' . $file, '') !== false ? $message = 'Arquivo criado.' : $error = 'Erro ao criar arquivo.'; } else $error = 'Nome inválido ou já existe.'; } // Renomear elseif (isset($_POST['old_name'], $_POST['new_name']) && $_POST['action'] === 'rename') { $old = $full_path . '/' . basename($_POST['old_name']); $new = $full_path . '/' . safe_path($_POST['new_name'], $config['root_path']); rename($old, $new) ? $message = 'Renomeado.' : $error = 'Erro ao renomear.'; } // Copiar (suporta destino com caminho relativo) elseif (isset($_POST['source'], $_POST['dest']) && $_POST['action'] === 'copy') { $src = $full_path . '/' . basename($_POST['source']); $dest_full = $config['root_path'] . '/' . safe_path($_POST['dest'], $config['root_path']); if (is_dir($dest_full)) $dest_full .= '/' . basename($_POST['source']); copy($src, $dest_full) ? $message = 'Copiado.' : $error = 'Erro ao copiar.'; } // Mover elseif (isset($_POST['source'], $_POST['dest']) && $_POST['action'] === 'move') { $src = $full_path . '/' . basename($_POST['source']); $dest_full = $config['root_path'] . '/' . safe_path($_POST['dest'], $config['root_path']); if (is_dir($dest_full)) $dest_full .= '/' . basename($_POST['source']); rename($src, $dest_full) ? $message = 'Movido.' : $error = 'Erro ao mover.'; } // Compactar ZIP elseif (isset($_POST['zip_items']) && $_POST['action'] === 'zip' && $config['allow_zip'] && class_exists('ZipArchive')) { $zip = new ZipArchive(); $zipname = 'backup_' . date('Ymd_His') . '.zip'; $zippath = $full_path . '/' . $zipname; if ($zip->open($zippath, ZipArchive::CREATE) === true) { foreach ($_POST['items'] as $item) { $itemPath = $full_path . '/' . basename($item); if (is_file($itemPath)) $zip->addFile($itemPath, basename($item)); elseif (is_dir($itemPath)) { $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($itemPath, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST); foreach ($files as $file) { $zip->addFile($file->getPathname(), substr($file->getPathname(), strlen($full_path)+1)); } } } $zip->close(); $message = "Arquivo ZIP criado: <a href='?action=download&p=".urlencode($current_path)."&item=".urlencode($zipname)."'>$zipname</a>"; } else $error = 'Erro ao criar ZIP.'; } // Extrair ZIP elseif (isset($_POST['extract_zip']) && $_POST['action'] === 'unzip' && $config['allow_zip'] && class_exists('ZipArchive')) { $zipfile = $full_path . '/' . basename($_POST['extract_zip']); $zip = new ZipArchive(); if ($zip->open($zipfile) === true) { $zip->extractTo($full_path); $zip->close(); $message = 'Arquivo extraído.'; } else $error = 'Erro ao extrair ZIP.'; } // Deletar elseif (isset($_GET['action']) && $_GET['action'] === 'delete' && isset($_GET['item'])) { $item = $full_path . '/' . basename($_GET['item']); if (is_dir($item)) rmdir($item) ? $message = 'Pasta deletada.' : $error = 'Pasta não vazia.'; else unlink($item) ? $message = 'Arquivo deletado.' : $error = 'Erro ao deletar.'; header('Location: ?p=' . urlencode($current_path)); exit; } // Salvar edição elseif (isset($_POST['save_file'], $_POST['file_path'], $_POST['content']) && $_POST['action'] === 'save') { $file = safe_path($_POST['file_path'], $config['root_path']); $full_file = $config['root_path'] . '/' . $file; if (file_put_contents($full_file, $_POST['content']) !== false) { $message = 'Arquivo salvo.'; $edit_file = $file; $edit_content = $_POST['content']; } else $error = 'Erro ao salvar.'; } } // Ações GET (download, view, edit, delete) $action = $_GET['action'] ?? ''; if ($action === 'download' && isset($_GET['item']) && is_logged_in()) { $file = $full_path . '/' . basename($_GET['item']); if (is_file($file)) { header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename="' . basename($file) . '"'); header('Content-Length: ' . filesize($file)); readfile($file); exit; } } if ($action === 'view' && isset($_GET['item']) && is_logged_in()) { $file = $full_path . '/' . basename($_GET['item']); if (is_file($file) && is_image_file($file)) { header('Content-Type: ' . mime_content_type($file)); readfile($file); exit; } } if ($action === 'edit' && isset($_GET['item']) && is_logged_in()) { $file = $full_path . '/' . basename($_GET['item']); if (is_file($file) && is_text_file($file)) { $edit_file = $current_path . '/' . basename($_GET['item']); $edit_content = file_get_contents($file); } } if ($action === 'delete' && isset($_GET['item']) && is_logged_in() && !is_readonly()) { // Já tratado via POST, mas manter GET com confirmação echo "<script>if(confirm('Deletar permanentemente?')) location.href='?action=delete_confirm&p=".urlencode($current_path)."&item=".urlencode($_GET['item'])."'; else history.back();</script>"; exit; } // Busca $search_results = []; if (isset($_GET['search']) && !empty($_GET['q'])) { $searchTerm = $_GET['q']; $depth = 0; $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($config['root_path'], RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST); foreach ($iterator as $file) { if ($iterator->getDepth() > $config['max_search_depth']) continue; if (strpos($file->getFilename(), $searchTerm) !== false || strpos($file->getPathname(), $searchTerm) !== false) { $search_results[] = [ 'name' => $file->getFilename(), 'path' => substr($file->getPathname(), strlen($config['root_path'])+1), 'is_dir' => $file->isDir(), 'size' => $file->isDir() ? '-' : format_size($file->getSize()), ]; } if (count($search_results) > 200) break; } } // Listagem de arquivos/pastas (com ordenação) $items = []; if (is_dir($full_path)) { $dh = opendir($full_path); while (($file = readdir($dh)) !== false) { if ($file === '.' || $file === '..') continue; if (!$config['show_hidden'] && $file[0] === '.') continue; $item_path = $full_path . '/' . $file; $is_dir = is_dir($item_path); $ext = $is_dir ? 'folder' : strtolower(pathinfo($file, PATHINFO_EXTENSION)); if (!empty($config['allowed_extensions']) && !$is_dir && !in_array($ext, $config['allowed_extensions'])) continue; $items[] = [ 'name' => $file, 'is_dir' => $is_dir, 'size' => $is_dir ? '-' : format_size(filesize($item_path)), 'modified' => date('d/m/Y H:i', filemtime($item_path)), 'perms' => substr(sprintf('%o', fileperms($item_path)), -4), 'icon' => get_file_icon($ext), ]; } closedir($dh); $order = $_GET['order'] ?? 'name'; $dir = $_GET['dir'] ?? 'asc'; usort($items, function($a, $b) use ($order, $dir) { if ($order === 'size') $cmp = ($a['size'] === '-' ? -1 : ($b['size'] === '-' ? 1 : (float)$a['size'] <=> (float)$b['size'])); elseif ($order === 'modified') $cmp = strtotime($a['modified']) <=> strtotime($b['modified']); else $cmp = strcasecmp($a['name'], $b['name']); return $dir === 'asc' ? $cmp : -$cmp; }); } // Breadcrumb $breadcrumbs = []; if ($current_path) { $parts = explode('/', $current_path); $path_build = ''; foreach ($parts as $part) { if ($part) { $path_build .= ($path_build ? '/' : '') . $part; $breadcrumbs[] = ['name' => $part, 'path' => $path_build]; } } } // Espaço em disco $disk_free = disk_free_space($config['root_path']); $disk_total = disk_total_space($config['root_path']); $disk_used_percent = round((1 - $disk_free / $disk_total) * 100); ?> <!DOCTYPE html> <html lang="pt-BR"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Simple File Manager PRO</title> <style> * { margin:0; padding:0; box-sizing:border-box; } body { font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif; background: var(--bg); color: var(--text); transition: background 0.2s; } :root { --bg:#0f172a; --surface:#1e293b; --border:#334155; --text:#e2e8f0; --text-muted:#94a3b8; --primary:#3b82f6; --primary-hover:#2563eb; --success:#10b981; --danger:#ef4444; --warning:#f59e0b; } .light { --bg:#f1f5f9; --surface:#ffffff; --border:#cbd5e1; --text:#0f172a; --text-muted:#475569; --primary:#3b82f6; --primary-hover:#2563eb; } .container { max-width:1400px; margin:0 auto; padding:20px; } .header { background: var(--surface); border-bottom:1px solid var(--border); padding:15px 20px; display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap; gap:10px; } .header h1 { font-size:1.5rem; color:var(--primary); display:flex; align-items:center; gap:10px; } .btn { padding:8px 16px; border:none; border-radius:6px; cursor:pointer; font-size:0.9rem; transition:0.2s; display:inline-flex; align-items:center; gap:6px; text-decoration:none; } .btn-primary { background:var(--primary); color:white; } .btn-primary:hover { background:var(--primary-hover); } .btn-success { background:var(--success); color:white; } .btn-success:hover { background:#059669; } .btn-danger { background:var(--danger); color:white; } .btn-danger:hover { background:#dc2626; } .btn-warning { background:var(--warning); color:white; } .btn-warning:hover { background:#d97706; } .btn-secondary { background:var(--text-muted); color:white; } .btn-secondary:hover { background:#475569; } .alert { padding:12px 16px; border-radius:6px; margin:15px 0; } .alert-success { background:#064e3b; color:#6ee7b7; border:1px solid #059669; } .alert-error { background:#450a0a; color:#fca5a5; border:1px solid #dc2626; } .breadcrumb { background:var(--surface); padding:10px 20px; border-bottom:1px solid var(--border); display:flex; align-items:center; gap:8px; flex-wrap:wrap; } .breadcrumb a { color:var(--primary); text-decoration:none; } .breadcrumb a:hover { text-decoration:underline; } .toolbar { background:var(--surface); padding:15px 20px; border-bottom:1px solid var(--border); display:flex; gap:10px; flex-wrap:wrap; align-items:center; } .toolbar input, .toolbar select { background:var(--bg); border:1px solid var(--border); color:var(--text); padding:8px 12px; border-radius:6px; } .file-table { width:100%; border-collapse:collapse; background:var(--surface); border-radius:8px; overflow:auto; } .file-table th, .file-table td { padding:12px 16px; text-align:left; border-bottom:1px solid var(--border); } .file-table th { background:var(--bg); color:var(--text-muted); font-weight:600; cursor:pointer; user-select:none; } .file-table tr:hover { background:rgba(59,130,246,0.1); } .file-name { display:flex; align-items:center; gap:10px; color:var(--text); text-decoration:none; } .file-actions { display:flex; gap:5px; flex-wrap:wrap; } .modal-overlay { display:none; position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.7); z-index:1000; justify-content:center; align-items:center; } .modal-overlay.active { display:flex; } .modal { background:var(--surface); border-radius:12px; padding:25px; width:90%; max-width:500px; border:1px solid var(--border); } .editor-textarea { width:100%; min-height:500px; background:var(--bg); color:var(--text); border:1px solid var(--border); padding:16px; font-family:monospace; font-size:0.9rem; line-height:1.6; resize:vertical; } @media (max-width:768px) { .file-table th:nth-child(3), .file-table td:nth-child(3) { display:none; } } .theme-toggle { cursor:pointer; background:var(--surface); border:1px solid var(--border); border-radius:6px; padding:6px 10px; } .search-box { display:flex; gap:6px; margin-left:auto; } .disk-usage { font-size:0.8rem; color:var(--text-muted); } </style> </head> <body class="<?php echo isset($_COOKIE['theme']) && $_COOKIE['theme'] === 'light' ? 'light' : ''; ?>"> <script> function toggleTheme() { document.body.classList.toggle('light'); const isLight = document.body.classList.contains('light'); document.cookie = "theme=" + (isLight ? 'light' : 'dark') + ";path=/;max-age=31536000"; } </script> <?php if (!is_logged_in() && $config['use_auth']): ?> <div class="login-container" style="display:flex; justify-content:center; align-items:center; min-height:100vh;"> <div class="modal" style="max-width:400px;"> <h2 style="text-align:center; margin-bottom:20px;">📁 Simple File Manager</h2> <?php if (isset($login_error)) echo "<div class='alert alert-error'>$login_error</div>"; ?> <form method="POST"> <div class="form-group" style="margin-bottom:15px;"><label>Usuário</label><input type="text" name="username" required autofocus style="width:100%; padding:10px;"></div> <div class="form-group" style="margin-bottom:20px;"><label>Senha</label><input type="password" name="password" required style="width:100%; padding:10px;"></div> <button type="submit" name="login" class="btn btn-primary" style="width:100%;">Entrar</button> </form> <p style="text-align:center; margin-top:20px; color:var(--text-muted);">Padrão: admin / admin</p> </div> </div> <?php else: ?> <div class="header"> <h1>📁 Simple File Manager PRO</h1> <div style="display:flex; gap:10px; align-items:center;"> <span class="theme-toggle" onclick="toggleTheme()">🌓 Tema</span> <span>👤 <?= htmlspecialchars($_SESSION['username'] ?? 'guest') ?> <?= is_readonly() ? '(leitura)' : '' ?></span> <a href="?logout=1" class="btn btn-danger btn-sm">Sair</a> </div> </div> <div class="breadcrumb"> <a href="?">🏠 Home</a> <?php foreach ($breadcrumbs as $crumb): ?> <span>/</span> <a href="?p=<?= urlencode($crumb['path']) ?>"><?= htmlspecialchars($crumb['name']) ?></a> <?php endforeach; ?> </div> <?php if ($message): ?><div class="container"><div class="alert alert-success">✅ <?= $message ?></div></div><?php endif; ?> <?php if ($error): ?><div class="container"><div class="alert alert-error">❌ <?= $error ?></div></div><?php endif; ?> <!-- Barra de ferramentas --> <div class="toolbar"> <?php if (!is_readonly()): ?> <form method="POST" enctype="multipart/form-data" style="display:flex; gap:6px;"> <input type="file" name="uploads[]" multiple> <input type="hidden" name="action" value="upload"> <input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>"> <button type="submit" class="btn btn-success">📤 Upload</button> </form> <button onclick="showModal('folderModal')" class="btn btn-primary">📁 Nova Pasta</button> <button onclick="showModal('fileModal')" class="btn btn-primary">📄 Novo Arquivo</button> <?php endif; ?> <form class="search-box" method="GET"> <input type="text" name="q" placeholder="Buscar..." value="<?= htmlspecialchars($_GET['q'] ?? '') ?>"> <button type="submit" name="search" class="btn btn-secondary">🔍</button> </form> <div class="disk-usage">💾 Disco: <?= $disk_used_percent ?>% usado (<?= format_size($disk_total - $disk_free) ?> / <?= format_size($disk_total) ?>)</div> </div> <div class="container"> <?php if (isset($_GET['search'])): ?> <h2>Resultados para "<?= htmlspecialchars($_GET['q']) ?>"</h2> <?php if (empty($search_results)): ?><p>Nenhum resultado.</p><?php else: ?> <table class="file-table"><thead><tr><th>Nome</th><th>Caminho</th><th>Tamanho</th></tr></thead><tbody> <?php foreach ($search_results as $res): ?> <tr><td><a href="?p=<?= urlencode(dirname($res['path'])) ?>" class="file-name"><?= $res['is_dir'] ? '📁' : '📄' ?> <?= htmlspecialchars($res['name']) ?></a></td> <td><?= htmlspecialchars($res['path']) ?></td><td><?= $res['size'] ?></td></tr> <?php endforeach; ?> </tbody></table> <?php endif; ?> <?php elseif (isset($edit_file)): ?> <!-- Editor de texto --> <div style="margin-bottom:15px;"><a href="?p=<?= urlencode($current_path) ?>" class="btn btn-secondary">⬅️ Voltar</a></div> <div class="editor-container"> <h3>✏️ Editando: <?= htmlspecialchars(basename($edit_file)) ?></h3> <form method="POST"> <input type="hidden" name="action" value="save"> <input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>"> <input type="hidden" name="file_path" value="<?= htmlspecialchars($edit_file) ?>"> <textarea name="content" class="editor-textarea" spellcheck="false"><?= htmlspecialchars($edit_content) ?></textarea> <div class="editor-footer" style="padding:15px; text-align:right;"> <button type="submit" name="save_file" class="btn btn-success">💾 Salvar (Ctrl+S)</button> </div> </form> </div> <?php else: ?> <!-- Listagem de arquivos --> <?php if (empty($items) && $current_path === ''): ?><div class="empty-state" style="text-align:center; padding:60px;">📂 Nenhum arquivo encontrado. Faça upload!</div> <?php else: ?> <table class="file-table"> <thead><tr> <th><a href="?p=<?= urlencode($current_path) ?>&order=name&dir=<?= ($_GET['order']=='name' && $_GET['dir']!='desc') ? 'desc' : 'asc' ?>">Nome</a></th> <th><a href="?p=<?= urlencode($current_path) ?>&order=size&dir=<?= ($_GET['order']=='size' && $_GET['dir']!='desc') ? 'desc' : 'asc' ?>">Tamanho</a></th> <th><a href="?p=<?= urlencode($current_path) ?>&order=modified&dir=<?= ($_GET['order']=='modified' && $_GET['dir']!='desc') ? 'desc' : 'asc' ?>">Modificado</a></th> <th>Ações</th> </tr></thead> <tbody> <?php if ($current_path): ?> <tr><td colspan="4"><a href="?p=<?= urlencode(dirname($current_path) === '.' ? '' : dirname($current_path)) ?>" class="file-name">⬆️ ..</a></td></tr> <?php endif; ?> <?php foreach ($items as $item): ?> <tr> <td><?php if ($item['is_dir']): ?><a href="?p=<?= urlencode(($current_path ? $current_path . '/' : '') . $item['name']) ?>" class="file-name"><span class="file-icon"><?= $item['icon'] ?></span> <?= htmlspecialchars($item['name']) ?></a> <?php else: ?><span class="file-name"><span class="file-icon"><?= $item['icon'] ?></span> <?= htmlspecialchars($item['name']) ?></span><?php endif; ?></td> <td><?= $item['size'] ?></td> <td><?= $item['modified'] ?></td> <td class="file-actions"> <?php if (!$item['is_dir']): ?> <a href="?action=download&p=<?= urlencode($current_path) ?>&item=<?= urlencode($item['name']) ?>" class="btn btn-secondary btn-sm">⬇️</a> <?php if (is_text_file($full_path . '/' . $item['name'])): ?> <a href="?action=edit&p=<?= urlencode($current_path) ?>&item=<?= urlencode($item['name']) ?>" class="btn btn-warning btn-sm">✏️</a> <?php endif; ?> <?php if (is_image_file($full_path . '/' . $item['name'])): ?> <a href="?action=view&p=<?= urlencode($current_path) ?>&item=<?= urlencode($item['name']) ?>" target="_blank" class="btn btn-secondary btn-sm">👁️</a> <?php endif; ?> <?php endif; ?> <?php if (!is_readonly()): ?> <button onclick="renameItem('<?= addslashes($item['name']) ?>')" class="btn btn-secondary btn-sm">📝</button> <button onclick="copyItem('<?= addslashes($item['name']) ?>')" class="btn btn-secondary btn-sm">📋</button> <?php if ($config['allow_zip'] && class_exists('ZipArchive') && $item['is_dir']): ?> <button onclick="zipItems(['<?= addslashes($item['name']) ?>'])" class="btn btn-secondary btn-sm">🗜️</button> <?php endif; ?> <a href="?action=delete&p=<?= urlencode($current_path) ?>&item=<?= urlencode($item['name']) ?>" onclick="return confirm('Deletar <?= addslashes($item['name']) ?>?')" class="btn btn-danger btn-sm">🗑️</a> <?php endif; ?> </td> </tr> <?php endforeach; ?> </tbody> </table> <?php endif; ?> <?php endif; ?> </div> <!-- Modais --> <div id="folderModal" class="modal-overlay"><div class="modal"><h3>📁 Nova Pasta</h3><form method="POST"><input type="hidden" name="action" value="mkdir"><input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>"><input type="text" name="folder_name" placeholder="nome" required><div class="modal-actions" style="margin-top:20px;"><button type="button" onclick="hideModal('folderModal')" class="btn btn-secondary">Cancelar</button><button type="submit" name="new_folder" class="btn btn-primary">Criar</button></div></form></div></div> <div id="fileModal" class="modal-overlay"><div class="modal"><h3>📄 Novo Arquivo</h3><form method="POST"><input type="hidden" name="action" value="touch"><input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>"><input type="text" name="file_name" placeholder="arquivo.txt" required><div class="modal-actions" style="margin-top:20px;"><button type="button" onclick="hideModal('fileModal')" class="btn btn-secondary">Cancelar</button><button type="submit" name="new_file" class="btn btn-primary">Criar</button></div></form></div></div> <div id="renameModal" class="modal-overlay"><div class="modal"><h3>📝 Renomear</h3><form method="POST"><input type="hidden" name="action" value="rename"><input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>"><input type="hidden" name="old_name" id="renameOld"><input type="text" name="new_name" id="renameNew" required><div class="modal-actions"><button type="button" onclick="hideModal('renameModal')" class="btn btn-secondary">Cancelar</button><button type="submit" class="btn btn-primary">Renomear</button></div></form></div></div> <div id="copyModal" class="modal-overlay"><div class="modal"><h3>📋 Copiar/Mover</h3><form method="POST"><input type="hidden" name="action" id="copyAction" value="copy"><input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>"><input type="hidden" name="source" id="copySource"><input type="text" name="dest" id="copyDest" placeholder="destino (caminho relativo)" required><div class="modal-actions"><button type="button" onclick="hideModal('copyModal')" class="btn btn-secondary">Cancelar</button><button type="submit" id="copySubmit" class="btn btn-primary">Copiar</button><button type="button" onclick="document.getElementById('copyAction').value='move'; document.getElementById('copySubmit').innerText='Mover'" class="btn btn-warning">Mover</button></div></form></div></div> <script> function showModal(id) { document.getElementById(id).classList.add('active'); } function hideModal(id) { document.getElementById(id).classList.remove('active'); } function renameItem(name) { document.getElementById('renameOld').value = name; document.getElementById('renameNew').value = name; showModal('renameModal'); } function copyItem(name) { document.getElementById('copySource').value = name; document.getElementById('copyDest').value = name; document.getElementById('copyAction').value = 'copy'; document.getElementById('copySubmit').innerText = 'Copiar'; showModal('copyModal'); } function zipItems(items) { if(confirm('Compactar item(s) selecionado?')) { var form = document.createElement('form'); form.method = 'POST'; form.innerHTML = '<input type="hidden" name="action" value="zip"><input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>"><input type="hidden" name="items[]" value="'+items[0]+'">'; document.body.appendChild(form); form.submit(); } } document.querySelectorAll('.modal-overlay').forEach(m => m.addEventListener('click', e => { if(e.target === m) hideModal(m.id); })); document.addEventListener('keydown', e => { if(e.ctrlKey && e.key === 's') { let btn = document.querySelector('button[name="save_file"]'); if(btn) { e.preventDefault(); btn.click(); } } }); </script> <?php endif; ?> </body> </html>⚙️ Como usar
Salve o código acima em
/var/www/html/filemanager.php.Ajuste as permissões:
sudo chown www-data:www-data /var/www/html/filemanager.php && sudo chmod 644 /var/www/html/filemanager.phpAcesse
http://seu-servidor/filemanager.phpe faça login com:
Usuário:
adminSenha:
adminRecomendação de segurança: Altere a senha imediatamente usando
password_hash('sua_nova_senha', PASSWORD_DEFAULT)e substitua o hash no array$config['auth_users'].🔒 Notas de segurança importantes
Este gerenciador dá acesso completo ao sistema de arquivos dentro da
root_path. Para produção, restrinjaroot_patha uma pasta específica (ex:/var/www/html/files).Sempre utilize HTTPS para evitar interceptação de credenciais.
Considere adicionar autenticação por IP ou .htaccess adicional.
Mantenha o PHP atualizado e desabilite funções perigosas se não forem necessárias (
exec,shell_exec).




0 Comentários
Deixe seu comentário e sua sugestão de conteúdo.