fix(security): mitigate CodeQL alerts by adding SRI attributes and sanitizing DOM content
This commit is contained in:
11
index.html
11
index.html
@@ -23,11 +23,12 @@
|
|||||||
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" />
|
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" />
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/codemirror.min.css" />
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/codemirror.min.css" />
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/theme/material-darker.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/theme/material-darker.min.css">
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/codemirror.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/codemirror.min.js" integrity="sha384-UXbkZAbZYZ/KCAslc6UO4d6UHNKsOxZ/sqROSQaPTZCuEIKhfbhmffQ64uXFOcma" crossorigin="anonymous"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/mode/xml/xml.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/mode/xml/xml.min.js" integrity="sha384-xPpkMo5nDgD98fIcuRVYhxkZV6/9Y4L8s3p0J5c4MxgJkyKJ8BJr+xfRkq7kn6Tw" crossorigin="anonymous"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/mode/css/css.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/mode/css/css.min.js" integrity="sha384-to8njsu2GAiXQnY/aLGzz0DIY/SFSeSDodtvSl869n2NmsBdHOTZNNqbEBPYh7Pa" crossorigin="anonymous"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/mode/javascript/javascript.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/mode/javascript/javascript.min.js" integrity="sha384-kmQrbJf09Uo1WRLMDVGoVG3nM6F48frIhcj7f3FDUjeRzsiHwyBWDjMUIttnIeAf" crossorigin="anonymous"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/resumable.js/1.1.0/resumable.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/resumable.js/1.1.0/resumable.min.js" integrity="sha384-EXTg7rRfdTPZWoKVCslusAAev2TYw76fm+Wox718iEtFQ+gdAdAc5Z/ndLHSo4mq" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/2.4.0/purify.min.js" integrity="sha384-Tsl3d5pUAO7a13enIvSsL3O0/95nsthPJiPto5NtLuY8w3+LbZOpr3Fl2MNmrh1E" crossorigin="anonymous"></script>
|
||||||
<link rel="stylesheet" href="css/styles.css" />
|
<link rel="stylesheet" href="css/styles.css" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// editor.js
|
// editor.js
|
||||||
import { showToast } from './domUtils.js';
|
import { escapeHTML, showToast } from './domUtils.js';
|
||||||
import { loadFileList } from './fileListView.js';
|
import { loadFileList } from './fileListView.js';
|
||||||
|
|
||||||
function getModeForFile(fileName) {
|
function getModeForFile(fileName) {
|
||||||
@@ -73,14 +73,14 @@ export function editFile(fileName, folder) {
|
|||||||
modal.classList.add("modal", "editor-modal");
|
modal.classList.add("modal", "editor-modal");
|
||||||
modal.innerHTML = `
|
modal.innerHTML = `
|
||||||
<div class="editor-header">
|
<div class="editor-header">
|
||||||
<h3 class="editor-title">Editing: ${fileName}</h3>
|
<h3 class="editor-title">Editing: ${escapeHTML(fileName)}</h3>
|
||||||
<div class="editor-controls">
|
<div class="editor-controls">
|
||||||
<button id="decreaseFont" class="btn btn-sm btn-secondary">A-</button>
|
<button id="decreaseFont" class="btn btn-sm btn-secondary">A-</button>
|
||||||
<button id="increaseFont" class="btn btn-sm btn-secondary">A+</button>
|
<button id="increaseFont" class="btn btn-sm btn-secondary">A+</button>
|
||||||
</div>
|
</div>
|
||||||
<button id="closeEditorX" class="editor-close-btn">×</button>
|
<button id="closeEditorX" class="editor-close-btn">×</button>
|
||||||
</div>
|
</div>
|
||||||
<textarea id="fileEditor" class="editor-textarea">${content}</textarea>
|
<textarea id="fileEditor" class="editor-textarea">${escapeHTML(content)}</textarea>
|
||||||
<div class="editor-footer">
|
<div class="editor-footer">
|
||||||
<button id="saveBtn" class="btn btn-primary">Save</button>
|
<button id="saveBtn" class="btn btn-primary">Save</button>
|
||||||
<button id="closeBtn" class="btn btn-secondary">Close</button>
|
<button id="closeBtn" class="btn btn-secondary">Close</button>
|
||||||
|
|||||||
@@ -237,15 +237,27 @@ export function previewFile(fileUrl, fileName) {
|
|||||||
// Added to preserve the original functionality.
|
// Added to preserve the original functionality.
|
||||||
export function displayFilePreview(file, container) {
|
export function displayFilePreview(file, container) {
|
||||||
const actualFile = file.file || file;
|
const actualFile = file.file || file;
|
||||||
|
|
||||||
|
// Validate that actualFile is indeed a File
|
||||||
|
if (!(actualFile instanceof File)) {
|
||||||
|
console.error("displayFilePreview called with an invalid file object");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
container.style.display = "inline-block";
|
container.style.display = "inline-block";
|
||||||
|
|
||||||
|
// Clear the container safely without using innerHTML
|
||||||
|
while (container.firstChild) {
|
||||||
|
container.removeChild(container.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
if (/\.(jpg|jpeg|png|gif|bmp|webp|svg|ico)$/i.test(actualFile.name)) {
|
if (/\.(jpg|jpeg|png|gif|bmp|webp|svg|ico)$/i.test(actualFile.name)) {
|
||||||
const img = document.createElement("img");
|
const img = document.createElement("img");
|
||||||
|
// Set the image source using a Blob URL (this is considered safe)
|
||||||
img.src = URL.createObjectURL(actualFile);
|
img.src = URL.createObjectURL(actualFile);
|
||||||
img.classList.add("file-preview-img");
|
img.classList.add("file-preview-img");
|
||||||
container.innerHTML = "";
|
|
||||||
container.appendChild(img);
|
container.appendChild(img);
|
||||||
} else {
|
} else {
|
||||||
container.innerHTML = "";
|
|
||||||
const iconSpan = document.createElement("span");
|
const iconSpan = document.createElement("span");
|
||||||
iconSpan.classList.add("material-icons", "file-icon");
|
iconSpan.classList.add("material-icons", "file-icon");
|
||||||
iconSpan.textContent = "insert_drive_file";
|
iconSpan.textContent = "insert_drive_file";
|
||||||
|
|||||||
@@ -58,9 +58,7 @@ function getParentFolder(folder) {
|
|||||||
return lastSlash === -1 ? "root" : folder.substring(0, lastSlash);
|
return lastSlash === -1 ? "root" : folder.substring(0, lastSlash);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------------
|
|
||||||
Breadcrumb Functions
|
|
||||||
----------------------*/
|
|
||||||
function renderBreadcrumb(normalizedFolder) {
|
function renderBreadcrumb(normalizedFolder) {
|
||||||
if (!normalizedFolder || normalizedFolder === "") return "";
|
if (!normalizedFolder || normalizedFolder === "") return "";
|
||||||
const parts = normalizedFolder.split("/");
|
const parts = normalizedFolder.split("/");
|
||||||
@@ -76,73 +74,113 @@ function renderBreadcrumb(normalizedFolder) {
|
|||||||
return breadcrumbItems.join('');
|
return breadcrumbItems.join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
function bindBreadcrumbEvents() {
|
// --- NEW: Breadcrumb Delegation Setup ---
|
||||||
const breadcrumbLinks = document.querySelectorAll(".breadcrumb-link");
|
export function setupBreadcrumbDelegation() {
|
||||||
breadcrumbLinks.forEach(link => {
|
const container = document.getElementById("fileListTitle");
|
||||||
link.addEventListener("click", function (e) {
|
if (!container) {
|
||||||
e.stopPropagation();
|
console.error("Breadcrumb container (fileListTitle) not found.");
|
||||||
let folder = this.getAttribute("data-folder");
|
return;
|
||||||
window.currentFolder = folder;
|
}
|
||||||
localStorage.setItem("lastOpenedFolder", folder);
|
|
||||||
const titleEl = document.getElementById("fileListTitle");
|
// Remove any previous delegation listeners if necessary
|
||||||
titleEl.innerHTML = "Files in (" + renderBreadcrumb(folder) + ")";
|
container.removeEventListener("click", breadcrumbClickHandler);
|
||||||
expandTreePath(folder);
|
container.removeEventListener("dragover", breadcrumbDragOverHandler);
|
||||||
document.querySelectorAll(".folder-option").forEach(item => item.classList.remove("selected"));
|
container.removeEventListener("dragleave", breadcrumbDragLeaveHandler);
|
||||||
const targetOption = document.querySelector(`.folder-option[data-folder="${folder}"]`);
|
container.removeEventListener("drop", breadcrumbDropHandler);
|
||||||
if (targetOption) targetOption.classList.add("selected");
|
|
||||||
loadFileList(folder);
|
// Attach delegated listeners
|
||||||
bindBreadcrumbEvents();
|
container.addEventListener("click", breadcrumbClickHandler);
|
||||||
});
|
container.addEventListener("dragover", breadcrumbDragOverHandler);
|
||||||
link.addEventListener("dragover", function (e) {
|
container.addEventListener("dragleave", breadcrumbDragLeaveHandler);
|
||||||
e.preventDefault();
|
container.addEventListener("drop", breadcrumbDropHandler);
|
||||||
this.classList.add("drop-hover");
|
|
||||||
});
|
|
||||||
link.addEventListener("dragleave", function (e) {
|
|
||||||
this.classList.remove("drop-hover");
|
|
||||||
});
|
|
||||||
link.addEventListener("drop", function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
this.classList.remove("drop-hover");
|
|
||||||
const dropFolder = this.getAttribute("data-folder");
|
|
||||||
let dragData;
|
|
||||||
try {
|
|
||||||
dragData = JSON.parse(e.dataTransfer.getData("application/json"));
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Invalid drag data on breadcrumb:", err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const filesToMove = dragData.files ? dragData.files : (dragData.fileName ? [dragData.fileName] : []);
|
|
||||||
if (filesToMove.length === 0) return;
|
|
||||||
fetch("moveFiles.php", {
|
|
||||||
method: "POST",
|
|
||||||
credentials: "include",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"X-CSRF-Token": document.querySelector('meta[name="csrf-token"]').getAttribute("content")
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
source: dragData.sourceFolder,
|
|
||||||
files: filesToMove,
|
|
||||||
destination: dropFolder
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
if (data.success) {
|
|
||||||
showToast(`File(s) moved successfully to ${dropFolder}!`);
|
|
||||||
loadFileList(dragData.sourceFolder);
|
|
||||||
} else {
|
|
||||||
showToast("Error moving files: " + (data.error || "Unknown error"));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error("Error moving files via drop on breadcrumb:", error);
|
|
||||||
showToast("Error moving files.");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Click handler via delegation
|
||||||
|
function breadcrumbClickHandler(e) {
|
||||||
|
// Look for the nearest ancestor with the "breadcrumb-link" class.
|
||||||
|
const link = e.target.closest(".breadcrumb-link");
|
||||||
|
if (!link) return;
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault(); // Prevent default link behavior if needed
|
||||||
|
|
||||||
|
const folder = link.getAttribute("data-folder");
|
||||||
|
window.currentFolder = folder;
|
||||||
|
localStorage.setItem("lastOpenedFolder", folder);
|
||||||
|
|
||||||
|
// Update the container with sanitized breadcrumbs.
|
||||||
|
// (If you prefer, you can render the breadcrumbs into a dedicated container
|
||||||
|
// and update the title separately.)
|
||||||
|
const container = document.getElementById("fileListTitle");
|
||||||
|
const sanitizedBreadcrumb = DOMPurify.sanitize(renderBreadcrumb(folder));
|
||||||
|
container.innerHTML = "Files in (" + sanitizedBreadcrumb + ")";
|
||||||
|
|
||||||
|
expandTreePath(folder);
|
||||||
|
document.querySelectorAll(".folder-option").forEach(item => item.classList.remove("selected"));
|
||||||
|
const targetOption = document.querySelector(`.folder-option[data-folder="${folder}"]`);
|
||||||
|
if (targetOption) targetOption.classList.add("selected");
|
||||||
|
loadFileList(folder);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dragover handler via delegation
|
||||||
|
function breadcrumbDragOverHandler(e) {
|
||||||
|
const link = e.target.closest(".breadcrumb-link");
|
||||||
|
if (!link) return;
|
||||||
|
e.preventDefault();
|
||||||
|
link.classList.add("drop-hover");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dragleave handler via delegation
|
||||||
|
function breadcrumbDragLeaveHandler(e) {
|
||||||
|
const link = e.target.closest(".breadcrumb-link");
|
||||||
|
if (!link) return;
|
||||||
|
link.classList.remove("drop-hover");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drop handler via delegation
|
||||||
|
function breadcrumbDropHandler(e) {
|
||||||
|
const link = e.target.closest(".breadcrumb-link");
|
||||||
|
if (!link) return;
|
||||||
|
e.preventDefault();
|
||||||
|
link.classList.remove("drop-hover");
|
||||||
|
const dropFolder = link.getAttribute("data-folder");
|
||||||
|
let dragData;
|
||||||
|
try {
|
||||||
|
dragData = JSON.parse(e.dataTransfer.getData("application/json"));
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Invalid drag data on breadcrumb:", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const filesToMove = dragData.files ? dragData.files : (dragData.fileName ? [dragData.fileName] : []);
|
||||||
|
if (filesToMove.length === 0) return;
|
||||||
|
fetch("moveFiles.php", {
|
||||||
|
method: "POST",
|
||||||
|
credentials: "include",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"X-CSRF-Token": document.querySelector('meta[name="csrf-token"]').getAttribute("content")
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
source: dragData.sourceFolder,
|
||||||
|
files: filesToMove,
|
||||||
|
destination: dropFolder
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
showToast(`File(s) moved successfully to ${dropFolder}!`);
|
||||||
|
loadFileList(dragData.sourceFolder);
|
||||||
|
} else {
|
||||||
|
showToast("Error moving files: " + (data.error || "Unknown error"));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error("Error moving files via drop on breadcrumb:", error);
|
||||||
|
showToast("Error moving files.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ----------------------
|
/* ----------------------
|
||||||
Check Current User's Folder-Only Permission
|
Check Current User's Folder-Only Permission
|
||||||
----------------------*/
|
----------------------*/
|
||||||
@@ -380,7 +418,7 @@ export async function loadFolderTree(selectedFolder) {
|
|||||||
|
|
||||||
const titleEl = document.getElementById("fileListTitle");
|
const titleEl = document.getElementById("fileListTitle");
|
||||||
titleEl.innerHTML = "Files in (" + renderBreadcrumb(window.currentFolder) + ")";
|
titleEl.innerHTML = "Files in (" + renderBreadcrumb(window.currentFolder) + ")";
|
||||||
bindBreadcrumbEvents();
|
setupBreadcrumbDelegation();
|
||||||
loadFileList(window.currentFolder);
|
loadFileList(window.currentFolder);
|
||||||
|
|
||||||
const folderState = loadFolderTreeState();
|
const folderState = loadFolderTreeState();
|
||||||
@@ -404,7 +442,7 @@ export async function loadFolderTree(selectedFolder) {
|
|||||||
localStorage.setItem("lastOpenedFolder", selected);
|
localStorage.setItem("lastOpenedFolder", selected);
|
||||||
const titleEl = document.getElementById("fileListTitle");
|
const titleEl = document.getElementById("fileListTitle");
|
||||||
titleEl.innerHTML = "Files in (" + renderBreadcrumb(selected) + ")";
|
titleEl.innerHTML = "Files in (" + renderBreadcrumb(selected) + ")";
|
||||||
bindBreadcrumbEvents();
|
setupBreadcrumbDelegation();
|
||||||
loadFileList(selected);
|
loadFileList(selected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user