2022-09-14 10:23:06 +08:00
|
|
|
From: Markus Koschany <apo@debian.org>
|
|
|
|
|
Date: Sat, 3 Jul 2021 20:47:31 +0200
|
|
|
|
|
Subject: CVE-2021-28169
|
2021-06-23 20:40:09 +08:00
|
|
|
|
2022-09-14 10:23:06 +08:00
|
|
|
Origin: https://github.com/eclipse/jetty.project/commit/1c05b0bcb181c759e98b060bded0b9376976b055
|
2021-06-23 20:40:09 +08:00
|
|
|
---
|
2022-09-14 10:23:06 +08:00
|
|
|
.../org/eclipse/jetty/server/ResourceService.java | 4 +-
|
|
|
|
|
.../org/eclipse/jetty/servlets/ConcatServlet.java | 4 +-
|
|
|
|
|
.../org/eclipse/jetty/servlets/WelcomeFilter.java | 8 +-
|
|
|
|
|
.../eclipse/jetty/servlets/ConcatServletTest.java | 34 +++--
|
|
|
|
|
.../eclipse/jetty/servlets/WelcomeFilterTest.java | 143 +++++++++++++++++++++
|
|
|
|
|
.../jetty/webapp/WebAppDefaultServletTest.java | 142 ++++++++++++++++++++
|
|
|
|
|
6 files changed, 313 insertions(+), 22 deletions(-)
|
2021-06-23 20:40:09 +08:00
|
|
|
create mode 100644 jetty-servlets/src/test/java/org/eclipse/jetty/servlets/WelcomeFilterTest.java
|
|
|
|
|
create mode 100644 jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppDefaultServletTest.java
|
|
|
|
|
|
|
|
|
|
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java
|
2022-09-14 10:23:06 +08:00
|
|
|
index 048bd71..737f461 100644
|
2021-06-23 20:40:09 +08:00
|
|
|
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java
|
|
|
|
|
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java
|
2022-09-14 10:23:06 +08:00
|
|
|
@@ -234,7 +234,7 @@ public class ResourceService
|
2021-06-23 20:40:09 +08:00
|
|
|
// Find the content
|
|
|
|
|
content=_contentFactory.getContent(pathInContext,response.getBufferSize());
|
|
|
|
|
if (LOG.isDebugEnabled())
|
|
|
|
|
- LOG.info("content={}",content);
|
|
|
|
|
+ LOG.debug("content={}", content);
|
|
|
|
|
|
|
|
|
|
// Not found?
|
|
|
|
|
if (content==null || !content.getResource().exists())
|
2022-09-14 10:23:06 +08:00
|
|
|
@@ -420,7 +420,7 @@ public class ResourceService
|
2021-06-23 20:40:09 +08:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- RequestDispatcher dispatcher=context.getRequestDispatcher(welcome);
|
2022-09-14 10:23:06 +08:00
|
|
|
+ RequestDispatcher dispatcher = context.getRequestDispatcher(URIUtil.encodePath(welcome));
|
2021-06-23 20:40:09 +08:00
|
|
|
if (dispatcher!=null)
|
|
|
|
|
{
|
|
|
|
|
// Forward to the index
|
|
|
|
|
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/ConcatServlet.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/ConcatServlet.java
|
|
|
|
|
index a4b7df0..f1d8e57 100644
|
|
|
|
|
--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/ConcatServlet.java
|
|
|
|
|
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/ConcatServlet.java
|
|
|
|
|
@@ -62,6 +62,7 @@ import org.eclipse.jetty.util.URIUtil;
|
|
|
|
|
* appropriate. This means that when not in development mode, the servlet must be
|
|
|
|
|
* restarted before changed content will be served.</p>
|
|
|
|
|
*/
|
|
|
|
|
+@Deprecated
|
|
|
|
|
public class ConcatServlet extends HttpServlet
|
|
|
|
|
{
|
|
|
|
|
private boolean _development;
|
|
|
|
|
@@ -126,7 +127,8 @@ public class ConcatServlet extends HttpServlet
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- RequestDispatcher dispatcher = getServletContext().getRequestDispatcher(path);
|
|
|
|
|
+ // Use the original string and not the decoded path as the Dispatcher will decode again.
|
|
|
|
|
+ RequestDispatcher dispatcher = getServletContext().getRequestDispatcher(part);
|
|
|
|
|
if (dispatcher != null)
|
|
|
|
|
dispatchers.add(dispatcher);
|
|
|
|
|
}
|
|
|
|
|
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/WelcomeFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/WelcomeFilter.java
|
2022-09-14 10:23:06 +08:00
|
|
|
index e67a067..22ea603 100644
|
2021-06-23 20:40:09 +08:00
|
|
|
--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/WelcomeFilter.java
|
|
|
|
|
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/WelcomeFilter.java
|
|
|
|
|
@@ -28,6 +28,8 @@ import javax.servlet.ServletRequest;
|
|
|
|
|
import javax.servlet.ServletResponse;
|
|
|
|
|
import javax.servlet.http.HttpServletRequest;
|
|
|
|
|
|
|
|
|
|
+import org.eclipse.jetty.util.URIUtil;
|
|
|
|
|
+
|
|
|
|
|
/* ------------------------------------------------------------ */
|
|
|
|
|
/** Welcome Filter
|
|
|
|
|
* This filter can be used to server an index file for a directory
|
|
|
|
|
@@ -42,6 +44,7 @@ import javax.servlet.http.HttpServletRequest;
|
|
|
|
|
*
|
|
|
|
|
* Requests to "/some/directory" will be redirected to "/some/directory/".
|
|
|
|
|
*/
|
|
|
|
|
+@Deprecated
|
|
|
|
|
public class WelcomeFilter implements Filter
|
|
|
|
|
{
|
|
|
|
|
private String welcome;
|
|
|
|
|
@@ -63,7 +66,10 @@ public class WelcomeFilter implements Filter
|
|
|
|
|
{
|
|
|
|
|
String path=((HttpServletRequest)request).getServletPath();
|
|
|
|
|
if (welcome!=null && path.endsWith("/"))
|
|
|
|
|
- request.getRequestDispatcher(path+welcome).forward(request,response);
|
|
|
|
|
+ {
|
|
|
|
|
+ String uriInContext = URIUtil.encodePath(URIUtil.addPaths(path, welcome));
|
2022-09-14 10:23:06 +08:00
|
|
|
+ request.getRequestDispatcher(uriInContext).forward(request, response);
|
|
|
|
|
+ }
|
2021-06-23 20:40:09 +08:00
|
|
|
else
|
|
|
|
|
chain.doFilter(request, response);
|
|
|
|
|
}
|
|
|
|
|
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/ConcatServletTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/ConcatServletTest.java
|
2022-09-14 10:23:06 +08:00
|
|
|
index 3fcb9af..f8ea087 100644
|
2021-06-23 20:40:09 +08:00
|
|
|
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/ConcatServletTest.java
|
|
|
|
|
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/ConcatServletTest.java
|
2022-09-14 10:23:06 +08:00
|
|
|
@@ -1,6 +1,6 @@
|
|
|
|
|
//
|
|
|
|
|
// ========================================================================
|
|
|
|
|
-// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
|
|
|
|
|
+// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
|
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
|
// All rights reserved. This program and the accompanying materials
|
|
|
|
|
// are made available under the terms of the Eclipse Public License v1.0
|
|
|
|
|
@@ -18,11 +18,6 @@
|
|
|
|
|
|
|
|
|
|
package org.eclipse.jetty.servlets;
|
|
|
|
|
|
|
|
|
|
-import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
|
|
|
-import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
|
|
|
-import static org.junit.jupiter.api.Assertions.assertNull;
|
|
|
|
|
-import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
|
|
|
-
|
|
|
|
|
import java.io.BufferedReader;
|
|
|
|
|
import java.io.File;
|
|
|
|
|
import java.io.IOException;
|
|
|
|
|
@@ -31,7 +26,6 @@ import java.io.StringReader;
|
|
|
|
|
import java.nio.charset.StandardCharsets;
|
2021-06-23 20:40:09 +08:00
|
|
|
import java.nio.file.Files;
|
|
|
|
|
import java.nio.file.Path;
|
2022-09-14 10:23:06 +08:00
|
|
|
-
|
2021-06-23 20:40:09 +08:00
|
|
|
import javax.servlet.RequestDispatcher;
|
|
|
|
|
import javax.servlet.ServletException;
|
|
|
|
|
import javax.servlet.http.HttpServlet;
|
2022-09-14 10:23:06 +08:00
|
|
|
@@ -45,10 +39,14 @@ import org.eclipse.jetty.servlet.ServletHolder;
|
|
|
|
|
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
|
|
|
|
import org.eclipse.jetty.webapp.WebAppContext;
|
|
|
|
|
import org.junit.jupiter.api.AfterEach;
|
|
|
|
|
-
|
2021-06-23 20:40:09 +08:00
|
|
|
import org.junit.jupiter.api.BeforeEach;
|
|
|
|
|
import org.junit.jupiter.api.Test;
|
|
|
|
|
|
2022-09-14 10:23:06 +08:00
|
|
|
+import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
|
|
|
+import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
|
|
|
+import static org.junit.jupiter.api.Assertions.assertNull;
|
|
|
|
|
+import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
|
|
|
+
|
2021-06-23 20:40:09 +08:00
|
|
|
public class ConcatServletTest
|
|
|
|
|
{
|
|
|
|
|
private Server server;
|
2022-09-14 10:23:06 +08:00
|
|
|
@@ -92,8 +90,8 @@ public class ConcatServletTest
|
|
|
|
|
String resource1 = "/resource/one.js";
|
|
|
|
|
String resource2 = "/resource/two.js";
|
|
|
|
|
String uri = contextPath + concatPath + "?" + resource1 + "&" + resource2;
|
|
|
|
|
- String request = "" +
|
|
|
|
|
- "GET " + uri + " HTTP/1.1\r\n" +
|
|
|
|
|
+ String request =
|
|
|
|
|
+ "GET " + uri + " HTTP/1.1\r\n" +
|
2021-06-23 20:40:09 +08:00
|
|
|
"Host: localhost\r\n" +
|
2022-09-14 10:23:06 +08:00
|
|
|
"Connection: close\r\n" +
|
2021-06-23 20:40:09 +08:00
|
|
|
"\r\n";
|
2022-09-14 10:23:06 +08:00
|
|
|
@@ -139,8 +137,8 @@ public class ConcatServletTest
|
|
|
|
|
// Having a path segment and then ".." triggers a special case
|
|
|
|
|
// that the ConcatServlet must detect and avoid.
|
|
|
|
|
String uri = contextPath + concatPath + "?/trick/../WEB-INF/one.js";
|
|
|
|
|
- String request = "" +
|
|
|
|
|
- "GET " + uri + " HTTP/1.1\r\n" +
|
|
|
|
|
+ String request =
|
|
|
|
|
+ "GET " + uri + " HTTP/1.1\r\n" +
|
|
|
|
|
"Host: localhost\r\n" +
|
|
|
|
|
"Connection: close\r\n" +
|
|
|
|
|
"\r\n";
|
|
|
|
|
@@ -149,8 +147,8 @@ public class ConcatServletTest
|
2021-06-23 20:40:09 +08:00
|
|
|
|
2022-09-14 10:23:06 +08:00
|
|
|
// Make sure ConcatServlet behaves well if it's case insensitive.
|
|
|
|
|
uri = contextPath + concatPath + "?/trick/../web-inf/one.js";
|
2021-06-23 20:40:09 +08:00
|
|
|
- request = "" +
|
|
|
|
|
- "GET " + uri + " HTTP/1.1\r\n" +
|
2022-09-14 10:23:06 +08:00
|
|
|
+ request =
|
|
|
|
|
+ "GET " + uri + " HTTP/1.1\r\n" +
|
|
|
|
|
"Host: localhost\r\n" +
|
|
|
|
|
"Connection: close\r\n" +
|
|
|
|
|
"\r\n";
|
|
|
|
|
@@ -159,8 +157,8 @@ public class ConcatServletTest
|
2021-06-23 20:40:09 +08:00
|
|
|
|
2022-09-14 10:23:06 +08:00
|
|
|
// Make sure ConcatServlet behaves well if encoded.
|
|
|
|
|
uri = contextPath + concatPath + "?/trick/..%2FWEB-INF%2Fone.js";
|
2021-06-23 20:40:09 +08:00
|
|
|
- request = "" +
|
|
|
|
|
- "GET " + uri + " HTTP/1.1\r\n" +
|
2022-09-14 10:23:06 +08:00
|
|
|
+ request =
|
|
|
|
|
+ "GET " + uri + " HTTP/1.1\r\n" +
|
|
|
|
|
"Host: localhost\r\n" +
|
|
|
|
|
"Connection: close\r\n" +
|
|
|
|
|
"\r\n";
|
|
|
|
|
@@ -169,8 +167,8 @@ public class ConcatServletTest
|
2021-06-23 20:40:09 +08:00
|
|
|
|
2022-09-14 10:23:06 +08:00
|
|
|
// Make sure ConcatServlet cannot see file system files.
|
|
|
|
|
uri = contextPath + concatPath + "?/trick/../../" + directoryFile.getName();
|
2021-06-23 20:40:09 +08:00
|
|
|
- request = "" +
|
2022-09-14 10:23:06 +08:00
|
|
|
- "GET " + uri + " HTTP/1.1\r\n" +
|
|
|
|
|
+ request =
|
|
|
|
|
+ "GET " + uri + " HTTP/1.1\r\n" +
|
2021-06-23 20:40:09 +08:00
|
|
|
"Host: localhost\r\n" +
|
|
|
|
|
"Connection: close\r\n" +
|
|
|
|
|
"\r\n";
|
|
|
|
|
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/WelcomeFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/WelcomeFilterTest.java
|
|
|
|
|
new file mode 100644
|
|
|
|
|
index 0000000..65e6503
|
|
|
|
|
--- /dev/null
|
|
|
|
|
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/WelcomeFilterTest.java
|
|
|
|
|
@@ -0,0 +1,143 @@
|
|
|
|
|
+//
|
|
|
|
|
+// ========================================================================
|
|
|
|
|
+// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
|
|
|
|
|
+// ------------------------------------------------------------------------
|
|
|
|
|
+// All rights reserved. This program and the accompanying materials
|
|
|
|
|
+// are made available under the terms of the Eclipse Public License v1.0
|
|
|
|
|
+// and Apache License v2.0 which accompanies this distribution.
|
|
|
|
|
+//
|
|
|
|
|
+// The Eclipse Public License is available at
|
|
|
|
|
+// http://www.eclipse.org/legal/epl-v10.html
|
|
|
|
|
+//
|
|
|
|
|
+// The Apache License v2.0 is available at
|
|
|
|
|
+// http://www.opensource.org/licenses/apache2.0.php
|
|
|
|
|
+//
|
|
|
|
|
+// You may elect to redistribute this code under either of these licenses.
|
|
|
|
|
+// ========================================================================
|
|
|
|
|
+//
|
|
|
|
|
+
|
|
|
|
|
+package org.eclipse.jetty.servlets;
|
|
|
|
|
+
|
|
|
|
|
+import java.io.OutputStream;
|
|
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
|
|
+import java.nio.file.Files;
|
|
|
|
|
+import java.nio.file.Path;
|
|
|
|
|
+import java.util.EnumSet;
|
|
|
|
|
+import java.util.stream.Stream;
|
|
|
|
|
+import javax.servlet.DispatcherType;
|
|
|
|
|
+
|
|
|
|
|
+import org.eclipse.jetty.server.LocalConnector;
|
|
|
|
|
+import org.eclipse.jetty.server.Server;
|
|
|
|
|
+import org.eclipse.jetty.servlet.FilterHolder;
|
|
|
|
|
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
|
|
|
|
+import org.eclipse.jetty.webapp.WebAppContext;
|
|
|
|
|
+import org.junit.jupiter.api.AfterEach;
|
|
|
|
|
+import org.junit.jupiter.api.BeforeEach;
|
|
|
|
|
+import org.junit.jupiter.params.ParameterizedTest;
|
|
|
|
|
+import org.junit.jupiter.params.provider.Arguments;
|
|
|
|
|
+import org.junit.jupiter.params.provider.MethodSource;
|
|
|
|
|
+
|
|
|
|
|
+import static org.hamcrest.MatcherAssert.assertThat;
|
|
|
|
|
+import static org.hamcrest.Matchers.containsString;
|
|
|
|
|
+import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
|
|
|
+
|
|
|
|
|
+public class WelcomeFilterTest
|
|
|
|
|
+{
|
|
|
|
|
+ private Server server;
|
|
|
|
|
+ private LocalConnector connector;
|
|
|
|
|
+
|
|
|
|
|
+ @BeforeEach
|
|
|
|
|
+ public void prepareServer() throws Exception
|
|
|
|
|
+ {
|
|
|
|
|
+ server = new Server();
|
|
|
|
|
+ connector = new LocalConnector(server);
|
|
|
|
|
+ server.addConnector(connector);
|
|
|
|
|
+
|
|
|
|
|
+ Path directoryPath = MavenTestingUtils.getTargetTestingDir().toPath();
|
|
|
|
|
+ Files.createDirectories(directoryPath);
|
|
|
|
|
+ Path welcomeResource = directoryPath.resolve("welcome.html");
|
|
|
|
|
+ try (OutputStream output = Files.newOutputStream(welcomeResource))
|
|
|
|
|
+ {
|
|
|
|
|
+ output.write("<h1>welcome page</h1>".getBytes(StandardCharsets.UTF_8));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Path otherResource = directoryPath.resolve("other.html");
|
|
|
|
|
+ try (OutputStream output = Files.newOutputStream(otherResource))
|
|
|
|
|
+ {
|
|
|
|
|
+ output.write("<h1>other resource</h1>".getBytes(StandardCharsets.UTF_8));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Path hiddenDirectory = directoryPath.resolve("WEB-INF");
|
|
|
|
|
+ Files.createDirectories(hiddenDirectory);
|
|
|
|
|
+ Path hiddenResource = hiddenDirectory.resolve("one.js");
|
|
|
|
|
+ try (OutputStream output = Files.newOutputStream(hiddenResource))
|
|
|
|
|
+ {
|
|
|
|
|
+ output.write("CONFIDENTIAL".getBytes(StandardCharsets.UTF_8));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Path hiddenWelcome = hiddenDirectory.resolve("index.html");
|
|
|
|
|
+ try (OutputStream output = Files.newOutputStream(hiddenWelcome))
|
|
|
|
|
+ {
|
|
|
|
|
+ output.write("CONFIDENTIAL".getBytes(StandardCharsets.UTF_8));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ WebAppContext context = new WebAppContext(server, directoryPath.toString(), "/");
|
|
|
|
|
+ server.setHandler(context);
|
|
|
|
|
+ String concatPath = "/*";
|
|
|
|
|
+
|
|
|
|
|
+ FilterHolder filterHolder = new FilterHolder(new WelcomeFilter());
|
|
|
|
|
+ filterHolder.setInitParameter("welcome", "welcome.html");
|
|
|
|
|
+ context.addFilter(filterHolder, concatPath, EnumSet.of(DispatcherType.REQUEST));
|
|
|
|
|
+ server.start();
|
|
|
|
|
+
|
|
|
|
|
+ // Verify that I can get the file programmatically, as required by the spec.
|
|
|
|
|
+ assertNotNull(context.getServletContext().getResource("/WEB-INF/one.js"));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @AfterEach
|
|
|
|
|
+ public void destroy() throws Exception
|
|
|
|
|
+ {
|
|
|
|
|
+ if (server != null)
|
|
|
|
|
+ server.stop();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static Stream<Arguments> argumentsStream()
|
|
|
|
|
+ {
|
|
|
|
|
+ return Stream.of(
|
|
|
|
|
+ // Normal requests for the directory are redirected to the welcome page.
|
|
|
|
|
+ Arguments.of("/", new String[]{"HTTP/1.1 200 ", "<h1>welcome page</h1>"}),
|
|
|
|
|
+
|
|
|
|
|
+ // Try a normal resource (will bypass the filter).
|
|
|
|
|
+ Arguments.of("/other.html", new String[]{"HTTP/1.1 200 ", "<h1>other resource</h1>"}),
|
|
|
|
|
+
|
|
|
|
|
+ // Cannot access files in WEB-INF.
|
|
|
|
|
+ Arguments.of("/WEB-INF/one.js", new String[]{"HTTP/1.1 404 "}),
|
|
|
|
|
+
|
|
|
|
|
+ // Cannot serve welcome from WEB-INF.
|
|
|
|
|
+ Arguments.of("/WEB-INF/", new String[]{"HTTP/1.1 404 "}),
|
|
|
|
|
+
|
|
|
|
|
+ // Try to trick the filter into serving a protected resource.
|
|
|
|
|
+ Arguments.of("/WEB-INF/one.js#/", new String[]{"HTTP/1.1 404 "}),
|
|
|
|
|
+ Arguments.of("/js/../WEB-INF/one.js#/", new String[]{"HTTP/1.1 404 "}),
|
|
|
|
|
+
|
|
|
|
|
+ // Test the URI is not double decoded in the dispatcher.
|
|
|
|
|
+ Arguments.of("/%2557EB-INF/one.js%23/", new String[]{"HTTP/1.1 404 "})
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @ParameterizedTest
|
|
|
|
|
+ @MethodSource("argumentsStream")
|
|
|
|
|
+ public void testWelcomeFilter(String uri, String[] contains) throws Exception
|
|
|
|
|
+ {
|
|
|
|
|
+ String request =
|
|
|
|
|
+ "GET " + uri + " HTTP/1.1\r\n" +
|
|
|
|
|
+ "Host: localhost\r\n" +
|
|
|
|
|
+ "Connection: close\r\n" +
|
|
|
|
|
+ "\r\n";
|
|
|
|
|
+ String response = connector.getResponse(request);
|
|
|
|
|
+ for (String s : contains)
|
|
|
|
|
+ {
|
|
|
|
|
+ assertThat(response, containsString(s));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
diff --git a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppDefaultServletTest.java b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppDefaultServletTest.java
|
|
|
|
|
new file mode 100644
|
|
|
|
|
index 0000000..933bb7a
|
|
|
|
|
--- /dev/null
|
|
|
|
|
+++ b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppDefaultServletTest.java
|
|
|
|
|
@@ -0,0 +1,142 @@
|
|
|
|
|
+//
|
|
|
|
|
+// ========================================================================
|
|
|
|
|
+// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
|
|
|
|
|
+// ------------------------------------------------------------------------
|
|
|
|
|
+// All rights reserved. This program and the accompanying materials
|
|
|
|
|
+// are made available under the terms of the Eclipse Public License v1.0
|
|
|
|
|
+// and Apache License v2.0 which accompanies this distribution.
|
|
|
|
|
+//
|
|
|
|
|
+// The Eclipse Public License is available at
|
|
|
|
|
+// http://www.eclipse.org/legal/epl-v10.html
|
|
|
|
|
+//
|
|
|
|
|
+// The Apache License v2.0 is available at
|
|
|
|
|
+// http://www.opensource.org/licenses/apache2.0.php
|
|
|
|
|
+//
|
|
|
|
|
+// You may elect to redistribute this code under either of these licenses.
|
|
|
|
|
+// ========================================================================
|
|
|
|
|
+//
|
|
|
|
|
+
|
|
|
|
|
+package org.eclipse.jetty.webapp;
|
|
|
|
|
+
|
|
|
|
|
+import java.io.OutputStream;
|
|
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
|
|
+import java.nio.file.Files;
|
|
|
|
|
+import java.nio.file.Path;
|
|
|
|
|
+import java.util.stream.Stream;
|
|
|
|
|
+
|
|
|
|
|
+import org.eclipse.jetty.server.LocalConnector;
|
|
|
|
|
+import org.eclipse.jetty.server.Server;
|
|
|
|
|
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
|
|
|
|
+import org.eclipse.jetty.util.IO;
|
|
|
|
|
+import org.junit.jupiter.api.AfterEach;
|
|
|
|
|
+import org.junit.jupiter.api.BeforeEach;
|
|
|
|
|
+import org.junit.jupiter.params.ParameterizedTest;
|
|
|
|
|
+import org.junit.jupiter.params.provider.Arguments;
|
|
|
|
|
+import org.junit.jupiter.params.provider.MethodSource;
|
|
|
|
|
+
|
|
|
|
|
+import static org.hamcrest.MatcherAssert.assertThat;
|
|
|
|
|
+import static org.hamcrest.Matchers.containsString;
|
|
|
|
|
+import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
|
|
|
+
|
|
|
|
|
+public class WebAppDefaultServletTest
|
|
|
|
|
+{
|
|
|
|
|
+ private Server server;
|
|
|
|
|
+ private LocalConnector connector;
|
|
|
|
|
+
|
|
|
|
|
+ @BeforeEach
|
|
|
|
|
+ public void prepareServer() throws Exception
|
|
|
|
|
+ {
|
|
|
|
|
+ server = new Server();
|
|
|
|
|
+ connector = new LocalConnector(server);
|
|
|
|
|
+ server.addConnector(connector);
|
|
|
|
|
+
|
|
|
|
|
+ Path directoryPath = MavenTestingUtils.getTargetTestingDir().toPath();
|
|
|
|
|
+ IO.delete(directoryPath.toFile());
|
|
|
|
|
+ Files.createDirectories(directoryPath);
|
|
|
|
|
+ Path welcomeResource = directoryPath.resolve("index.html");
|
|
|
|
|
+ try (OutputStream output = Files.newOutputStream(welcomeResource))
|
|
|
|
|
+ {
|
|
|
|
|
+ output.write("<h1>welcome page</h1>".getBytes(StandardCharsets.UTF_8));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Path otherResource = directoryPath.resolve("other.html");
|
|
|
|
|
+ try (OutputStream output = Files.newOutputStream(otherResource))
|
|
|
|
|
+ {
|
|
|
|
|
+ output.write("<h1>other resource</h1>".getBytes(StandardCharsets.UTF_8));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Path hiddenDirectory = directoryPath.resolve("WEB-INF");
|
|
|
|
|
+ Files.createDirectories(hiddenDirectory);
|
|
|
|
|
+ Path hiddenResource = hiddenDirectory.resolve("one.js");
|
|
|
|
|
+ try (OutputStream output = Files.newOutputStream(hiddenResource))
|
|
|
|
|
+ {
|
|
|
|
|
+ output.write("this is confidential".getBytes(StandardCharsets.UTF_8));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Create directory to trick resource service.
|
|
|
|
|
+ Path hackPath = directoryPath.resolve("%57EB-INF/one.js#/");
|
|
|
|
|
+ Files.createDirectories(hackPath);
|
|
|
|
|
+ try (OutputStream output = Files.newOutputStream(hackPath.resolve("index.html")))
|
|
|
|
|
+ {
|
|
|
|
|
+ output.write("this content does not matter".getBytes(StandardCharsets.UTF_8));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Path standardHashDir = directoryPath.resolve("welcome#");
|
|
|
|
|
+ Files.createDirectories(standardHashDir);
|
|
|
|
|
+ try (OutputStream output = Files.newOutputStream(standardHashDir.resolve("index.html")))
|
|
|
|
|
+ {
|
|
|
|
|
+ output.write("standard hash dir welcome".getBytes(StandardCharsets.UTF_8));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ WebAppContext context = new WebAppContext(server, directoryPath.toString(), "/");
|
|
|
|
|
+ server.setHandler(context);
|
|
|
|
|
+ server.start();
|
|
|
|
|
+
|
|
|
|
|
+ // Verify that I can get the file programmatically, as required by the spec.
|
|
|
|
|
+ assertNotNull(context.getServletContext().getResource("/WEB-INF/one.js"));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @AfterEach
|
|
|
|
|
+ public void destroy() throws Exception
|
|
|
|
|
+ {
|
|
|
|
|
+ if (server != null)
|
|
|
|
|
+ server.stop();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static Stream<Arguments> argumentsStream()
|
|
|
|
|
+ {
|
|
|
|
|
+ return Stream.of(
|
|
|
|
|
+ Arguments.of("/WEB-INF/", new String[]{"HTTP/1.1 404 "}),
|
|
|
|
|
+ Arguments.of("/welcome%23/", new String[]{"HTTP/1.1 200 ", "standard hash dir welcome"}),
|
|
|
|
|
+
|
|
|
|
|
+ // Normal requests for the directory are redirected to the welcome page.
|
|
|
|
|
+ Arguments.of("/", new String[]{"HTTP/1.1 200 ", "<h1>welcome page</h1>"}),
|
|
|
|
|
+
|
|
|
|
|
+ // We can be served other resources.
|
|
|
|
|
+ Arguments.of("/other.html", new String[]{"HTTP/1.1 200 ", "<h1>other resource</h1>"}),
|
|
|
|
|
+
|
|
|
|
|
+ // The ContextHandler will filter these ones out as as WEB-INF is a protected target.
|
|
|
|
|
+ Arguments.of("/WEB-INF/one.js#/", new String[]{"HTTP/1.1 404 "}),
|
|
|
|
|
+ Arguments.of("/js/../WEB-INF/one.js#/", new String[]{"HTTP/1.1 404 "}),
|
|
|
|
|
+
|
|
|
|
|
+ // Test the URI is not double decoded by the dispatcher that serves the welcome file (we get index.html not one.js).
|
|
|
|
|
+ Arguments.of("/%2557EB-INF/one.js%23/", new String[]{"HTTP/1.1 200 ", "this content does not matter"})
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @ParameterizedTest
|
|
|
|
|
+ @MethodSource("argumentsStream")
|
|
|
|
|
+ public void testResourceService(String uri, String[] contains) throws Exception
|
|
|
|
|
+ {
|
|
|
|
|
+ String request =
|
|
|
|
|
+ "GET " + uri + " HTTP/1.1\r\n" +
|
|
|
|
|
+ "Host: localhost\r\n" +
|
|
|
|
|
+ "Connection: close\r\n" +
|
|
|
|
|
+ "\r\n";
|
|
|
|
|
+ String response = connector.getResponse(request);
|
|
|
|
|
+ for (String s : contains)
|
|
|
|
|
+ {
|
|
|
|
|
+ assertThat(response, containsString(s));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|