This is an automated email from the ASF dual-hosted git repository.
markt-asf pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/9.0.x by this push:
new 1c0acfab6c Review "Match the JSP namespace exactly when detecting JSP
documents"
1c0acfab6c is described below
commit 1c0acfab6cf16eec0c21d967ea89e0344a177658
Author: Mark Thomas <[email protected]>
AuthorDate: Thu Nov 5 19:28:01 2026 +0000
Review "Match the JSP namespace exactly when detecting JSP documents"
Further improve matching
---
.../apache/jasper/compiler/ParserController.java | 69 +++++++----
.../jasper/compiler/TestParserController.java | 137 +++++++++++++++++++--
2 files changed, 177 insertions(+), 29 deletions(-)
diff --git a/java/org/apache/jasper/compiler/ParserController.java
b/java/org/apache/jasper/compiler/ParserController.java
index 3e34e3d815..b1b81685c9 100644
--- a/java/org/apache/jasper/compiler/ParserController.java
+++ b/java/org/apache/jasper/compiler/ParserController.java
@@ -536,32 +536,59 @@ public class ParserController implements TagConstants {
String prefix = reader.getText(start, stop).substring(1);
start = stop;
- stop = reader.skipUntil(">");
- if (stop == null) {
- return false;
+ // The root element ends at the first '>' that is not within a quoted
attribute value. The mark is taken
+ // immediately before the '>' so, as with skipUntil(), the text of the
element does not include it.
+ int openQuote = -1;
+ while (true) {
+ stop = reader.mark();
+ int ch = reader.nextChar();
+ if (ch == -1) {
+ return false;
+ }
+ if (openQuote != -1) {
+ if (ch == openQuote) {
+ openQuote = -1;
+ }
+ } else if (ch == '"' || ch == '\'') {
+ openQuote = ch;
+ } else if (ch == '>') {
+ break;
+ }
}
// Determine namespace associated with <root> element's prefix
String root = reader.getText(start, stop);
String xmlnsDecl = "xmlns:" + prefix;
- int index = root.indexOf(xmlnsDecl);
- if (index == -1) {
- return false;
- }
- index += xmlnsDecl.length();
- while (index < root.length() &&
Character.isWhitespace(root.charAt(index))) {
- index++;
- }
- if (index < root.length() && root.charAt(index) == '=') {
- do {
- 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++;
- int end = root.indexOf(quote, index);
- if (end > index) {
- return JSP_URI.equals(root.substring(index, end));
+ // Skip quoted attribute values so namespace-like text within them
cannot be mistaken for a declaration.
+ // Other declarations may use prefixes that start with the root
element's prefix.
+ for (int declIndex = 0; declIndex < root.length(); declIndex++) {
+ char ch = root.charAt(declIndex);
+ if (ch == '"' || ch == '\'') {
+ int end = root.indexOf(ch, declIndex + 1);
+ if (end == -1) {
+ return false;
+ }
+ declIndex = end;
+ continue;
+ }
+ if ((declIndex == 0 ||
Character.isWhitespace(root.charAt(declIndex - 1))) &&
+ root.startsWith(xmlnsDecl, declIndex)) {
+ int index = declIndex + xmlnsDecl.length();
+ while (index < root.length() &&
Character.isWhitespace(root.charAt(index))) {
+ index++;
+ }
+ if (index < root.length() && root.charAt(index) == '=') {
+ do {
+ 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++;
+ 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 1dcfb4759f..13535d6154 100644
--- a/test/org/apache/jasper/compiler/TestParserController.java
+++ b/test/org/apache/jasper/compiler/TestParserController.java
@@ -18,16 +18,42 @@ package org.apache.jasper.compiler;
import java.io.File;
import java.io.FileWriter;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.After;
import org.junit.Assert;
import org.junit.Test;
+import org.apache.catalina.startup.ExpandWar;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.startup.TomcatBaseTest;
import org.apache.tomcat.util.buf.ByteChunk;
public class TestParserController extends TomcatBaseTest {
+ private final List<File> appDirs = new ArrayList<>();
+
+ @After
+ public void deleteAppDirs() {
+ // Remove the web applications generated by the tests
+ for (File appDir : appDirs) {
+ ExpandWar.delete(appDir);
+ }
+ appDirs.clear();
+ }
+
+
+ private File createAppDir(String name) {
+ File appDir = new File(getTemporaryDirectory(), name);
+ Assert.assertTrue(appDir.mkdirs() || appDir.isDirectory());
+ appDirs.add(appDir);
+ return appDir;
+ }
+
+
@Test
public void testIncludeInvalidUri() throws Exception {
getTomcatInstanceTestWebapp(false, true);
@@ -51,8 +77,7 @@ public class TestParserController extends TomcatBaseTest {
// 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());
+ File appDir = createAppDir("root-detection");
// The root element may use any prefix as long as the prefix is bound
// to the exact JSP namespace URI
@@ -82,17 +107,113 @@ public class TestParserController extends TomcatBaseTest {
Assert.assertEquals("OK", res.toString());
}
+ @Test
+ public void testJspRootDetectionOtherPrefixDeclaredFirst() throws
Exception {
+ // Other namespace declarations on the root element, including ones
whose prefix starts with the prefix of the
+ // root element, must not hide the declaration of the prefix of the
root element
+ Tomcat tomcat = getTomcatInstance();
+ File appDir = createAppDir("root-detection-prefixes");
+
+ Files.write(new File(appDir, "first.jsp").toPath(),
+ ("<jsp:root xmlns:jspx=\"urn:example:other\" xmlns:jsp=\"" +
TagConstants.JSP_URI +
+ "\"
version=\"2.0\">\n<jsp:body>OK</jsp:body>\n</jsp:root>\n").getBytes(StandardCharsets.UTF_8));
+ // The prefix of the root is also a suffix of another declared prefix
+ Files.write(new File(appDir, "suffix.jsp").toPath(),
+ ("<jsp:root xmlns:myjsp=\"urn:example:other\" xmlns:jsp=\"" +
TagConstants.JSP_URI +
+ "\"
version=\"2.0\">\n<jsp:body>OK</jsp:body>\n</jsp:root>\n").getBytes(StandardCharsets.UTF_8));
+
+ tomcat.addWebapp(null, "/test", appDir.getAbsolutePath());
+ tomcat.start();
+
+ for (String page : new String[] { "first.jsp", "suffix.jsp" }) {
+ ByteChunk res = new ByteChunk();
+ int sc = getUrl("http://localhost:" + getPort() + "/test/" + page,
res, null);
+ Assert.assertEquals(page, 200, sc);
+ Assert.assertEquals(page, "OK", res.toString());
+ }
+ }
+
+
+ @Test
+ public void testRootNamespaceTextInAttributeValue() throws Exception {
+ Tomcat tomcat = getTomcatInstance();
+ File appDir = createAppDir("root-quoted-namespace");
+ List<String> pages = new ArrayList<>();
+ for (char quote : new char[] { '"', '\'' }) {
+ char innerQuote = quote == '"' ? '\'' : '"';
+ // Check the real declaration before and after the attribute
value, and an undeclared prefix. None of
+ // these pages is a JSP document, even though the attribute value
contains a JSP namespace declaration.
+ for (int declaration = 0; declaration < 3; declaration++) {
+ String namespace = "xmlns:foo=\"urn:example\"";
+ String content = "<foo:root xmlns:foobar=\"urn:other\" " +
+ (declaration == 0 ? namespace + " " : "") + "data=" +
quote + " xmlns:foo=" +
+ innerQuote + TagConstants.JSP_URI + innerQuote + quote
+
+ (declaration == 1 ? " " + namespace : "") +
">\n<p>hello</p>\n</foo:root>\n";
+ String page = "quoted-" + pages.size() + ".jsp";
+ Files.write(new File(appDir, page).toPath(),
content.getBytes(StandardCharsets.UTF_8));
+ pages.add(page);
+ }
+ }
+
+ tomcat.addWebapp(null, "/test", appDir.getAbsolutePath());
+ tomcat.start();
+
+ for (String page : pages) {
+ ByteChunk res = new ByteChunk();
+ int sc = getUrl("http://localhost:" + getPort() + "/test/" + page,
res, null);
+ Assert.assertEquals(page, 200, sc);
+ Assert.assertEquals(page, new String(Files.readAllBytes(new
File(appDir, page).toPath()), StandardCharsets.UTF_8),
+ res.toString());
+ }
+ }
+
+
+ @Test
+ public void testJspRootDetectionGreaterThanInAttributeValue() throws
Exception {
+ // A '>' within a quoted attribute value does not end the root
element, so the declaration of the prefix that
+ // follows the attribute must still be found
+ Tomcat tomcat = getTomcatInstance();
+ File appDir = createAppDir("root-detection-gt");
+
+ Files.write(new File(appDir, "double.jsp").toPath(),
+ ("<jsp:root xmlns:x=\"urn:a>b\" xmlns:jsp=\"" +
TagConstants.JSP_URI +
+ "\"
version=\"2.0\">\n<jsp:body>OK</jsp:body>\n</jsp:root>\n").getBytes(StandardCharsets.UTF_8));
+ Files.write(new File(appDir, "single.jsp").toPath(),
+ ("<jsp:root xmlns:x='urn:a>\"b' xmlns:jsp='" +
TagConstants.JSP_URI +
+ "'
version='2.0'>\n<jsp:body>OK</jsp:body>\n</jsp:root>\n").getBytes(StandardCharsets.UTF_8));
+ // A '>' in an attribute value of a root element that is not bound to
the JSP namespace must not make the
+ // page a JSP document
+ Files.write(new File(appDir, "other.jsp").toPath(),
+ ("<foo:root data=\"a>xmlns:foo='" + TagConstants.JSP_URI +
"'\" xmlns:foo=\"urn:example\">\n" +
+
"<p>hello</p>\n</foo:root>\n").getBytes(StandardCharsets.UTF_8));
+
+ tomcat.addWebapp(null, "/test", appDir.getAbsolutePath());
+ tomcat.start();
+
+ for (String page : new String[] { "double.jsp", "single.jsp" }) {
+ ByteChunk res = new ByteChunk();
+ int sc = getUrl("http://localhost:" + getPort() + "/test/" + page,
res, null);
+ Assert.assertEquals(page, 200, sc);
+ Assert.assertEquals(page, "OK", res.toString());
+ }
+
+ ByteChunk res = new ByteChunk();
+ int sc = getUrl("http://localhost:" + getPort() + "/test/other.jsp",
res, null);
+ Assert.assertEquals(200, sc);
+ Assert.assertEquals(new String(Files.readAllBytes(new File(appDir,
"other.jsp").toPath()), StandardCharsets.UTF_8),
+ 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());
- try (FileWriter writer = new FileWriter(new File(appDir,
"prefix.jsp"))) {
- writer.write("<foo:root xmlns:foo=\"" + TagConstants.JSP_URI +
"PageExtra\">\n" +
- "<p>hello</p>\n</foo:root>\n");
- }
+ File appDir = createAppDir("root-prefix");
+ Files.write(new File(appDir, "prefix.jsp").toPath(),
+ ("<foo:root xmlns:foo=\"" + TagConstants.JSP_URI +
"PageExtra\">\n" +
+
"<p>hello</p>\n</foo:root>\n").getBytes(StandardCharsets.UTF_8));
tomcat.addWebapp(null, "/test", appDir.getAbsolutePath());
tomcat.start();
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]