Skip to content

feature/update-web-ui#337

Draft
mar10 wants to merge 109 commits intomasterfrom
mar10/issue336
Draft

feature/update-web-ui#337
mar10 wants to merge 109 commits intomasterfrom
mar10/issue336

Conversation

@mar10
Copy link
Copy Markdown
Owner

@mar10 mar10 commented Mar 25, 2025

No description provided.

@mar10 mar10 linked an issue Mar 25, 2025 that may be closed by this pull request
@mar10 mar10 self-assigned this Mar 25, 2025
@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 25, 2025

Codecov Report

❌ Patch coverage is 65.17857% with 39 lines in your changes missing coverage. Please review.
✅ Project coverage is 41.53%. Comparing base (1a63cc0) to head (3728f4a).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
wsgidav/dc/htdigest_dc.py 0.00% 23 Missing ⚠️
wsgidav/mw/dav_explorer/app.py 82.27% 14 Missing ⚠️
wsgidav/dav_error.py 0.00% 1 Missing ⚠️
wsgidav/server/ext_wsgiutils_server.py 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #337      +/-   ##
==========================================
- Coverage   43.66%   41.53%   -2.14%     
==========================================
  Files          31       33       +2     
  Lines        4967     5051      +84     
==========================================
- Hits         2169     2098      -71     
- Misses       2798     2953     +155     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@mar10 mar10 added enhancement dir-browser DirBrowser middleware labels Mar 25, 2025
Comment thread wsgidav/dir_browser_2/htdocs/previews.js Fixed
Comment thread wsgidav/dir_browser_2/htdocs/previews.js Fixed
Comment thread wsgidav/mw/dav_explorer/htdocs/previews.js Fixed
Comment thread wsgidav/mw/dav_explorer/htdocs/previews.js Fixed
Comment thread wsgidav/mw/dav_explorer/htdocs/previews.js Fixed
Repository owner deleted a comment from Copilot AI Mar 24, 2026
Repository owner deleted a comment from Copilot AI Mar 24, 2026
Repository owner deleted a comment from Copilot AI Mar 24, 2026
Repository owner deleted a comment from Copilot AI Mar 25, 2026
Repository owner deleted a comment from Copilot AI Mar 25, 2026
Repository owner deleted a comment from Copilot AI Mar 25, 2026
mar10 added 4 commits April 18, 2026 20:22
Add push and pull_request triggers for master/main and branch patterns (release/**, hotfix/**) and expand docker/metadata-action tagging to include latest, semver, branch, PR and commit-sha tags. Add OCI labels for the image. Make docker build/push conditional so only releases, the default branch, or manual runs push to the registry (PRs/feature branches only build). Restrict artifact attestation to runs where the push succeeded and a digest was produced.
// to prevent XSS
placeholderElem.innerHTML = `File is too large (> ${maxSizeKB} KiB).<br>
<a href="" target="_blank">Click here</a> to preview.`;
placeholderElem.querySelector("a").href = url;

Check failure

Code scanning / CodeQL

DOM text reinterpreted as HTML High

DOM text
is reinterpreted as HTML without escaping meta-characters.

Copilot Autofix

AI 14 days ago

The fix is to validate and constrain any untrusted URL before writing it to DOM URL sinks (href, src, iframe src).
Best approach here: add a small helper in previews.js that parses URLs with new URL(candidate, window.location.href) and only allows safe protocols (http: and https:). If invalid/unsafe, fall back to about:blank (or skip preview).

In wsgidav/mw/dav_explorer/htdocs/previews.js:

  • Add a helper function (near other utilities) to sanitize preview URLs.
  • In showPreview, create a safeUrl from url.
  • Replace assignments at:
    • line 313 (a.href = url) → a.href = safeUrl
    • line 348 (iframeElem.setAttribute("src", url)) → safeUrl
    • and image assignment in line 335 should also use safeUrl for consistency.

No external dependency is required.

Suggested changeset 1
wsgidav/mw/dav_explorer/htdocs/previews.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/wsgidav/mw/dav_explorer/htdocs/previews.js b/wsgidav/mw/dav_explorer/htdocs/previews.js
--- a/wsgidav/mw/dav_explorer/htdocs/previews.js
+++ b/wsgidav/mw/dav_explorer/htdocs/previews.js
@@ -273,6 +273,18 @@
 	}
 }
 
+function sanitizePreviewUrl(url) {
+	try {
+		const parsed = new URL(url, window.location.href);
+		if (parsed.protocol === "http:" || parsed.protocol === "https:") {
+			return parsed.href;
+		}
+	} catch (_err) {
+		// Invalid URL
+	}
+	return "about:blank";
+}
+
 export async function showPreview(urlOrNode, options = {}) {
 	let { autoOpen = false, iframe = false, maxSizeKB = settingsStore.get("max_preview_size_kb") } = options;
 
@@ -290,6 +302,7 @@
 	}
 	const node = (!urlOrNode || typeof urlOrNode === "string") ? null : urlOrNode;
 	const url = node ? getNodeResourceUrl(urlOrNode) : urlOrNode;
+	const safeUrl = sanitizePreviewUrl(url);
 	const isFolder = node?.type === "directory";
 	let preview = null;
 
@@ -310,7 +323,7 @@
 		// to prevent XSS
 		placeholderElem.innerHTML = `File is too large (> ${maxSizeKB} KiB).<br>
 		<a href="" target="_blank">Click here</a> to preview.`;
-		placeholderElem.querySelector("a").href = url;
+		placeholderElem.querySelector("a").href = safeUrl;
 		preview = null;
 	}
 
@@ -332,11 +345,11 @@
 		case "image":
 			imgElem.onload = () => {
 				imgElem.onload = null;
-				imgElem.setAttribute("src", url);
+				imgElem.setAttribute("src", safeUrl);
 			};
 			imgElem.onerror = (e) => {
 				imgElem.onerror = null;
-				console.warn("Error loading preview %s", url, e);
+				console.warn("Error loading preview %s", safeUrl, e);
 				imgElem.src = imgPlaceholderErrorSvg;
 			};
 			imgElem.setAttribute("src", imgPlaceholderLoadingSvg);
@@ -345,7 +355,7 @@
 			// const iframe = document.querySelector("aside.right div#preview-iframe iframe");
 			// const iframe = iframeElem.querySelector("iframe");
 			iframeElem.src = "about:blank"; // Reset before setting new src
-			iframeElem.setAttribute("src", url);
+			iframeElem.setAttribute("src", safeUrl);
 			break;
 	}
 
EOF
@@ -273,6 +273,18 @@
}
}

function sanitizePreviewUrl(url) {
try {
const parsed = new URL(url, window.location.href);
if (parsed.protocol === "http:" || parsed.protocol === "https:") {
return parsed.href;
}
} catch (_err) {
// Invalid URL
}
return "about:blank";
}

export async function showPreview(urlOrNode, options = {}) {
let { autoOpen = false, iframe = false, maxSizeKB = settingsStore.get("max_preview_size_kb") } = options;

@@ -290,6 +302,7 @@
}
const node = (!urlOrNode || typeof urlOrNode === "string") ? null : urlOrNode;
const url = node ? getNodeResourceUrl(urlOrNode) : urlOrNode;
const safeUrl = sanitizePreviewUrl(url);
const isFolder = node?.type === "directory";
let preview = null;

@@ -310,7 +323,7 @@
// to prevent XSS
placeholderElem.innerHTML = `File is too large (> ${maxSizeKB} KiB).<br>
<a href="" target="_blank">Click here</a> to preview.`;
placeholderElem.querySelector("a").href = url;
placeholderElem.querySelector("a").href = safeUrl;
preview = null;
}

@@ -332,11 +345,11 @@
case "image":
imgElem.onload = () => {
imgElem.onload = null;
imgElem.setAttribute("src", url);
imgElem.setAttribute("src", safeUrl);
};
imgElem.onerror = (e) => {
imgElem.onerror = null;
console.warn("Error loading preview %s", url, e);
console.warn("Error loading preview %s", safeUrl, e);
imgElem.src = imgPlaceholderErrorSvg;
};
imgElem.setAttribute("src", imgPlaceholderLoadingSvg);
@@ -345,7 +355,7 @@
// const iframe = document.querySelector("aside.right div#preview-iframe iframe");
// const iframe = iframeElem.querySelector("iframe");
iframeElem.src = "about:blank"; // Reset before setting new src
iframeElem.setAttribute("src", url);
iframeElem.setAttribute("src", safeUrl);
break;
}

Copilot is powered by AI and may make mistakes. Always verify output.
case "image":
imgElem.onload = () => {
imgElem.onload = null;
imgElem.setAttribute("src", url);

Check failure

Code scanning / CodeQL

DOM text reinterpreted as HTML High

DOM text
is reinterpreted as HTML without escaping meta-characters.

Copilot Autofix

AI 14 days ago

General fix: validate and constrain any URL derived from potentially tainted sources before assigning it to DOM URL attributes (src, href). Only allow safe protocols (http:, https:) and optionally same-origin for preview contexts.

Best fix here (without changing intended behavior): in showPreview (wsgidav/mw/dav_explorer/htdocs/previews.js), normalize url via new URL(url, window.location.href), reject invalid or non-HTTP(S) schemes, and abort preview safely if invalid. Use the sanitized URL string for all downstream uses (fetch, img.src, iframe.src, link href). This contains taint at one choke point and preserves existing UX for valid URLs.

Suggested changeset 1
wsgidav/mw/dav_explorer/htdocs/previews.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/wsgidav/mw/dav_explorer/htdocs/previews.js b/wsgidav/mw/dav_explorer/htdocs/previews.js
--- a/wsgidav/mw/dav_explorer/htdocs/previews.js
+++ b/wsgidav/mw/dav_explorer/htdocs/previews.js
@@ -289,7 +289,27 @@
 		}
 	}
 	const node = (!urlOrNode || typeof urlOrNode === "string") ? null : urlOrNode;
-	const url = node ? getNodeResourceUrl(urlOrNode) : urlOrNode;
+	const rawUrl = node ? getNodeResourceUrl(urlOrNode) : urlOrNode;
+	let url;
+	try {
+		const parsedUrl = new URL(rawUrl, window.location.href);
+		if (!["http:", "https:"].includes(parsedUrl.protocol)) {
+			throw new Error(`Unsupported URL protocol: ${parsedUrl.protocol}`);
+		}
+		url = parsedUrl.toString();
+	} catch (e) {
+		console.warn("Blocked unsafe preview URL:", rawUrl, e);
+		placeholderElem.textContent = "No preview available.";
+		placeholderElem.classList.remove("hidden");
+		imgElem.classList.add("hidden");
+		textElem.classList.add("hidden");
+		settingsElem.classList.add("hidden");
+		folderElem.classList.add("hidden");
+		iframeElem.classList.add("hidden");
+		nodeInfo.classList.toggle("hidden", !node);
+		updateNodePreviewInfo(node);
+		return false;
+	}
 	const isFolder = node?.type === "directory";
 	let preview = null;
 
EOF
@@ -289,7 +289,27 @@
}
}
const node = (!urlOrNode || typeof urlOrNode === "string") ? null : urlOrNode;
const url = node ? getNodeResourceUrl(urlOrNode) : urlOrNode;
const rawUrl = node ? getNodeResourceUrl(urlOrNode) : urlOrNode;
let url;
try {
const parsedUrl = new URL(rawUrl, window.location.href);
if (!["http:", "https:"].includes(parsedUrl.protocol)) {
throw new Error(`Unsupported URL protocol: ${parsedUrl.protocol}`);
}
url = parsedUrl.toString();
} catch (e) {
console.warn("Blocked unsafe preview URL:", rawUrl, e);
placeholderElem.textContent = "No preview available.";
placeholderElem.classList.remove("hidden");
imgElem.classList.add("hidden");
textElem.classList.add("hidden");
settingsElem.classList.add("hidden");
folderElem.classList.add("hidden");
iframeElem.classList.add("hidden");
nodeInfo.classList.toggle("hidden", !node);
updateNodePreviewInfo(node);
return false;
}
const isFolder = node?.type === "directory";
let preview = null;

Copilot is powered by AI and may make mistakes. Always verify output.
// const iframe = document.querySelector("aside.right div#preview-iframe iframe");
// const iframe = iframeElem.querySelector("iframe");
iframeElem.src = "about:blank"; // Reset before setting new src
iframeElem.setAttribute("src", url);

Check failure

Code scanning / CodeQL

DOM text reinterpreted as HTML High

DOM text
is reinterpreted as HTML without escaping meta-characters.

Copilot Autofix

AI 14 days ago

The best fix is to validate and normalize iframe URLs before assignment, allowing only safe protocols (http: / https:), and reject everything else. This preserves current behavior for normal help/document links while preventing scriptable or ambiguous URL schemes from being interpreted by the browser.

Make the change in wsgidav/mw/dav_explorer/htdocs/previews.js inside showPreview:

  • In the "iframe" case, replace direct assignment (setAttribute("src", url)) with:
    1. Parse via new URL(url, window.location.href) (supports relative URLs safely).
    2. Check protocol whitelist (http: and https:).
    3. If invalid, keep iframe blank and show a warning/placeholder message.
    4. If valid, assign iframeElem.src = parsed.href.

No new imports or dependencies are required.

Suggested changeset 1
wsgidav/mw/dav_explorer/htdocs/previews.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/wsgidav/mw/dav_explorer/htdocs/previews.js b/wsgidav/mw/dav_explorer/htdocs/previews.js
--- a/wsgidav/mw/dav_explorer/htdocs/previews.js
+++ b/wsgidav/mw/dav_explorer/htdocs/previews.js
@@ -345,7 +345,20 @@
 			// const iframe = document.querySelector("aside.right div#preview-iframe iframe");
 			// const iframe = iframeElem.querySelector("iframe");
 			iframeElem.src = "about:blank"; // Reset before setting new src
-			iframeElem.setAttribute("src", url);
+			try {
+				const iframeUrl = new URL(url, window.location.href);
+				if (iframeUrl.protocol === "http:" || iframeUrl.protocol === "https:") {
+					iframeElem.src = iframeUrl.href;
+				} else {
+					console.warn("Blocked unsafe iframe URL protocol:", iframeUrl.protocol, url);
+					placeholderElem.textContent = "Blocked unsafe preview URL.";
+					placeholderElem.classList.remove("hidden");
+				}
+			} catch (e) {
+				console.warn("Invalid iframe preview URL:", url, e);
+				placeholderElem.textContent = "Invalid preview URL.";
+				placeholderElem.classList.remove("hidden");
+			}
 			break;
 	}
 
EOF
@@ -345,7 +345,20 @@
// const iframe = document.querySelector("aside.right div#preview-iframe iframe");
// const iframe = iframeElem.querySelector("iframe");
iframeElem.src = "about:blank"; // Reset before setting new src
iframeElem.setAttribute("src", url);
try {
const iframeUrl = new URL(url, window.location.href);
if (iframeUrl.protocol === "http:" || iframeUrl.protocol === "https:") {
iframeElem.src = iframeUrl.href;
} else {
console.warn("Blocked unsafe iframe URL protocol:", iframeUrl.protocol, url);
placeholderElem.textContent = "Blocked unsafe preview URL.";
placeholderElem.classList.remove("hidden");
}
} catch (e) {
console.warn("Invalid iframe preview URL:", url, e);
placeholderElem.textContent = "Invalid preview URL.";
placeholderElem.classList.remove("hidden");
}
break;
}

Copilot is powered by AI and may make mistakes. Always verify output.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve the web UI

4 participants