Compare commits

...
1 Commits
Author SHA1 Message Date
Michal Sedlák 99b1c96f4b revise backend 2026-10-18 15:55:34 +02:00
23 changed files with 681 additions and 720 deletions
+5
View File
@@ -129,6 +129,7 @@ Z kořene repozitáře:
php tests/workspace_test.php php tests/workspace_test.php
php tests/serverstats_test.php php tests/serverstats_test.php
php tests/remotepaths_test.php php tests/remotepaths_test.php
php tests/backend_test.php
node --check Web/js/theme.js node --check Web/js/theme.js
node --check Web/js/ui.js node --check Web/js/ui.js
node --check Web/content/ftp/js/index.js node --check Web/content/ftp/js/index.js
@@ -136,6 +137,10 @@ node --check Web/content/ftp/js/index.js
PHP testy používají testovací data a nepotřebují připojení k databázi ani SSH. Kontrolují vykreslení stránek, navigaci podle role, zpracování statistik a stavy RAID polí. Node.js slouží u uvedených příkazů ke kontrole syntaxe; pro běh aplikace není potřeba. PHP testy používají testovací data a nepotřebují připojení k databázi ani SSH. Kontrolují vykreslení stránek, navigaci podle role, zpracování statistik a stavy RAID polí. Node.js slouží u uvedených příkazů ke kontrole syntaxe; pro běh aplikace není potřeba.
Test backendu navíc ověřuje přihlášení, oprávnění operací, hranice vzdálených cest, vytváření prázdných souborů a upload. Spouští handlery v oddělených PHP procesech s testovacími náhradami databáze a SFTP; vyžaduje dostupné `proc_open`.
V backendu používej popisné názvy lokálních proměnných ve stylu `camelCase`, například `$username`, `$passwordHash`, `$database`, `$statement`, `$directoryPath` a `$canUpload`. Konfigurační proměnné `$DB_*`, `$SFTP_*`, `$SSH_*` a existující klíče databáze, formulářů a session (`uname`, `pswd`, `defPath`, `upPer` apod.) zůstávají kvůli kompatibilitě zachované. Společné kontroly přihlášení, oprávnění a hesel jsou v `Web/includes/auth.php`; práci s POSIX cestami soustřeď do `Web/includes/remotepaths.php`.
## Časté problémy ## Časté problémy
| Problém | Co zkontrolovat | | Problém | Co zkontrolovat |
+6 -6
View File
@@ -19,7 +19,7 @@ $SFTP_HOST = '172.16.1.11';
$SFTP_USER = 'usbraid'; $SFTP_USER = 'usbraid';
$SFTP_PASS = 'usbraid'; $SFTP_PASS = 'usbraid';
// Default remote path – uses session value if set // Default remote path – uses session value if set
$defaultPath = $_SESSION['defPath'] ?? '/'; $defaultPath = normalizePath($_SESSION['defPath'] ?? '/');
// SSH credentials (placeholders) // SSH credentials (placeholders)
$SSH_HOST = '172.16.1.11'; $SSH_HOST = '172.16.1.11';
@@ -29,12 +29,12 @@ $SSH_PASS = 'usbraid';
/** Helper: Get a MySQLi connection */ /** Helper: Get a MySQLi connection */
function getDatabaseConnection(): mysqli { function getDatabaseConnection(): mysqli {
global $DB_HOST, $DB_USER, $DB_PASS, $DB_NAME; global $DB_HOST, $DB_USER, $DB_PASS, $DB_NAME;
$conn = new mysqli($DB_HOST, $DB_USER, $DB_PASS, $DB_NAME); $database = new mysqli($DB_HOST, $DB_USER, $DB_PASS, $DB_NAME);
if ($conn->connect_error) { if ($database->connect_error) {
die('Connection failed: ' . $conn->connect_error); die('Connection failed: ' . $database->connect_error);
} }
$conn->set_charset('utf8'); $database->set_charset('utf8');
return $conn; return $database;
} }
/** Helper: Initialise SFTP connection */ /** Helper: Initialise SFTP connection */
+69 -128
View File
@@ -1,139 +1,80 @@
<?php <?php
session_start(); require_once __DIR__ . '/../includes/auth.php';
if (empty($_SESSION['admin']) || $_SESSION['admin'] !== true) { requireLogin('../index.php');
session_destroy(); requirePermission('admin', 'You do not have permission to manage users.');
header("location: ../index.php");
exit();
}
ini_set('display_errors', 1);
error_reporting(E_ALL);
require_once __DIR__ . '/../config/config.php'; require_once __DIR__ . '/../config/config.php';
require_once __DIR__ . '/../includes/workspace.php'; require_once __DIR__ . '/../includes/workspace.php';
// $username = "UNAME";
// $password = "PSWD";
// $db = "usbraidlogin";
$conn = getDatabaseConnection(); $database = getDatabaseConnection();
// charset already set in getDatabaseConnection()
if ($conn->connect_error) {
die("Database connection failed: " . $conn->connect_error);
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['uname']) && isset($_POST['pswd'])) { $username = is_string($_POST['uname'] ?? null) ? trim($_POST['uname']) : '';
$uname = trim($_POST['uname']); $password = is_string($_POST['pswd'] ?? null) ? $_POST['pswd'] : '';
$pswd = trim($_POST['pswd']); $defaultPath = is_string($_POST['defPath'] ?? null) ? normalizePath(trim($_POST['defPath'])) : '/';
$is_admin = isset($_POST['admin']) ? 1 : 0; $isAdmin = isset($_POST['admin']) ? 1 : 0;
$defPath = isset($_POST['defPath']) ? trim($_POST['defPath']) : ''; $canDelete = isset($_POST['delPer']) ? 1 : 0;
$delPer = isset($_POST['delPer']) ? (int)$_POST['delPer'] : 0; $canDownload = isset($_POST['downPer']) ? 1 : 0;
$dowPer = isset($_POST['downPer']) ? (int)$_POST['downPer'] : 0; $canUpload = isset($_POST['upPer']) ? 1 : 0;
$upPer = isset($_POST['upPer']) ? (int)$_POST['upPer'] : 0; $_SESSION['message_type'] = 'error';
if (empty($uname) || empty($pswd)) { if ($username === '' || $password === '') {
$_SESSION['message'] = 'Error: Username and password are required!'; $_SESSION['message'] = 'Error: Username and password are required!';
$_SESSION['message_type'] = 'error'; } elseif (!isPasswordValid($password)) {
} else if (!CheckPassword($pswd)) { $_SESSION['message'] = 'Error: Password must be at least 8 characters long, contain at least one number and one uppercase letter!';
$_SESSION['message'] = 'Error: Password must be at least 8 characters long, contain at least one number and one uppercase letter!'; } else {
$_SESSION['message_type'] = 'error'; try {
} else { $statement = $database->prepare('SELECT uname FROM users WHERE uname = ?');
$sql_check = "SELECT * FROM users WHERE uname = ?"; $statement->bind_param('s', $username);
$stmt_check = $conn->prepare($sql_check); $statement->execute();
if (!$stmt_check) { $result = $statement->get_result();
$_SESSION['message'] = 'Error: Database preparation failed.'; $userExists = $result->num_rows > 0;
$_SESSION['message_type'] = 'error'; $result->free();
$statement->close();
if ($userExists) {
$_SESSION['message'] = 'Error: Username already exists!';
} else { } else {
$stmt_check->bind_param("s", $uname); $passwordHash = password_hash($password, PASSWORD_BCRYPT);
$stmt_check->execute(); $statement = $database->prepare('INSERT INTO users (uname, pswd, admin, defPath, delPer, downPer, upPer) VALUES (?, ?, ?, ?, ?, ?, ?)');
$result_check = $stmt_check->get_result(); $statement->bind_param('ssisiii', $username, $passwordHash, $isAdmin, $defaultPath, $canDelete, $canDownload, $canUpload);
$statement->execute();
if ($result_check->num_rows > 0) { $statement->close();
$_SESSION['message'] = 'Error: Username already exists!'; $_SESSION['message'] = 'User added successfully!';
$_SESSION['message_type'] = 'error'; $_SESSION['message_type'] = 'success';
} else {
$sql_insert = "INSERT INTO users (uname, pswd, admin, defPath, delPer, downPer, upPer) VALUES (?, ?, ?, ?, ?, ?, ?)";
$stmt_insert = $conn->prepare($sql_insert);
if (!$stmt_insert) {
$_SESSION['message'] = 'Error: Database preparation failed.';
$_SESSION['message_type'] = 'error';
} else {
$hash = password_hash($pswd, PASSWORD_BCRYPT);
if (!$hash) {
$_SESSION['message'] = 'Error: Password hashing failed.';
$_SESSION['message_type'] = 'error';
} else {
$stmt_insert->bind_param("ssisiii", $uname, $hash, $is_admin, $defPath, $delPer, $dowPer, $upPer);
if ($stmt_insert->execute()) {
$_SESSION['message'] = 'User added successfully!';
$_SESSION['message_type'] = 'success';
} else {
$_SESSION['message'] = 'Error: Failed to add user. Please try again later.';
$_SESSION['message_type'] = 'error';
}
}
}
}
$stmt_check->close();
if (isset($stmt_insert)) {
$stmt_insert->close();
}
} }
} catch (mysqli_sql_exception $exception) {
error_log($exception->getMessage());
$_SESSION['message'] = 'Error: Failed to add user. Please try again later.';
} }
header("Location: adminpanel.php");
exit();
} else {
$_SESSION['message'] = 'Error: Missing form data!';
$_SESSION['message_type'] = 'error';
header("Location: adminpanel.php");
exit();
} }
$database->close();
header('Location: adminpanel.php');
exit;
} }
if (isset($_GET['delete'])) { if (isset($_GET['delete']) && is_string($_GET['delete'])) {
$delete_uname = htmlspecialchars($_GET['delete']); $usernameToDelete = $_GET['delete'];
$sql = "DELETE FROM users WHERE uname=?"; try {
$stmt = $conn->prepare($sql); $statement = $database->prepare('DELETE FROM users WHERE uname = ?');
if ($stmt) { $statement->bind_param('s', $usernameToDelete);
$stmt->bind_param("s", $delete_uname); $statement->execute();
if ($stmt->execute()) { $statement->close();
$_SESSION['message'] = 'User deleted successfully!'; $_SESSION['message'] = 'User deleted successfully!';
$_SESSION['message_type'] = 'success'; $_SESSION['message_type'] = 'success';
} else { } catch (mysqli_sql_exception $exception) {
$_SESSION['message'] = 'Error: Failed to delete user.'; error_log($exception->getMessage());
$_SESSION['message_type'] = 'error'; $_SESSION['message'] = 'Error: Failed to delete user.';
}
$stmt->close();
} else {
$_SESSION['message'] = 'Error: Database preparation failed.';
$_SESSION['message_type'] = 'error'; $_SESSION['message_type'] = 'error';
} }
$database->close();
header("Location: adminpanel.php"); header('Location: adminpanel.php');
exit(); exit;
} }
$result = $conn->query("SELECT uname, admin, defPath, delPer, downPer, upPer FROM users"); $result = $database->query('SELECT uname, admin, defPath, delPer, downPer, upPer FROM users');
$message = $_SESSION['message'] ?? ''; $message = $_SESSION['message'] ?? '';
$message_type = $_SESSION['message_type'] ?? ''; $messageType = $_SESSION['message_type'] ?? '';
unset($_SESSION['message']); unset($_SESSION['message'], $_SESSION['message_type']);
unset($_SESSION['message_type']);
function CheckPassword($password) {
if (strlen($password) < 8) {
return false;
}
if (!preg_match('/[0-9]/', $password)) {
return false;
}
if (!preg_match('/[A-Z]/', $password)) {
return false;
}
return true;
}
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
@@ -169,7 +110,7 @@ function CheckPassword($password) {
<section class="users-layout"> <section class="users-layout">
<?php if ($message): ?> <?php if ($message): ?>
<div class="alert alert-<?php echo $message_type === 'success' ? 'success' : 'danger'; ?>"> <div class="alert alert-<?php echo $messageType === 'success' ? 'success' : 'danger'; ?>">
<?php echo htmlspecialchars($message); ?> <?php echo htmlspecialchars($message); ?>
</div> </div>
<?php endif; ?> <?php endif; ?>
@@ -223,16 +164,16 @@ function CheckPassword($password) {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<?php while ($row = $result->fetch_assoc()) { ?> <?php while ($user = $result->fetch_assoc()) { ?>
<tr> <tr>
<td><?php echo htmlspecialchars($row['uname']); ?></td> <td><?php echo htmlspecialchars($user['uname']); ?></td>
<td><?php echo $row['admin'] ? 'Yes' : 'No'; ?></td> <td><?php echo $user['admin'] ? 'Yes' : 'No'; ?></td>
<td><?php echo htmlspecialchars($row['defPath']); ?></td> <td><?php echo htmlspecialchars($user['defPath']); ?></td>
<td><?php echo $row['delPer'] ? 'Yes' : 'No'; ?></td> <td><?php echo $user['delPer'] ? 'Yes' : 'No'; ?></td>
<td><?php echo $row['downPer'] ? 'Yes' : 'No'; ?></td> <td><?php echo $user['downPer'] ? 'Yes' : 'No'; ?></td>
<td><?php echo $row['upPer'] ? 'Yes' : 'No'; ?></td> <td><?php echo $user['upPer'] ? 'Yes' : 'No'; ?></td>
<td> <td>
<button class="btn btn-danger btn-sm" onclick="confirmDelete('<?php echo htmlspecialchars($row['uname']); ?>')">Delete</button> <button class="btn btn-danger btn-sm" onclick="confirmDelete(<?= workspaceEscape(json_encode($user['uname'])) ?>)">Delete</button>
</td> </td>
</tr> </tr>
<?php } ?> <?php } ?>
@@ -244,5 +185,5 @@ function CheckPassword($password) {
</body> </body>
</html> </html>
<?php <?php
$conn->close(); $database->close();
?> ?>
+43 -82
View File
@@ -1,95 +1,56 @@
<?php <?php
session_start(); require_once __DIR__ . '/../includes/auth.php';
requireLogin('../index.php');
if (!isset($_SESSION['uname'])) {
header("location: ../index.php");
exit();
}
require_once __DIR__ . '/../config/config.php'; require_once __DIR__ . '/../config/config.php';
require_once __DIR__ . '/../includes/workspace.php'; require_once __DIR__ . '/../includes/workspace.php';
// $username = "UNAME";
// $password = "PSWD";
// $db = "usbraidlogin";
$message = ""; $message = '';
$messageType = ""; $messageType = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['current_password'], $_POST['new_password'], $_POST['confirm_password'])) { $messageType = 'danger';
$current_password = htmlspecialchars($_POST['current_password']); $currentPassword = $_POST['current_password'] ?? null;
$new_password = htmlspecialchars($_POST['new_password']); $newPassword = $_POST['new_password'] ?? null;
$confirm_password = htmlspecialchars($_POST['confirm_password']); $confirmPassword = $_POST['confirm_password'] ?? null;
$uname = $_SESSION['uname'];
if(!CheckPassword($new_password)) { if (!is_string($currentPassword) || !is_string($newPassword) || !is_string($confirmPassword)) {
$message = "New password must be at least 8 characters long, contain at least one number and one uppercase letter!"; $message = 'All fields are required!';
$messageType = "danger"; } elseif (!isPasswordValid($newPassword)) {
} else if ($new_password !== $confirm_password) { $message = 'New password must be at least 8 characters long, contain at least one number and one uppercase letter!';
$message = "New passwords do not match!"; } elseif ($newPassword !== $confirmPassword) {
$messageType = "danger"; $message = 'New passwords do not match!';
} else {
$conn = getDatabaseConnection();
// charset already set in getDatabaseConnection()
if ($conn->connect_error) {
$message = "Database connection failed!";
$messageType = "danger";
} else {
$sql = "SELECT pswd FROM users WHERE uname=?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("s", $uname);
$stmt->execute();
$result = $stmt->get_result();
if ($result && $result->num_rows > 0) {
$row = $result->fetch_assoc();
$stored_hash = $row["pswd"];
if (password_verify($current_password, $stored_hash)) {
$new_hash = password_hash($new_password, PASSWORD_BCRYPT);
$update_sql = "UPDATE users SET pswd=? WHERE uname=?";
$update_stmt = $conn->prepare($update_sql);
$update_stmt->bind_param("ss", $new_hash, $uname);
if ($update_stmt->execute()) {
$message = "Password changed successfully!";
$messageType = "success";
} else {
$message = "Error updating password: " . $conn->error;
$messageType = "danger";
}
$update_stmt->close();
} else {
$message = "Current password is incorrect!";
$messageType = "danger";
}
} else {
$message = "User not found!";
$messageType = "danger";
}
$stmt->close();
$conn->close();
}
}
} else { } else {
$message = "All fields are required!"; $database = getDatabaseConnection();
$messageType = "danger"; try {
} $username = $_SESSION['uname'];
} $statement = $database->prepare('SELECT pswd FROM users WHERE uname = ?');
$statement->bind_param('s', $username);
$statement->execute();
$result = $statement->get_result();
$user = $result->fetch_assoc();
$result->free();
$statement->close();
function CheckPassword($password) { if (!$user) {
if (strlen($password) < 8) { $message = 'User not found!';
return false; } elseif (!password_verify($currentPassword, $user['pswd'])) {
$message = 'Current password is incorrect!';
} else {
$passwordHash = password_hash($newPassword, PASSWORD_BCRYPT);
$statement = $database->prepare('UPDATE users SET pswd = ? WHERE uname = ?');
$statement->bind_param('ss', $passwordHash, $username);
$statement->execute();
$statement->close();
$message = 'Password changed successfully!';
$messageType = 'success';
}
} catch (mysqli_sql_exception $exception) {
error_log($exception->getMessage());
$message = 'Unable to change password. Please try again.';
} finally {
$database->close();
}
} }
if (!preg_match('/[0-9]/', $password)) {
return false;
}
if (!preg_match('/[A-Z]/', $password)) {
return false;
}
return true;
} }
?> ?>
+12 -12
View File
@@ -1,15 +1,14 @@
<?php <?php
// Include this at the top to see potential errors require_once __DIR__ . '/../../includes/auth.php';
// Comment out in production requireLogin('../../index.php');
ini_set('display_errors', 1); requirePermission('upPer', 'You do not have permission to perform this operation.');
error_reporting(E_ALL);
require_once __DIR__ . '/../../config/config.php'; require_once __DIR__ . '/../../config/config.php';
$sftp = initializeSFTP(); $sftp = initializeSFTP();
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'createDir') { if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'createDir') {
$currentPath = isset($_POST['currentPath']) ? normalizePath($_POST['currentPath']) : $defaultPath; $currentPath = requireRemotePath($_POST['currentPath'] ?? $defaultPath, $defaultPath);
if (!isset($_POST['dirName'])) { if (!isset($_POST['dirName'])) {
http_response_code(400); http_response_code(400);
@@ -17,17 +16,18 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['
exit; exit;
} }
$dirName = $_POST['dirName']; $directoryName = $_POST['dirName'];
$dirName = preg_replace('/[^\w\-\.]/', '_', $dirName); $directoryName = preg_replace('/[^\w\-\.]/', '_', $directoryName);
if (strpos($currentPath, $defaultPath) !== 0) { if (!isRemoteNameValid($directoryName)) {
$currentPath = $defaultPath; http_response_code(400);
exit('Invalid directory name.');
} }
$newDirPath = $currentPath . '/' . $dirName; $newDirectoryPath = rtrim($currentPath, '/') . '/' . $directoryName;
if ($sftp->mkdir($newDirPath)) { if ($sftp->mkdir($newDirectoryPath)) {
echo "Directory created successfully!"; echo "Directory created successfully!";
} else { } else {
http_response_code(500); http_response_code(500);
@@ -38,4 +38,4 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['
http_response_code(400); http_response_code(400);
echo "Invalid request"; echo "Invalid request";
exit; exit;
+11 -32
View File
@@ -1,29 +1,14 @@
<?php <?php
// Include this at the top to see potential errors require_once __DIR__ . '/../../includes/auth.php';
// Comment out in production requireLogin('../../index.php');
ini_set('display_errors', 1); requirePermission('upPer', 'You do not have permission to perform this operation.');
error_reporting(E_ALL);
session_start();
if(!isset($_SESSION['uname'])){
header("location: ../index.html");
session_destroy();
exit;
}
if(!isset($_SESSION["upPer"]) || $_SESSION["upPer"] != true) {
http_response_code(403);
echo "You don't have permission to create files.";
exit;
}
require_once __DIR__ . '/../../config/config.php'; require_once __DIR__ . '/../../config/config.php';
$sftp = initializeSFTP(); $sftp = initializeSFTP();
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'createFile') { if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'createFile') {
$currentPath = isset($_POST['currentPath']) ? normalizePath($_POST['currentPath']) : $defaultPath; $currentPath = requireRemotePath($_POST['currentPath'] ?? $defaultPath, $defaultPath);
if (!isset($_POST['fileName'])) { if (!isset($_POST['fileName'])) {
http_response_code(400); http_response_code(400);
@@ -41,22 +26,16 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['
$fileName = preg_replace('/[^\w\-\.]/', '_', $fileName); $fileName = preg_replace('/[^\w\-\.]/', '_', $fileName);
if (strpos($currentPath, $defaultPath) !== 0) { if (!isRemoteNameValid($fileName)) {
$currentPath = $defaultPath; http_response_code(400);
exit('Invalid file name.');
} }
$newFilePath = $currentPath . '/' . $fileName; $newFilePath = rtrim($currentPath, '/') . '/' . $fileName;
header('Content-Type: application/json; charset=UTF-8');
$tempFile = tempnam(sys_get_temp_dir(), 'new_file_'); if ($sftp->put($newFilePath, '')) {
file_put_contents($tempFile, '');
if ($sftp->put($newFilePath, $tempFile)) {
@unlink($tempFile);
echo json_encode(['success' => true, 'filePath' => $newFilePath]); echo json_encode(['success' => true, 'filePath' => $newFilePath]);
} else { } else {
@unlink($tempFile);
http_response_code(500); http_response_code(500);
echo json_encode(['success' => false, 'message' => 'Failed to create file.']); echo json_encode(['success' => false, 'message' => 'Failed to create file.']);
} }
@@ -65,4 +44,4 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['
http_response_code(400); http_response_code(400);
echo "Invalid request"; echo "Invalid request";
exit; exit;
+21 -8
View File
@@ -1,26 +1,35 @@
<?php <?php
require_once __DIR__ . '/../../includes/auth.php';
requireLogin('../../index.php');
requirePermission('delPer', 'You do not have permission to perform this operation.');
require_once __DIR__ . '/../../config/config.php'; require_once __DIR__ . '/../../config/config.php';
$sftp = initializeSFTP(); $sftp = initializeSFTP();
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete'])) { if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete'])) {
$itemToDelete = $_POST['delete']; $itemToDelete = requireRemotePath($_POST['delete'], $defaultPath);
$parentDir = remoteDirname($itemToDelete); if ($itemToDelete === $defaultPath) {
http_response_code(400);
exit('Cannot delete the workspace root.');
}
$parentPath = remoteDirname($itemToDelete);
if ($parentDir === '/' || $parentDir === '.') { if ($parentPath === '/' || $parentPath === '.') {
$parentDir = $defaultPath; $parentPath = $defaultPath;
} }
if ($sftp->is_dir($itemToDelete)) { if ($sftp->is_dir($itemToDelete)) {
function deleteFolder($sftp, $folderPath) { function deleteFolder($sftp, $folderPath) {
$items = $sftp->nlist($folderPath); $items = $sftp->nlist($folderPath);
if ($items === false) return false;
foreach ($items as $item) { foreach ($items as $item) {
if ($item === '.' || $item === '..') continue; if ($item === '.' || $item === '..') continue;
$itemPath = $folderPath . '/' . $item; $itemPath = $folderPath . '/' . $item;
if ($sftp->is_dir($itemPath)) { if ($sftp->is_dir($itemPath)) {
deleteFolder($sftp, $itemPath); if (!deleteFolder($sftp, $itemPath)) return false;
} else { } else {
$sftp->delete($itemPath); if (!$sftp->delete($itemPath)) return false;
} }
} }
return $sftp->rmdir($folderPath); return $sftp->rmdir($folderPath);
@@ -31,9 +40,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete'])) {
$success = $sftp->delete($itemToDelete); $success = $sftp->delete($itemToDelete);
} }
header("Location: index.php?path=" . urlencode($parentDir)); if (!$success) {
http_response_code(500);
exit('Failed to delete the item.');
}
header("Location: index.php?path=" . urlencode($parentPath));
exit; exit;
} }
header("Location: index.php"); header("Location: index.php");
exit; exit;
+58 -59
View File
@@ -1,10 +1,10 @@
<?php <?php
set_time_limit(900); // 15 minutes max execution time require_once __DIR__ . '/../../includes/auth.php';
requireLogin('../../index.php');
requirePermission('downPer', 'You do not have permission to perform this operation.');
session_write_close();
// Include this at the top to see potential errors set_time_limit(900); // 15 minutes max execution time
// Comment out in production
ini_set('display_errors', 0);
error_reporting(0);
ob_start(); ob_start();
@@ -18,12 +18,12 @@ try {
try { try {
$sftp = initializeSFTP(); $sftp = initializeSFTP();
file_put_contents($logFile, "SFTP connection established\n", FILE_APPEND); file_put_contents($logFile, "SFTP connection established\n", FILE_APPEND);
} catch (Exception $e) { } catch (Exception $exception) {
file_put_contents($logFile, "SFTP connection failed: " . $e->getMessage() . "\n", FILE_APPEND); file_put_contents($logFile, "SFTP connection failed: " . $exception->getMessage() . "\n", FILE_APPEND);
throw new Exception("Failed to connect to SFTP server: " . $e->getMessage()); throw new Exception("Failed to connect to SFTP server: " . $exception->getMessage());
} }
function zipFolderRecursive($sftp, $remoteBasePath, $currentPath, $zip, $logFile, &$tempFiles) { function zipFolderRecursive($sftp, $remoteBasePath, $currentPath, $zip, $logFile, &$temporaryFiles) {
file_put_contents($logFile, "Processing directory: $currentPath\n", FILE_APPEND); file_put_contents($logFile, "Processing directory: $currentPath\n", FILE_APPEND);
$files = $sftp->nlist($currentPath); $files = $sftp->nlist($currentPath);
@@ -35,36 +35,35 @@ try {
file_put_contents($logFile, "Found " . count($files) . " items in $currentPath\n", FILE_APPEND); file_put_contents($logFile, "Found " . count($files) . " items in $currentPath\n", FILE_APPEND);
foreach ($files as $file) { foreach ($files as $fileName) {
if ($file == '.' || $file == '..') continue; if ($fileName == '.' || $fileName == '..') continue;
$fullRemotePath = rtrim($currentPath, '/') . '/' . $file; $remotePath = rtrim($currentPath, '/') . '/' . $fileName;
$baseDirName = basename($remoteBasePath); $relativePath = substr($remotePath, strlen(rtrim(remoteDirname($remoteBasePath), '/')) + 1);
$relPathFromBase = substr($fullRemotePath, strlen(rtrim(remoteDirname($remoteBasePath), '/')) + 1); file_put_contents($logFile, "Processing: $remotePath (relative: $relativePath)\n", FILE_APPEND);
file_put_contents($logFile, "Processing: $fullRemotePath (relative: $relPathFromBase)\n", FILE_APPEND);
$isDir = $sftp->is_dir($fullRemotePath); $isDirectory = $sftp->is_dir($remotePath);
if ($isDir) { if ($isDirectory) {
file_put_contents($logFile, "Found subdirectory: $fullRemotePath\n", FILE_APPEND); file_put_contents($logFile, "Found subdirectory: $remotePath\n", FILE_APPEND);
$zip->addEmptyDir($relPathFromBase); $zip->addEmptyDir($relativePath);
zipFolderRecursive($sftp, $remoteBasePath, $fullRemotePath, $zip, $logFile, $tempFiles); zipFolderRecursive($sftp, $remoteBasePath, $remotePath, $zip, $logFile, $temporaryFiles);
} else { } else {
$localTempFile = tempnam(sys_get_temp_dir(), 'sftp'); $temporaryFile = tempnam(sys_get_temp_dir(), 'sftp');
$tempFiles[] = $localTempFile; $temporaryFiles[] = $temporaryFile;
file_put_contents($logFile, "Downloading to temp file: $localTempFile\n", FILE_APPEND); file_put_contents($logFile, "Downloading to temp file: $temporaryFile\n", FILE_APPEND);
$downloadStart = time(); $downloadStart = time();
$downloadSuccess = $sftp->get($fullRemotePath, $localTempFile); $downloadSuccess = $sftp->get($remotePath, $temporaryFile);
$downloadTime = time() - $downloadStart; $downloadTime = time() - $downloadStart;
if ($downloadSuccess) { if ($downloadSuccess) {
$fileSize = filesize($localTempFile); $fileSize = filesize($temporaryFile);
file_put_contents($logFile, "Download successful ($downloadTime seconds), size: $fileSize bytes\n", FILE_APPEND); file_put_contents($logFile, "Download successful ($downloadTime seconds), size: $fileSize bytes\n", FILE_APPEND);
$zip->addFile($localTempFile, $relPathFromBase); $zip->addFile($temporaryFile, $relativePath);
} else { } else {
file_put_contents($logFile, "Failed to download file after $downloadTime seconds\n", FILE_APPEND); file_put_contents($logFile, "Failed to download file after $downloadTime seconds\n", FILE_APPEND);
} }
@@ -83,17 +82,17 @@ try {
} }
$tempFiles = []; $temporaryFiles = [];
try { try {
zipFolderRecursive($sftp, $folderPath, $folderPath, $zip, $logFile, $tempFiles); zipFolderRecursive($sftp, $folderPath, $folderPath, $zip, $logFile, $temporaryFiles);
file_put_contents($logFile, "Closing zip file\n", FILE_APPEND); file_put_contents($logFile, "Closing zip file\n", FILE_APPEND);
$zipSuccess = $zip->close(); $zipSuccess = $zip->close();
foreach ($tempFiles as $tempFile) { foreach ($temporaryFiles as $temporaryFile) {
if (file_exists($tempFile)) { if (file_exists($temporaryFile)) {
@unlink($tempFile); @unlink($temporaryFile);
} }
} }
@@ -104,39 +103,39 @@ try {
file_put_contents($logFile, "Failed to create zip\n", FILE_APPEND); file_put_contents($logFile, "Failed to create zip\n", FILE_APPEND);
return false; return false;
} }
} catch (Exception $e) { } catch (Exception $exception) {
foreach ($tempFiles as $tempFile) { foreach ($temporaryFiles as $temporaryFile) {
if (file_exists($tempFile)) { if (file_exists($temporaryFile)) {
@unlink($tempFile); @unlink($temporaryFile);
} }
} }
file_put_contents($logFile, "Error during zip creation: " . $e->getMessage() . "\n", FILE_APPEND); file_put_contents($logFile, "Error during zip creation: " . $exception->getMessage() . "\n", FILE_APPEND);
throw $e; throw $exception;
} }
} }
if (($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['file'])) || if (($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['file'])) ||
($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['file']))) { ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['file']))) {
$file = $_SERVER['REQUEST_METHOD'] === 'POST' ? $_POST['file'] : $_GET['file']; $filePath = requireRemotePath($_SERVER['REQUEST_METHOD'] === 'POST' ? $_POST['file'] : $_GET['file'], $defaultPath);
file_put_contents($logFile, "Requested file: $file\n", FILE_APPEND); file_put_contents($logFile, "Requested file: $filePath\n", FILE_APPEND);
$fileExists = $sftp->file_exists($file); $fileExists = $sftp->file_exists($filePath);
$isDir = $sftp->is_dir($file); $isDirectory = $sftp->is_dir($filePath);
file_put_contents($logFile, "File exists: " . ($fileExists ? "Yes" : "No") . "\n", FILE_APPEND); file_put_contents($logFile, "File exists: " . ($fileExists ? "Yes" : "No") . "\n", FILE_APPEND);
file_put_contents($logFile, "Is directory: " . ($isDir ? "Yes" : "No") . "\n", FILE_APPEND); file_put_contents($logFile, "Is directory: " . ($isDirectory ? "Yes" : "No") . "\n", FILE_APPEND);
if (!$fileExists && !$isDir) { if (!$fileExists && !$isDirectory) {
throw new Exception("File not found: $file"); throw new Exception("File not found: $filePath");
} }
if ($isDir) { if ($isDirectory) {
$zipFilePath = tempnam(sys_get_temp_dir(), 'folder_') . '.zip'; $zipFilePath = tempnam(sys_get_temp_dir(), 'folder_') . '.zip';
file_put_contents($logFile, "Creating zip at: $zipFilePath\n", FILE_APPEND); file_put_contents($logFile, "Creating zip at: $zipFilePath\n", FILE_APPEND);
if (zipFolder($sftp, $file, $zipFilePath, $logFile)) { if (zipFolder($sftp, $filePath, $zipFilePath, $logFile)) {
if (file_exists($zipFilePath) && filesize($zipFilePath) > 0) { if (file_exists($zipFilePath) && filesize($zipFilePath) > 0) {
file_put_contents($logFile, "Sending zip file to browser, size: " . filesize($zipFilePath) . " bytes\n", FILE_APPEND); file_put_contents($logFile, "Sending zip file to browser, size: " . filesize($zipFilePath) . " bytes\n", FILE_APPEND);
@@ -145,7 +144,7 @@ try {
} }
header('Content-Type: application/zip'); header('Content-Type: application/zip');
header('Content-Disposition: attachment; filename="' . basename($file) . '.zip"'); header('Content-Disposition: attachment; filename="' . basename($filePath) . '.zip"');
header('Content-Length: ' . filesize($zipFilePath)); header('Content-Length: ' . filesize($zipFilePath));
header('Cache-Control: no-cache, must-revalidate'); header('Cache-Control: no-cache, must-revalidate');
header('Pragma: no-cache'); header('Pragma: no-cache');
@@ -161,19 +160,19 @@ try {
throw new Exception("Failed to create zip archive"); throw new Exception("Failed to create zip archive");
} }
} else { } else {
$localFile = basename($file); $fileName = basename($filePath);
file_put_contents($logFile, "Downloading single file: $localFile\n", FILE_APPEND); file_put_contents($logFile, "Downloading single file: $fileName\n", FILE_APPEND);
$tempFile = tempnam(sys_get_temp_dir(), 'file_'); $temporaryFile = tempnam(sys_get_temp_dir(), 'file_');
$downloadStart = time(); $downloadStart = time();
$downloadSuccess = $sftp->get($file, $tempFile); $downloadSuccess = $sftp->get($filePath, $temporaryFile);
$downloadTime = time() - $downloadStart; $downloadTime = time() - $downloadStart;
file_put_contents($logFile, "Download " . ($downloadSuccess ? "successful" : "failed") . " ($downloadTime seconds)\n", FILE_APPEND); file_put_contents($logFile, "Download " . ($downloadSuccess ? "successful" : "failed") . " ($downloadTime seconds)\n", FILE_APPEND);
if ($downloadSuccess) { if ($downloadSuccess) {
$fileSize = filesize($tempFile); $fileSize = filesize($temporaryFile);
file_put_contents($logFile, "Downloaded file size: $fileSize bytes\n", FILE_APPEND); file_put_contents($logFile, "Downloaded file size: $fileSize bytes\n", FILE_APPEND);
if ($fileSize > 0) { if ($fileSize > 0) {
@@ -182,29 +181,29 @@ try {
} }
header('Content-Type: application/octet-stream'); header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $localFile . '"'); header('Content-Disposition: attachment; filename="' . $fileName . '"');
header('Content-Length: ' . $fileSize); header('Content-Length: ' . $fileSize);
header('Cache-Control: no-cache, must-revalidate'); header('Cache-Control: no-cache, must-revalidate');
header('Pragma: no-cache'); header('Pragma: no-cache');
readfile($tempFile); readfile($temporaryFile);
file_put_contents($logFile, "Download completed\n", FILE_APPEND); file_put_contents($logFile, "Download completed\n", FILE_APPEND);
@unlink($tempFile); @unlink($temporaryFile);
exit; exit;
} else { } else {
@unlink($tempFile); @unlink($temporaryFile);
throw new Exception("Downloaded file is empty"); throw new Exception("Downloaded file is empty");
} }
} else { } else {
@unlink($tempFile); @unlink($temporaryFile);
throw new Exception("Failed to download file from SFTP server"); throw new Exception("Failed to download file from SFTP server");
} }
} }
} else { } else {
throw new Exception("Invalid request method or missing file parameter"); throw new Exception("Invalid request method or missing file parameter");
} }
} catch (Exception $e) { } catch (Exception $exception) {
$errorMessage = "Error: " . $e->getMessage(); $errorMessage = "Error: " . $exception->getMessage();
file_put_contents($logFile, $errorMessage . "\n", FILE_APPEND); file_put_contents($logFile, $errorMessage . "\n", FILE_APPEND);
while (ob_get_level()) { while (ob_get_level()) {
+24 -37
View File
@@ -1,20 +1,7 @@
<?php <?php
session_start(); require_once __DIR__ . '/../../includes/auth.php';
requireLogin('../../index.php');
if(!isset($_SESSION['uname'])){ requirePermission('upPer', 'You do not have permission to perform this operation.');
header("location: ../../index.php");
session_destroy();
exit;
}
// Check if user has permission to upload/modify
if(!isset($_SESSION["upPer"]) || $_SESSION["upPer"] != true) {
die("You don't have permission to extract archives.");
}
// Include error reporting for debugging
ini_set('display_errors', 1);
error_reporting(E_ALL);
require_once __DIR__ . '/../../config/config.php'; require_once __DIR__ . '/../../config/config.php';
use phpseclib3\Net\SFTP; use phpseclib3\Net\SFTP;
@@ -23,21 +10,21 @@ $sftp = initializeSFTP();
// Process extraction request // Process extraction request
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['archive'])) { if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['archive'])) {
$archivePath = $_POST['archive']; $archivePath = requireRemotePath($_POST['archive'], $defaultPath);
$dirPath = remoteDirname($archivePath); $directoryPath = remoteDirname($archivePath);
$archiveName = basename($archivePath); $archiveName = basename($archivePath);
$extension = pathinfo($archiveName, PATHINFO_EXTENSION); $extension = pathinfo($archiveName, PATHINFO_EXTENSION);
// Create a temporary local directory for extraction // Create a temporary local directory for extraction
$tempDir = sys_get_temp_dir() . '/sftp_extract_' . time(); $temporaryDirectory = sys_get_temp_dir() . '/sftp_extract_' . time();
if (!mkdir($tempDir, 0777, true)) { if (!mkdir($temporaryDirectory, 0777, true)) {
die("Failed to create temporary directory"); die("Failed to create temporary directory");
} }
// Download the archive to the temp directory // Download the archive to the temp directory
$localArchivePath = $tempDir . '/' . $archiveName; $localArchivePath = $temporaryDirectory . '/' . $archiveName;
if (!$sftp->get($archivePath, $localArchivePath)) { if (!$sftp->get($archivePath, $localArchivePath)) {
rmdir($tempDir); rmdir($temporaryDirectory);
die("Failed to download the archive"); die("Failed to download the archive");
} }
@@ -48,11 +35,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['archive'])) {
// Determine the extraction method based on file extension // Determine the extraction method based on file extension
switch (strtolower($extension)) { switch (strtolower($extension)) {
case 'zip': case 'zip':
$extractionSuccess = extractZip($localArchivePath, $tempDir); $extractionSuccess = extractZip($localArchivePath, $temporaryDirectory);
break; break;
case 'rar': case 'rar':
$extractionSuccess = extractRar($localArchivePath, $tempDir); $extractionSuccess = extractRar($localArchivePath, $temporaryDirectory);
break; break;
case 'tar': case 'tar':
@@ -60,7 +47,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['archive'])) {
case 'bz2': case 'bz2':
case 'xz': case 'xz':
case '7z': case '7z':
$extractionSuccess = extractArchive($localArchivePath, $tempDir); $extractionSuccess = extractArchive($localArchivePath, $temporaryDirectory);
break; break;
default: default:
@@ -69,20 +56,20 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['archive'])) {
if ($extractionSuccess) { if ($extractionSuccess) {
// Upload extracted files back to the server // Upload extracted files back to the server
uploadExtractedFiles($sftp, $tempDir, $dirPath); uploadExtractedFiles($sftp, $temporaryDirectory, $directoryPath);
// Clean up temporary directory // Clean up temporary directory
deleteDirectory($tempDir); deleteDirectory($temporaryDirectory);
// Redirect back to the file listing // Redirect back to the file listing
header("Location: index.php?path=" . urlencode($dirPath)); header("Location: index.php?path=" . urlencode($directoryPath));
exit; exit;
} else { } else {
die("Failed to extract the archive"); die("Failed to extract the archive");
} }
} catch (Exception $e) { } catch (Exception $exception) {
deleteDirectory($tempDir); deleteDirectory($temporaryDirectory);
die("Error during extraction: " . $e->getMessage()); die("Error during extraction: " . $exception->getMessage());
} }
} else { } else {
header("Location: index.php"); header("Location: index.php");
@@ -140,7 +127,7 @@ function extractArchive($archivePath, $destination) {
$command = ''; $command = '';
// Change to the destination directory // Change to the destination directory
$currentDir = getcwd(); $originalDirectory = getcwd();
chdir($destination); chdir($destination);
if (PHP_OS_FAMILY === 'Windows') { if (PHP_OS_FAMILY === 'Windows') {
@@ -189,20 +176,20 @@ function extractArchive($archivePath, $destination) {
exec($command, $output, $returnCode); exec($command, $output, $returnCode);
// Change back to the original directory // Change back to the original directory
chdir($currentDir); chdir($originalDirectory);
return $returnCode === 0; return $returnCode === 0;
} }
// Upload extracted files back to the SFTP server // Upload extracted files back to the SFTP server
function uploadExtractedFiles($sftp, $localDir, $remoteDir) { function uploadExtractedFiles($sftp, $localDirectory, $remoteDirectory) {
$items = scandir($localDir); $items = scandir($localDirectory);
foreach ($items as $item) { foreach ($items as $item) {
if ($item === '.' || $item === '..') continue; if ($item === '.' || $item === '..') continue;
$localPath = $localDir . '/' . $item; $localPath = $localDirectory . '/' . $item;
$remotePath = $remoteDir . '/' . $item; $remotePath = $remoteDirectory . '/' . $item;
if (is_dir($localPath)) { if (is_dir($localPath)) {
// Create the directory on the remote server // Create the directory on the remote server
+34 -41
View File
@@ -1,38 +1,31 @@
<?php <?php
session_start(); require_once __DIR__ . '/../../includes/auth.php';
requireLogin('../../index.php');
if(!isset($_SESSION['uname'])){ require_once __DIR__ . '/../../config/config.php';
header("location: ../../index.php"); require_once __DIR__ . '/../../includes/workspace.php';
session_destroy(); $sftp = initializeSFTP();
exit;
}
// Include this at the top to see potential errors $currentPath = isset($_GET['path']) ? normalizePath($_GET['path']) : $defaultPath;
// Comment out in production if (!isRemotePathWithin($currentPath, $defaultPath)) {
ini_set('display_errors', 1); $currentPath = $defaultPath;
error_reporting(E_ALL); }
require_once __DIR__ . '/../../config/config.php'; if (!$sftp->chdir($currentPath)) {
require_once __DIR__ . '/../../includes/workspace.php'; die("Failed to navigate to the selected folder: $currentPath");
$sftp = initializeSFTP(); }
$defaultPath = normalizePath($defaultPath); $items = $sftp->nlist();
$currentPath = isset($_GET['path']) ? normalizePath($_GET['path']) : $defaultPath; if ($items === false) {
if ($currentPath !== $defaultPath && strpos($currentPath, rtrim($defaultPath, '/') . '/') !== 0) { http_response_code(500);
$currentPath = $defaultPath; exit('Failed to list directory contents.');
} }
if (!$sftp->chdir($currentPath)) { $imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'];
die("Failed to navigate to the selected folder: $currentPath"); $videoExtensions = ['mp4', 'webm', 'ogg', 'mov', 'avi', 'mkv'];
} $audioExtensions = ['mp3', 'wav', 'm4a', 'flac', 'aac'];
$editableExtensions = ['txt', 'html', 'css', 'js', 'php', 'xml', 'json', 'md', 'csv', 'log', 'ini', 'conf', 'sh', 'bat', 'py', 'rb', 'java', 'c', 'cpp', 'h', 'hpp'];
$items = $sftp->nlist(); $archiveExtensions = ['zip', 'rar', 'tar', 'gz', '7z', 'bz2', 'xz', 'tar.gz', 'tar.bz2', 'tar.xz'];
$imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'];
$videoExtensions = ['mp4', 'webm', 'ogg', 'mov', 'avi', 'mkv'];
$audioExtensions = ['mp3', 'wav', 'm4a', 'flac', 'aac'];
$editableExtensions = ['txt', 'html', 'css', 'js', 'php', 'xml', 'json', 'md', 'csv', 'log', 'ini', 'conf', 'sh', 'bat', 'py', 'rb', 'java', 'c', 'cpp', 'h', 'hpp'];
$archiveExtensions = ['zip', 'rar', 'tar', 'gz', '7z', 'bz2', 'xz', 'tar.gz', 'tar.bz2', 'tar.xz'];
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
@@ -67,7 +60,7 @@
<div class="file-content"> <div class="file-content">
<!-- Action Buttons --> <!-- Action Buttons -->
<div class="action-buttons"> <div class="action-buttons">
<?php if (isset($_SESSION["upPer"]) && $_SESSION["upPer"] == true) { ?> <?php if (hasPermission('upPer')) { ?>
<button type="button" class="btn" data-dialog="createFileModal">+ New file</button> <button type="button" class="btn" data-dialog="createFileModal">+ New file</button>
<button type="button" class="btn btn-primary" onclick="document.getElementById('fileInput').click()">↑ Upload files</button> <button type="button" class="btn btn-primary" onclick="document.getElementById('fileInput').click()">↑ Upload files</button>
<button type="button" class="btn" data-dialog="createDirModal">+ New folder</button> <button type="button" class="btn" data-dialog="createDirModal">+ New folder</button>
@@ -154,7 +147,7 @@
</dialog> </dialog>
<!-- End of Modal Forms --> <!-- End of Modal Forms -->
<?php if (isset($_SESSION["upPer"]) && $_SESSION["upPer"] == true) { ?> <?php if (hasPermission('upPer')) { ?>
<div class="dropzone" ondragover="handleDragOver(event)" ondragleave="handleDragLeave(event)" ondrop="handleDrop(event)"> <div class="dropzone" ondragover="handleDragOver(event)" ondragleave="handleDragLeave(event)" ondrop="handleDrop(event)">
<span class="upload-symbol" aria-hidden="true">↑</span><span><strong>Drop files or folders here</strong><small>Or use the upload buttons to browse your device</small></span> <span class="upload-symbol" aria-hidden="true">↑</span><span><strong>Drop files or folders here</strong><small>Or use the upload buttons to browse your device</small></span>
</div> </div>
@@ -198,20 +191,20 @@
<td><span class="file-icon folder-icon" aria-hidden="true">▰</span> <td><span class="file-icon folder-icon" aria-hidden="true">▰</span>
<a href="?path=<?= urlencode(rtrim($currentPath, '/') . '/' . $directory) ?>"><?= htmlspecialchars($directory) ?></a></td><td class="size-cell">—</td> <a href="?path=<?= urlencode(rtrim($currentPath, '/') . '/' . $directory) ?>"><?= htmlspecialchars($directory) ?></a></td><td class="size-cell">—</td>
<td> <td>
<?php if (isset($_SESSION["delPer"]) && $_SESSION["delPer"] == true) : ?> <?php if (hasPermission('delPer')) : ?>
<form method="POST" action="delete.php" style="display:inline;"> <form method="POST" action="delete.php" style="display:inline;">
<input type="hidden" name="delete" value="<?= htmlspecialchars(rtrim($currentPath, '/') . '/' . $directory) ?>"> <input type="hidden" name="delete" value="<?= htmlspecialchars(rtrim($currentPath, '/') . '/' . $directory) ?>">
<button type="submit" class="btn btn-danger mt-1 mb-1" onclick="confirmDelete(event)">Delete</button> <button type="submit" class="btn btn-danger mt-1 mb-1" onclick="confirmDelete(event)">Delete</button>
</form> </form>
<?php endif; ?> <?php endif; ?>
<?php if (isset($_SESSION["downPer"]) && $_SESSION["downPer"] == true) : ?> <?php if (hasPermission('downPer')) : ?>
<form method="POST" action="download.php" style="display:inline;"> <form method="POST" action="download.php" style="display:inline;">
<input type="hidden" name="file" value="<?= htmlspecialchars(rtrim($currentPath, '/') . '/' . $directory) ?>"> <input type="hidden" name="file" value="<?= htmlspecialchars(rtrim($currentPath, '/') . '/' . $directory) ?>">
<button type="submit" class="btn btn-success mt-1 mb-1">Download</button> <button type="submit" class="btn btn-success mt-1 mb-1">Download</button>
</form> </form>
<?php endif; ?> <?php endif; ?>
<?php if (isset($_SESSION["upPer"]) && $_SESSION["upPer"] == true) : ?> <?php if (hasPermission('upPer')) : ?>
<button type="button" class="btn btn-warning mt-1 mb-1" onclick="openRenameModal('<?= htmlspecialchars(addslashes($directory)) ?>', '<?= htmlspecialchars(addslashes(rtrim($currentPath, '/') . '/' . $directory)) ?>', true)">Rename</button> <button type="button" class="btn btn-warning mt-1 mb-1" onclick="openRenameModal('<?= htmlspecialchars(addslashes($directory)) ?>', '<?= htmlspecialchars(addslashes(rtrim($currentPath, '/') . '/' . $directory)) ?>', true)">Rename</button>
<?php endif; ?> <?php endif; ?>
</td> </td>
@@ -221,8 +214,8 @@
foreach ($files as $file) : ?> foreach ($files as $file) : ?>
<?php <?php
$fileExtension = pathinfo($file, PATHINFO_EXTENSION); $fileExtension = strtolower(pathinfo($file, PATHINFO_EXTENSION));
$isPDF = strtolower($fileExtension) === 'pdf'; $isPdf = $fileExtension === 'pdf';
$isImage = in_array($fileExtension, $imageExtensions); $isImage = in_array($fileExtension, $imageExtensions);
$isVideo = in_array($fileExtension, $videoExtensions); $isVideo = in_array($fileExtension, $videoExtensions);
$isAudio = in_array($fileExtension, $audioExtensions); $isAudio = in_array($fileExtension, $audioExtensions);
@@ -251,7 +244,7 @@
<?= htmlspecialchars($file) ?> <?= htmlspecialchars($file) ?>
</a> </a>
<span class="badge bg-secondary rounded-pill"><?= htmlspecialchars($fileExtension) ?></span> <span class="badge bg-secondary rounded-pill"><?= htmlspecialchars($fileExtension) ?></span>
<?php elseif ($isPDF): ?> <?php elseif ($isPdf): ?>
<a class="text-info-emphasis" href="pdf.php?file=<?= urlencode(rtrim($currentPath, '/') . '/' . $file) ?>&type=pdf"> <a class="text-info-emphasis" href="pdf.php?file=<?= urlencode(rtrim($currentPath, '/') . '/' . $file) ?>&type=pdf">
<?= htmlspecialchars($file) ?> <?= htmlspecialchars($file) ?>
</a> </a>
@@ -265,23 +258,23 @@
<span class="text"><?= formatBytes($sftp->stat(rtrim($currentPath, '/') . '/' . $file)['size']) ?></span> <span class="text"><?= formatBytes($sftp->stat(rtrim($currentPath, '/') . '/' . $file)['size']) ?></span>
</td> </td>
<td> <td>
<?php if (isset($_SESSION["delPer"]) && $_SESSION["delPer"] == true) : ?> <?php if (hasPermission('delPer')) : ?>
<form method="POST" action="delete.php" style="display:inline;"> <form method="POST" action="delete.php" style="display:inline;">
<input type="hidden" name="delete" value="<?= htmlspecialchars(rtrim($currentPath, '/') . '/' . $file) ?>"> <input type="hidden" name="delete" value="<?= htmlspecialchars(rtrim($currentPath, '/') . '/' . $file) ?>">
<button type="submit" class="btn btn-danger mt-1 mb-1" onclick="confirmDelete(event)">Delete</button> <button type="submit" class="btn btn-danger mt-1 mb-1" onclick="confirmDelete(event)">Delete</button>
</form> </form>
<?php endif; ?> <?php endif; ?>
<?php if (isset($_SESSION["downPer"]) && $_SESSION["downPer"] == true) : ?> <?php if (hasPermission('downPer')) : ?>
<form method="POST" action="download.php" style="display:inline;"> <form method="POST" action="download.php" style="display:inline;">
<input type="hidden" name="file" value="<?= htmlspecialchars(rtrim($currentPath, '/') . '/' . $file) ?>"> <input type="hidden" name="file" value="<?= htmlspecialchars(rtrim($currentPath, '/') . '/' . $file) ?>">
<button type="submit" class="btn btn-success mt-1 mb-1">Download</button> <button type="submit" class="btn btn-success mt-1 mb-1">Download</button>
</form> </form>
<?php endif; ?> <?php endif; ?>
<?php if (isset($_SESSION["upPer"]) && $_SESSION["upPer"] == true) : ?> <?php if (hasPermission('upPer')) : ?>
<button type="button" class="btn btn-warning mt-1 mb-1" onclick="openRenameModal('<?= htmlspecialchars(addslashes($file)) ?>', '<?= htmlspecialchars(addslashes(rtrim($currentPath, '/') . '/' . $file)) ?>')">Rename</button> <button type="button" class="btn btn-warning mt-1 mb-1" onclick="openRenameModal('<?= htmlspecialchars(addslashes($file)) ?>', '<?= htmlspecialchars(addslashes(rtrim($currentPath, '/') . '/' . $file)) ?>')">Rename</button>
<?php endif; ?> <?php endif; ?>
<?php if ($isArchive && isset($_SESSION["upPer"]) && $_SESSION["upPer"] == true) : ?> <?php if ($isArchive && hasPermission('upPer')) : ?>
<form method="POST" action="extract.php" style="display:inline;"> <form method="POST" action="extract.php" style="display:inline;">
<input type="hidden" name="archive" value="<?= htmlspecialchars(rtrim($currentPath, '/') . '/' . $file) ?>"> <input type="hidden" name="archive" value="<?= htmlspecialchars(rtrim($currentPath, '/') . '/' . $file) ?>">
<button type="submit" class="btn btn-info mt-1 mb-1">Extract</button> <button type="submit" class="btn btn-info mt-1 mb-1">Extract</button>
+9 -19
View File
@@ -1,22 +1,12 @@
<?php <?php
// Include this at the top to see potential errors require_once __DIR__ . '/../../includes/auth.php';
// Comment out in production requireLogin('../../index.php');
ini_set('display_errors', 1);
error_reporting(E_ALL);
session_start();
if(!isset($_SESSION['uname'])){
header("location: ../../index.php");
session_destroy();
exit;
}
require_once __DIR__ . '/../../config/config.php'; require_once __DIR__ . '/../../config/config.php';
require_once __DIR__ . '/../../includes/workspace.php'; require_once __DIR__ . '/../../includes/workspace.php';
$sftp = initializeSFTP(); $sftp = initializeSFTP();
$filePath = isset($_GET['file']) ? $_GET['file'] : ''; $filePath = requireRemotePath($_GET['file'] ?? $defaultPath, $defaultPath);
$content = ''; $content = '';
$fileName = basename($filePath); $fileName = basename($filePath);
@@ -29,7 +19,7 @@ if (!empty($filePath) && $sftp->file_exists($filePath) && !$sftp->is_dir($filePa
if (in_array(strtolower($extension), $editableExtensions)) { if (in_array(strtolower($extension), $editableExtensions)) {
$editable = true; $editable = true;
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['content']) && isset($_SESSION["upPer"]) && $_SESSION["upPer"] == true) { if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['content']) && hasPermission('upPer')) {
$newContent = $_POST['content']; $newContent = $_POST['content'];
if ($sftp->put($filePath, $newContent)) { if ($sftp->put($filePath, $newContent)) {
@@ -52,7 +42,7 @@ if (!empty($filePath) && $sftp->file_exists($filePath) && !$sftp->is_dir($filePa
$showLineNumbers = in_array(strtolower($extension), ['php', 'js', 'html', 'css', 'py', 'java', 'c', 'cpp', 'h', 'hpp', 'rb', 'sh', 'xml', 'json']); $showLineNumbers = in_array(strtolower($extension), ['php', 'js', 'html', 'css', 'py', 'java', 'c', 'cpp', 'h', 'hpp', 'rb', 'sh', 'xml', 'json']);
$parentDir = remoteDirname($filePath); $parentPath = remoteDirname($filePath);
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
@@ -75,7 +65,7 @@ $parentDir = remoteDirname($filePath);
'title' => $fileName, 'title' => $fileName,
'description' => 'View and edit text files in your remote workspace.', 'description' => 'View and edit text files in your remote workspace.',
'connected' => true, 'connected' => true,
'back' => 'index.php?path=' . urlencode($parentDir), 'back' => 'index.php?path=' . urlencode($parentPath),
'class' => 'editor-page', 'class' => 'editor-page',
]); ?> ]); ?>
<section class="content-panel"> <section class="content-panel">
@@ -94,7 +84,7 @@ $parentDir = remoteDirname($filePath);
<div class="mb-3"> <div class="mb-3">
<div class="d-flex justify-content-between align-items-center mb-2"> <div class="d-flex justify-content-between align-items-center mb-2">
<label for="editor" class="form-label">File Content</label> <label for="editor" class="form-label">File Content</label>
<?php if (!isset($_SESSION["upPer"]) || $_SESSION["upPer"] != true): ?> <?php if (!hasPermission('upPer')): ?>
<span class="readonly-notice">Read-only mode (you don't have upload permissions)</span> <span class="readonly-notice">Read-only mode (you don't have upload permissions)</span>
<?php endif; ?> <?php endif; ?>
</div> </div>
@@ -102,11 +92,11 @@ $parentDir = remoteDirname($filePath);
<?php if ($showLineNumbers): ?> <?php if ($showLineNumbers): ?>
<div id="lineNumbers" class="line-numbers"></div> <div id="lineNumbers" class="line-numbers"></div>
<?php endif; ?> <?php endif; ?>
<textarea id="editor" name="content" class="form-control" <?= (!isset($_SESSION["upPer"]) || $_SESSION["upPer"] != true) ? 'readonly' : '' ?>><?= htmlspecialchars($content) ?></textarea> <textarea id="editor" name="content" class="form-control" <?= (!hasPermission('upPer')) ? 'readonly' : '' ?>><?= htmlspecialchars($content) ?></textarea>
</div> </div>
</div> </div>
<?php if (isset($_SESSION["upPer"]) && $_SESSION["upPer"] == true): ?> <?php if (hasPermission('upPer')): ?>
<div class="mb-3"> <div class="mb-3">
<button type="submit" class="btn btn-primary">Save Changes</button> <button type="submit" class="btn btn-primary">Save Changes</button>
</div> </div>
+6 -12
View File
@@ -1,11 +1,6 @@
<?php <?php
session_start(); require_once __DIR__ . '/../../includes/auth.php';
requireLogin('../../index.php');
if(!isset($_SESSION['uname'])){
header("location: ../../index.php");
session_destroy();
exit;
}
require_once __DIR__ . '/../../config/config.php'; require_once __DIR__ . '/../../config/config.php';
require_once __DIR__ . '/../../includes/workspace.php'; require_once __DIR__ . '/../../includes/workspace.php';
@@ -15,7 +10,7 @@ if (!isset($_GET['file'])) {
die("No file specified"); die("No file specified");
} }
$filePath = $_GET['file']; $filePath = requireRemotePath($_GET['file'], $defaultPath);
$fileName = basename($filePath); $fileName = basename($filePath);
$fileSize = $sftp->stat($filePath)['size']; $fileSize = $sftp->stat($filePath)['size'];
@@ -105,8 +100,8 @@ if (isset($_GET['stream'])) {
break; break;
} }
} }
} catch (Exception $e) { } catch (Exception $exception) {
error_log("Streaming error: " . $e->getMessage()); error_log("Streaming error: " . $exception->getMessage());
} }
exit; exit;
} }
@@ -138,7 +133,6 @@ if (isset($_GET['stream'])) {
'class' => 'pdf-page', 'class' => 'pdf-page',
]); ?> ]); ?>
<section class="content-panel pdf-workspace" aria-label="PDF document"> <section class="content-panel pdf-workspace" aria-label="PDF document">
<div id="pdfContainer" class="container-fluid position-relative"> <div id="pdfContainer" class="container-fluid position-relative">
<div id="loadingMessage" class="d-flex align-items-center justify-content-center w-100 h-100"> <div id="loadingMessage" class="d-flex align-items-center justify-content-center w-100 h-100">
@@ -166,7 +160,7 @@ if (isset($_GET['stream'])) {
</div> </div>
</div> </div>
<div class="actionButton"> <div class="actionButton">
<?php if (isset($_SESSION["downPer"]) && $_SESSION["downPer"] == true) : ?> <?php if (hasPermission('downPer')) : ?>
<a href="download.php?file=<?= urlencode($filePath) ?>" class="btn btn-success m-3">Download</a> <a href="download.php?file=<?= urlencode($filePath) ?>" class="btn btn-success m-3">Download</a>
<?php endif; ?> <?php endif; ?>
</div> </div>
+12 -20
View File
@@ -1,18 +1,7 @@
<?php <?php
session_start(); require_once __DIR__ . '/../../includes/auth.php';
requireLogin('../../index.php');
if(!isset($_SESSION['uname'])){ requirePermission('upPer', 'You do not have permission to perform this operation.');
header("location: ../../index.php");
session_destroy();
exit;
}
if(!isset($_SESSION["upPer"]) || $_SESSION["upPer"] != true) {
die("You don't have permission to rename files or directories.");
}
ini_set('display_errors', 1);
error_reporting(E_ALL);
require_once __DIR__ . '/../../config/config.php'; require_once __DIR__ . '/../../config/config.php';
$sftp = initializeSFTP(); $sftp = initializeSFTP();
@@ -20,23 +9,26 @@ $sftp = initializeSFTP();
if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$oldPath = isset($_POST['path']) ? $_POST['path'] : ''; $oldPath = isset($_POST['path']) ? $_POST['path'] : '';
$newName = isset($_POST['newName']) ? trim($_POST['newName']) : ''; $newName = isset($_POST['newName']) ? trim($_POST['newName']) : '';
$isDirectory = isset($_POST['isDirectory']) && $_POST['isDirectory'] == '1';
if (empty($oldPath) || empty($newName)) { if (empty($oldPath) || empty($newName)) {
die("Missing required information for renaming."); die("Missing required information for renaming.");
} }
$dirPath = remoteDirname($oldPath); $oldPath = requireRemotePath($oldPath, $defaultPath);
$oldName = basename($oldPath); if ($oldPath === $defaultPath || !isRemoteNameValid($newName)) {
http_response_code(400);
exit('Invalid item name or workspace root.');
}
$directoryPath = remoteDirname($oldPath);
$newPath = $dirPath . '/' . $newName; $newPath = rtrim($directoryPath, '/') . '/' . $newName;
if ($sftp->file_exists($newPath) || $sftp->is_dir($newPath)) { if ($sftp->file_exists($newPath) || $sftp->is_dir($newPath)) {
die("Error: A file or directory with this name already exists."); die("Error: A file or directory with this name already exists.");
} }
if ($sftp->rename($oldPath, $newPath)) { if ($sftp->rename($oldPath, $newPath)) {
header("Location: index.php?path=" . urlencode($dirPath)); header("Location: index.php?path=" . urlencode($directoryPath));
exit; exit;
} else { } else {
die("Failed to rename the item. Please try again."); die("Failed to rename the item. Please try again.");
@@ -45,4 +37,4 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
header("Location: index.php"); header("Location: index.php");
exit; exit;
} }
?> ?>
+3 -12
View File
@@ -1,16 +1,8 @@
<?php <?php
// Include this at the top to see potential errors require_once __DIR__ . '/../../includes/auth.php';
// Comment out in production requireLogin('../../index.php');
ini_set('display_errors', 1);
error_reporting(E_ALL);
session_start(); requirePermission('admin', 'You do not have permission to view server statistics.');
if (empty($_SESSION['admin']) || $_SESSION['admin'] !== true) {
session_destroy();
header("location: ../../index.php");
exit();
}
require_once __DIR__ . '/../../config/config.php'; require_once __DIR__ . '/../../config/config.php';
require_once __DIR__ . '/../../includes/workspace.php'; require_once __DIR__ . '/../../includes/workspace.php';
@@ -90,7 +82,6 @@ $monitors = [
'class' => 'server-page', 'class' => 'server-page',
]); ?> ]); ?>
<div class="content-toolbar"><p>Live statistics · refreshes every 5 seconds</p><span class="badge bg-light text-dark"><i class="ui-icon icon-clock" aria-hidden="true"></i> <?= htmlspecialchars($systemData['lastUpdate']) ?></span></div> <div class="content-toolbar"><p>Live statistics · refreshes every 5 seconds</p><span class="badge bg-light text-dark"><i class="ui-icon icon-clock" aria-hidden="true"></i> <?= htmlspecialchars($systemData['lastUpdate']) ?></span></div>
<div class="stats-grid"> <div class="stats-grid">
<?php foreach ($monitors as $monitor): ?> <?php foreach ($monitors as $monitor): ?>
+17 -25
View File
@@ -1,26 +1,19 @@
<?php <?php
// Include this at the top to see potential errors
// Comment out in production
ini_set('display_errors', 1);
error_reporting(E_ALL);
require_once __DIR__ . '/../../config/config.php'; require_once __DIR__ . '/../../config/config.php';
require_once __DIR__ . '/../../includes/workspace.php'; require_once __DIR__ . '/../../includes/workspace.php';
// getDatabaseConnection relocated to central config
if (!isset($_GET['token'])) { if (!isset($_GET['token'])) {
die("Invalid request. No token provided."); die("Invalid request. No token provided.");
} }
$token = $_GET['token']; $token = $_GET['token'];
$conn = getDatabaseConnection(); $database = getDatabaseConnection();
// Get share information // Get share information
$stmt = $conn->prepare("SELECT * FROM share WHERE token = ? AND expiration > NOW()"); $statement = $database->prepare("SELECT * FROM share WHERE token = ? AND expiration > NOW()");
$stmt->bind_param("s", $token); $statement->bind_param("s", $token);
$stmt->execute(); $statement->execute();
$result = $stmt->get_result(); $result = $statement->get_result();
if ($result->num_rows === 0) { if ($result->num_rows === 0) {
die("Invalid or expired share link."); die("Invalid or expired share link.");
@@ -29,7 +22,9 @@ if ($result->num_rows === 0) {
$share = $result->fetch_assoc(); $share = $result->fetch_assoc();
$filePath = $share['file_path']; $filePath = $share['file_path'];
$downloadAllowed = $share['download_allowed'] == 1; $downloadAllowed = $share['download_allowed'] == 1;
$stmt->close(); $result->free();
$statement->close();
$database->close();
// Initialize SFTP // Initialize SFTP
$sftp = initializeSFTP(); $sftp = initializeSFTP();
@@ -42,13 +37,13 @@ $fileName = basename($filePath);
$fileExtension = strtolower(pathinfo($fileName, PATHINFO_EXTENSION)); $fileExtension = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
$fileSize = $sftp->stat($filePath)['size']; $fileSize = $sftp->stat($filePath)['size'];
$imageTypes = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg']; $imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'];
$videoTypes = ['mp4', 'webm', 'ogg', 'mov', 'avi', 'mkv']; $videoExtensions = ['mp4', 'webm', 'ogg', 'mov', 'avi', 'mkv'];
$audioTypes = ['mp3', 'wav', 'ogg', 'm4a', 'flac', 'aac']; $audioExtensions = ['mp3', 'wav', 'ogg', 'm4a', 'flac', 'aac'];
$isImage = in_array($fileExtension, $imageTypes); $isImage = in_array($fileExtension, $imageExtensions);
$isVideo = in_array($fileExtension, $videoTypes); $isVideo = in_array($fileExtension, $videoExtensions);
$isAudio = in_array($fileExtension, $audioTypes); $isAudio = in_array($fileExtension, $audioExtensions);
if (!$isImage && !$isVideo && !$isAudio) { if (!$isImage && !$isVideo && !$isAudio) {
die("Unsupported file type"); die("Unsupported file type");
@@ -165,8 +160,8 @@ if (isset($_GET['stream'])) {
usleep(100000); usleep(100000);
} }
} catch (Exception $e) { } catch (Exception $exception) {
error_log("Shared streaming error: " . $e->getMessage()); error_log("Shared streaming error: " . $exception->getMessage());
} }
exit; exit;
} }
@@ -242,7 +237,6 @@ function formatBytes($bytes, $precision = 2) {
'class' => 'media-page', 'class' => 'media-page',
]); ?> ]); ?>
<div class="content-toolbar"><span class="badge">Shared link</span><p>Available until <?= htmlspecialchars(date('F j, Y, g:i a', strtotime($share['expiration']))) ?></p></div> <div class="content-toolbar"><span class="badge">Shared link</span><p>Available until <?= htmlspecialchars(date('F j, Y, g:i a', strtotime($share['expiration']))) ?></p></div>
<section class="content-panel media-panel"> <section class="content-panel media-panel">
<article class="col-12"> <article class="col-12">
@@ -293,8 +287,6 @@ function formatBytes($bytes, $precision = 2) {
<?php renderWorkspaceEnd(); ?> <?php renderWorkspaceEnd(); ?>
<script> <script>
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const mediaElement = document.querySelector('video, audio'); const mediaElement = document.querySelector('video, audio');
@@ -312,4 +304,4 @@ document.addEventListener('DOMContentLoaded', () => {
}); });
</script> </script>
</body> </body>
</html> </html>
+66 -111
View File
@@ -1,130 +1,85 @@
<?php <?php
// Include this at the top to see potential errors require_once __DIR__ . '/../../includes/auth.php';
// Comment out in production requireLogin('../../index.php');
ini_set('display_errors', 1); requirePermission('upPer', 'You do not have permission to upload files.');
error_reporting(E_ALL); session_write_close();
require_once __DIR__ . '/../../config/config.php'; require_once __DIR__ . '/../../config/config.php';
use phpseclib3\Net\SFTP; use phpseclib3\Net\SFTP;
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(400);
exit('No files received or invalid request.');
}
$currentPath = requireRemotePath($_POST['currentPath'] ?? $defaultPath, $defaultPath);
$sftp = initializeSFTP(); $sftp = initializeSFTP();
$currentPath = isset($_POST['currentPath']) ? normalizePath($_POST['currentPath']) : $defaultPath; if (isset($_POST['create_dirs'])) {
$directories = json_decode($_POST['create_dirs'], true);
if (strpos($currentPath, $defaultPath) !== 0) { if (!is_array($directories)) {
$currentPath = $defaultPath; http_response_code(400);
exit('Invalid directory list.');
}
foreach ($directories as $directory) {
$directoryPath = requireRemotePath(rtrim($currentPath, '/') . '/' . $directory, $currentPath);
if (!$sftp->is_dir($directoryPath) && !$sftp->mkdir($directoryPath, 0755, true)) {
http_response_code(500);
exit('Failed to create upload directory.');
}
}
} }
function createDirectoryRecursive($sftp, $path) { $uploadedFiles = $_FILES['files'] ?? null;
if ($sftp->is_dir($path)) { if (!$uploadedFiles || empty($uploadedFiles['name'][0])) {
return true; http_response_code(400);
} exit('No files received or file size exceeds PHP limits.');
$parent = remoteDirname($path);
if ($parent != '/' && !$sftp->is_dir($parent)) {
createDirectoryRecursive($sftp, $parent);
}
return $sftp->mkdir($path, 0755);
} }
if ($_SERVER['REQUEST_METHOD'] === 'POST') { $uploadErrors = [
if (isset($_POST['create_dirs'])) { UPLOAD_ERR_INI_SIZE => 'File exceeds upload_max_filesize directive in php.ini',
$directories = json_decode($_POST['create_dirs'], true); UPLOAD_ERR_FORM_SIZE => 'File exceeds MAX_FILE_SIZE directive in the HTML form',
UPLOAD_ERR_PARTIAL => 'File was only partially uploaded',
if (is_array($directories)) { UPLOAD_ERR_NO_FILE => 'No file was uploaded',
foreach ($directories as $dir) { UPLOAD_ERR_NO_TMP_DIR => 'Missing a temporary folder',
$remoteDirPath = $currentPath . '/' . $dir; UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk',
createDirectoryRecursive($sftp, $remoteDirPath); UPLOAD_ERR_EXTENSION => 'A PHP extension stopped the file upload',
} ];
} $uploadMessages = [];
$anySuccess = false;
$failureStatus = 500;
foreach ($uploadedFiles['tmp_name'] as $index => $temporaryPath) {
$fileName = $uploadedFiles['name'][$index];
$relativePath = $_POST['paths'][$index] ?? '';
$uploadError = $uploadedFiles['error'][$index];
if ($uploadError !== UPLOAD_ERR_OK) {
$failureStatus = 400;
$uploadMessages[] = $fileName . ': ' . ($uploadErrors[$uploadError] ?? 'Unknown upload error');
continue;
} }
if (!isset($_FILES['files']) || empty($_FILES['files']['name'][0])) { $remotePath = requireRemotePath(rtrim($currentPath, '/') . '/' . ($relativePath !== '' ? $relativePath : $fileName), $currentPath);
echo "No files received or file size exceeds PHP limits."; if ($remotePath === $currentPath) {
http_response_code(400); http_response_code(400);
exit; exit('Invalid upload path.');
} }
$directoryPath = remoteDirname($remotePath);
if ($_FILES['files']['error'][0] !== 0) { if (!$sftp->is_dir($directoryPath) && !$sftp->mkdir($directoryPath, 0755, true)) {
$error = $_FILES['files']['error'][0]; $uploadMessages[] = 'Failed to create directory for: ' . $fileName;
$errorMessage = "Upload error code: $error"; continue;
switch ($error) {
case UPLOAD_ERR_INI_SIZE:
$errorMessage = "File exceeds upload_max_filesize directive in php.ini";
break;
case UPLOAD_ERR_FORM_SIZE:
$errorMessage = "File exceeds MAX_FILE_SIZE directive in the HTML form";
break;
case UPLOAD_ERR_PARTIAL:
$errorMessage = "File was only partially uploaded";
break;
case UPLOAD_ERR_NO_FILE:
$errorMessage = "No file was uploaded";
break;
case UPLOAD_ERR_NO_TMP_DIR:
$errorMessage = "Missing a temporary folder";
break;
case UPLOAD_ERR_CANT_WRITE:
$errorMessage = "Failed to write file to disk";
break;
case UPLOAD_ERR_EXTENSION:
$errorMessage = "A PHP extension stopped the file upload";
break;
}
echo $errorMessage;
http_response_code(400);
exit;
} }
$uploadStatus = array(); if ($sftp->put($remotePath, $temporaryPath, SFTP::SOURCE_LOCAL_FILE)) {
$anySuccess = false; $uploadMessages[] = 'Uploaded: ' . ($relativePath !== '' ? $relativePath : $fileName);
$anySuccess = true;
foreach ($_FILES['files']['tmp_name'] as $index => $tmpName) {
if (empty($tmpName)) continue;
$fileName = $_FILES['files']['name'][$index];
$relativePath = isset($_POST['paths']) && isset($_POST['paths'][$index]) ? $_POST['paths'][$index] : '';
if (!empty($relativePath)) {
$fileName = basename($relativePath);
$dirPart = remoteDirname($relativePath);
if ($dirPart !== '.' && $dirPart !== '') {
$remoteDirPath = $currentPath . '/' . $dirPart;
if (!$sftp->is_dir($remoteDirPath)) {
createDirectoryRecursive($sftp, $remoteDirPath);
}
$remotePath = $remoteDirPath . '/' . $fileName;
} else {
$remotePath = $currentPath . '/' . $fileName;
}
} else {
$remotePath = $currentPath . '/' . $fileName;
}
if ($sftp->put($remotePath, $tmpName, SFTP::SOURCE_LOCAL_FILE)) {
$uploadStatus[] = "Uploaded: " . ($relativePath ? $relativePath : $fileName);
$anySuccess = true;
} else {
$uploadStatus[] = "Failed to upload: " . ($relativePath ? $relativePath : $fileName);
}
}
if ($anySuccess) {
echo implode("\n", $uploadStatus);
exit;
} else { } else {
echo "Failed to upload any files. Please check SFTP connection and permissions."; $uploadMessages[] = 'Failed to upload: ' . ($relativePath !== '' ? $relativePath : $fileName);
http_response_code(500);
exit;
} }
} }
echo "No files received or invalid request."; if (!$anySuccess) {
http_response_code(400); http_response_code($failureStatus);
exit; }
header('Content-Type: text/plain; charset=UTF-8');
echo implode("\n", $uploadMessages);
+24 -35
View File
@@ -1,16 +1,7 @@
<?php <?php
session_start(); require_once __DIR__ . '/../../includes/auth.php';
requireLogin('../../index.php');
if(!isset($_SESSION['uname'])){
header("location: ../../index.php");
session_destroy();
exit;
}
// Include this at the top to see potential errors
// Comment out in production
ini_set('display_errors', 1);
error_reporting(E_ALL);
ini_set('memory_limit', '512M'); ini_set('memory_limit', '512M');
require_once __DIR__ . '/../../config/config.php'; require_once __DIR__ . '/../../config/config.php';
@@ -24,7 +15,7 @@ if (!isset($_GET['file'])) {
if (isset($_POST['createShare'])) { if (isset($_POST['createShare'])) {
$duration = intval($_POST['duration']); $duration = intval($_POST['duration']);
$downloadAllowed = isset($_POST['downloadAllowed']) ? 1 : 0; $downloadAllowed = isset($_POST['downloadAllowed']) ? 1 : 0;
$filePath = $_POST['filePath']; $filePath = requireRemotePath($_POST['filePath'], $defaultPath);
$token = bin2hex(random_bytes(16)); $token = bin2hex(random_bytes(16));
@@ -32,11 +23,15 @@ if (isset($_POST['createShare'])) {
$expiration = date('Y-m-d H:i:s', strtotime("+$duration hours")); $expiration = date('Y-m-d H:i:s', strtotime("+$duration hours"));
$conn = getDatabaseConnection(); $database = getDatabaseConnection();
$stmt = $conn->prepare("INSERT INTO share (token, file_path, expiration, download_allowed) VALUES (?, ?, ?, ?)"); $statement = $database->prepare("INSERT INTO share (token, file_path, expiration, download_allowed) VALUES (?, ?, ?, ?)");
$stmt->bind_param("sssi", $token, $filePath, $expiration, $downloadAllowed); $statement->bind_param("sssi", $token, $filePath, $expiration, $downloadAllowed);
if ($stmt->execute()) { $shareSaved = $statement->execute();
$statement->close();
$database->close();
if ($shareSaved) {
$shareUrl = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://$_SERVER[HTTP_HOST]" . $shareUrl = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://$_SERVER[HTTP_HOST]" .
remoteDirname($_SERVER['PHP_SELF']) . "/shared.php?token=$token"; remoteDirname($_SERVER['PHP_SELF']) . "/shared.php?token=$token";
@@ -47,13 +42,11 @@ if (isset($_POST['createShare'])) {
header("Location: view.php?file=" . urlencode($filePath) . "&shared=1"); header("Location: view.php?file=" . urlencode($filePath) . "&shared=1");
exit; exit;
} else { } else {
$_SESSION['shareError'] = "Failed to create share: " . $conn->error; $_SESSION['shareError'] = 'Failed to create share. Please try again.';
header("Location: view.php?file=" . urlencode($filePath) . "&shared=0"); header("Location: view.php?file=" . urlencode($filePath) . "&shared=0");
exit; exit;
} }
$stmt->close();
$conn->close();
} }
$shareCreated = false; $shareCreated = false;
@@ -71,7 +64,7 @@ if (isset($_GET['shared'])) {
} }
} }
$filePath = $_GET['file']; $filePath = requireRemotePath($_GET['file'], $defaultPath);
$fileName = basename($filePath); $fileName = basename($filePath);
$fileExtension = strtolower(pathinfo($fileName, PATHINFO_EXTENSION)); $fileExtension = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
@@ -79,13 +72,13 @@ if (!$sftp->stat($filePath)) {
die("File not found: $filePath"); die("File not found: $filePath");
} }
$imageTypes = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg']; $imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'];
$videoTypes = ['mp4', 'webm', 'ogg', 'mov', 'avi', 'mkv']; $videoExtensions = ['mp4', 'webm', 'ogg', 'mov', 'avi', 'mkv'];
$audioTypes = ['mp3', 'wav', 'ogg', 'm4a', 'flac', 'aac']; $audioExtensions = ['mp3', 'wav', 'ogg', 'm4a', 'flac', 'aac'];
$isImage = in_array($fileExtension, $imageTypes); $isImage = in_array($fileExtension, $imageExtensions);
$isVideo = in_array($fileExtension, $videoTypes); $isVideo = in_array($fileExtension, $videoExtensions);
$isAudio = in_array($fileExtension, $audioTypes); $isAudio = in_array($fileExtension, $audioExtensions);
if (!$isImage && !$isVideo && !$isAudio) { if (!$isImage && !$isVideo && !$isAudio) {
die("Unsupported file type"); die("Unsupported file type");
@@ -238,10 +231,10 @@ if (isset($_GET['stream'])) {
// Throttle to prevent CPU overload // Throttle to prevent CPU overload
usleep(100000); // 100ms usleep(100000); // 100ms
} }
} catch (Exception $e) { } catch (Exception $exception) {
error_log("Streaming error: " . $e->getMessage()); error_log("Streaming error: " . $exception->getMessage());
if (isset($_GET['debug'])) { if (isset($_GET['debug'])) {
header("X-Stream-Error: " . $e->getMessage()); header("X-Stream-Error: " . $exception->getMessage());
} }
} }
@@ -252,7 +245,6 @@ if (isset($_GET['stream'])) {
} }
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
@@ -278,7 +270,6 @@ if (isset($_GET['stream'])) {
'class' => 'media-page', 'class' => 'media-page',
]); ?> ]); ?>
<section class="content-panel media-panel"> <section class="content-panel media-panel">
<article class="col-12"> <article class="col-12">
<div class="media-container position-relative"> <div class="media-container position-relative">
@@ -301,7 +292,7 @@ if (isset($_GET['stream'])) {
</audio> </audio>
<?php endif; ?> <?php endif; ?>
<?php if (isset($_SESSION["downPer"]) && $_SESSION["downPer"] == true) : ?> <?php if (hasPermission('downPer')) : ?>
<a href="download.php?file=<?= urlencode($filePath) ?>" class="btn btn-success m-3">Download</a> <a href="download.php?file=<?= urlencode($filePath) ?>" class="btn btn-success m-3">Download</a>
<?php endif; ?> <?php endif; ?>
@@ -412,8 +403,6 @@ function copyShareUrl() {
} }
</script> </script>
<script> <script>
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const mediaElement = document.querySelector('video, audio'); const mediaElement = document.querySelector('video, audio');
@@ -444,4 +433,4 @@ function formatBytes($bytes, $precision = 2) {
$bytes /= (1 << (10 * $pow)); $bytes /= (1 << (10 * $pow));
return round($bytes, $precision) . ' ' . $units[$pow]; return round($bytes, $precision) . ' ' . $units[$pow];
} }
?> ?>
+46 -78
View File
@@ -1,83 +1,51 @@
<?php <?php
session_start(); session_start();
if (isset($_POST['uname'], $_POST['pswd'])) {
require_once __DIR__ . '/../config/config.php'; require_once __DIR__ . '/../config/config.php';
// $username = "UNAME";
// $password = "PSWD";
// $db = "usbraidlogin";
$uname = htmlspecialchars($_POST['uname']); if (!isset($_POST['uname'], $_POST['pswd'])
$pswd = htmlspecialchars($_POST['pswd']); || !is_string($_POST['uname']) || !is_string($_POST['pswd'])) {
$_SESSION['login_error'] = 'Please fill in all fields.';
$conn = getDatabaseConnection(); header('Location: ../index.php');
// charset already set in getDatabaseConnection() exit;
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
$sql = "SELECT * FROM users WHERE uname=?";
$stmt = $conn->prepare($sql);
if (!$stmt) {
$_SESSION['login_error'] = "Database error. Please try again.";
$conn->close();
header("Location: ../index.php");
exit();
}
$stmt->bind_param("s", $uname);
$stmt->execute();
$result = $stmt->get_result();
if ($result && $result->num_rows > 0) {
$login_successful = false;
while ($row = $result->fetch_assoc()) {
$hash = $row["pswd"];
$admin = (bool)$row["admin"];
$defPath = $row["defPath"];
$delPer = $row["delPer"];
$downPer = $row["downPer"];
$upPer = $row["upPer"];
if (password_verify($pswd, $hash)) {
// Login successful - store info in session
$_SESSION['uname'] = $uname;
$_SESSION['admin'] = $admin;
$_SESSION['defPath'] = $defPath;
$_SESSION['downPer'] = $downPer;
$_SESSION['delPer'] = $delPer;
$_SESSION['upPer'] = $upPer;
$login_successful = true;
break;
}
}
$result->free_result();
$stmt->close();
$conn->close();
if ($login_successful) {
header("Location: ftp/index.php");
exit();
} else {
// If password verification fails
$_SESSION['login_error'] = "Invalid username or password.";
header("Location: ../index.php");
exit();
}
} else {
// If no user is found
$result->free_result();
$stmt->close();
$conn->close();
$_SESSION['login_error'] = "Invalid username or password.";
header("Location: ../index.php");
exit();
}
} else {
// If form fields are not set
$_SESSION['login_error'] = "Please fill in all fields.";
header("Location: ../index.php");
exit();
} }
?>
$username = trim($_POST['uname']);
$password = $_POST['pswd'];
$loginSuccessful = false;
$database = getDatabaseConnection();
try {
$statement = $database->prepare('SELECT uname, pswd, admin, defPath, downPer, delPer, upPer FROM users WHERE uname = ?');
$statement->bind_param('s', $username);
$statement->execute();
$result = $statement->get_result();
while ($user = $result->fetch_assoc()) {
if (!password_verify($password, $user['pswd'])) {
continue;
}
session_regenerate_id(true);
$_SESSION['uname'] = $user['uname'];
$_SESSION['admin'] = (bool) $user['admin'];
$_SESSION['defPath'] = $user['defPath'];
foreach (['downPer', 'delPer', 'upPer'] as $permission) {
$_SESSION[$permission] = (bool) $user[$permission];
}
unset($_SESSION['login_error']);
$loginSuccessful = true;
break;
}
$result->free();
$statement->close();
if (!$loginSuccessful) {
$_SESSION['login_error'] = 'Invalid username or password.';
}
} catch (mysqli_sql_exception $exception) {
error_log($exception->getMessage());
$_SESSION['login_error'] = 'Database error. Please try again.';
} finally {
$database->close();
}
header('Location: ' . ($loginSuccessful ? 'ftp/index.php' : '../index.php'));
exit;
+28
View File
@@ -0,0 +1,28 @@
<?php
/** Shared session checks. Database, form and session keys retain their legacy names. */
function requireLogin(string $loginUrl): void {
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
if (!isset($_SESSION['uname'])) {
header('Location: ' . $loginUrl);
exit;
}
}
function hasPermission(string $permission): bool {
return !empty($_SESSION[$permission]);
}
function requirePermission(string $permission, string $message): void {
if (!hasPermission($permission)) {
http_response_code(403);
exit($message);
}
}
function isPasswordValid(string $password): bool {
return strlen($password) >= 8
&& preg_match('/[0-9]/', $password) === 1
&& preg_match('/[A-Z]/', $password) === 1;
}
+22
View File
@@ -22,3 +22,25 @@ function remoteDirname(string $path): string {
$parent = rtrim(substr($path, 0, $separator), '/'); $parent = rtrim(substr($path, 0, $separator), '/');
return $parent === '' ? '/' : $parent; return $parent === '' ? '/' : $parent;
} }
/** Compare directory boundaries, so /data-other is not considered part of /data. */
function isRemotePathWithin(string $path, string $basePath): bool {
$path = normalizePath($path);
$basePath = normalizePath($basePath);
return $path === $basePath || strpos($path, rtrim($basePath, '/') . '/') === 0;
}
/** Validate a single POSIX filename before joining it to a directory. */
function isRemoteNameValid(string $name): bool {
return $name !== '' && $name !== '.' && $name !== '..'
&& strpos($name, '/') === false && strpos($name, "\0") === false;
}
/** Resolve a request path inside the user's configured workspace. */
function requireRemotePath(string $path, string $basePath): string {
if (strpos($path, "\0") !== false || !isRemotePathWithin($path, $basePath)) {
http_response_code(403);
exit('Path is outside your workspace.');
}
return normalizePath($path);
}
+147
View File
@@ -0,0 +1,147 @@
<?php
require_once __DIR__ . '/../Web/includes/auth.php';
function expectBackend(bool $condition, string $message): void {
if (!$condition) throw new RuntimeException($message);
}
// Run handlers in separate processes because redirects and permission checks exit.
if (($argv[1] ?? '') === '--handler') {
session_set_save_handler(new class implements SessionHandlerInterface {
public function open(string $path, string $name): bool { return true; }
public function close(): bool { return true; }
public function read(string $id): string|false { return ''; }
public function write(string $id, string $data): bool { return true; }
public function destroy(string $id): bool { return true; }
public function gc(int $maxLifetime): int|false { return 0; }
}, true);
session_start(['use_cookies' => false, 'cache_limiter' => '']);
http_response_code(200);
$scenario = json_decode($argv[3], true, 512, JSON_THROW_ON_ERROR);
$_SESSION = $scenario['session'] ?? [];
$_POST = $scenario['post'] ?? [];
$_GET = $scenario['get'] ?? [];
$_FILES = $scenario['files'] ?? [];
$_SERVER['REQUEST_METHOD'] = $scenario['method'] ?? 'POST';
$defaultPath = '/mnt/raid';
$calls = [];
function initializeSFTP() {
global $calls;
$calls[] = ['connect'];
return new class {
function put($path, $data, $mode = null) {
global $calls;
$calls[] = $mode === null ? ['put', $path, $data] : ['put', $path, $data, $mode];
return true;
}
function is_dir($path) { return true; }
function mkdir($path) {
global $calls;
$calls[] = ['mkdir', $path];
return true;
}
};
}
// Minimal database double exercises login without a live database or network.
function getDatabaseConnection() {
return new class {
function prepare($sql) {
return new class {
function bind_param($types, &...$values) {}
function execute() {}
function get_result() {
return new class {
private $fetched = false;
function fetch_assoc() {
if ($this->fetched) return null;
$this->fetched = true;
return ['uname' => 'member', 'pswd' => password_hash(' Secret1<&" ', PASSWORD_BCRYPT),
'admin' => 0, 'defPath' => '/mnt/raid', 'downPer' => 1, 'delPer' => 0, 'upPer' => 1];
}
function free() {}
};
}
function close() {}
};
}
function close() {}
};
}
register_shutdown_function(function () use (&$calls) {
echo "\n__RESULT__" . json_encode(['status' => http_response_code(), 'calls' => $calls, 'session' => $_SESSION]);
});
$path = realpath(__DIR__ . '/../Web/content/' . $argv[2] . '.php');
$source = file_get_contents($path);
// Replace only configuration loading, retaining the real guards and handlers.
$source = preg_replace("~require_once __DIR__ \\. '/(?:\\.\\./)+config/config\\.php';~", '', $source);
$source = str_replace('__DIR__', var_export(dirname($path), true), $source);
$source = str_replace('session_start();', '', $source);
require_once __DIR__ . '/../Web/includes/remotepaths.php';
require_once __DIR__ . '/../Web/vendor/autoload.php';
eval('?>' . $source);
exit;
}
function runBackendHandler(string $handler, array $scenario): array {
$process = proc_open([PHP_BINARY, __FILE__, '--handler', $handler, json_encode($scenario)],
[0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes);
fclose($pipes[0]);
$output = stream_get_contents($pipes[1]);
$errors = stream_get_contents($pipes[2]);
fclose($pipes[1]);
fclose($pipes[2]);
$exitCode = proc_close($process);
expectBackend($exitCode === 0 && $errors === '', $handler . ' failed: ' . $errors . $output);
[$body, $result] = explode("\n__RESULT__", $output, 2);
return ['body' => $body] + json_decode($result, true, 512, JSON_THROW_ON_ERROR);
}
foreach (['short1A' => false, 'lowercase1' => false, 'UppercaseOnly' => false, 'Password1' => true, ' Secret1<&" ' => true] as $password => $expected) {
expectBackend(isPasswordValid($password) === $expected, 'Incorrect password validation');
}
$member = ['uname' => 'member', 'upPer' => true, 'downPer' => true, 'delPer' => true];
foreach (['upload' => 'upPer', 'createdir' => 'upPer', 'createfile' => 'upPer', 'rename' => 'upPer', 'extract' => 'upPer', 'delete' => 'delPer', 'download' => 'downPer'] as $handler => $permission) {
$result = runBackendHandler('ftp/' . $handler, []);
expectBackend($result['calls'] === [] && $result['body'] === '', $handler . ' must require login before connecting');
$result = runBackendHandler('ftp/' . $handler, ['session' => ['uname' => 'member', $permission => false]]);
expectBackend($result['status'] === 403 && $result['calls'] === [], $handler . ' must enforce permissions before connecting');
}
$result = runBackendHandler('ftp/createfile', ['session' => $member,
'post' => ['action' => 'createFile', 'currentPath' => '/mnt/raid', 'fileName' => 'empty.txt']]);
expectBackend($result['status'] === 200 && $result['calls'][1] === ['put', '/mnt/raid/empty.txt', ''], 'New files must contain zero bytes');
expectBackend(json_decode($result['body'], true)['success'] === true, 'Create file response must retain its API');
foreach (['/mnt/raid-other', '/mnt/raid/../private'] as $path) {
$result = runBackendHandler('ftp/createdir', ['session' => $member,
'post' => ['action' => 'createDir', 'currentPath' => $path, 'dirName' => 'new']]);
expectBackend($result['status'] === 403 && count($result['calls']) === 1, 'Reject writes outside workspace');
}
$result = runBackendHandler('ftp/createdir', ['session' => $member, 'post' => ['action' => 'createDir', 'dirName' => '..']]);
expectBackend($result['status'] === 400 && count($result['calls']) === 1, 'Reject parent directory names');
$result = runBackendHandler('ftp/delete', ['session' => $member, 'post' => ['delete' => '/mnt/raid']]);
expectBackend($result['status'] === 400 && count($result['calls']) === 1, 'Protect workspace root from deletion');
$result = runBackendHandler('ftp/rename', ['session' => $member, 'post' => ['path' => '/mnt/raid/item', 'newName' => '../escape']]);
expectBackend($result['status'] === 400 && count($result['calls']) === 1, 'Rename must accept a single filename');
$upload = ['session' => $member, 'post' => ['paths' => ['folder/ok.txt', 'bad.txt']],
'files' => ['files' => ['name' => ['ok.txt', 'bad.txt'], 'tmp_name' => ['local-upload', ''],
'error' => [UPLOAD_ERR_OK, UPLOAD_ERR_PARTIAL]]]];
$result = runBackendHandler('ftp/upload', $upload);
expectBackend($result['status'] === 200 && $result['calls'][1] === ['put', '/mnt/raid/folder/ok.txt', 'local-upload', 1], 'Upload must use local file mode and preserve relative directories');
expectBackend(strpos($result['body'], 'File was only partially uploaded') !== false, 'Report errors for each uploaded file');
$upload['post']['paths'][0] = '../private.txt';
$result = runBackendHandler('ftp/upload', $upload);
expectBackend($result['status'] === 403 && count($result['calls']) === 1, 'Upload paths must stay in the selected directory');
$result = runBackendHandler('login', ['post' => ['uname' => 'member', 'pswd' => ' Secret1<&" ']]);
expectBackend(($result['session']['uname'] ?? '') === 'member', 'Login must preserve special characters and whitespace in passwords');
expectBackend($result['session']['admin'] === false && $result['session']['delPer'] === false && $result['session']['upPer'] === true, 'Login must retain boolean permissions');
$result = runBackendHandler('login', ['post' => ['uname' => 'member', 'pswd' => 'wrong']]);
expectBackend(!isset($result['session']['uname']) && isset($result['session']['login_error']), 'Reject incorrect passwords');
echo "Backend: handler permissions, path boundaries, empty files and login checks passed.\n";
+15 -1
View File
@@ -28,4 +28,18 @@ foreach (['/etc' => '/etc/config.txt', '/mnt/raid' => '/mnt/raid/config.txt'] as
$relative = substr($file, strlen(rtrim(remoteDirname($base), '/')) + 1); $relative = substr($file, strlen(rtrim(remoteDirname($base), '/')) + 1);
if ($relative !== ($base === '/etc' ? 'etc/config.txt' : 'raid/config.txt')) throw new RuntimeException('Incorrect ZIP member path'); if ($relative !== ($base === '/etc' ? 'etc/config.txt' : 'raid/config.txt')) throw new RuntimeException('Incorrect ZIP member path');
} }
echo "Remote paths: POSIX parents, normalization, root navigation and ZIP paths passed.\n"; foreach ([
['/mnt/raid', '/mnt/raid', true],
['/mnt/raid/folder', '/mnt/raid/', true],
['/mnt/raid-other/file', '/mnt/raid', false],
['/mnt/raid/../private', '/mnt/raid', false],
['/mnt/raid/sub/../../private', '/mnt/raid', false],
['/mnt/raid/sub/../file', '/mnt/raid', true],
['/etc', '/', true],
] as [$path, $basePath, $expected]) {
if (isRemotePathWithin($path, $basePath) !== $expected) throw new RuntimeException('Incorrect path boundary: ' . $path);
}
foreach (['' => false, '.' => false, '..' => false, 'a/b' => false, "a\0b" => false, 'file.txt' => true, '0' => true, 'a\\b' => true] as $name => $expected) {
if (isRemoteNameValid((string) $name) !== $expected) throw new RuntimeException('Incorrect filename validation');
}
echo "Remote paths: POSIX parents, normalization, workspace boundaries, names and ZIP paths passed.\n";
+3 -2
View File
@@ -1,5 +1,6 @@
<?php <?php
require_once __DIR__ . '/../Web/includes/workspace.php'; require_once __DIR__ . '/../Web/includes/workspace.php';
require_once __DIR__ . '/../Web/includes/auth.php';
require_once __DIR__ . '/../Web/includes/remotepaths.php'; require_once __DIR__ . '/../Web/includes/remotepaths.php';
require_once __DIR__ . '/../Web/content/ftp/lib/serverstats.php'; require_once __DIR__ . '/../Web/content/ftp/lib/serverstats.php';
set_error_handler(function ($severity, $message, $file, $line) { set_error_handler(function ($severity, $message, $file, $line) {
@@ -11,11 +12,11 @@ function expectWorkspace($condition, $message) {
function renderWorkspaceFixture($name, $admin = true, $defaultPath = '/mnt/raid', $currentPath = '/mnt/raid/Workspace') { function renderWorkspaceFixture($name, $admin = true, $defaultPath = '/mnt/raid', $currentPath = '/mnt/raid/Workspace') {
$_SESSION = ['uname' => 'michal', 'admin' => $admin, 'upPer' => true, 'downPer' => true, 'delPer' => true]; $_SESSION = ['uname' => 'michal', 'admin' => $admin, 'upPer' => true, 'downPer' => true, 'delPer' => true];
$filePath = $currentPath . '/Example.pdf'; $fileName = basename($filePath); $parentDir = $currentPath; $filePath = $currentPath . '/Example.pdf'; $fileName = basename($filePath); $parentPath = $currentPath;
$fileExtension = 'png'; $fileSize = 245760; $mimeType = 'image/png'; $fileExtension = 'png'; $fileSize = 245760; $mimeType = 'image/png';
$isImage = true; $isVideo = false; $isAudio = false; $isImage = true; $isVideo = false; $isAudio = false;
$editable = true; $showLineNumbers = true; $content = "{\n \"workspace\": \"USB RAID\",\n \"status\": \"connected\"\n}\n"; $editable = true; $showLineNumbers = true; $content = "{\n \"workspace\": \"USB RAID\",\n \"status\": \"connected\"\n}\n";
$message = ''; $messageType = ''; $message_type = ''; $shareCreated = false; $shareError = ''; $message = ''; $messageType = ''; $shareCreated = false; $shareError = '';
$share = ['expiration' => '2030-01-01']; $downloadAllowed = false; $token = 'test-only'; $share = ['expiration' => '2030-01-01']; $downloadAllowed = false; $token = 'test-only';
$items = ['Documents', 'Photos', 'README.md', 'Example.pdf']; $items = ['Documents', 'Photos', 'README.md', 'Example.pdf'];
$imageExtensions = ['png']; $videoExtensions = []; $audioExtensions = []; $editableExtensions = ['md']; $archiveExtensions = []; $imageExtensions = ['png']; $videoExtensions = []; $audioExtensions = []; $editableExtensions = ['md']; $archiveExtensions = [];