From c8b80c40410d7c7dc7a7748c1fc9cd1da8e32bb1 Mon Sep 17 00:00:00 2001 From: supahgreg Date: Tue, 21 Oct 2025 04:43:21 +0000 Subject: [PATCH] Improve handling of URLs on the frontend. --- js/App.js | 25 +++++++++++++++++++++++++ js/Article.js | 20 +++++++------------- js/CommonDialogs.js | 4 ++-- js/Feeds.js | 2 +- js/Headlines.js | 12 ++++++------ js/PrefHelpers.js | 4 ++-- 6 files changed, 43 insertions(+), 24 deletions(-) diff --git a/js/App.js b/js/App.js index 69d98eea4..0dce1a5b7 100644 --- a/js/App.js +++ b/js/App.js @@ -427,6 +427,31 @@ const App = { return p.replace(/[&<>"'/]/g, m => map[m]); }, + /** + * Sanitize a URL for safe use in href attributes and window.open() + * @param {string} url - URL to sanitize + * @param {string} fallback - Optional fallback value if URL is invalid (default: empty string) + * @return {string} Safe URL or fallback + */ + sanitizeUrl: function(url, fallback = '') { + if (!url || typeof url !== 'string') return fallback; + + // Remove NULL bytes and other control characters + // eslint-disable-next-line no-control-regex + const cleaned = url.replace(/[\x00-\x1F\x7F]/g, ''); + + const trimmed = cleaned.trim(); + if (!trimmed) return fallback; + + return /^https?:\/\/.+/i.test(trimmed) ? trimmed : fallback; + }, + openUrl: function(url) { + const sanitized = this.sanitizeUrl(url); + if (sanitized) { + const w = window.open(sanitized); + w.opener = null; + } + }, unescapeHtml: function(p) { if (typeof p !== 'string' || p.indexOf('&') === -1) return p; diff --git a/js/Article.js b/js/Article.js index b061b4fdf..6bfe87c8d 100644 --- a/js/Article.js +++ b/js/Article.js @@ -76,12 +76,6 @@ const Article = { } } }, - popupOpenUrl: function(url) { - const w = window.open(""); - - w.opener = null; - w.location = url; - }, cdmToggleGridSpan: function(id) { const row = document.getElementById(`RROW-${id}`); @@ -160,26 +154,26 @@ const Article = {

` } else if (enc.content_type && enc.content_type.indexOf("audio/") !== -1 && App.audioCanPlay(enc.content_type)) { return `

`; } else { return `

- ${App.escapeHtml(enc.content_url)}

` } } else { return `

- ${App.escapeHtml(enc.content_url)}

` @@ -191,7 +185,7 @@ const Article = { ${__('Attachments')}
${enclosures.entries.map((enc) => ` -
${enc.title ? enc.title : enc.filename}
@@ -233,7 +227,7 @@ const Article = { comments_msg = hl.num_comments + " " + ngettext("comment", "comments", hl.num_comments) } - comments = `(${comments_msg})`; + comments = `(${comments_msg})`; } return comments; @@ -288,7 +282,7 @@ const Article = {
+ href="${App.escapeHtml(App.sanitizeUrl(hl.link))}">${hl.title}
${hl.updated_long}
diff --git a/js/CommonDialogs.js b/js/CommonDialogs.js index c33763f88..5709a7047 100644 --- a/js/CommonDialogs.js +++ b/js/CommonDialogs.js @@ -609,7 +609,7 @@ const CommonDialogs = {
-
+