fix drag-drop, UI glitches, & update validation

This commit is contained in:
Ryan
2025-04-11 03:21:09 -04:00
committed by GitHub
parent 4360f2830a
commit 337f529afd
36 changed files with 161 additions and 73 deletions

View File

@@ -1,5 +1,18 @@
# Changelog # Changelog
## Changes 4/11/2025
- Fixed fileDragDrop issue from previous update.
- Fixed User Panel height changing unexpectedly on mouse over.
- Improved JS file comments for better documentation.
- Fixed userPermissions not updating after initial setting.
- Disabled folder and file sharing for readOnly users.
- Moved change password close button to the top right of the modal.
- Updated upload regex pattern to be Unicodeenabled and added additional security measures. [(#19)](https://github.com/error311/FileRise/issues/19)
- Updated filename, folder, and username regex acceptance patterns.
---
## Shift Key MultiSelection Changes 4/10/2025 v1.1.1 ## Shift Key MultiSelection Changes 4/10/2025 v1.1.1
- **Implemented Range Selection:** - **Implemented Range Selection:**

View File

@@ -49,7 +49,7 @@ if (!$newUsername || !$newPassword) {
} }
// Validate username using preg_match (allow letters, numbers, underscores, dashes, and spaces). // Validate username using preg_match (allow letters, numbers, underscores, dashes, and spaces).
if (!preg_match('/^[A-Za-z0-9_\- ]+$/', $newUsername)) { if (!preg_match('/^[\p{L}\p{N}_\- ]+$/u', $newUsername)) {
echo json_encode(["error" => "Invalid username. Only letters, numbers, underscores, dashes, and spaces are allowed."]); echo json_encode(["error" => "Invalid username. Only letters, numbers, underscores, dashes, and spaces are allowed."]);
exit; exit;
} }

View File

@@ -44,7 +44,7 @@ $destinationFolder = trim($data['destination']);
$files = $data['files']; $files = $data['files'];
// Validate folder names: allow letters, numbers, underscores, dashes, spaces, and forward slashes. // Validate folder names: allow letters, numbers, underscores, dashes, spaces, and forward slashes.
$folderPattern = '/^[A-Za-z0-9_\- \/]+$/'; $folderPattern = '/^[\p{L}\p{N}_\-\s\/\\\\]+$/u';
if ($sourceFolder !== 'root' && !preg_match($folderPattern, $sourceFolder)) { if ($sourceFolder !== 'root' && !preg_match($folderPattern, $sourceFolder)) {
echo json_encode(["error" => "Invalid source folder name."]); echo json_encode(["error" => "Invalid source folder name."]);
exit; exit;
@@ -104,7 +104,7 @@ $destMetadata = file_exists($destMetaFile) ? json_decode(file_get_contents($dest
$errors = []; $errors = [];
// Define a safe file name pattern: letters, numbers, underscores, dashes, dots, parentheses, and spaces. // Define a safe file name pattern: letters, numbers, underscores, dashes, dots, parentheses, and spaces.
$safeFileNamePattern = '/^[A-Za-z0-9_\-\.\(\) ]+$/'; $safeFileNamePattern = '/^[\p{L}\p{N}\p{M}%\-\.\(\) _]+$/u';
foreach ($files as $fileName) { foreach ($files as $fileName) {
// Save the original name for metadata lookup. // Save the original name for metadata lookup.

View File

@@ -45,13 +45,13 @@ $folderName = trim($input['folderName']);
$parent = isset($input['parent']) ? trim($input['parent']) : ""; $parent = isset($input['parent']) ? trim($input['parent']) : "";
// Basic sanitation: allow only letters, numbers, underscores, dashes, and spaces in folderName // Basic sanitation: allow only letters, numbers, underscores, dashes, and spaces in folderName
if (!preg_match('/^[A-Za-z0-9_\- ]+$/', $folderName)) { if (!preg_match('/^[\p{L}\p{N}_\-\s\/\\\\]+$/u', $folderName)) {
echo json_encode(['success' => false, 'error' => 'Invalid folder name.']); echo json_encode(['success' => false, 'error' => 'Invalid folder name.']);
exit; exit;
} }
// Optionally, sanitize the parent folder if needed. // Optionally, sanitize the parent folder if needed.
if ($parent && !preg_match('/^[A-Za-z0-9_\- \/]+$/', $parent)) { if ($parent && !preg_match('/^[\p{L}\p{N}_\-\s\/\\\\]+$/u', $parent)) {
echo json_encode(['success' => false, 'error' => 'Invalid parent folder name.']); echo json_encode(['success' => false, 'error' => 'Invalid parent folder name.']);
exit; exit;
} }

View File

@@ -10,6 +10,16 @@ if (!$input) {
exit; exit;
} }
$username = $_SESSION['username'] ?? '';
$userPermissions = loadUserPermissions($username);
if ($username) {
$userPermissions = loadUserPermissions($username);
if (isset($userPermissions['readOnly']) && $userPermissions['readOnly'] === true) {
echo json_encode(["error" => "Read-only users are not allowed to create shared folders."]);
exit();
}
}
$folder = isset($input['folder']) ? trim($input['folder']) : ""; $folder = isset($input['folder']) ? trim($input['folder']) : "";
$expirationMinutes = isset($input['expirationMinutes']) ? intval($input['expirationMinutes']) : 60; $expirationMinutes = isset($input['expirationMinutes']) ? intval($input['expirationMinutes']) : 60;
$password = isset($input['password']) ? $input['password'] : ""; $password = isset($input['password']) ? $input['password'] : "";
@@ -17,7 +27,7 @@ $allowUpload = isset($input['allowUpload']) ? intval($input['allowUpload']) : 0;
// Validate folder name using regex. // Validate folder name using regex.
// Allow letters, numbers, underscores, hyphens, spaces and slashes. // Allow letters, numbers, underscores, hyphens, spaces and slashes.
if ($folder !== 'root' && !preg_match('/^[A-Za-z0-9_\- \/]+$/', $folder)) { if ($folder !== 'root' && !preg_match('/^[\p{L}\p{N}_\-\s\/\\\\]+$/u', $folder)) {
echo json_encode(["error" => "Invalid folder name."]); echo json_encode(["error" => "Invalid folder name."]);
exit; exit;
} }

View File

@@ -9,13 +9,23 @@ if (!$input) {
exit; exit;
} }
$username = $_SESSION['username'] ?? '';
$userPermissions = loadUserPermissions($username);
if ($username) {
$userPermissions = loadUserPermissions($username);
if (isset($userPermissions['readOnly']) && $userPermissions['readOnly'] === true) {
echo json_encode(["error" => "Read-only users are not allowed to create share files."]);
exit();
}
}
$folder = isset($input['folder']) ? trim($input['folder']) : ""; $folder = isset($input['folder']) ? trim($input['folder']) : "";
$file = isset($input['file']) ? basename($input['file']) : ""; $file = isset($input['file']) ? basename($input['file']) : "";
$expirationMinutes = isset($input['expirationMinutes']) ? intval($input['expirationMinutes']) : 60; $expirationMinutes = isset($input['expirationMinutes']) ? intval($input['expirationMinutes']) : 60;
$password = isset($input['password']) ? $input['password'] : ""; $password = isset($input['password']) ? $input['password'] : "";
// Validate folder using regex. // Validate folder using regex.
if ($folder !== 'root' && !preg_match('/^[A-Za-z0-9_\- \/]+$/', $folder)) { if ($folder !== 'root' && !preg_match('/^[\p{L}\p{N}_\-\s\/\\\\]+$/u', $folder)) {
echo json_encode(["error" => "Invalid folder name."]); echo json_encode(["error" => "Invalid folder name."]);
exit; exit;
} }

View File

@@ -69,7 +69,7 @@ if (!isset($data['files']) || !is_array($data['files'])) {
$folder = isset($data['folder']) ? trim($data['folder']) : 'root'; $folder = isset($data['folder']) ? trim($data['folder']) : 'root';
// Validate folder: allow letters, numbers, underscores, dashes, spaces, and forward slashes // Validate folder: allow letters, numbers, underscores, dashes, spaces, and forward slashes
if ($folder !== 'root' && !preg_match('/^[A-Za-z0-9_\- \/]+$/', $folder)) { if ($folder !== 'root' && !preg_match('/^[\p{L}\p{N}_\-\s\/\\\\]+$/u', $folder)) {
echo json_encode(["error" => "Invalid folder name."]); echo json_encode(["error" => "Invalid folder name."]);
exit; exit;
} }
@@ -96,7 +96,7 @@ $movedFiles = [];
$errors = []; $errors = [];
// Define a safe file name pattern: allow letters, numbers, underscores, dashes, dots, and spaces. // Define a safe file name pattern: allow letters, numbers, underscores, dashes, dots, and spaces.
$safeFileNamePattern = '/^[A-Za-z0-9_\-\.\(\) ]+$/'; $safeFileNamePattern = '/^[\p{L}\p{N}\p{M}%\-\.\(\) _]+$/u';
foreach ($data['files'] as $fileName) { foreach ($data['files'] as $fileName) {
$basename = basename(trim($fileName)); $basename = basename(trim($fileName));

View File

@@ -50,7 +50,7 @@ if ($folderName === 'root') {
} }
// Allow letters, numbers, underscores, dashes, spaces, and forward slashes. // Allow letters, numbers, underscores, dashes, spaces, and forward slashes.
if (!preg_match('/^[A-Za-z0-9_\- \/]+$/', $folderName)) { if (!preg_match('/^[\p{L}\p{N}_\-\s\/\\\\]+$/u', $folderName)) {
echo json_encode(['success' => false, 'error' => 'Invalid folder name.']); echo json_encode(['success' => false, 'error' => 'Invalid folder name.']);
exit; exit;
} }

View File

@@ -62,7 +62,7 @@ $deletedFiles = [];
$errors = []; $errors = [];
// Define a safe file name pattern. // Define a safe file name pattern.
$safeFileNamePattern = '/^[A-Za-z0-9_\-\.\(\) ]+$/'; $safeFileNamePattern = '/^[\p{L}\p{N}\p{M}%\-\.\(\) _]+$/u';
foreach ($filesToDelete as $trashName) { foreach ($filesToDelete as $trashName) {
$trashName = trim($trashName); $trashName = trim($trashName);

View File

@@ -14,7 +14,7 @@ $file = isset($_GET['file']) ? basename($_GET['file']) : '';
$folder = isset($_GET['folder']) ? trim($_GET['folder']) : 'root'; $folder = isset($_GET['folder']) ? trim($_GET['folder']) : 'root';
// Validate file name (allowing letters, numbers, underscores, dashes, dots, and parentheses) // Validate file name (allowing letters, numbers, underscores, dashes, dots, and parentheses)
if (!preg_match('/^[A-Za-z0-9_\-\.\(\) ]+$/', $file)) { if (!preg_match('/^[\p{L}\p{N}\p{M}%\-\.\(\) _]+$/u', $file)) {
http_response_code(400); http_response_code(400);
echo json_encode(["error" => "Invalid file name."]); echo json_encode(["error" => "Invalid file name."]);
exit; exit;

View File

@@ -38,7 +38,7 @@ $files = $data['files'];
if ($folder !== "root") { if ($folder !== "root") {
$parts = explode('/', $folder); $parts = explode('/', $folder);
foreach ($parts as $part) { foreach ($parts as $part) {
if (empty($part) || $part === '.' || $part === '..' || !preg_match('/^[A-Za-z0-9_\-\.\(\) ]+$/', $part)) { if (empty($part) || $part === '.' || $part === '..' || !preg_match('/^[\p{L}\p{N}_\-\s\/\\\\]+$/u', $part)) {
http_response_code(400); http_response_code(400);
header('Content-Type: application/json'); header('Content-Type: application/json');
echo json_encode(["error" => "Invalid folder name."]); echo json_encode(["error" => "Invalid folder name."]);
@@ -76,7 +76,7 @@ if (empty($files)) {
} }
foreach ($files as $fileName) { foreach ($files as $fileName) {
if (!preg_match('/^[A-Za-z0-9_\-\.\(\) ]+$/', $fileName)) { if (!preg_match('/^[\p{L}\p{N}\p{M}%\-\.\(\) _]+$/u', $fileName)) {
http_response_code(400); http_response_code(400);
header('Content-Type: application/json'); header('Content-Type: application/json');
echo json_encode(["error" => "Invalid file name: " . $fileName]); echo json_encode(["error" => "Invalid file name: " . $fileName]);

View File

@@ -50,7 +50,7 @@ if (empty($files)) {
if ($folder !== "root") { if ($folder !== "root") {
$parts = explode('/', $folder); $parts = explode('/', $folder);
foreach ($parts as $part) { foreach ($parts as $part) {
if (empty($part) || $part === '.' || $part === '..' || !preg_match('/^[A-Za-z0-9_\-\.\(\) ]+$/', $part)) { if (empty($part) || $part === '.' || $part === '..' || !preg_match('/^[\p{L}\p{N}_\-\s\/\\\\]+$/u', $part)) {
http_response_code(400); http_response_code(400);
echo json_encode(["error" => "Invalid folder name."]); echo json_encode(["error" => "Invalid folder name."]);
exit; exit;
@@ -92,7 +92,7 @@ $destMetadata = file_exists($destMetaFile) ? json_decode(file_get_contents($dest
$errors = []; $errors = [];
$allSuccess = true; $allSuccess = true;
$extractedFiles = array(); // Array to collect names of extracted files $extractedFiles = array(); // Array to collect names of extracted files
$safeFileNamePattern = '/^[A-Za-z0-9_\-\.\(\) ]+$/'; $safeFileNamePattern = '/^[\p{L}\p{N}\p{M}%\-\.\(\) _]+$/u';
// ---------- Process Each File ---------- // ---------- Process Each File ----------
foreach ($files as $zipFileName) { foreach ($files as $zipFileName) {

View File

@@ -14,7 +14,7 @@ if (!isset($_SESSION['authenticated']) || $_SESSION['authenticated'] !== true) {
$folder = isset($_GET['folder']) ? trim($_GET['folder']) : 'root'; $folder = isset($_GET['folder']) ? trim($_GET['folder']) : 'root';
// Allow only safe characters in the folder parameter (letters, numbers, underscores, dashes, spaces, and forward slashes). // Allow only safe characters in the folder parameter (letters, numbers, underscores, dashes, spaces, and forward slashes).
if ($folder !== 'root' && !preg_match('/^[A-Za-z0-9_\- \/]+$/', $folder)) { if ($folder !== 'root' && !preg_match('/^[\p{L}\p{N}_\-\s\/\\\\]+$/u', $folder)) {
echo json_encode(["error" => "Invalid folder name."]); echo json_encode(["error" => "Invalid folder name."]);
exit; exit;
} }
@@ -53,7 +53,7 @@ $files = array_values(array_diff(scandir($directory), array('.', '..')));
$fileList = []; $fileList = [];
// Define a safe file name pattern: letters, numbers, underscores, dashes, dots, parentheses, and spaces. // Define a safe file name pattern: letters, numbers, underscores, dashes, dots, parentheses, and spaces.
$safeFileNamePattern = '/^[A-Za-z0-9_\-\.\(\) ]+$/'; $safeFileNamePattern = '/^[\p{L}\p{N}\p{M}%\-\.\(\) _]+$/u';
foreach ($files as $file) { foreach ($files as $file) {
// Skip hidden files (those that begin with a dot) // Skip hidden files (those that begin with a dot)

View File

@@ -20,7 +20,7 @@ function getSubfolders($dir, $relative = '') {
$folders = []; $folders = [];
$items = scandir($dir); $items = scandir($dir);
// Allow letters, numbers, underscores, dashes, and spaces in folder names. // Allow letters, numbers, underscores, dashes, and spaces in folder names.
$safeFolderNamePattern = '/^[A-Za-z0-9_\- ]+$/'; $safeFolderNamePattern = '/^[\p{L}\p{N}_\-\s\/\\\\]+$/u';
foreach ($items as $item) { foreach ($items as $item) {
if ($item === '.' || $item === '..') continue; if ($item === '.' || $item === '..') continue;
if (!preg_match($safeFolderNamePattern, $item)) { if (!preg_match($safeFolderNamePattern, $item)) {

View File

@@ -17,7 +17,7 @@ if (file_exists($usersFile)) {
$parts = explode(':', trim($line)); $parts = explode(':', trim($line));
if (count($parts) >= 3) { if (count($parts) >= 3) {
// Validate username format: // Validate username format:
if (preg_match('/^[A-Za-z0-9_\- ]+$/', $parts[0])) { if (preg_match('/^[\p{L}\p{N}_\- ]+$/u', $parts[0])) {
$users[] = [ $users[] = [
"username" => $parts[0], "username" => $parts[0],
"role" => trim($parts[2]) "role" => trim($parts[2])

View File

@@ -413,7 +413,7 @@
<!-- Change Password, Add User, Remove User, Rename File, and Custom Confirm Modals (unchanged) --> <!-- Change Password, Add User, Remove User, Rename File, and Custom Confirm Modals (unchanged) -->
<div id="changePasswordModal" class="modal" style="display:none;"> <div id="changePasswordModal" class="modal" style="display:none;">
<div class="modal-content" style="max-width:400px; margin:auto;"> <div class="modal-content" style="max-width:400px; margin:auto;">
<span id="closeChangePasswordModal" style="cursor:pointer;">&times;</span> <span id="closeChangePasswordModal" style="position:absolute; top:10px; right:10px; cursor:pointer; font-size:24px;">&times;</span>
<h3 data-i18n-key="change_password_title">Change Password</h3> <h3 data-i18n-key="change_password_title">Change Password</h3>
<input type="password" id="oldPassword" class="form-control" data-i18n-placeholder="old_password" <input type="password" id="oldPassword" class="form-control" data-i18n-placeholder="old_password"
placeholder="Old Password" style="width:100%; margin: 5px 0;" /> placeholder="Old Password" style="width:100%; margin: 5px 0;" />

View File

@@ -132,10 +132,11 @@ function updateAuthenticatedUI(data) {
if (data.username) { if (data.username) {
localStorage.setItem("username", data.username); localStorage.setItem("username", data.username);
} }
/*
if (typeof data.folderOnly !== "undefined") { if (typeof data.folderOnly !== "undefined") {
localStorage.setItem("folderOnly", data.folderOnly ? "true" : "false"); localStorage.setItem("folderOnly", data.folderOnly ? "true" : "false");
} }
*/
const headerButtons = document.querySelector(".header-buttons"); const headerButtons = document.querySelector(".header-buttons");
const firstButton = headerButtons.firstElementChild; const firstButton = headerButtons.firstElementChild;
@@ -227,11 +228,25 @@ function checkAuthentication(showLoginToast = true) {
function submitLogin(data) { function submitLogin(data) {
setLastLoginData(data); setLastLoginData(data);
window.__lastLoginData = data; window.__lastLoginData = data;
sendRequest("auth.php", "POST", data, { "X-CSRF-Token": window.csrfToken }) sendRequest("auth.php", "POST", data, { "X-CSRF-Token": window.csrfToken })
.then(response => { .then(response => {
if (response.success || response.status === "ok") { if (response.success || response.status === "ok") {
sessionStorage.setItem("welcomeMessage", "Welcome back, " + data.username + "!"); sessionStorage.setItem("welcomeMessage", "Welcome back, " + data.username + "!");
// Fetch and update permissions, then reload.
sendRequest("getUserPermissions.php", "GET")
.then(permissionData => {
if (permissionData && typeof permissionData === "object") {
localStorage.setItem("folderOnly", permissionData.folderOnly ? "true" : "false");
localStorage.setItem("readOnly", permissionData.readOnly ? "true" : "false");
localStorage.setItem("disableUpload", permissionData.disableUpload ? "true" : "false");
}
})
.catch(() => {
// if fetching permissions fails.
})
.finally(() => {
window.location.reload(); window.location.reload();
});
} else if (response.totp_required) { } else if (response.totp_required) {
openTOTPLoginModal(); openTOTPLoginModal();
} else if (response.error && response.error.includes("Too many failed login attempts")) { } else if (response.error && response.error.includes("Too many failed login attempts")) {
@@ -293,7 +308,7 @@ function loadUserList() {
closeRemoveUserModal(); closeRemoveUserModal();
} }
}) })
.catch(() => {}); .catch(() => { });
} }
window.loadUserList = loadUserList; window.loadUserList = loadUserList;
@@ -320,7 +335,7 @@ function initAuth() {
method: "POST", method: "POST",
credentials: "include", credentials: "include",
headers: { "X-CSRF-Token": window.csrfToken } headers: { "X-CSRF-Token": window.csrfToken }
}).then(() => window.location.reload(true)).catch(() => {}); }).then(() => window.location.reload(true)).catch(() => { });
}); });
document.getElementById("addUserBtn").addEventListener("click", function () { document.getElementById("addUserBtn").addEventListener("click", function () {
resetUserForm(); resetUserForm();
@@ -386,7 +401,7 @@ function initAuth() {
showToast("Error: " + (data.error || "Could not remove user")); showToast("Error: " + (data.error || "Could not remove user"));
} }
}) })
.catch(() => {}); .catch(() => { });
}); });
document.getElementById("cancelRemoveUserBtn").addEventListener("click", closeRemoveUserModal); document.getElementById("cancelRemoveUserBtn").addEventListener("click", closeRemoveUserModal);
document.getElementById("changePasswordBtn").addEventListener("click", function () { document.getElementById("changePasswordBtn").addEventListener("click", function () {

View File

@@ -162,9 +162,9 @@ export function openUserPanel() {
max-width: 600px; max-width: 600px;
width: 90%; width: 90%;
border-radius: 8px; border-radius: 8px;
position: relative; position: fixed;
overflow-y: auto; overflow-y: auto;
max-height: 90vh; max-height: 350px !important;
border: ${isDarkMode ? "1px solid #444" : "1px solid #ccc"}; border: ${isDarkMode ? "1px solid #444" : "1px solid #ccc"};
transform: none; transform: none;
transition: none; transition: none;
@@ -187,7 +187,7 @@ export function openUserPanel() {
z-index: 3000; z-index: 3000;
`; `;
userPanelModal.innerHTML = ` userPanelModal.innerHTML = `
<div class="modal-content" style="${modalContentStyles}"> <div class="modal-content user-panel-content" style="${modalContentStyles}">
<span id="closeUserPanel" style="position: absolute; top: 10px; right: 10px; cursor: pointer; font-size: 24px;">&times;</span> <span id="closeUserPanel" style="position: absolute; top: 10px; right: 10px; cursor: pointer; font-size: 24px;">&times;</span>
<h3>User Panel (${username})</h3> <h3>User Panel (${username})</h3>
<button type="button" id="openChangePasswordModalBtn" class="btn btn-primary" style="margin-bottom: 15px;">Change Password</button> <button type="button" id="openChangePasswordModalBtn" class="btn btn-primary" style="margin-bottom: 15px;">Change Password</button>
@@ -800,12 +800,18 @@ function loadUserPermissionsList() {
if ((user.role && user.role === "1") || user.username.toLowerCase() === "admin") return; if ((user.role && user.role === "1") || user.username.toLowerCase() === "admin") return;
// Use stored permissions if available; otherwise fall back to localStorage defaults. // Use stored permissions if available; otherwise fall back to localStorage defaults.
const defaultPerm = { const defaultPerm = {
folderOnly: localStorage.getItem("folderOnly") === "true", folderOnly: false,
readOnly: localStorage.getItem("readOnly") === "true", readOnly: false,
disableUpload: localStorage.getItem("disableUpload") === "true" disableUpload: false,
}; };
const userPerm = (permissionsData && typeof permissionsData === "object" && permissionsData[user.username]) || defaultPerm;
// Normalize the username key to match server storage (e.g., lowercase)
const usernameKey = user.username.toLowerCase();
const userPerm = (permissionsData && typeof permissionsData === "object" && (usernameKey in permissionsData))
? permissionsData[usernameKey]
: defaultPerm;
// Create a row for the user. // Create a row for the user.
const row = document.createElement("div"); const row = document.createElement("div");

View File

@@ -1,4 +1,4 @@
// dragDrop.js // fileDragDrop.js
import { showToast } from './domUtils.js'; import { showToast } from './domUtils.js';
import { loadFileList } from './fileListView.js'; import { loadFileList } from './fileListView.js';

View File

@@ -1,4 +1,4 @@
// editor.js // fileEditor.js
import { escapeHTML, showToast } from './domUtils.js'; import { escapeHTML, showToast } from './domUtils.js';
import { loadFileList } from './fileListView.js'; import { loadFileList } from './fileListView.js';
import { t } from './i18n.js'; import { t } from './i18n.js';

View File

@@ -299,7 +299,7 @@ export function renderFileTable(folder, container) {
}); });
}); });
updateFileActionButtons(); updateFileActionButtons();
document.querySelectorAll("#fileListContent tbody tr").forEach(row => { document.querySelectorAll("#fileList tbody tr").forEach(row => {
row.setAttribute("draggable", "true"); row.setAttribute("draggable", "true");
import('./fileDragDrop.js').then(module => { import('./fileDragDrop.js').then(module => {
row.addEventListener("dragstart", module.fileDragStartHandler); row.addEventListener("dragstart", module.fileDragStartHandler);

View File

@@ -1,4 +1,4 @@
// contextMenu.js // fileMenu.js
import { updateRowHighlight, showToast } from './domUtils.js'; import { updateRowHighlight, showToast } from './domUtils.js';
import { handleDeleteSelected, handleCopySelected, handleMoveSelected, handleDownloadZipSelected, handleExtractZipSelected, renameFile } from './fileActions.js'; import { handleDeleteSelected, handleCopySelected, handleMoveSelected, handleDownloadZipSelected, handleExtractZipSelected, renameFile } from './fileActions.js';
import { previewFile } from './filePreview.js'; import { previewFile } from './filePreview.js';

View File

@@ -81,7 +81,7 @@ $username = trim($_SERVER['PHP_AUTH_USER']);
$password = trim($_SERVER['PHP_AUTH_PW']); $password = trim($_SERVER['PHP_AUTH_PW']);
// Validate username format (optional) // Validate username format (optional)
if (!preg_match('/^[A-Za-z0-9_\- ]+$/', $username)) { if (!preg_match('/^[\p{L}\p{N}_\- ]+$/u', $username)) {
header('WWW-Authenticate: Basic realm="FileRise Login"'); header('WWW-Authenticate: Basic realm="FileRise Login"');
header('HTTP/1.0 401 Unauthorized'); header('HTTP/1.0 401 Unauthorized');
echo 'Invalid username format'; echo 'Invalid username format';

View File

@@ -45,7 +45,7 @@ $sourceFolder = trim($data['source']) ?: 'root';
$destinationFolder = trim($data['destination']) ?: 'root'; $destinationFolder = trim($data['destination']) ?: 'root';
// Allow only letters, numbers, underscores, dashes, spaces, and forward slashes in folder names. // Allow only letters, numbers, underscores, dashes, spaces, and forward slashes in folder names.
$folderPattern = '/^[A-Za-z0-9_\- \/]+$/'; $folderPattern = '/^[\p{L}\p{N}_\-\s\/\\\\]+$/u';
if ($sourceFolder !== 'root' && !preg_match($folderPattern, $sourceFolder)) { if ($sourceFolder !== 'root' && !preg_match($folderPattern, $sourceFolder)) {
echo json_encode(["error" => "Invalid source folder name."]); echo json_encode(["error" => "Invalid source folder name."]);
exit; exit;
@@ -111,7 +111,7 @@ $srcMetadata = file_exists($srcMetaFile) ? json_decode(file_get_contents($srcMet
$destMetadata = file_exists($destMetaFile) ? json_decode(file_get_contents($destMetaFile), true) : []; $destMetadata = file_exists($destMetaFile) ? json_decode(file_get_contents($destMetaFile), true) : [];
$errors = []; $errors = [];
$safeFileNamePattern = '/^[A-Za-z0-9_\-\.\(\) ]+$/'; $safeFileNamePattern = '/^[\p{L}\p{N}\p{M}%\-\.\(\) _]+$/u';
foreach ($data['files'] as $fileName) { foreach ($data['files'] as $fileName) {
// Save the original name for metadata lookup. // Save the original name for metadata lookup.

View File

@@ -17,14 +17,12 @@ if (!isset($_POST['folder'])) {
exit; exit;
} }
$folder = $_POST['folder']; $folder = urldecode($_POST['folder']);
// Validate the folder name (only alphanumerics, dashes allowed) if (!preg_match('/^resumable_[\p{L}\p{N}_\-\s\/\\\\]+$/u', $folder)) {
if (!preg_match('/^resumable_[A-Za-z0-9\-]+$/', $folder)) {
echo json_encode(["error" => "Invalid folder name"]); echo json_encode(["error" => "Invalid folder name"]);
http_response_code(400); http_response_code(400);
exit; exit;
} }
$tempDir = rtrim(UPLOAD_DIR, '/\\') . DIRECTORY_SEPARATOR . $folder; $tempDir = rtrim(UPLOAD_DIR, '/\\') . DIRECTORY_SEPARATOR . $folder;
// If the folder doesn't exist, simply return success. // If the folder doesn't exist, simply return success.

View File

@@ -30,7 +30,7 @@ if (!$usernameToRemove) {
} }
// Optional: Validate the username format (allow letters, numbers, underscores, dashes, and spaces) // Optional: Validate the username format (allow letters, numbers, underscores, dashes, and spaces)
if (!preg_match('/^[A-Za-z0-9_\- ]+$/', $usernameToRemove)) { if (!preg_match('/^[\p{L}\p{N}_\- ]+$/u', $usernameToRemove)) {
echo json_encode(["error" => "Invalid username format"]); echo json_encode(["error" => "Invalid username format"]);
exit; exit;
} }

View File

@@ -40,7 +40,7 @@ if (!$data || !isset($data['folder']) || !isset($data['oldName']) || !isset($dat
$folder = trim($data['folder']) ?: 'root'; $folder = trim($data['folder']) ?: 'root';
// For subfolders, allow letters, numbers, underscores, dashes, spaces, and forward slashes. // For subfolders, allow letters, numbers, underscores, dashes, spaces, and forward slashes.
if ($folder !== 'root' && !preg_match('/^[A-Za-z0-9_\- \/]+$/', $folder)) { if ($folder !== 'root' && !preg_match('/^[\p{L}\p{N}_\-\s\/\\\\]+$/u', $folder)) {
echo json_encode(["error" => "Invalid folder name"]); echo json_encode(["error" => "Invalid folder name"]);
exit; exit;
} }
@@ -49,7 +49,7 @@ $oldName = basename(trim($data['oldName']));
$newName = basename(trim($data['newName'])); $newName = basename(trim($data['newName']));
// Validate file names: allow letters, numbers, underscores, dashes, dots, parentheses, and spaces. // Validate file names: allow letters, numbers, underscores, dashes, dots, parentheses, and spaces.
if (!preg_match('/^[A-Za-z0-9_\-\. \(\)]+$/', $oldName) || !preg_match('/^[A-Za-z0-9_\-\. \(\)]+$/', $newName)) { if (!preg_match('/^[\p{L}\p{N}\p{M}%\-\.\(\) _]+$/u', $oldName) || !preg_match('/^[\p{L}\p{N}\p{M}%\-\.\(\) _]+$/u', $newName)) {
echo json_encode(["error" => "Invalid file name."]); echo json_encode(["error" => "Invalid file name."]);
exit; exit;
} }

View File

@@ -48,7 +48,7 @@ $oldFolder = trim($input['oldFolder']);
$newFolder = trim($input['newFolder']); $newFolder = trim($input['newFolder']);
// Validate folder names // Validate folder names
if (!preg_match('/^[A-Za-z0-9_\- \/]+$/', $oldFolder) || !preg_match('/^[A-Za-z0-9_\- \/]+$/', $newFolder)) { if (!preg_match('/^[\p{L}\p{N}_\-\s\/\\\\]+$/u', $oldFolder) || !preg_match('/^[\p{L}\p{N}_\-\s\/\\\\]+$/u', $newFolder)) {
echo json_encode(['success' => false, 'error' => 'Invalid folder name(s).']); echo json_encode(['success' => false, 'error' => 'Invalid folder name(s).']);
exit; exit;
} }

View File

@@ -53,7 +53,7 @@ if (!isset($data['files']) || !is_array($data['files'])) {
} }
// Define a safe file name pattern. // Define a safe file name pattern.
$safeFileNamePattern = '/^[A-Za-z0-9_\-\.\(\) ]+$/'; $safeFileNamePattern = '/^[\p{L}\p{N}\p{M}%\-\.\(\) _]+$/u';
$restoredItems = []; $restoredItems = [];
$errors = []; $errors = [];

View File

@@ -48,7 +48,7 @@ $folder = isset($data["folder"]) ? trim($data["folder"]) : "root";
// If a subfolder is provided, validate it. // If a subfolder is provided, validate it.
// Allow letters, numbers, underscores, dashes, spaces, and forward slashes. // Allow letters, numbers, underscores, dashes, spaces, and forward slashes.
if ($folder !== "root" && !preg_match('/^[A-Za-z0-9_\- \/]+$/', $folder)) { if ($folder !== "root" && !preg_match('/^[\p{L}\p{N}_\-\s\/\\\\]+$/u', $folder)) {
echo json_encode(["error" => "Invalid folder name"]); echo json_encode(["error" => "Invalid folder name"]);
exit; exit;
} }

View File

@@ -20,6 +20,16 @@ if (!isset($headers['X-CSRF-Token']) || $headers['X-CSRF-Token'] !== $_SESSION['
exit; exit;
} }
$username = $_SESSION['username'] ?? '';
$userPermissions = loadUserPermissions($username);
if ($username) {
$userPermissions = loadUserPermissions($username);
if (isset($userPermissions['readOnly']) && $userPermissions['readOnly'] === true) {
echo json_encode(["error" => "Read-only users are not allowed to file tags"]);
exit();
}
}
// Retrieve and sanitize input. // Retrieve and sanitize input.
$data = json_decode(file_get_contents('php://input'), true); $data = json_decode(file_get_contents('php://input'), true);
$file = isset($data['file']) ? trim($data['file']) : ''; $file = isset($data['file']) ? trim($data['file']) : '';
@@ -77,7 +87,7 @@ if ($file === "global") {
} }
// Validate folder name. // Validate folder name.
if ($folder !== 'root' && !preg_match('/^[A-Za-z0-9_\- \/]+$/', $folder)) { if ($folder !== 'root' && !preg_match('/^[\p{L}\p{N}_\-\s\/\\\\]+$/u', $folder)) {
echo json_encode(["error" => "Invalid folder name."]); echo json_encode(["error" => "Invalid folder name."]);
exit; exit;
} }

View File

@@ -32,7 +32,7 @@ if (!$userId) {
} }
// ——— Validate userId format ——— // ——— Validate userId format ———
if (!preg_match('/^[A-Za-z0-9_\-]+$/', $userId)) { if (!preg_match('/^[\p{L}\p{N}_\- ]+$/u', $userId)) {
http_response_code(400); http_response_code(400);
error_log("Invalid userId format: {$userId}"); error_log("Invalid userId format: {$userId}");
exit(json_encode(['status'=>'error','message'=>'Invalid user identifier'])); exit(json_encode(['status'=>'error','message'=>'Invalid user identifier']));

View File

@@ -29,7 +29,7 @@ if (empty($_SESSION['username'])) {
// 4) Validate username format // 4) Validate username format
$userId = $_SESSION['username']; $userId = $_SESSION['username'];
if (!preg_match('/^[A-Za-z0-9_\-]+$/', $userId)) { if (!preg_match('/^[\p{L}\p{N}_\- ]+$/u', $userId)) {
http_response_code(400); http_response_code(400);
error_log("totp_saveCode: invalid username format: {$userId}"); error_log("totp_saveCode: invalid username format: {$userId}");
exit(json_encode(['status'=>'error','message'=>'Invalid user identifier'])); exit(json_encode(['status'=>'error','message'=>'Invalid user identifier']));

View File

@@ -40,16 +40,39 @@ if (file_exists($permissionsFile)) {
$existingPermissions = []; $existingPermissions = [];
} }
// Load user roles from the users file (similar to getUsers.php)
$usersFile = USERS_DIR . USERS_FILE;
$userRoles = [];
if (file_exists($usersFile)) {
$lines = file($usersFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
$parts = explode(':', trim($line));
if (count($parts) >= 3) {
// Validate username format:
if (preg_match('/^[\p{L}\p{N}_\- ]+$/u', $parts[0])) {
// Use a lowercase key for consistency.
$userRoles[strtolower($parts[0])] = trim($parts[2]);
}
}
}
}
// Loop through each permission update. // Loop through each permission update.
foreach ($permissions as $perm) { foreach ($permissions as $perm) {
// Ensure username is provided. // Ensure username is provided.
if (!isset($perm['username'])) continue; if (!isset($perm['username'])) continue;
$username = $perm['username']; $username = $perm['username'];
// Look up the user's role from the users file.
$role = isset($userRoles[strtolower($username)]) ? $userRoles[strtolower($username)] : null;
// Skip updating permissions for admin users. // Skip updating permissions for admin users.
if (strtolower($username) === "admin") continue; if ($role === "1") {
continue;
}
// Update permissions: default any missing value to false. // Update permissions: default any missing value to false.
$existingPermissions[$username] = [ $existingPermissions[strtolower($username)] = [
'folderOnly' => isset($perm['folderOnly']) ? (bool)$perm['folderOnly'] : false, 'folderOnly' => isset($perm['folderOnly']) ? (bool)$perm['folderOnly'] : false,
'readOnly' => isset($perm['readOnly']) ? (bool)$perm['readOnly'] : false, 'readOnly' => isset($perm['readOnly']) ? (bool)$perm['readOnly'] : false,
'disableUpload' => isset($perm['disableUpload']) ? (bool)$perm['disableUpload'] : false 'disableUpload' => isset($perm['disableUpload']) ? (bool)$perm['disableUpload'] : false

View File

@@ -65,14 +65,16 @@ if (isset($_POST['resumableChunkNumber'])) {
$resumableFilename = $_POST['resumableFilename']; $resumableFilename = $_POST['resumableFilename'];
if (!preg_match('/^[A-Za-z0-9_\-\.\(\) ]+$/', $resumableFilename)) { // First, strip directory components.
http_response_code(400); // Set an error HTTP status code $resumableFilename = urldecode(basename($_POST['resumableFilename']));
if (!preg_match('/^[\p{L}\p{N}\p{M}%\-\.\(\) _]+$/u', $resumableFilename)) {
http_response_code(400);
echo json_encode(["error" => "Invalid file name: " . $resumableFilename]); echo json_encode(["error" => "Invalid file name: " . $resumableFilename]);
exit; exit;
} }
$folder = isset($_POST['folder']) ? trim($_POST['folder']) : 'root'; $folder = isset($_POST['folder']) ? trim($_POST['folder']) : 'root';
if ($folder !== 'root' && !preg_match('/^[A-Za-z0-9_\- \/]+$/', $folder)) { if ($folder !== 'root' && !preg_match('/^[\p{L}\p{N}_\-\s\/\\\\]+$/u', $folder)) {
echo json_encode(["error" => "Invalid folder name"]); echo json_encode(["error" => "Invalid folder name"]);
exit; exit;
} }
@@ -173,7 +175,7 @@ if (!preg_match('/^[A-Za-z0-9_\-\.\(\) ]+$/', $resumableFilename)) {
// ------------- Full Upload (Non-chunked) ------------- // ------------- Full Upload (Non-chunked) -------------
// Validate folder name input. // Validate folder name input.
$folder = isset($_POST['folder']) ? trim($_POST['folder']) : 'root'; $folder = isset($_POST['folder']) ? trim($_POST['folder']) : 'root';
if ($folder !== 'root' && !preg_match('/^[A-Za-z0-9_\- \/]+$/', $folder)) { if ($folder !== 'root' && !preg_match('/^[\p{L}\p{N}_\-\s\/\\\\]+$/u', $folder)) {
echo json_encode(["error" => "Invalid folder name"]); echo json_encode(["error" => "Invalid folder name"]);
exit; exit;
} }
@@ -195,10 +197,12 @@ if (!preg_match('/^[A-Za-z0-9_\-\.\(\) ]+$/', $resumableFilename)) {
$metadataCollection = []; // key: folder path, value: metadata array $metadataCollection = []; // key: folder path, value: metadata array
$metadataChanged = []; // key: folder path, value: boolean $metadataChanged = []; // key: folder path, value: boolean
$safeFileNamePattern = '/^[A-Za-z0-9_\-\.\(\) ]+$/'; // Use a Unicode-enabled pattern to allow special characters.
$safeFileNamePattern = '/^[\p{L}\p{N}\p{M}%\-\.\(\) _]+$/u';
foreach ($_FILES["file"]["name"] as $index => $fileName) { foreach ($_FILES["file"]["name"] as $index => $fileName) {
$safeFileName = basename($fileName); // First, ensure we only work with the base filename to avoid traversal issues.
$safeFileName = trim(urldecode(basename($fileName)));
if (!preg_match($safeFileNamePattern, $safeFileName)) { if (!preg_match($safeFileNamePattern, $safeFileName)) {
echo json_encode(["error" => "Invalid file name: " . $fileName]); echo json_encode(["error" => "Invalid file name: " . $fileName]);
exit; exit;
@@ -224,6 +228,7 @@ if (!preg_match('/^[A-Za-z0-9_\-\.\(\) ]+$/', $resumableFilename)) {
$uploadDir = rtrim(UPLOAD_DIR, '/\\') . DIRECTORY_SEPARATOR $uploadDir = rtrim(UPLOAD_DIR, '/\\') . DIRECTORY_SEPARATOR
. str_replace('/', DIRECTORY_SEPARATOR, $folderPath) . DIRECTORY_SEPARATOR; . str_replace('/', DIRECTORY_SEPARATOR, $folderPath) . DIRECTORY_SEPARATOR;
} }
// Reapply basename to the relativePath to get the final safe file name.
$safeFileName = basename($relativePath); $safeFileName = basename($relativePath);
} }
// --- End Minimal Folder/Subfolder Logic --- // --- End Minimal Folder/Subfolder Logic ---

View File

@@ -109,8 +109,6 @@ if (!move_uploaded_file($fileUpload['tmp_name'], $targetPath)) {
} }
// --- Metadata Update for Shared Upload --- // --- Metadata Update for Shared Upload ---
// We want to update metadata similarly to your normal upload.
// Determine a key for metadata storage for the folder.
$metadataKey = ($folder === '' || $folder === 'root') ? "root" : $folder; $metadataKey = ($folder === '' || $folder === 'root') ? "root" : $folder;
// Sanitize the metadata file name. // Sanitize the metadata file name.
$metadataFileName = str_replace(['/', '\\', ' '], '-', $metadataKey) . '_metadata.json'; $metadataFileName = str_replace(['/', '\\', ' '], '-', $metadataKey) . '_metadata.json';