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

rmaucher pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git

commit 607a599e707ad9414a7cc14d4c38c23fcc841b9e
Author: opencode <[email protected]>
AuthorDate: Mon Oct 19 12:26:56 2026 +0200

    Resolve JspC resources whose path contains URI-illegal characters
    
    JspCServletContext.getResource() built the resource URI by raw string
    concatenation of the encoded base URL and the decoded logical resource
    path. Any path containing characters that are not permitted in a URI,
    for example a space or a non-ASCII character, produced a
    URISyntaxException that was swallowed by a catch-all, so the resource
    was silently reported as not found and JspC failed with a confusing
    resource not found error, even though the same application resolved and
    compiled fine at runtime.
    
    Percent-encode each segment of the path before appending it to the base
    URL, matching the behaviour of the runtime container. With that in
    place, only an IOException (openStream failing or a malformed URL) and
    a URISyntaxException indicate a missing resource, so the catch-all that
    previously converted any Throwable, including Errors, into a null
    result could be narrowed. Add a unit test with a space and a non-ASCII
    character in the resource name.
    
    Found by code review.
---
 .../apache/jasper/servlet/JspCServletContext.java  | 37 ++++++++++++++++--
 .../jasper/servlet/TestJspCServletContext.java     | 44 ++++++++++++++++++++++
 2 files changed, 78 insertions(+), 3 deletions(-)

diff --git a/java/org/apache/jasper/servlet/JspCServletContext.java 
b/java/org/apache/jasper/servlet/JspCServletContext.java
index 1d7dfd359b..08de607613 100644
--- a/java/org/apache/jasper/servlet/JspCServletContext.java
+++ b/java/org/apache/jasper/servlet/JspCServletContext.java
@@ -24,6 +24,8 @@ import java.net.MalformedURLException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URL;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -320,12 +322,17 @@ public class JspCServletContext implements ServletContext 
{
 
         URL url;
         try {
-            URI uri = new URI(myResourceBaseURL.toExternalForm() + path);
+            // The path is a decoded logical path. Percent-encode it before
+            // appending it to the (already encoded) base URL so that
+            // resources whose name contains characters that are not
+            // permitted in a URI, for example a space, can be resolved, as
+            // they are by the runtime container.
+            URI uri = new URI(myResourceBaseURL.toExternalForm() + 
encodePath(path));
             url = uri.toURL();
             try (InputStream is = url.openStream()) {
             }
-        } catch (Throwable t) {
-            ExceptionUtils.handleThrowable(t);
+        } catch (IOException | URISyntaxException e) {
+            // The resource does not exist or the URL could not be built.
             url = null;
         }
 
@@ -347,6 +354,30 @@ public class JspCServletContext implements ServletContext {
     }
 
 
+    /*
+     * Percent-encodes each segment of the given decoded resource path so the
+     * result can be appended to an already encoded base URL.
+     */
+    private static String encodePath(String path) {
+        StringBuilder result = new StringBuilder(path.length());
+        int start = 0;
+        for (;;) {
+            int slash = path.indexOf('/', start);
+            String segment = slash < 0 ? path.substring(start) : 
path.substring(start, slash);
+            // URLEncoder implements form encoding. It uses '+' for a space,
+            // which is not equivalent in a path segment. A literal '+' in the
+            // input is encoded as "%2B", so any '+' in the output is a space.
+            result.append(URLEncoder.encode(segment, 
StandardCharsets.UTF_8).replace("+", "%20"));
+            if (slash < 0) {
+                break;
+            }
+            result.append('/');
+            start = slash + 1;
+        }
+        return result.toString();
+    }
+
+
     @Override
     public InputStream getResourceAsStream(String path) {
         try {
diff --git a/test/org/apache/jasper/servlet/TestJspCServletContext.java 
b/test/org/apache/jasper/servlet/TestJspCServletContext.java
index dbbdaa2aba..a30e7b4910 100644
--- a/test/org/apache/jasper/servlet/TestJspCServletContext.java
+++ b/test/org/apache/jasper/servlet/TestJspCServletContext.java
@@ -17,6 +17,10 @@
 package org.apache.jasper.servlet;
 
 import java.io.File;
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.Set;
@@ -134,6 +138,46 @@ public class TestJspCServletContext {
     }
 
 
+    @Test
+    public void testGetResourceWithUriIllegalCharacters() throws Exception {
+        // A resource path is a decoded logical path. Resources whose name
+        // contains characters that are not permitted in a URI, for example a
+        // space or a non-ASCII character, resolve correctly at runtime so
+        // they must also resolve for the offline compiler rather than be
+        // silently reported as not found.
+        File docBase = 
Files.createTempDirectory("jspc-illegal-chars").toFile();
+        try {
+            Files.writeString(new File(docBase, "my file.jsp").toPath(),
+                    "<jsp/>", StandardCharsets.UTF_8);
+            Files.writeString(new File(docBase, "unicode-\u00fc.jsp").toPath(),
+                    "<jsp/>", StandardCharsets.UTF_8);
+            JspCServletContext context = new JspCServletContext(
+                    null, docBase.toURI().toURL(), null, false, false);
+
+            URL url = context.getResource("/my file.jsp");
+            Assert.assertNotNull(url);
+            try (InputStream is = url.openStream()) {
+                Assert.assertEquals("<jsp/>",
+                        new String(is.readAllBytes(), StandardCharsets.UTF_8));
+            }
+            Assert.assertNotNull(context.getResource("/unicode-\u00fc.jsp"));
+        } finally {
+            deleteRecursively(docBase);
+        }
+    }
+
+
+    private static void deleteRecursively(File file) {
+        File[] children = file.listFiles();
+        if (children != null) {
+            for (File child : children) {
+                deleteRecursively(child);
+            }
+        }
+        Assert.assertTrue(file.delete());
+    }
+
+
     @Test
     public void testResourceJARs() throws Exception {
         File appDir = new File("test/webapp-fragments");


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

Reply via email to