From a59951444413189221fe7dbd2c66230981288973 Mon Sep 17 00:00:00 2001 From: Holden Karau Date: Thu, 24 Sep 2026 22:38:22 +0000 Subject: [PATCH] [SPARK-59406][UI] Fix UI display on weird custom log names ### What changes were proposed in this pull request? Better handling for custom log names and URLs ### Why are the changes needed? Custom log names can be weird, so let's validate that they're displayable before we display them. ### Does this PR introduce _any_ user-facing change? Invalid log URLs will be replaced with plain text. ### How was this patch tested? New unit tests ### Was this patch authored or co-authored using generative AI tooling? Generated-by: Claude (mixture of models) and Cursor (Kimi K3) Backport of b3149c6aaf6 (Closes #58699) to branch-3.5. Two deviations from the master commit: the ui-test jest module does not exist on branch-3.5 so the JS unit test is not included, and the escapeHtml helper (added on master by 432adc74d4c, not backported) is included here since formatLogsCells now depends on it. Co-authored-by: Cursor Co-Authored-By: Holden Karau --- .../org/apache/spark/ui/static/utils.js | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/core/src/main/resources/org/apache/spark/ui/static/utils.js b/core/src/main/resources/org/apache/spark/ui/static/utils.js index 08701c86fa21e..bf4ff15de8787 100644 --- a/core/src/main/resources/org/apache/spark/ui/static/utils.js +++ b/core/src/main/resources/org/apache/spark/ui/static/utils.js @@ -85,12 +85,28 @@ function getTimeZone() { } } +/* Custom log URLs are rendered as links only when they are http(s) or relative + * URLs; anything else is shown as plain text. Browsers strip ASCII whitespace + * and control characters when parsing URLs, so remove them before checking + * the scheme. */ +function isHttpOrRelativeUrl(url) { + if (typeof url !== 'string') return false; + /* eslint-disable-next-line no-control-regex */ + var normalized = url.replace(/[\u0000-\u0020]/g, '').toLowerCase(); + return /^https?:\/\//.test(normalized) || !/^[a-z][a-z0-9+.-]*:/.test(normalized); +} + function formatLogsCells(execLogs, type) { if (type !== 'display') return Object.keys(execLogs); if (!execLogs) return; var result = ''; $.each(execLogs, function (logName, logUrl) { - result += '
' + logName + '
' + // Custom log names and URLs can contain markup characters. + if (isHttpOrRelativeUrl(logUrl)) { + result += '
' + escapeHtml(logName) + '
' + } else { + result += '
' + escapeHtml(logName) + '
' + } }); return result; } @@ -231,4 +247,16 @@ function createRESTEndPointForMiscellaneousProcess(appId) { function getBaseURI() { return document.baseURI || document.URL; } + +/* Escape a value for safe inclusion in HTML text content. Non-string values are + * coerced to a string first; null/undefined are returned unchanged. */ +function escapeHtml(text) { + if (text === null || text === undefined) return text; + return String(text) + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +} /* eslint-enable no-unused-vars */ \ No newline at end of file