This is an automated email from the ASF dual-hosted git repository. rmaucher pushed a commit to branch 10.1.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit bab89e9ac2810d5199eca99775adbb881cc5d4f8 Author: opencode <[email protected]> AuthorDate: Sun Oct 18 23:15:46 2026 +0200 Match the JSP namespace exactly when detecting JSP documents ParserController.hasJspRoot() used a regionMatches() prefix comparison when checking the namespace bound to the root element's prefix. A namespace that merely started with "http://java.sun.com/JSP/Page", such as "http://java.sun.com/JSP/PageExtra", incorrectly triggered parsing of the page as a JSP document. Such pages were silently miscompiled as XML: a spurious XML declaration was prepended to the response, whitespace text between elements was removed and the namespace declaration was dropped from the root element. Read the full quoted attribute value and compare it with the JSP namespace using String.equals(), as the rest of Jasper already does when comparing this URI. Add tests that cover detection of a root element bound to the exact JSP namespace using both the jsp prefix and an arbitrary prefix, and non-detection when the namespace only starts with the JSP namespace. --- .../apache/jasper/compiler/ParserController.java | 6 +- .../jasper/compiler/TestParserController.java | 67 ++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/java/org/apache/jasper/compiler/ParserController.java b/java/org/apache/jasper/compiler/ParserController.java index 7dbe122479..e9852b73ff 100644 --- a/java/org/apache/jasper/compiler/ParserController.java +++ b/java/org/apache/jasper/compiler/ParserController.java @@ -557,8 +557,12 @@ public class ParserController implements TagConstants { index++; } while (index < root.length() && Character.isWhitespace(root.charAt(index))); if (index < root.length() && (root.charAt(index) == '"' || root.charAt(index) == '\'')) { + char quote = root.charAt(index); index++; - return root.regionMatches(index, JSP_URI, 0, JSP_URI.length()); + int end = root.indexOf(quote, index); + if (end > index) { + return JSP_URI.equals(root.substring(index, end)); + } } } diff --git a/test/org/apache/jasper/compiler/TestParserController.java b/test/org/apache/jasper/compiler/TestParserController.java index 51594b2512..09f33c6027 100644 --- a/test/org/apache/jasper/compiler/TestParserController.java +++ b/test/org/apache/jasper/compiler/TestParserController.java @@ -16,9 +16,14 @@ */ package org.apache.jasper.compiler; +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; + import org.junit.Assert; import org.junit.Test; +import org.apache.catalina.startup.Tomcat; import org.apache.catalina.startup.TomcatBaseTest; import org.apache.tomcat.util.buf.ByteChunk; @@ -40,4 +45,66 @@ public class TestParserController extends TomcatBaseTest { Assert.assertTrue(result, result.contains("The include [a b.jsp] is invalid")); Assert.assertFalse(result, result.contains("Cannot invoke")); } + + @Test + public void testJspRootDetection() throws Exception { + // The pages for these tests are generated rather than stored with the + // tests so that the output, which is compared exactly, always uses + // Unix line endings for the template text + Tomcat tomcat = getTomcatInstance(); + File appDir = new File(getTemporaryDirectory(), "root-detection"); + Assert.assertTrue(appDir.mkdirs() || appDir.isDirectory()); + + // The root element may use any prefix as long as the prefix is bound + // to the exact JSP namespace URI + Files.writeString(new File(appDir, "jsp.jsp").toPath(), + "<jsp:root xmlns:jsp=\"" + TagConstants.JSP_URI + "\" version=\"2.0\">\n" + + "<jsp:body>OK</jsp:body>\n</jsp:root>\n", StandardCharsets.UTF_8); + Files.writeString(new File(appDir, "other.jsp").toPath(), + "<mypfx:root xmlns:mypfx=\"" + TagConstants.JSP_URI + "\" version=\"2.0\">\n" + + "<mypfx:body>OK</mypfx:body>\n</mypfx:root>\n", StandardCharsets.UTF_8); + + tomcat.addWebapp(null, "/test", appDir.getAbsolutePath()); + tomcat.start(); + + // Both pages must be parsed as JSP documents. They are handled as XML + // syntax so, as the output below shows, only the body content is + // written; the whitespace text between the elements is removed + ByteChunk res = new ByteChunk(); + int sc = getUrl("http://localhost:" + getPort() + "/test/jsp.jsp", res, null); + Assert.assertEquals(200, sc); + Assert.assertEquals("OK", res.toString()); + + res = new ByteChunk(); + sc = getUrl("http://localhost:" + getPort() + "/test/other.jsp", res, null); + Assert.assertEquals(200, sc); + Assert.assertEquals("OK", res.toString()); + } + + @Test + public void testRootPrefixNamespaceIsNotJspDocument() throws Exception { + // A namespace that only starts with the JSP namespace URI must not + // trigger parsing as a JSP document + Tomcat tomcat = getTomcatInstance(); + File appDir = new File(getTemporaryDirectory(), "root-prefix"); + Assert.assertTrue(appDir.mkdirs() || appDir.isDirectory()); + Files.writeString(new File(appDir, "prefix.jsp").toPath(), + "<foo:root xmlns:foo=\"" + TagConstants.JSP_URI + "PageExtra\">\n" + + "<p>hello</p>\n</foo:root>\n", StandardCharsets.UTF_8); + + tomcat.addWebapp(null, "/test", appDir.getAbsolutePath()); + tomcat.start(); + + ByteChunk res = new ByteChunk(); + int sc = getUrl("http://localhost:" + getPort() + "/test/prefix.jsp", res, null); + + Assert.assertEquals(200, sc); + + // The page must be handled as standard syntax: the template text, + // including the line breaks written above, is output unchanged and no + // XML declaration is added + String result = res.toString(); + Assert.assertTrue(result, result.contains("PageExtra\">\n<p>hello</p>")); + Assert.assertFalse(result, result.contains("<?xml")); + } } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
