From 1cb16fc6f3854351052e9c0660455a9fbb7dd4ce Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 2 Jul 2026 13:05:23 -0700 Subject: [PATCH 1/4] [MINOR][UI] Encode query parameters when generating log page scripts The log viewer pages assemble a query string from request parameters and embed it into an inline script literal. Encode that value for a JavaScript string context using the bundled commons-text StringEscapeUtils, matching the existing pattern in AllJobsPage, so the generated script stays well-formed regardless of the parameter contents. Also sanitize the parameters on the client side in log-view.js, and add a regression test. Co-Authored-By: Claude Opus 4.8 (1M context) Co-Authored-By: Holden Karau Co-Authored-By: Holden Karau --- .../org/apache/spark/ui/static/log-view.js | 12 +++++- .../apache/spark/deploy/history/LogPage.scala | 6 ++- .../spark/deploy/master/ui/LogPage.scala | 6 ++- .../spark/deploy/worker/ui/LogPage.scala | 6 ++- .../org/apache/spark/ui/DriverLogPage.scala | 6 ++- .../spark/deploy/history/LogPageSuite.scala | 40 +++++++++++++++++++ 6 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 core/src/test/scala/org/apache/spark/deploy/history/LogPageSuite.scala diff --git a/core/src/main/resources/org/apache/spark/ui/static/log-view.js b/core/src/main/resources/org/apache/spark/ui/static/log-view.js index f1fc6c915d5c7..2ec3a7ed31975 100644 --- a/core/src/main/resources/org/apache/spark/ui/static/log-view.js +++ b/core/src/main/resources/org/apache/spark/ui/static/log-view.js @@ -131,8 +131,18 @@ function loadNew() { }); } +function encodeLogParams(params) { + // baseParams is only ever concatenated into the /log request URL (see loadMore/loadNew), never + // written to the DOM, so URL-encode it for that context rather than stripping characters. + // encodeURI keeps the ?, &, = and / separators intact while percent-encoding characters such as + // spaces, angle brackets, quotes, backslash and CR/LF that have no place in the query string. + // The server decodes the values again, so this is lossless for legitimate parameters (the + // executor/app/driver ids and the allowlisted logType). + return encodeURI(String(params)).replace(/'/g, "%27"); +} + function initLogPage(params, logLen, start, end, totLogLen, defaultLen) { - baseParams = params; + baseParams = encodeLogParams(params); curLogLength = logLen; startByte = start; endByte = end; diff --git a/core/src/main/scala/org/apache/spark/deploy/history/LogPage.scala b/core/src/main/scala/org/apache/spark/deploy/history/LogPage.scala index 1b809b80ee328..99b599a14c903 100644 --- a/core/src/main/scala/org/apache/spark/deploy/history/LogPage.scala +++ b/core/src/main/scala/org/apache/spark/deploy/history/LogPage.scala @@ -20,6 +20,7 @@ package org.apache.spark.deploy.history import scala.xml.{Node, Unparsed} import jakarta.servlet.http.HttpServletRequest +import org.apache.commons.text.StringEscapeUtils import org.apache.spark.SparkConf import org.apache.spark.deploy.Utils.{getLog, DEFAULT_BYTES} @@ -57,8 +58,11 @@ private[history] class LogPage(conf: SparkConf) extends WebUIPage("logPage") wit val logParams = "?self&logType=%s".format(logType) + // The query string carries request-supplied values into an inline script literal, so encode + // it for a JavaScript string context before embedding it via Unparsed below. val jsOnload = "window.onload = " + - s"initLogPage('$logParams', $curLogLength, $startByte, $endByte, $logLength, $byteLength);" + s"initLogPage('${StringEscapeUtils.escapeEcmaScript(logParams)}', " + + s"$curLogLength, $startByte, $endByte, $logLength, $byteLength);" val content = ++ diff --git a/core/src/main/scala/org/apache/spark/deploy/master/ui/LogPage.scala b/core/src/main/scala/org/apache/spark/deploy/master/ui/LogPage.scala index 780f8a2b6c53a..aaa7a5e3ff693 100644 --- a/core/src/main/scala/org/apache/spark/deploy/master/ui/LogPage.scala +++ b/core/src/main/scala/org/apache/spark/deploy/master/ui/LogPage.scala @@ -20,6 +20,7 @@ package org.apache.spark.deploy.master.ui import scala.xml.{Node, Unparsed} import jakarta.servlet.http.HttpServletRequest +import org.apache.commons.text.StringEscapeUtils import org.apache.spark.deploy.Utils.{getLog, DEFAULT_BYTES} import org.apache.spark.internal.Logging @@ -56,8 +57,11 @@ private[ui] class LogPage(parent: MasterWebUI) extends WebUIPage("logPage") with val logParams = "?self&logType=%s".format(logType) + // The query string carries request-supplied values into an inline script literal, so encode + // it for a JavaScript string context before embedding it via Unparsed below. val jsOnload = "window.onload = " + - s"initLogPage('$logParams', $curLogLength, $startByte, $endByte, $logLength, $byteLength);" + s"initLogPage('${StringEscapeUtils.escapeEcmaScript(logParams)}', " + + s"$curLogLength, $startByte, $endByte, $logLength, $byteLength);" val content = ++ diff --git a/core/src/main/scala/org/apache/spark/deploy/worker/ui/LogPage.scala b/core/src/main/scala/org/apache/spark/deploy/worker/ui/LogPage.scala index adfe9670f371f..8c17fc41cd4f9 100644 --- a/core/src/main/scala/org/apache/spark/deploy/worker/ui/LogPage.scala +++ b/core/src/main/scala/org/apache/spark/deploy/worker/ui/LogPage.scala @@ -22,6 +22,7 @@ import java.io.File import scala.xml.{Node, Unparsed} import jakarta.servlet.http.HttpServletRequest +import org.apache.commons.text.StringEscapeUtils import org.apache.spark.internal.Logging import org.apache.spark.internal.LogKeys.{LOG_TYPE, PATH} @@ -106,8 +107,11 @@ private[ui] class LogPage(parent: WorkerWebUI) extends WebUIPage("logPage") with val logParams = "?%s&logType=%s".format(params, logType) + // The query string carries request-supplied values into an inline script literal, so encode + // it for a JavaScript string context before embedding it via Unparsed below. val jsOnload = "window.onload = " + - s"initLogPage('$logParams', $curLogLength, $startByte, $endByte, $logLength, $byteLength);" + s"initLogPage('${StringEscapeUtils.escapeEcmaScript(logParams)}', " + + s"$curLogLength, $startByte, $endByte, $logLength, $byteLength);" val content = ++ diff --git a/core/src/main/scala/org/apache/spark/ui/DriverLogPage.scala b/core/src/main/scala/org/apache/spark/ui/DriverLogPage.scala index 7b427cffcc5e7..66d6d83a0b88f 100644 --- a/core/src/main/scala/org/apache/spark/ui/DriverLogPage.scala +++ b/core/src/main/scala/org/apache/spark/ui/DriverLogPage.scala @@ -19,6 +19,7 @@ package org.apache.spark.ui import scala.xml.{Node, Unparsed} import jakarta.servlet.http.HttpServletRequest +import org.apache.commons.text.StringEscapeUtils import org.apache.spark.SparkConf import org.apache.spark.internal.Logging @@ -71,8 +72,11 @@ private[ui] class DriverLogPage( val logParams = "/?logType=%s".format(logType) + // The query string carries request-supplied values into an inline script literal, so encode + // it for a JavaScript string context before embedding it via Unparsed below. val jsOnload = "window.onload = " + - s"initLogPage('$logParams', $curLogLength, $startByte, $endByte, $logLength, $byteLength);" + s"initLogPage('${StringEscapeUtils.escapeEcmaScript(logParams)}', " + + s"$curLogLength, $startByte, $endByte, $logLength, $byteLength);" val content = ++ diff --git a/core/src/test/scala/org/apache/spark/deploy/history/LogPageSuite.scala b/core/src/test/scala/org/apache/spark/deploy/history/LogPageSuite.scala new file mode 100644 index 0000000000000..bd6bd70d5f3eb --- /dev/null +++ b/core/src/test/scala/org/apache/spark/deploy/history/LogPageSuite.scala @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.spark.deploy.history + +import jakarta.servlet.http.HttpServletRequest +import org.mockito.Mockito.{mock, when} + +import org.apache.spark.{SparkConf, SparkFunSuite} + +class LogPageSuite extends SparkFunSuite { + + test("render encodes the logType parameter embedded in the inline script") { + val page = new LogPage(new SparkConf(false)) + val request = mock(classOf[HttpServletRequest]) + // A value that would break out of the single-quoted JavaScript string literal, close the + // script element, or start a new statement if it were emitted into the page verbatim. + when(request.getParameter("logType")).thenReturn("stdout');alert('xss')") + val html = page.render(request).mkString + + // The value must be encoded for the JavaScript string context: quotes, backslashes and the + // slash in a closing tag are all escaped, so no raw breakout sequence reaches the browser. + assert(html.contains("""stdout\');alert(\'xss\')<\/script>""")) + assert(!html.contains("stdout');alert('xss')")) + } +} From 4a5e27e030339236a3bd00dd7a21b8f23c859c38 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 2 Jul 2026 15:42:07 -0700 Subject: [PATCH 2/4] [MINOR][UI] URL-encode log page request parameters Encode individual query-parameter values on the server when building the log page bootstrap script, so the resulting /log requests are unambiguous. Extract the shared script-building helper into UIUtils to keep the log pages consistent. The server now emits a fully-encoded query string, so the client-side handling is reverted to a straight pass-through. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01HmZGbR3Y8L84RfcfqptgGW --- .../org/apache/spark/ui/static/log-view.js | 12 +------- .../apache/spark/deploy/history/LogPage.scala | 10 ++----- .../spark/deploy/master/ui/LogPage.scala | 10 ++----- .../spark/deploy/worker/ui/LogPage.scala | 19 ++++++------ .../org/apache/spark/ui/DriverLogPage.scala | 10 ++----- .../scala/org/apache/spark/ui/UIUtils.scala | 29 ++++++++++++++++++- .../spark/deploy/history/LogPageSuite.scala | 10 ++++--- .../spark/deploy/worker/ui/LogPageSuite.scala | 27 +++++++++++++++++ 8 files changed, 81 insertions(+), 46 deletions(-) diff --git a/core/src/main/resources/org/apache/spark/ui/static/log-view.js b/core/src/main/resources/org/apache/spark/ui/static/log-view.js index 2ec3a7ed31975..f1fc6c915d5c7 100644 --- a/core/src/main/resources/org/apache/spark/ui/static/log-view.js +++ b/core/src/main/resources/org/apache/spark/ui/static/log-view.js @@ -131,18 +131,8 @@ function loadNew() { }); } -function encodeLogParams(params) { - // baseParams is only ever concatenated into the /log request URL (see loadMore/loadNew), never - // written to the DOM, so URL-encode it for that context rather than stripping characters. - // encodeURI keeps the ?, &, = and / separators intact while percent-encoding characters such as - // spaces, angle brackets, quotes, backslash and CR/LF that have no place in the query string. - // The server decodes the values again, so this is lossless for legitimate parameters (the - // executor/app/driver ids and the allowlisted logType). - return encodeURI(String(params)).replace(/'/g, "%27"); -} - function initLogPage(params, logLen, start, end, totLogLen, defaultLen) { - baseParams = encodeLogParams(params); + baseParams = params; curLogLength = logLen; startByte = start; endByte = end; diff --git a/core/src/main/scala/org/apache/spark/deploy/history/LogPage.scala b/core/src/main/scala/org/apache/spark/deploy/history/LogPage.scala index 99b599a14c903..9b14e76db37a7 100644 --- a/core/src/main/scala/org/apache/spark/deploy/history/LogPage.scala +++ b/core/src/main/scala/org/apache/spark/deploy/history/LogPage.scala @@ -20,7 +20,6 @@ package org.apache.spark.deploy.history import scala.xml.{Node, Unparsed} import jakarta.servlet.http.HttpServletRequest -import org.apache.commons.text.StringEscapeUtils import org.apache.spark.SparkConf import org.apache.spark.deploy.Utils.{getLog, DEFAULT_BYTES} @@ -57,12 +56,9 @@ private[history] class LogPage(conf: SparkConf) extends WebUIPage("logPage") wit End of Log - val logParams = "?self&logType=%s".format(logType) - // The query string carries request-supplied values into an inline script literal, so encode - // it for a JavaScript string context before embedding it via Unparsed below. - val jsOnload = "window.onload = " + - s"initLogPage('${StringEscapeUtils.escapeEcmaScript(logParams)}', " + - s"$curLogLength, $startByte, $endByte, $logLength, $byteLength);" + val logParams = "?self&logType=%s".format(UIUtils.encodeLogParam(logType)) + val jsOnload = UIUtils.logPageOnloadScript( + logParams, curLogLength, startByte, endByte, logLength, byteLength) val content = ++ diff --git a/core/src/main/scala/org/apache/spark/deploy/master/ui/LogPage.scala b/core/src/main/scala/org/apache/spark/deploy/master/ui/LogPage.scala index aaa7a5e3ff693..7da8736222026 100644 --- a/core/src/main/scala/org/apache/spark/deploy/master/ui/LogPage.scala +++ b/core/src/main/scala/org/apache/spark/deploy/master/ui/LogPage.scala @@ -20,7 +20,6 @@ package org.apache.spark.deploy.master.ui import scala.xml.{Node, Unparsed} import jakarta.servlet.http.HttpServletRequest -import org.apache.commons.text.StringEscapeUtils import org.apache.spark.deploy.Utils.{getLog, DEFAULT_BYTES} import org.apache.spark.internal.Logging @@ -56,12 +55,9 @@ private[ui] class LogPage(parent: MasterWebUI) extends WebUIPage("logPage") with End of Log - val logParams = "?self&logType=%s".format(logType) - // The query string carries request-supplied values into an inline script literal, so encode - // it for a JavaScript string context before embedding it via Unparsed below. - val jsOnload = "window.onload = " + - s"initLogPage('${StringEscapeUtils.escapeEcmaScript(logParams)}', " + - s"$curLogLength, $startByte, $endByte, $logLength, $byteLength);" + val logParams = "?self&logType=%s".format(UIUtils.encodeLogParam(logType)) + val jsOnload = UIUtils.logPageOnloadScript( + logParams, curLogLength, startByte, endByte, logLength, byteLength) val content = ++ diff --git a/core/src/main/scala/org/apache/spark/deploy/worker/ui/LogPage.scala b/core/src/main/scala/org/apache/spark/deploy/worker/ui/LogPage.scala index 8c17fc41cd4f9..4c3c27c0d9f91 100644 --- a/core/src/main/scala/org/apache/spark/deploy/worker/ui/LogPage.scala +++ b/core/src/main/scala/org/apache/spark/deploy/worker/ui/LogPage.scala @@ -22,7 +22,6 @@ import java.io.File import scala.xml.{Node, Unparsed} import jakarta.servlet.http.HttpServletRequest -import org.apache.commons.text.StringEscapeUtils import org.apache.spark.internal.Logging import org.apache.spark.internal.LogKeys.{LOG_TYPE, PATH} @@ -72,11 +71,16 @@ private[ui] class LogPage(parent: WorkerWebUI) extends WebUIPage("logPage") with val byteLength = Option(request.getParameter("byteLength")).map(_.toInt) .getOrElse(defaultBytes) + // The identifiers below flow both into a filesystem path (logDir, kept raw and guarded by + // Utils.isInDirectory in getLog) and into the query string of the generated /log requests + // (params). URL-encode the query-string copy so request-supplied values cannot inject extra + // parameters or truncate the request. val (logDir, params, pageName) = (appId, executorId, driverId, self) match { case (Some(a), Some(e), None, None) => - (s"${workDir.getPath}/$a/$e/", s"appId=$a&executorId=$e", s"$a/$e") + (s"${workDir.getPath}/$a/$e/", + s"appId=${UIUtils.encodeLogParam(a)}&executorId=${UIUtils.encodeLogParam(e)}", s"$a/$e") case (None, None, Some(d), None) => - (s"${workDir.getPath}/$d/", s"driverId=$d", d) + (s"${workDir.getPath}/$d/", s"driverId=${UIUtils.encodeLogParam(d)}", d) case (None, None, None, Some(_)) => (s"${sys.env.getOrElse("SPARK_LOG_DIR", workDir.getPath)}/", "self", "worker") case _ => @@ -106,12 +110,9 @@ private[ui] class LogPage(parent: WorkerWebUI) extends WebUIPage("logPage") with End of Log - val logParams = "?%s&logType=%s".format(params, logType) - // The query string carries request-supplied values into an inline script literal, so encode - // it for a JavaScript string context before embedding it via Unparsed below. - val jsOnload = "window.onload = " + - s"initLogPage('${StringEscapeUtils.escapeEcmaScript(logParams)}', " + - s"$curLogLength, $startByte, $endByte, $logLength, $byteLength);" + val logParams = "?%s&logType=%s".format(params, UIUtils.encodeLogParam(logType)) + val jsOnload = UIUtils.logPageOnloadScript( + logParams, curLogLength, startByte, endByte, logLength, byteLength) val content = ++ diff --git a/core/src/main/scala/org/apache/spark/ui/DriverLogPage.scala b/core/src/main/scala/org/apache/spark/ui/DriverLogPage.scala index 66d6d83a0b88f..9cd2fe50715f0 100644 --- a/core/src/main/scala/org/apache/spark/ui/DriverLogPage.scala +++ b/core/src/main/scala/org/apache/spark/ui/DriverLogPage.scala @@ -19,7 +19,6 @@ package org.apache.spark.ui import scala.xml.{Node, Unparsed} import jakarta.servlet.http.HttpServletRequest -import org.apache.commons.text.StringEscapeUtils import org.apache.spark.SparkConf import org.apache.spark.internal.Logging @@ -71,12 +70,9 @@ private[ui] class DriverLogPage( End of Log - val logParams = "/?logType=%s".format(logType) - // The query string carries request-supplied values into an inline script literal, so encode - // it for a JavaScript string context before embedding it via Unparsed below. - val jsOnload = "window.onload = " + - s"initLogPage('${StringEscapeUtils.escapeEcmaScript(logParams)}', " + - s"$curLogLength, $startByte, $endByte, $logLength, $byteLength);" + val logParams = "/?logType=%s".format(UIUtils.encodeLogParam(logType)) + val jsOnload = UIUtils.logPageOnloadScript( + logParams, curLogLength, startByte, endByte, logLength, byteLength) val content = ++ diff --git a/core/src/main/scala/org/apache/spark/ui/UIUtils.scala b/core/src/main/scala/org/apache/spark/ui/UIUtils.scala index eedec979f7158..e597614de20d6 100644 --- a/core/src/main/scala/org/apache/spark/ui/UIUtils.scala +++ b/core/src/main/scala/org/apache/spark/ui/UIUtils.scala @@ -19,7 +19,7 @@ package org.apache.spark.ui import java.{util => ju} import java.lang.{Long => JLong} -import java.net.URLDecoder +import java.net.{URLDecoder, URLEncoder} import java.nio.charset.StandardCharsets.UTF_8 import java.time.{Instant, ZoneId} import java.time.format.DateTimeFormatter @@ -32,6 +32,7 @@ import scala.xml.transform.{RewriteRule, RuleTransformer} import jakarta.servlet.http.HttpServletRequest import jakarta.ws.rs.core.{MediaType, MultivaluedMap, Response} +import org.apache.commons.text.StringEscapeUtils import org.eclipse.jetty.server.Request import org.glassfish.jersey.internal.util.collection.MultivaluedStringMap @@ -751,6 +752,32 @@ private[spark] object UIUtils extends Logging { def getTimeZoneOffset() : Int = TimeZone.getDefault().getOffset(System.currentTimeMillis()) / 1000 / 60 + /** + * URL-encode a single request-supplied value before embedding it in a log page query string. + * This keeps characters such as '&', '=', '#' and whitespace from changing the structure of the + * subsequent `/log` requests (parameter injection / query truncation). + */ + def encodeLogParam(value: String): String = URLEncoder.encode(String.valueOf(value), UTF_8) + + /** + * Build the inline `window.onload = initLogPage(...)` bootstrap script shared by the log pages + * (history / master / worker / driver). Individual parameter values in `logParams` must already + * be URL-encoded via [[encodeLogParam]] so the query string is unambiguous; the assembled string + * is additionally escaped for the surrounding JavaScript string literal so request-supplied + * values cannot break out of the inline <script> block. + */ + def logPageOnloadScript( + logParams: String, + curLogLength: Long, + startByte: Long, + endByte: Long, + logLength: Long, + byteLength: Long): String = { + "window.onload = " + + s"initLogPage('${StringEscapeUtils.escapeEcmaScript(logParams)}', " + + s"$curLogLength, $startByte, $endByte, $logLength, $byteLength);" + } + /** * Return the correct Href after checking if master is running in the * reverse proxy mode or not. diff --git a/core/src/test/scala/org/apache/spark/deploy/history/LogPageSuite.scala b/core/src/test/scala/org/apache/spark/deploy/history/LogPageSuite.scala index bd6bd70d5f3eb..f37eac47cceac 100644 --- a/core/src/test/scala/org/apache/spark/deploy/history/LogPageSuite.scala +++ b/core/src/test/scala/org/apache/spark/deploy/history/LogPageSuite.scala @@ -32,9 +32,11 @@ class LogPageSuite extends SparkFunSuite { when(request.getParameter("logType")).thenReturn("stdout');alert('xss')") val html = page.render(request).mkString - // The value must be encoded for the JavaScript string context: quotes, backslashes and the - // slash in a closing tag are all escaped, so no raw breakout sequence reaches the browser. - assert(html.contains("""stdout\');alert(\'xss\')<\/script>""")) - assert(!html.contains("stdout');alert('xss')")) + // The value is URL-encoded, so the quotes, angle brackets and slash that would break out of + // the inline ' would break out of the inline ") + when(request.getParameter("executorId")).thenReturn("0") + when(request.getParameter("logType")).thenReturn("stdout") + val html = logPage.render(request).mkString + + // The value is percent-encoded, so it stays a single opaque appId value and cannot inject a + // parameter or close the script element. + assert(html.contains("appId=app%26byteLength%3D0%3C%2Fscript%3E&executorId=0")) + assert(!html.contains("app&byteLength=0")) + } + /** Write the specified string to the file. */ private def write(f: File, s: String): Unit = { val writer = new FileWriter(f) From ac718961fc19c4de9847a58f004e6a4df77831cb Mon Sep 17 00:00:00 2001 From: Holden Karau Date: Thu, 9 Jul 2026 22:19:39 -0700 Subject: [PATCH 3/4] Handle nulls in encoding log params Co-authored-by: Dongjoon Hyun --- core/src/main/scala/org/apache/spark/ui/UIUtils.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/org/apache/spark/ui/UIUtils.scala b/core/src/main/scala/org/apache/spark/ui/UIUtils.scala index e597614de20d6..c0d36ef3a8acf 100644 --- a/core/src/main/scala/org/apache/spark/ui/UIUtils.scala +++ b/core/src/main/scala/org/apache/spark/ui/UIUtils.scala @@ -757,7 +757,8 @@ private[spark] object UIUtils extends Logging { * This keeps characters such as '&', '=', '#' and whitespace from changing the structure of the * subsequent `/log` requests (parameter injection / query truncation). */ - def encodeLogParam(value: String): String = URLEncoder.encode(String.valueOf(value), UTF_8) + def encodeLogParam(value: String): String = + URLEncoder.encode(Option(value).getOrElse("null"), UTF_8) /** * Build the inline `window.onload = initLogPage(...)` bootstrap script shared by the log pages From 585876c1a06696d6c8bfd16282e410c0ee0499e3 Mon Sep 17 00:00:00 2001 From: Holden Karau Date: Thu, 9 Jul 2026 22:31:04 -0700 Subject: [PATCH 4/4] Use withTempDir --- .../spark/deploy/worker/ui/LogPageSuite.scala | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/core/src/test/scala/org/apache/spark/deploy/worker/ui/LogPageSuite.scala b/core/src/test/scala/org/apache/spark/deploy/worker/ui/LogPageSuite.scala index 753df6fd6e8b7..cd5e475b87713 100644 --- a/core/src/test/scala/org/apache/spark/deploy/worker/ui/LogPageSuite.scala +++ b/core/src/test/scala/org/apache/spark/deploy/worker/ui/LogPageSuite.scala @@ -79,27 +79,28 @@ class LogPageSuite extends SparkFunSuite with PrivateMethodTester { test("render encodes request parameters embedded in the inline script") { val webui = mock(classOf[WorkerWebUI]) val worker = mock(classOf[Worker]) - val tmpDir = new File(sys.props("java.io.tmpdir")) - val workDir = new File(tmpDir, "work-dir") - workDir.mkdir() - when(webui.workDir).thenReturn(workDir) - when(webui.worker).thenReturn(worker) - when(worker.conf).thenReturn(new SparkConf()) - when(worker.activeMasterWebUiUrl).thenReturn("http://master:8080") - val logPage = new LogPage(webui) + withTempDir { tmpDir => + val workDir = new File(tmpDir, "work-dir") + workDir.mkdir() + when(webui.workDir).thenReturn(workDir) + when(webui.worker).thenReturn(worker) + when(worker.conf).thenReturn(new SparkConf()) + when(worker.activeMasterWebUiUrl).thenReturn("http://master:8080") + val logPage = new LogPage(webui) - val request = mock(classOf[HttpServletRequest]) - // appId is user-controlled; a '&' in it would inject an extra parameter into the /log - // requests, and the '' would break out of the inline ") - when(request.getParameter("executorId")).thenReturn("0") - when(request.getParameter("logType")).thenReturn("stdout") - val html = logPage.render(request).mkString + val request = mock(classOf[HttpServletRequest]) + // appId is user-controlled; a '&' in it would inject an extra parameter into the /log + // requests, and the '' would break out of the inline ") + when(request.getParameter("executorId")).thenReturn("0") + when(request.getParameter("logType")).thenReturn("stdout") + val html = logPage.render(request).mkString - // The value is percent-encoded, so it stays a single opaque appId value and cannot inject a - // parameter or close the script element. - assert(html.contains("appId=app%26byteLength%3D0%3C%2Fscript%3E&executorId=0")) - assert(!html.contains("app&byteLength=0")) + // The value is percent-encoded, so it stays a single opaque appId value and cannot inject a + // parameter or close the script element. + assert(html.contains("appId=app%26byteLength%3D0%3C%2Fscript%3E&executorId=0")) + assert(!html.contains("app&byteLength=0")) + } } /** Write the specified string to the file. */