This is an automated email from the ASF dual-hosted git repository.

markt-asf pushed a commit to branch 10.1.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/10.1.x by this push:
     new 87341a2363 Review "Match the JSP namespace exactly when detecting JSP 
documents"
87341a2363 is described below

commit 87341a236330d76381ea4e146a995deb55c4d05a
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      | 128 ++++++++++++++++++++-
 2 files changed, 172 insertions(+), 25 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 09f33c6027..6e0f7557d1 100644
--- a/test/org/apache/jasper/compiler/TestParserController.java
+++ b/test/org/apache/jasper/compiler/TestParserController.java
@@ -19,16 +19,40 @@ package org.apache.jasper.compiler;
 import java.io.File;
 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);
@@ -52,8 +76,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
@@ -81,13 +104,110 @@ 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.writeString(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", 
StandardCharsets.UTF_8);
+        // The prefix of the root is also a suffix of another declared prefix
+        Files.writeString(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", 
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.writeString(new File(appDir, page).toPath(), content, 
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, Files.readString(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.writeString(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", 
StandardCharsets.UTF_8);
+        Files.writeString(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", 
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.writeString(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", 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(Files.readString(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());
+        File appDir = createAppDir("root-prefix");
         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);


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to