Skip to content

Commit

Permalink
Fix ServletContext.getResourcePaths() (#9974)
Browse files Browse the repository at this point in the history
#9972 Fix ServletContextApi.getResource* path normalization

Signed-off-by: Ludovic Orban <[email protected]>
  • Loading branch information
lorban authored Jun 28, 2023
1 parent 08d9535 commit c002adb
Show file tree
Hide file tree
Showing 7 changed files with 213 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2747,10 +2747,10 @@ public RequestDispatcher getRequestDispatcher(String uriInContext)
@Override
public String getRealPath(String path)
{
// This is an API call from the application which may pass non-canonical paths.
// Thus, we canonicalize here, to avoid the enforcement of canonical paths in
// ContextHandler.this.getResource(path).
path = URIUtil.canonicalPath(path);
// This is an API call from the application which may pass non-normalized paths.
// Thus, we normalize here, to avoid the enforcement of normalized paths in
// ServletContextHandler.this.getResource(path).
path = URIUtil.normalizePath(path);
if (path == null)
return null;
if (path.length() == 0)
Expand All @@ -2761,12 +2761,22 @@ else if (path.charAt(0) != '/')
try
{
Resource resource = ServletContextHandler.this.getResource(path);
if (resource != null)
if (resource == null)
return null;

for (Resource r : resource)
{
Path resourcePath = resource.getPath();
if (resourcePath != null)
return resourcePath.normalize().toString();
// return first
if (Resources.exists(r))
{
Path resourcePath = r.getPath();
if (resourcePath != null)
return resourcePath.normalize().toString();
}
}

// A Resource was returned, but did not exist
return null;
}
catch (Exception e)
{
Expand All @@ -2779,10 +2789,10 @@ else if (path.charAt(0) != '/')
@Override
public URL getResource(String path) throws MalformedURLException
{
// This is an API call from the application which may pass non-canonical paths.
// Thus, we canonicalize here, to avoid the enforcement of canonical paths in
// ContextHandler.this.getResource(path).
path = URIUtil.canonicalPath(path);
// This is an API call from the application which may pass non-normalized paths.
// Thus, we normalize here, to avoid the enforcement of normalized paths in
// ServletContextHandler.this.getResource(path).
path = URIUtil.normalizePath(path);
if (path == null)
return null;

Expand Down Expand Up @@ -2829,10 +2839,10 @@ public InputStream getResourceAsStream(String path)
@Override
public Set<String> getResourcePaths(String path)
{
// This is an API call from the application which may pass non-canonical paths.
// Thus, we canonicalize here, to avoid the enforcement of canonical paths in
// ContextHandler.this.getResource(path).
path = URIUtil.canonicalPath(path);
// This is an API call from the application which may pass non-normalized paths.
// Thus, we normalize here, to avoid the enforcement of normalized paths in
// ServletContextHandler.this.getResource(path).
path = URIUtil.normalizePath(path);
if (path == null)
return null;
return ServletContextHandler.this.getResourcePaths(path);
Expand Down
1 change: 1 addition & 0 deletions jetty-ee10/jetty-ee10-webapp/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
<configuration>
<argLine>
@{argLine} ${jetty.surefire.argLine}
--add-exports org.eclipse.jetty.ee10.webapp/org.acme.webapp=org.eclipse.jetty.ee10.servlet
</argLine>
<useManifestOnlyJar>false</useManifestOnlyJar>
<additionalClasspathElements>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.acme.webapp;

import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.Set;

import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

public class GetRealPathsServlet extends HttpServlet
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
{
collectResourcePaths("/").stream()
.map(p -> getServletContext().getRealPath(p))
.forEach(resp.getWriter()::println);
resp.getWriter().flush();
}

private Set<String> collectResourcePaths(String path)
{
Set<String> allResourcePaths = new LinkedHashSet<>();
Set<String> pathsForPath = getServletContext().getResourcePaths(path);
if (pathsForPath != null)
{
for (String resourcePath : pathsForPath)
{
allResourcePaths.add(resourcePath);
allResourcePaths.addAll(collectResourcePaths(resourcePath));
}
}
return allResourcePaths;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.acme.webapp;

import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.Set;

import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

public class GetResourcePathsServlet extends HttpServlet
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
{
collectResourcePaths("/").forEach(resp.getWriter()::println);
resp.getWriter().flush();
}

private Set<String> collectResourcePaths(String path)
{
Set<String> allResourcePaths = new LinkedHashSet<>();
Set<String> pathsForPath = getServletContext().getResourcePaths(path);
if (pathsForPath != null)
{
for (String resourcePath : pathsForPath)
{
allResourcePaths.add(resourcePath);
allResourcePaths.addAll(collectResourcePaths(resourcePath));
}
}
return allResourcePaths;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,10 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.either;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
Expand Down Expand Up @@ -230,14 +232,15 @@ public void testConfigurationInstances()
}

@Test
public void testRealPathDoesNotExist() throws Exception
public void testRealPath() throws Exception
{
Server server = newServer();
WebAppContext context = new WebAppContext(".", "/");
server.setHandler(context);
server.start();

ServletContext ctx = context.getServletContext();
assertNotNull(ctx.getRealPath("/"));
assertNull(ctx.getRealPath("/doesnotexist"));
assertNull(ctx.getRealPath("/doesnotexist/"));
}
Expand Down Expand Up @@ -536,6 +539,62 @@ public void testGetResourcePathsFromCollection() throws Exception
assertThat(servletContext.getResourcePaths("/WEB-INF"), containsInAnyOrder("/WEB-INF/zero.xml", "/WEB-INF/one.xml"));
}

@Test
public void testGetResourcePaths() throws Exception
{
Server server = newServer();
LocalConnector connector = new LocalConnector(server);
server.addConnector(connector);

WebAppContext context = new WebAppContext(MavenTestingUtils.getBasePath().resolve("src/test/webapp-with-resources").toString(), "/");
server.setHandler(context);
server.start();

ServletContext servletContext = context.getServletContext();

List<String> resourcePaths = List.copyOf(servletContext.getResourcePaths("/"));
assertThat(resourcePaths.size(), is(2));
assertThat(resourcePaths.get(0), is("/WEB-INF"));
assertThat(resourcePaths.get(1), is("/nested-reserved-!#\\\\$%&()*+,:=?@[]-meta-inf-resource.txt"));

String realPath = servletContext.getRealPath("/");
assertThat(realPath, notNullValue());
assertThat(servletContext.getRealPath(resourcePaths.get(0)), endsWith("/WEB-INF"));
// TODO the following assertion fails because of a bug in the JDK (see #9978)
//assertThat(servletContext.getRealPath(resourcePaths.get(1)), endsWith("/nested-reserved-!#\\\\$%&()*+,:=?@[]-meta-inf-resource.txt"));

assertThat(servletContext.getResource("/WEB-INF"), notNullValue());
// TODO the following assertion fails because of a bug in the JDK (see #9978)
//assertThat(servletContext.getResource("/nested-reserved-!#\\\\$%&()*+,:=?@[]-meta-inf-resource.txt"), notNullValue());

HttpTester.Response response1 = HttpTester.parseResponse(connector.getResponse("""
GET /resource HTTP/1.1\r
Host: local\r
Connection: close\r
\r
"""));

assertThat(response1.getStatus(), is(HttpStatus.OK_200));
assertThat(response1.getContent(), containsString("/WEB-INF"));
assertThat(response1.getContent(), containsString("/WEB-INF/lib"));
assertThat(response1.getContent(), containsString("/WEB-INF/lib/odd-resource.jar"));
assertThat(response1.getContent(), containsString("/nested-reserved-!#\\\\$%&()*+,:=?@[]-meta-inf-resource.txt"));

HttpTester.Response response2 = HttpTester.parseResponse(connector.getResponse("""
GET /real HTTP/1.1\r
Host: local\r
Connection: close\r
\r
"""));

assertThat(response2.getStatus(), is(HttpStatus.OK_200));
assertThat(response2.getContent(), containsString("/WEB-INF"));
assertThat(response2.getContent(), containsString("/WEB-INF/lib"));
assertThat(response2.getContent(), containsString("/WEB-INF/lib/odd-resource.jar"));
// TODO the following assertion fails because of a bug in the JDK (see #9978)
//assertThat(response2.getContent(), containsString("/nested-reserved-!#\\\\$%&()*+,:=?@[]-meta-inf-resource.txt"));
}

public static Stream<Arguments> extraClasspathGlob()
{
List<Arguments> references = new ArrayList<>();
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app
xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
metadata-complete="false"
version="6.0">

<servlet>
<servlet-name>GetResourcePathsServlet</servlet-name>
<servlet-class>org.acme.webapp.GetResourcePathsServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>GetRealPathsServlet</servlet-name>
<servlet-class>org.acme.webapp.GetRealPathsServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>GetResourcePathsServlet</servlet-name>
<url-pattern>/resource/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>GetRealPathsServlet</servlet-name>
<url-pattern>/real/*</url-pattern>
</servlet-mapping>
</web-app>


0 comments on commit c002adb

Please sign in to comment.