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 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">
|
||||
<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/mode/xml/xml.min.js"></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/javascript/javascript.min.js"></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/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" 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" 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" integrity="sha384-kmQrbJf09Uo1WRLMDVGoVG3nM6F48frIhcj7f3FDUjeRzsiHwyBWDjMUIttnIeAf" crossorigin="anonymous"></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" />
|
||||
</head>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// editor.js
|
||||
import { showToast } from './domUtils.js';
|
||||
import { escapeHTML, showToast } from './domUtils.js';
|
||||
import { loadFileList } from './fileListView.js';
|
||||
|
||||
function getModeForFile(fileName) {
|
||||
@@ -73,14 +73,14 @@ export function editFile(fileName, folder) {
|
||||
modal.classList.add("modal", "editor-modal");
|
||||
modal.innerHTML = `
|
||||
<div class="editor-header">
|
||||
<h3 class="editor-title">Editing: ${fileName}</h3>
|
||||
<h3 class="editor-title">Editing: ${escapeHTML(fileName)}</h3>
|
||||
<div class="editor-controls">
|
||||
<button id="decreaseFont" class="btn btn-sm btn-secondary">A-</button>
|
||||
<button id="increaseFont" class="btn btn-sm btn-secondary">A+</button>
|
||||
</div>
|
||||
<button id="closeEditorX" class="editor-close-btn">×</button>
|
||||
</div>
|
||||
<textarea id="fileEditor" class="editor-textarea">${content}</textarea>
|
||||
<textarea id="fileEditor" class="editor-textarea">${escapeHTML(content)}</textarea>
|
||||
<div class="editor-footer">
|
||||
<button id="saveBtn" class="btn btn-primary">Save</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.
|
||||
export function displayFilePreview(file, container) {
|
||||
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";
|
||||
|
||||
// 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)) {
|
||||
const img = document.createElement("img");
|
||||
// Set the image source using a Blob URL (this is considered safe)
|
||||
img.src = URL.createObjectURL(actualFile);
|
||||
img.classList.add("file-preview-img");
|
||||
container.innerHTML = "";
|
||||
container.appendChild(img);
|
||||
} else {
|
||||
container.innerHTML = "";
|
||||
const iconSpan = document.createElement("span");
|
||||
iconSpan.classList.add("material-icons", "file-icon");
|
||||
iconSpan.textContent = "insert_drive_file";
|
||||
|
||||
@@ -58,9 +58,7 @@ function getParentFolder(folder) {
|
||||
return lastSlash === -1 ? "root" : folder.substring(0, lastSlash);
|
||||
}
|
||||
|
||||
/* ----------------------
|
||||
Breadcrumb Functions
|
||||
----------------------*/
|
||||
|
||||
function renderBreadcrumb(normalizedFolder) {
|
||||
if (!normalizedFolder || normalizedFolder === "") return "";
|
||||
const parts = normalizedFolder.split("/");
|
||||
@@ -76,73 +74,113 @@ function renderBreadcrumb(normalizedFolder) {
|
||||
return breadcrumbItems.join('');
|
||||
}
|
||||
|
||||
function bindBreadcrumbEvents() {
|
||||
const breadcrumbLinks = document.querySelectorAll(".breadcrumb-link");
|
||||
breadcrumbLinks.forEach(link => {
|
||||
link.addEventListener("click", function (e) {
|
||||
e.stopPropagation();
|
||||
let folder = this.getAttribute("data-folder");
|
||||
window.currentFolder = folder;
|
||||
localStorage.setItem("lastOpenedFolder", folder);
|
||||
const titleEl = document.getElementById("fileListTitle");
|
||||
titleEl.innerHTML = "Files in (" + renderBreadcrumb(folder) + ")";
|
||||
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);
|
||||
bindBreadcrumbEvents();
|
||||
});
|
||||
link.addEventListener("dragover", function (e) {
|
||||
e.preventDefault();
|
||||
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.");
|
||||
});
|
||||
});
|
||||
});
|
||||
// --- NEW: Breadcrumb Delegation Setup ---
|
||||
export function setupBreadcrumbDelegation() {
|
||||
const container = document.getElementById("fileListTitle");
|
||||
if (!container) {
|
||||
console.error("Breadcrumb container (fileListTitle) not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove any previous delegation listeners if necessary
|
||||
container.removeEventListener("click", breadcrumbClickHandler);
|
||||
container.removeEventListener("dragover", breadcrumbDragOverHandler);
|
||||
container.removeEventListener("dragleave", breadcrumbDragLeaveHandler);
|
||||
container.removeEventListener("drop", breadcrumbDropHandler);
|
||||
|
||||
// Attach delegated listeners
|
||||
container.addEventListener("click", breadcrumbClickHandler);
|
||||
container.addEventListener("dragover", breadcrumbDragOverHandler);
|
||||
container.addEventListener("dragleave", breadcrumbDragLeaveHandler);
|
||||
container.addEventListener("drop", breadcrumbDropHandler);
|
||||
}
|
||||
|
||||
// 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
|
||||
----------------------*/
|
||||
@@ -380,7 +418,7 @@ export async function loadFolderTree(selectedFolder) {
|
||||
|
||||
const titleEl = document.getElementById("fileListTitle");
|
||||
titleEl.innerHTML = "Files in (" + renderBreadcrumb(window.currentFolder) + ")";
|
||||
bindBreadcrumbEvents();
|
||||
setupBreadcrumbDelegation();
|
||||
loadFileList(window.currentFolder);
|
||||
|
||||
const folderState = loadFolderTreeState();
|
||||
@@ -404,7 +442,7 @@ export async function loadFolderTree(selectedFolder) {
|
||||
localStorage.setItem("lastOpenedFolder", selected);
|
||||
const titleEl = document.getElementById("fileListTitle");
|
||||
titleEl.innerHTML = "Files in (" + renderBreadcrumb(selected) + ")";
|
||||
bindBreadcrumbEvents();
|
||||
setupBreadcrumbDelegation();
|
||||
loadFileList(selected);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user