addEmptyDir($zip_base);
$entries = scandir($src_path);
if ($entries === false) {
return;
}
foreach ($entries as $entry_name) {
if ($entry_name === '.' || $entry_name === '..') {
continue;
}
$entry_path = $src_path . DIRECTORY_SEPARATOR . $entry_name;
// Sembolik linkleri atla — traversal güvenliği.
if (is_link($entry_path)) {
continue;
}
// ZIP içindeki yol her zaman / ayracı kullanır.
$zip_entry = $zip_base . $entry_name;
if (is_dir($entry_path)) {
// Alt klasörü "/" ile sonlandırarak özyinelemeli ekle.
// Boş olsa bile zip_add_dir içinde addEmptyDir çağrılır.
zip_add_dir($zip, $entry_path, $zip_entry . '/');
} else {
$zip->addFile($entry_path, $zip_entry);
}
}
}
// =============================================================================
// --- Authentication ---
// =============================================================================
// Set secure cookie parameters BEFORE session_start().
// session_set_cookie_params() array form and 'samesite' key are PHP 7.3+ only.
// Use ini_set() for full PHP 7.0–8.4 compatibility.
ini_set('session.cookie_httponly', '1');
ini_set('session.cookie_lifetime', '0');
ini_set('session.cookie_path', '/');
if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') {
ini_set('session.cookie_secure', '1');
}
// SameSite via ini_set is supported from PHP 7.3+; silently ignored on older.
ini_set('session.cookie_samesite', 'Strict');
session_start();
// Security headers — sent early, before any output.
header('X-Frame-Options: SAMEORIGIN');
header('X-Content-Type-Options: nosniff');
header('Referrer-Policy: no-referrer');
// Per-session CSRF token — embedded in every form, validated on every POST.
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
if (isset($_GET['logout'])) {
session_destroy();
header('Location: ' . basename(__FILE__));
exit;
}
// --- Simple session-based brute-force rate limiting ---
if (!isset($_SESSION['login_attempts'])) {
$_SESSION['login_attempts'] = 0;
$_SESSION['login_last'] = 0;
}
if (isset($_POST['password'])) {
$locked = $_SESSION['login_attempts'] >= 5
&& (time() - (int) $_SESSION['login_last']) < 300;
if ($locked) {
$wait = 300 - (time() - (int) $_SESSION['login_last']);
http_response_code(429);
die('
Too many failed attempts. Please wait ' . $wait . ' seconds.
');
}
if (password_verify($_POST['password'], PASSWORD_HASH)) {
// Regenerate session ID on successful login to prevent session fixation.
session_regenerate_id(true);
$_SESSION['auth'] = true;
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
$_SESSION['login_attempts'] = 0;
header('Location: ' . basename(__FILE__));
exit;
}
$_SESSION['login_attempts']++;
$_SESSION['login_last'] = time();
}
if (!isset($_SESSION['auth'])) {
http_response_code(401);
die('Login');
}
// =============================================================================
// --- Current Directory Resolution ---
// =============================================================================
// Navigation is unrestricted across the filesystem.
// Individual file operations are each guarded by safe_item_path(),
// which ensures every action stays within $current_dir.
$current_dir = isset($_GET['path']) ? realpath($_GET['path']) : false;
if ($current_dir === false || !is_dir($current_dir)) {
$current_dir = realpath(__DIR__);
}
// =============================================================================
// --- CSRF Verification ---
// =============================================================================
function verify_csrf(): void
{
if (!hash_equals($_SESSION['csrf_token'] ?? '', $_POST['csrf_token'] ?? '')) {
http_response_code(403);
exit('Forbidden: invalid or missing CSRF token.');
}
}
// =============================================================================
// --- Flash Message ---
// =============================================================================
// Messages are stored raw in the session; escaping happens at render time.
$message = $_SESSION['message'] ?? '';
unset($_SESSION['message']);
// =============================================================================
// --- POST Action Handlers ---
// =============================================================================
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
verify_csrf();
$action = (string) ($_POST['action'] ?? '');
// -------------------------------------------------------------------------
// Bulk: Create ZIP from selected items
// -------------------------------------------------------------------------
if ($action === 'zip_selected') {
if (empty($_POST['selected_items'])) {
$_SESSION['message'] = 'No items selected.';
} elseif (!class_exists('ZipArchive')) {
$_SESSION['message'] = 'Error: ZipArchive extension is not available on this server.';
} else {
// Remove execution time limit for large backups.
set_time_limit(0);
$zip_filename = 'archive-' . date('Y-m-d-His') . '.zip';
$zip_filepath = $current_dir . DIRECTORY_SEPARATOR . $zip_filename;
$zip = new ZipArchive();
if ($zip->open($zip_filepath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
$_SESSION['message'] = 'Error: Could not create the archive file. Check directory permissions.';
} else {
$items_added = 0;
foreach ($_POST['selected_items'] as $raw_item) {
$item = basename((string) $raw_item);
$item_path = safe_item_path($current_dir, $item);
if ($item_path === false) continue;
if (is_file($item_path)) {
// Single file — add directly with forward-slash name.
$zip->addFile($item_path, $item);
$items_added++;
} elseif (is_dir($item_path)) {
// Directory — recurse, including empty sub-dirs.
// Boş klasörler de geçerli öğedir: addEmptyDir içinde çağrılır.
// Symlinks inside are skipped for security (see zip_add_dir).
zip_add_dir($zip, $item_path, $item . '/');
$items_added++;
}
}
if ($items_added === 0) {
// Seçilen tüm öğeler geçersiz veya erişilemez — temizlik yap.
$zip->close();
if (file_exists($zip_filepath)) {
@unlink($zip_filepath);
}
$_SESSION['message'] = 'Error: No valid items could be added to the archive. Check permissions.';
} elseif ($zip->close() === false) {
// close() başarısız — arşiv bozuk olabilir — temizlik yap.
if (file_exists($zip_filepath)) {
@unlink($zip_filepath);
}
$_SESSION['message'] = 'Error: Failed to finalize the archive. The file has been removed.';
} else {
$_SESSION['message'] = 'Archive "' . $zip_filename . '" created successfully.';
}
}
}
// -------------------------------------------------------------------------
// Bulk: Delete selected items
// -------------------------------------------------------------------------
} elseif ($action === 'delete_selected') {
if (empty($_POST['selected_items'])) {
$_SESSION['message'] = 'No items selected for deletion.';
} else {
$total = count($_POST['selected_items']);
$deleted_count = 0;
foreach ($_POST['selected_items'] as $raw_item) {
$item_path = safe_item_path($current_dir, (string) $raw_item);
if ($item_path === false) continue;
if (is_dir($item_path)) {
if (recursive_delete($item_path)) $deleted_count++;
} elseif (is_file($item_path)) {
if (unlink($item_path)) $deleted_count++;
}
}
if ($deleted_count === 0) {
$_SESSION['message'] = 'Error: No items could be deleted. Check permissions.';
} elseif ($deleted_count < $total) {
// Partial failure — shown as error (red)
$_SESSION['message'] = 'Error: Only ' . $deleted_count . ' of ' . $total . ' item(s) deleted. Some could not be removed.';
} else {
$_SESSION['message'] = $deleted_count . ' item(s) deleted successfully.';
}
}
// -------------------------------------------------------------------------
// Upload
// -------------------------------------------------------------------------
} elseif ($action === 'upload' && isset($_FILES['file'])) {
if ($_FILES['file']['error'] !== UPLOAD_ERR_OK) {
$upload_errors = [
UPLOAD_ERR_INI_SIZE => 'File exceeds the server size limit (php.ini)',
UPLOAD_ERR_FORM_SIZE => 'File exceeds the form size limit',
UPLOAD_ERR_PARTIAL => 'File was only partially uploaded',
UPLOAD_ERR_NO_FILE => 'No file was sent',
UPLOAD_ERR_NO_TMP_DIR => 'Temporary upload folder is missing',
UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk',
UPLOAD_ERR_EXTENSION => 'A PHP extension stopped the upload',
];
$code = $_FILES['file']['error'];
$_SESSION['message'] = 'Upload error: ' . ($upload_errors[$code] ?? 'Unknown error (code ' . $code . ')');
} else {
// Upload hedefi: dosya yöneticisindeki aktif dizin ($current_dir).
// İzin kontrolü aynı dizine karşı yapılır.
// Dosya türü / uzantı kısıtlaması yoktur — genel amaçlı dosya yöneticisi.
$dest = $current_dir . DIRECTORY_SEPARATOR . basename($_FILES['file']['name']);
if (file_exists($dest) && !is_writable($dest)) {
$_SESSION['message'] = 'Error: Destination file exists and is not writable.';
} elseif (!is_writable($current_dir)) {
$_SESSION['message'] = 'Error: No write permission for this directory.';
} elseif (move_uploaded_file($_FILES['file']['tmp_name'], $dest)) {
$_SESSION['message'] = 'File uploaded successfully.';
} elseif (copy($_FILES['file']['tmp_name'], $dest)) {
unlink($_FILES['file']['tmp_name']);
$_SESSION['message'] = 'File uploaded successfully (via copy).';
} else {
$err = error_get_last();
$_SESSION['message'] = 'Upload failed. System error: ' . ($err['message'] ?? 'Unknown');
}
}
// -------------------------------------------------------------------------
// Delete (single item)
// -------------------------------------------------------------------------
} elseif ($action === 'delete') {
$target = safe_item_path($current_dir, (string) ($_POST['target'] ?? ''));
if ($target === false) {
$_SESSION['message'] = 'Error: Item not found or access denied.';
} else {
$name = basename($target);
$deleted = is_dir($target) ? recursive_delete($target) : unlink($target);
$_SESSION['message'] = $deleted
? '"' . $name . '" deleted successfully.'
: 'Error: Could not delete "' . $name . '".';
}
// -------------------------------------------------------------------------
// Rename
// -------------------------------------------------------------------------
} elseif ($action === 'rename') {
$target = safe_item_path($current_dir, (string) ($_POST['target'] ?? ''));
$new_name = basename(trim((string) ($_POST['new_name'] ?? '')));
if ($target === false) {
$_SESSION['message'] = 'Error: Item not found or access denied.';
} elseif ($new_name === '' || $new_name === '.' || $new_name === '..') {
$_SESSION['message'] = 'Error: Invalid new name.';
} else {
$old_name = basename($target);
$new_path = $current_dir . DIRECTORY_SEPARATOR . $new_name;
if (file_exists($new_path)) {
// Hedef isim zaten var — sessiz üzerine yazma yerine açık hata ver.
$_SESSION['message'] = 'Error: "' . h($new_name) . '" already exists. Rename aborted.';
} elseif (rename($target, $new_path)) {
$_SESSION['message'] = 'Renamed "' . $old_name . '" to "' . $new_name . '".';
} else {
$_SESSION['message'] = 'Error: Rename failed.';
}
}
// -------------------------------------------------------------------------
// Chmod
// -------------------------------------------------------------------------
} elseif ($action === 'chmod') {
$target = safe_item_path($current_dir, (string) ($_POST['target'] ?? ''));
$new_perms = trim((string) ($_POST['new_perms'] ?? ''));
if ($target === false) {
$_SESSION['message'] = 'Error: Item not found or access denied.';
} elseif (!preg_match('/^[0-7]{3,4}$/', $new_perms)) {
$_SESSION['message'] = 'Error: Invalid permission code "' . h($new_perms) . '".';
} elseif (chmod($target, octdec($new_perms))) {
$_SESSION['message'] = 'Permissions for "' . basename($target) . '" set to ' . $new_perms . '.';
} else {
$_SESSION['message'] = 'Error: Could not change permissions (check OS support).';
}
// -------------------------------------------------------------------------
// Save (file editor)
// -------------------------------------------------------------------------
} elseif ($action === 'save') {
$filename = basename((string) ($_POST['target'] ?? ''));
$target = safe_item_path($current_dir, $filename);
$content = (string) ($_POST['content'] ?? '');
if ($target === false || !is_file($target)) {
$_SESSION['message'] = 'Error: File not found or access denied.';
header('Location: ' . basename(__FILE__) . '?path=' . urlencode($current_dir));
} elseif (!is_writable($target)) {
$_SESSION['message'] = 'Error: File is not writable. Check permissions.';
header('Location: ' . basename(__FILE__) . '?path=' . urlencode($current_dir) . '&edit=' . urlencode($filename));
} elseif (file_put_contents($target, $content, LOCK_EX) !== false) {
$_SESSION['message'] = '"' . $filename . '" saved successfully.';
header('Location: ' . basename(__FILE__) . '?path=' . urlencode($current_dir) . '&edit=' . urlencode($filename));
} else {
$_SESSION['message'] = 'Error: Could not save "' . $filename . '".';
header('Location: ' . basename(__FILE__) . '?path=' . urlencode($current_dir) . '&edit=' . urlencode($filename));
}
exit;
}
header('Location: ' . basename(__FILE__) . '?path=' . urlencode($current_dir));
exit;
}
// =============================================================================
// --- Download Handler ---
// =============================================================================
if (isset($_GET['download'])) {
$file = safe_item_path($current_dir, (string) $_GET['download']);
if ($file !== false && is_file($file) && is_readable($file)) {
$file_size = filesize($file);
// Clean any open output buffers to avoid buffering large files in RAM.
if (ob_get_level()) {
ob_end_clean();
}
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
// Escape any double-quotes in the filename for the Content-Disposition header.
header('Content-Disposition: attachment; filename="' . str_replace('"', '\\"', basename($file)) . '"');
if ($file_size !== false) {
header('Content-Length: ' . $file_size);
}
readfile($file);
exit;
}
}
// =============================================================================
// --- File Editor ---
// =============================================================================
if (isset($_GET['edit'])) {
$edit_file = safe_item_path($current_dir, (string) $_GET['edit']);
if ($edit_file !== false && is_file($edit_file)) {
$edit_name = basename($edit_file);
$edit_readable = is_readable($edit_file);
$edit_writable = is_writable($edit_file);
$edit_size = filesize($edit_file);
$edit_content = $edit_readable ? file_get_contents($edit_file) : '';
$edit_csrf = h($_SESSION['csrf_token']);
$edit_is_err = str_starts_with($message, 'Error:');
?>
Edit: —
⚠ This file is not readable.
⚠ This file is read-only. You can view but not save changes.
File:
Size:
Modified:
Permissions:
✓ Editable
Ctrl+S to save