diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java index 2b998eaaccc..08f0c5047aa 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java @@ -1074,6 +1074,12 @@ public class ContextHandler extends Handler.Wrapper implements Attributes, Alias protected boolean handleByContextHandler(String pathInContext, ContextRequest request, Response response, Callback callback) { + if (isProtectedTarget(pathInContext)) + { + Response.writeError(request, response, callback, HttpStatus.NOT_FOUND_404, null); + return true; + } + return false; } diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletContextHandler.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletContextHandler.java index 8494ab7ff42..c7f07432d53 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletContextHandler.java +++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletContextHandler.java @@ -62,7 +62,6 @@ import jakarta.servlet.descriptor.JspPropertyGroupDescriptor; import jakarta.servlet.descriptor.TaglibDescriptor; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSessionActivationListener; import jakarta.servlet.http.HttpSessionAttributeListener; import jakarta.servlet.http.HttpSessionBindingListener; @@ -1191,12 +1190,8 @@ public class ServletContextHandler extends ContextHandler protected boolean handleByContextHandler(String pathInContext, ContextRequest request, Response response, Callback callback) { boolean initialDispatch = request instanceof ServletContextRequest; - if (initialDispatch && isProtectedTarget(pathInContext)) - { - Response.writeError(request, response, callback, HttpServletResponse.SC_NOT_FOUND, null); - return true; - } - + if (!initialDispatch) + return false; return super.handleByContextHandler(pathInContext, request, response, callback); } diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/java/org/eclipse/jetty/ee10/test/AliasCheckerSymlinkTest.java b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/java/org/eclipse/jetty/ee10/test/AliasCheckerSymlinkTest.java deleted file mode 100644 index c87274703e6..00000000000 --- a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/java/org/eclipse/jetty/ee10/test/AliasCheckerSymlinkTest.java +++ /dev/null @@ -1,324 +0,0 @@ -// -// ======================================================================== -// 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.eclipse.jetty.ee10.test; - -import java.io.File; -import java.io.IOException; -import java.net.URI; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Stream; - -import org.eclipse.jetty.client.ContentResponse; -import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.ee10.servlet.DefaultServlet; -import org.eclipse.jetty.ee10.servlet.ServletContextHandler; -import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.server.AliasCheck; -import org.eclipse.jetty.server.AllowedResourceAliasChecker; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.server.SymlinkAllowedResourceAliasChecker; -import org.eclipse.jetty.server.handler.ContextHandler; -import org.eclipse.jetty.server.handler.HotSwapHandler; -import org.eclipse.jetty.util.IO; -import org.eclipse.jetty.util.resource.Resource; -import org.eclipse.jetty.util.resource.ResourceFactory; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -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.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -public class AliasCheckerSymlinkTest -{ - private static Server _server; - private static ServerConnector _connector; - private static HttpClient _client; - private static HotSwapHandler _hotSwapHandler; - private static ServletContextHandler _context1; - private static ServletContextHandler _context2; - - private static final List _createdFiles = new ArrayList<>(); - - private static Path getResource(String path) throws Exception - { - URL url = AliasCheckerSymlinkTest.class.getClassLoader().getResource(path); - assertNotNull(url); - return new File(url.toURI()).toPath(); - } - - private static void delete(Path path) - { - IO.delete(path.toFile()); - } - - private static void setAliasChecker(ContextHandler contextHandler, AliasCheck aliasChecker) throws Exception - { - _hotSwapHandler.setHandler(contextHandler); - contextHandler.clearAliasChecks(); - if (aliasChecker != null) - contextHandler.addAliasCheck(aliasChecker); - } - - private static void createSymbolicLink(Path symlinkFile, Path target) throws IOException - { - delete(symlinkFile); - _createdFiles.add(symlinkFile); - Files.createSymbolicLink(symlinkFile, target).toFile().deleteOnExit(); - } - - @BeforeAll - public static void beforeAll() throws Exception - { - Path webRootPath = getResource("webroot"); - Path combinedPath = getResource("combined"); - - // Create symlink file that targets inside the webroot directory. - createSymbolicLink( - webRootPath.resolve("symlinkFile"), - webRootPath.resolve("file")); - - // Create symlink file that targets outside the webroot directory. - createSymbolicLink( - webRootPath.resolve("symlinkExternalFile"), - getResource("file")); - - // Symlink to a directory inside the webroot. - createSymbolicLink( - webRootPath.resolve("symlinkDir"), - webRootPath.resolve("documents")); - - // Symlink to a directory parent of the webroot. - createSymbolicLink( - webRootPath.resolve("symlinkParentDir"), - webRootPath.resolve("..")); - - // Symlink to a directory outside the webroot. - createSymbolicLink( - webRootPath.resolve("symlinkSiblingDir"), - webRootPath.resolve("../sibling")); - - // Symlink to the WEB-INF directory. - createSymbolicLink( - webRootPath.resolve("webInfSymlink"), - webRootPath.resolve("WEB-INF")); - - // Symlink file from the combined resource dir to the webroot. - createSymbolicLink( - combinedPath.resolve("combinedSymlinkFile"), - webRootPath.resolve("file")); - - // Symlink file from the combined resource dir to the webroot WEB-INF. - createSymbolicLink( - combinedPath.resolve("combinedWebInfSymlink"), - webRootPath.resolve("WEB-INF")); - - // Symlink file from the combined resource dir to outside the webroot. - createSymbolicLink( - combinedPath.resolve("externalCombinedSymlinkFile"), - webRootPath.resolve("../sibling")); - - - // Create and start Server and Client. - _server = new Server(); - _server.setDynamic(true); - _connector = new ServerConnector(_server); - _server.addConnector(_connector); - _hotSwapHandler = new HotSwapHandler(); - _server.setHandler(_hotSwapHandler); - - // Standard tests. - _context1 = new ServletContextHandler(); - _context1.setContextPath("/"); - _context1.setBaseResourceAsPath(webRootPath); - _context1.setWelcomeFiles(new String[]{"index.html"}); - _context1.setProtectedTargets(new String[]{"/WEB-INF", "/META-INF"}); - _context1.getMimeTypes().addMimeMapping("txt", "text/plain;charset=utf-8"); - _context1.addServlet(DefaultServlet.class, "/"); - _context1.clearAliasChecks(); - - // CombinedResource tests. - ResourceFactory resourceFactory = ResourceFactory.of(_server); - Resource resource = ResourceFactory.combine( - resourceFactory.newResource(webRootPath), - resourceFactory.newResource(getResource("combined"))); - _context2 = new ServletContextHandler(); - _context2.setContextPath("/"); - _context2.setBaseResource(resource); - _context2.setWelcomeFiles(new String[]{"index.html"}); - _context2.setProtectedTargets(new String[]{"/WEB-INF", "/META-INF"}); - _context2.getMimeTypes().addMimeMapping("txt", "text/plain;charset=utf-8"); - _context2.addServlet(DefaultServlet.class, "/"); - _context2.clearAliasChecks(); - - _server.start(); - _client = new HttpClient(); - _client.start(); - } - - @AfterAll - public static void afterAll() throws Exception - { - // Try to delete all files now so that the symlinks do not confuse other tests. - for (Path p : _createdFiles) - { - try - { - Files.delete(p); - } - catch (Throwable t) - { - // Ignored. - } - } - _createdFiles.clear(); - - _client.stop(); - _server.stop(); - } - - private static class ApproveAliases implements AliasCheck - { - @Override - public boolean checkAlias(String pathInContext, Resource resource) - { - return true; - } - } - - public static Stream testCases() - { - return testCases(_context1); - } - - public static Stream testCases(ContextHandler context) - { - AllowedResourceAliasChecker allowedResource = new AllowedResourceAliasChecker(context); - SymlinkAllowedResourceAliasChecker symlinkAllowedResource = new SymlinkAllowedResourceAliasChecker(context); - ApproveAliases approveAliases = new ApproveAliases(); - - return Stream.of( - // AllowedResourceAliasChecker that checks the target of symlinks. - Arguments.of(allowedResource, "/symlinkFile", HttpStatus.OK_200, "This file is inside webroot."), - Arguments.of(allowedResource, "/symlinkExternalFile", HttpStatus.NOT_FOUND_404, null), - Arguments.of(allowedResource, "/symlinkDir/file", HttpStatus.OK_200, "This file is inside webroot/documents."), - Arguments.of(allowedResource, "/symlinkParentDir/webroot/file", HttpStatus.OK_200, "This file is inside webroot."), - Arguments.of(allowedResource, "/symlinkParentDir/webroot/WEB-INF/web.xml", HttpStatus.NOT_FOUND_404, null), - Arguments.of(allowedResource, "/symlinkSiblingDir/file", HttpStatus.NOT_FOUND_404, null), - Arguments.of(allowedResource, "/webInfSymlink/web.xml", HttpStatus.NOT_FOUND_404, null), - - // SymlinkAllowedResourceAliasChecker that does not check the target of symlinks, but only approves files obtained through a symlink. - Arguments.of(symlinkAllowedResource, "/symlinkFile", HttpStatus.OK_200, "This file is inside webroot."), - Arguments.of(symlinkAllowedResource, "/symlinkExternalFile", HttpStatus.OK_200, "This file is outside webroot."), - Arguments.of(symlinkAllowedResource, "/symlinkDir/file", HttpStatus.OK_200, "This file is inside webroot/documents."), - Arguments.of(symlinkAllowedResource, "/symlinkParentDir/webroot/file", HttpStatus.OK_200, "This file is inside webroot."), - Arguments.of(symlinkAllowedResource, "/symlinkParentDir/webroot/WEB-INF/web.xml", HttpStatus.OK_200, "This is the web.xml file."), - Arguments.of(symlinkAllowedResource, "/symlinkSiblingDir/file", HttpStatus.OK_200, "This file is inside a sibling dir to webroot."), - Arguments.of(symlinkAllowedResource, "/webInfSymlink/web.xml", HttpStatus.OK_200, "This is the web.xml file."), - - // The ApproveAliases (approves everything regardless). - Arguments.of(approveAliases, "/symlinkFile", HttpStatus.OK_200, "This file is inside webroot."), - Arguments.of(approveAliases, "/symlinkExternalFile", HttpStatus.OK_200, "This file is outside webroot."), - Arguments.of(approveAliases, "/symlinkDir/file", HttpStatus.OK_200, "This file is inside webroot/documents."), - Arguments.of(approveAliases, "/symlinkParentDir/webroot/file", HttpStatus.OK_200, "This file is inside webroot."), - Arguments.of(approveAliases, "/symlinkParentDir/webroot/WEB-INF/web.xml", HttpStatus.OK_200, "This is the web.xml file."), - Arguments.of(approveAliases, "/symlinkSiblingDir/file", HttpStatus.OK_200, "This file is inside a sibling dir to webroot."), - Arguments.of(approveAliases, "/webInfSymlink/web.xml", HttpStatus.OK_200, "This is the web.xml file."), - - // No alias checker (any symlink should be an alias). - Arguments.of(null, "/symlinkFile", HttpStatus.NOT_FOUND_404, null), - Arguments.of(null, "/symlinkExternalFile", HttpStatus.NOT_FOUND_404, null), - Arguments.of(null, "/symlinkDir/file", HttpStatus.NOT_FOUND_404, null), - Arguments.of(null, "/symlinkParentDir/webroot/file", HttpStatus.NOT_FOUND_404, null), - Arguments.of(null, "/symlinkParentDir/webroot/WEB-INF/web.xml", HttpStatus.NOT_FOUND_404, null), - Arguments.of(null, "/symlinkSiblingDir/file", HttpStatus.NOT_FOUND_404, null), - Arguments.of(null, "/webInfSymlink/web.xml", HttpStatus.NOT_FOUND_404, null), - - // We should only be able to list contents of a symlinked directory if the alias checker is installed. - Arguments.of(null, "/symlinkDir", HttpStatus.NOT_FOUND_404, null), - Arguments.of(allowedResource, "/symlinkDir", HttpStatus.OK_200, null) - ); - } - - public static Stream combinedResourceTestCases() - { - AllowedResourceAliasChecker allowedResource = new AllowedResourceAliasChecker(_context2); - SymlinkAllowedResourceAliasChecker symlinkAllowedResource = new SymlinkAllowedResourceAliasChecker(_context2); - - Stream combinedResourceTests = Stream.of( - Arguments.of(allowedResource, "/file", HttpStatus.OK_200, "This file is inside webroot."), - Arguments.of(allowedResource, "/combinedFile", HttpStatus.OK_200, "This is a file in the combined resource dir."), - Arguments.of(allowedResource, "/WEB-INF/file", HttpStatus.NOT_FOUND_404, null), - Arguments.of(allowedResource, "/files", HttpStatus.OK_200, "Directory: /files/|/files/file1|/files/file2"), - Arguments.of(allowedResource, "/files/file1", HttpStatus.OK_200, "file1 from combined dir"), - Arguments.of(allowedResource, "/files/file2", HttpStatus.OK_200, "file1 from webroot"), - - Arguments.of(allowedResource, "/combinedSymlinkFile", HttpStatus.OK_200, "This file is inside webroot."), - Arguments.of(allowedResource, "/externalCombinedSymlinkFile/file", HttpStatus.NOT_FOUND_404, null), - Arguments.of(allowedResource, "/combinedWebInfSymlink/web.xml", HttpStatus.NOT_FOUND_404, null), - - Arguments.of(symlinkAllowedResource, "/combinedSymlinkFile", HttpStatus.OK_200, "This file is inside webroot."), - Arguments.of(symlinkAllowedResource, "/externalCombinedSymlinkFile/file", HttpStatus.OK_200, "This file is inside a sibling dir to webroot."), - Arguments.of(symlinkAllowedResource, "/combinedWebInfSymlink/web.xml", HttpStatus.OK_200, "This is the web.xml file.") - ); - return Stream.concat(testCases(_context2), combinedResourceTests); - } - - @ParameterizedTest - @MethodSource("testCases") - public void test(AliasCheck aliasChecker, String path, int httpStatus, String responseContent) throws Exception - { - setAliasChecker(_context1, aliasChecker); - URI uri = URI.create("http://localhost:" + _connector.getLocalPort() + path); - ContentResponse response = _client.GET(uri); - assertThat(response.getStatus(), is(httpStatus)); - if (responseContent != null) - assertThat(response.getContentAsString(), is(responseContent)); - } - - @ParameterizedTest - @MethodSource("combinedResourceTestCases") - public void testCombinedResource(AliasCheck aliasChecker, String path, int httpStatus, String responseContent) throws Exception - { - setAliasChecker(_context2, aliasChecker); - URI uri = URI.create("http://localhost:" + _connector.getLocalPort() + path); - ContentResponse response = _client.GET(uri); - assertThat(response.getStatus(), is(httpStatus)); - - if (responseContent != null) - { - if (responseContent.contains("|")) - { - for (String s : responseContent.split("\\|")) - { - assertThat("Could not find " + s, response.getContentAsString(), containsString(s)); - } - } - else - { - assertThat(response.getContentAsString(), equalTo(responseContent)); - } - } - } -} diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/altDir1/file1 b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/altDir1/file1 deleted file mode 100644 index a732ac0b9eb..00000000000 --- a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/altDir1/file1 +++ /dev/null @@ -1 +0,0 @@ -file 1 contents \ No newline at end of file diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/altDir2/file2 b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/altDir2/file2 deleted file mode 100644 index 211e2282c76..00000000000 --- a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/altDir2/file2 +++ /dev/null @@ -1 +0,0 @@ -file 2 contents \ No newline at end of file diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/sibling/file b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/sibling/file deleted file mode 100644 index f57b0b89d7a..00000000000 --- a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/sibling/file +++ /dev/null @@ -1 +0,0 @@ -This file is inside a sibling dir to webroot. \ No newline at end of file diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/webroot/WEB-INF/web.xml b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/webroot/WEB-INF/web.xml deleted file mode 100644 index 47d79144910..00000000000 --- a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/webroot/WEB-INF/web.xml +++ /dev/null @@ -1 +0,0 @@ -This is the web.xml file. \ No newline at end of file diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/webroot/documents/file b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/webroot/documents/file deleted file mode 100644 index b4508805362..00000000000 --- a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/webroot/documents/file +++ /dev/null @@ -1 +0,0 @@ -This file is inside webroot/documents. \ No newline at end of file diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/webroot/file b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/webroot/file deleted file mode 100644 index 7a16a16aa08..00000000000 --- a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/webroot/file +++ /dev/null @@ -1 +0,0 @@ -This file is inside webroot. \ No newline at end of file diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/webroot/index.html b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/webroot/index.html deleted file mode 100644 index f7dc59cdc6b..00000000000 --- a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/webroot/index.html +++ /dev/null @@ -1,4 +0,0 @@ - -

hello world

-

body of index.html

- \ No newline at end of file diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-integration/src/test/java/org/eclipse/jetty/ee9/test/AliasCheckerSymlinkTest.java b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-integration/src/test/java/org/eclipse/jetty/ee9/test/AliasCheckerSymlinkTest.java index dd33170a0ce..7ea416c523f 100644 --- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-integration/src/test/java/org/eclipse/jetty/ee9/test/AliasCheckerSymlinkTest.java +++ b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-integration/src/test/java/org/eclipse/jetty/ee9/test/AliasCheckerSymlinkTest.java @@ -14,10 +14,13 @@ package org.eclipse.jetty.ee9.test; import java.io.File; +import java.io.IOException; import java.net.URI; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; import java.util.stream.Stream; import org.eclipse.jetty.client.ContentResponse; @@ -30,8 +33,10 @@ import org.eclipse.jetty.server.AllowedResourceAliasChecker; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.SymlinkAllowedResourceAliasChecker; +import org.eclipse.jetty.server.handler.HotSwapHandler; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.resource.ResourceFactory; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.params.ParameterizedTest; @@ -39,6 +44,8 @@ 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.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -47,15 +54,11 @@ public class AliasCheckerSymlinkTest private static Server _server; private static ServerConnector _connector; private static HttpClient _client; - private static ServletContextHandler _context; + private static HotSwapHandler _hotSwapHandler; + private static ServletContextHandler _context1; + private static ServletContextHandler _context2; - private static Path _symlinkFile; - private static Path _symlinkExternalFile; - private static Path _symlinkDir; - private static Path _symlinkParentDir; - private static Path _symlinkSiblingDir; - private static Path _webInfSymlink; - private static Path _webrootSymlink; + private static final List _createdFiles = new ArrayList<>(); private static Path getResource(String path) throws Exception { @@ -69,69 +72,102 @@ public class AliasCheckerSymlinkTest IO.delete(path.toFile()); } - private static void setAliasChecker(AliasCheck aliasChecker) + private static void setAliasChecker(ServletContextHandler contextHandler, AliasCheck aliasChecker) throws Exception { - _context.clearAliasChecks(); + _hotSwapHandler.setHandler(contextHandler); + contextHandler.clearAliasChecks(); if (aliasChecker != null) - _context.addAliasCheck(aliasChecker); + contextHandler.addAliasCheck(aliasChecker); + } + + private static void createSymbolicLink(Path symlinkFile, Path target) throws IOException + { + delete(symlinkFile); + _createdFiles.add(symlinkFile); + Files.createSymbolicLink(symlinkFile, target).toFile().deleteOnExit(); } @BeforeAll public static void beforeAll() throws Exception { Path webRootPath = getResource("webroot"); - Path fileInWebroot = webRootPath.resolve("file"); + Path combinedPath = getResource("combined"); // Create symlink file that targets inside the webroot directory. - _symlinkFile = webRootPath.resolve("symlinkFile"); - delete(_symlinkFile); - Files.createSymbolicLink(_symlinkFile, fileInWebroot).toFile().deleteOnExit(); + createSymbolicLink( + webRootPath.resolve("symlinkFile"), + webRootPath.resolve("file")); // Create symlink file that targets outside the webroot directory. - _symlinkExternalFile = webRootPath.resolve("symlinkExternalFile"); - delete(_symlinkExternalFile); - Files.createSymbolicLink(_symlinkExternalFile, getResource("file")).toFile().deleteOnExit(); + createSymbolicLink( + webRootPath.resolve("symlinkExternalFile"), + getResource("file")); - // Symlink to a directory inside of the webroot. - _symlinkDir = webRootPath.resolve("symlinkDir"); - delete(_symlinkDir); - Files.createSymbolicLink(_symlinkDir, webRootPath.resolve("documents")).toFile().deleteOnExit(); + // Symlink to a directory inside the webroot. + createSymbolicLink( + webRootPath.resolve("symlinkDir"), + webRootPath.resolve("documents")); // Symlink to a directory parent of the webroot. - _symlinkParentDir = webRootPath.resolve("symlinkParentDir"); - delete(_symlinkParentDir); - Files.createSymbolicLink(_symlinkParentDir, webRootPath.resolve("..")).toFile().deleteOnExit(); + createSymbolicLink( + webRootPath.resolve("symlinkParentDir"), + webRootPath.resolve("..")); - // Symlink to a directory outside of the webroot. - _symlinkSiblingDir = webRootPath.resolve("symlinkSiblingDir"); - delete(_symlinkSiblingDir); - Files.createSymbolicLink(_symlinkSiblingDir, webRootPath.resolve("../sibling")).toFile().deleteOnExit(); + // Symlink to a directory outside the webroot. + createSymbolicLink( + webRootPath.resolve("symlinkSiblingDir"), + webRootPath.resolve("../sibling")); // Symlink to the WEB-INF directory. - _webInfSymlink = webRootPath.resolve("webInfSymlink"); - delete(_webInfSymlink); - Files.createSymbolicLink(_webInfSymlink, webRootPath.resolve("WEB-INF")).toFile().deleteOnExit(); + createSymbolicLink( + webRootPath.resolve("webInfSymlink"), + webRootPath.resolve("WEB-INF")); + + // Symlink file from the combined resource dir to the webroot. + createSymbolicLink( + combinedPath.resolve("combinedSymlinkFile"), + webRootPath.resolve("file")); + + // Symlink file from the combined resource dir to the webroot WEB-INF. + createSymbolicLink( + combinedPath.resolve("combinedWebInfSymlink"), + webRootPath.resolve("WEB-INF")); + + // Symlink file from the combined resource dir to outside the webroot. + createSymbolicLink( + combinedPath.resolve("externalCombinedSymlinkFile"), + webRootPath.resolve("../sibling")); - // External symlink to webroot. - _webrootSymlink = webRootPath.resolve("../webrootSymlink"); - delete(_webrootSymlink); - Files.createSymbolicLink(_webrootSymlink, webRootPath).toFile().deleteOnExit(); // Create and start Server and Client. _server = new Server(); + _server.setDynamic(true); _connector = new ServerConnector(_server); _server.addConnector(_connector); - _context = new ServletContextHandler(); - _context.setContextPath("/"); - _context.setBaseResourceAsPath(webRootPath); - _context.setWelcomeFiles(new String[]{"index.html"}); - _context.setProtectedTargets(new String[]{"/WEB-INF", "/META-INF"}); - _context.getMimeTypes().addMimeMapping("txt", "text/plain;charset=utf-8"); - _server.setHandler(_context); - _context.addServlet(DefaultServlet.class, "/"); - _context.clearAliasChecks(); - _server.start(); + _hotSwapHandler = new HotSwapHandler(); + _server.setHandler(_hotSwapHandler); + // Standard tests. + _context1 = new ServletContextHandler(); + _context1.setContextPath("/"); + _context1.setBaseResourceAsPath(webRootPath); + _context1.setProtectedTargets(new String[]{"/WEB-INF", "/META-INF"}); + _context1.addServlet(DefaultServlet.class, "/"); + _context1.clearAliasChecks(); + + // CombinedResource tests. + ResourceFactory resourceFactory = ResourceFactory.of(_server); + Resource resource = ResourceFactory.combine( + resourceFactory.newResource(webRootPath), + resourceFactory.newResource(getResource("combined"))); + _context2 = new ServletContextHandler(); + _context2.setContextPath("/"); + _context2.setBaseResource(resource); + _context2.setProtectedTargets(new String[]{"/WEB-INF", "/META-INF"}); + _context2.addServlet(DefaultServlet.class, "/"); + _context2.clearAliasChecks(); + + _server.start(); _client = new HttpClient(); _client.start(); } @@ -140,13 +176,18 @@ public class AliasCheckerSymlinkTest public static void afterAll() throws Exception { // Try to delete all files now so that the symlinks do not confuse other tests. - Files.delete(_symlinkFile); - Files.delete(_symlinkExternalFile); - Files.delete(_symlinkDir); - Files.delete(_symlinkParentDir); - Files.delete(_symlinkSiblingDir); - Files.delete(_webInfSymlink); - Files.delete(_webrootSymlink); + for (Path p : _createdFiles) + { + try + { + Files.delete(p); + } + catch (Throwable t) + { + // Ignored. + } + } + _createdFiles.clear(); _client.stop(); _server.stop(); @@ -160,61 +201,119 @@ public class AliasCheckerSymlinkTest return true; } } - + public static Stream testCases() { - AllowedResourceAliasChecker allowedResource = new AllowedResourceAliasChecker(_context.getCoreContextHandler()); - SymlinkAllowedResourceAliasChecker symlinkAllowedResource = new SymlinkAllowedResourceAliasChecker(_context.getCoreContextHandler()); + return testCases(_context1); + } + + public static Stream testCases(ServletContextHandler context) + { + AllowedResourceAliasChecker allowedResource = new AllowedResourceAliasChecker(context.getCoreContextHandler()); + SymlinkAllowedResourceAliasChecker symlinkAllowedResource = new SymlinkAllowedResourceAliasChecker(context.getCoreContextHandler()); ApproveAliases approveAliases = new ApproveAliases(); return Stream.of( - // AllowedResourceAliasChecker that checks the target of symlinks. - Arguments.of(allowedResource, "/symlinkFile", HttpStatus.OK_200, "This file is inside webroot."), - Arguments.of(allowedResource, "/symlinkExternalFile", HttpStatus.NOT_FOUND_404, null), - Arguments.of(allowedResource, "/symlinkDir/file", HttpStatus.OK_200, "This file is inside webroot/documents."), - Arguments.of(allowedResource, "/symlinkParentDir/webroot/file", HttpStatus.OK_200, "This file is inside webroot."), - Arguments.of(allowedResource, "/symlinkParentDir/webroot/WEB-INF/web.xml", HttpStatus.NOT_FOUND_404, null), - Arguments.of(allowedResource, "/symlinkSiblingDir/file", HttpStatus.NOT_FOUND_404, null), - Arguments.of(allowedResource, "/webInfSymlink/web.xml", HttpStatus.NOT_FOUND_404, null), + // AllowedResourceAliasChecker that checks the target of symlinks. + Arguments.of(allowedResource, "/symlinkFile", HttpStatus.OK_200, "This file is inside webroot."), + Arguments.of(allowedResource, "/symlinkExternalFile", HttpStatus.NOT_FOUND_404, null), + Arguments.of(allowedResource, "/symlinkDir/file", HttpStatus.OK_200, "This file is inside webroot/documents."), + Arguments.of(allowedResource, "/symlinkParentDir/webroot/file", HttpStatus.OK_200, "This file is inside webroot."), + Arguments.of(allowedResource, "/symlinkParentDir/webroot/WEB-INF/web.xml", HttpStatus.NOT_FOUND_404, null), + Arguments.of(allowedResource, "/symlinkSiblingDir/file", HttpStatus.NOT_FOUND_404, null), + Arguments.of(allowedResource, "/webInfSymlink/web.xml", HttpStatus.NOT_FOUND_404, null), - // SymlinkAllowedResourceAliasChecker that does not check the target of symlinks, but only approves files obtained through a symlink. - Arguments.of(symlinkAllowedResource, "/symlinkFile", HttpStatus.OK_200, "This file is inside webroot."), - Arguments.of(symlinkAllowedResource, "/symlinkExternalFile", HttpStatus.OK_200, "This file is outside webroot."), - Arguments.of(symlinkAllowedResource, "/symlinkDir/file", HttpStatus.OK_200, "This file is inside webroot/documents."), - Arguments.of(symlinkAllowedResource, "/symlinkParentDir/webroot/file", HttpStatus.OK_200, "This file is inside webroot."), - Arguments.of(symlinkAllowedResource, "/symlinkParentDir/webroot/WEB-INF/web.xml", HttpStatus.OK_200, "This is the web.xml file."), - Arguments.of(symlinkAllowedResource, "/symlinkSiblingDir/file", HttpStatus.OK_200, "This file is inside a sibling dir to webroot."), - Arguments.of(symlinkAllowedResource, "/webInfSymlink/web.xml", HttpStatus.OK_200, "This is the web.xml file."), + // SymlinkAllowedResourceAliasChecker that does not check the target of symlinks, but only approves files obtained through a symlink. + Arguments.of(symlinkAllowedResource, "/symlinkFile", HttpStatus.OK_200, "This file is inside webroot."), + Arguments.of(symlinkAllowedResource, "/symlinkExternalFile", HttpStatus.OK_200, "This file is outside webroot."), + Arguments.of(symlinkAllowedResource, "/symlinkDir/file", HttpStatus.OK_200, "This file is inside webroot/documents."), + Arguments.of(symlinkAllowedResource, "/symlinkParentDir/webroot/file", HttpStatus.OK_200, "This file is inside webroot."), + Arguments.of(symlinkAllowedResource, "/symlinkParentDir/webroot/WEB-INF/web.xml", HttpStatus.OK_200, "This is the web.xml file."), + Arguments.of(symlinkAllowedResource, "/symlinkSiblingDir/file", HttpStatus.OK_200, "This file is inside a sibling dir to webroot."), + Arguments.of(symlinkAllowedResource, "/webInfSymlink/web.xml", HttpStatus.OK_200, "This is the web.xml file."), - // The ApproveAliases (approves everything regardless). - Arguments.of(approveAliases, "/symlinkFile", HttpStatus.OK_200, "This file is inside webroot."), - Arguments.of(approveAliases, "/symlinkExternalFile", HttpStatus.OK_200, "This file is outside webroot."), - Arguments.of(approveAliases, "/symlinkDir/file", HttpStatus.OK_200, "This file is inside webroot/documents."), - Arguments.of(approveAliases, "/symlinkParentDir/webroot/file", HttpStatus.OK_200, "This file is inside webroot."), - Arguments.of(approveAliases, "/symlinkParentDir/webroot/WEB-INF/web.xml", HttpStatus.OK_200, "This is the web.xml file."), - Arguments.of(approveAliases, "/symlinkSiblingDir/file", HttpStatus.OK_200, "This file is inside a sibling dir to webroot."), - Arguments.of(approveAliases, "/webInfSymlink/web.xml", HttpStatus.OK_200, "This is the web.xml file."), + // The ApproveAliases (approves everything regardless). + Arguments.of(approveAliases, "/symlinkFile", HttpStatus.OK_200, "This file is inside webroot."), + Arguments.of(approveAliases, "/symlinkExternalFile", HttpStatus.OK_200, "This file is outside webroot."), + Arguments.of(approveAliases, "/symlinkDir/file", HttpStatus.OK_200, "This file is inside webroot/documents."), + Arguments.of(approveAliases, "/symlinkParentDir/webroot/file", HttpStatus.OK_200, "This file is inside webroot."), + Arguments.of(approveAliases, "/symlinkParentDir/webroot/WEB-INF/web.xml", HttpStatus.OK_200, "This is the web.xml file."), + Arguments.of(approveAliases, "/symlinkSiblingDir/file", HttpStatus.OK_200, "This file is inside a sibling dir to webroot."), + Arguments.of(approveAliases, "/webInfSymlink/web.xml", HttpStatus.OK_200, "This is the web.xml file."), - // No alias checker (any symlink should be an alias). - Arguments.of(null, "/symlinkFile", HttpStatus.NOT_FOUND_404, null), - Arguments.of(null, "/symlinkExternalFile", HttpStatus.NOT_FOUND_404, null), - Arguments.of(null, "/symlinkDir/file", HttpStatus.NOT_FOUND_404, null), - Arguments.of(null, "/symlinkParentDir/webroot/file", HttpStatus.NOT_FOUND_404, null), - Arguments.of(null, "/symlinkParentDir/webroot/WEB-INF/web.xml", HttpStatus.NOT_FOUND_404, null), - Arguments.of(null, "/symlinkSiblingDir/file", HttpStatus.NOT_FOUND_404, null), - Arguments.of(null, "/webInfSymlink/web.xml", HttpStatus.NOT_FOUND_404, null) + // No alias checker (any symlink should be an alias). + Arguments.of(null, "/symlinkFile", HttpStatus.NOT_FOUND_404, null), + Arguments.of(null, "/symlinkExternalFile", HttpStatus.NOT_FOUND_404, null), + Arguments.of(null, "/symlinkDir/file", HttpStatus.NOT_FOUND_404, null), + Arguments.of(null, "/symlinkParentDir/webroot/file", HttpStatus.NOT_FOUND_404, null), + Arguments.of(null, "/symlinkParentDir/webroot/WEB-INF/web.xml", HttpStatus.NOT_FOUND_404, null), + Arguments.of(null, "/symlinkSiblingDir/file", HttpStatus.NOT_FOUND_404, null), + Arguments.of(null, "/webInfSymlink/web.xml", HttpStatus.NOT_FOUND_404, null), + + // We should only be able to list contents of a symlinked directory if the alias checker is installed. + Arguments.of(null, "/symlinkDir", HttpStatus.NOT_FOUND_404, null), + Arguments.of(allowedResource, "/symlinkDir", HttpStatus.OK_200, null) ); } + public static Stream combinedResourceTestCases() + { + AllowedResourceAliasChecker allowedResource = new AllowedResourceAliasChecker(_context2.getCoreContextHandler()); + SymlinkAllowedResourceAliasChecker symlinkAllowedResource = new SymlinkAllowedResourceAliasChecker(_context2.getCoreContextHandler()); + + Stream combinedResourceTests = Stream.of( + Arguments.of(allowedResource, "/file", HttpStatus.OK_200, "This file is inside webroot."), + Arguments.of(allowedResource, "/combinedFile", HttpStatus.OK_200, "This is a file in the combined resource dir."), + Arguments.of(allowedResource, "/WEB-INF/file", HttpStatus.NOT_FOUND_404, null), + Arguments.of(allowedResource, "/files", HttpStatus.OK_200, "Directory: /files/|/files/file1|/files/file2"), + Arguments.of(allowedResource, "/files/file1", HttpStatus.OK_200, "file1 from combined dir"), + Arguments.of(allowedResource, "/files/file2", HttpStatus.OK_200, "file1 from webroot"), + + Arguments.of(allowedResource, "/combinedSymlinkFile", HttpStatus.OK_200, "This file is inside webroot."), + Arguments.of(allowedResource, "/externalCombinedSymlinkFile/file", HttpStatus.NOT_FOUND_404, null), + Arguments.of(allowedResource, "/combinedWebInfSymlink/web.xml", HttpStatus.NOT_FOUND_404, null), + + Arguments.of(symlinkAllowedResource, "/combinedSymlinkFile", HttpStatus.OK_200, "This file is inside webroot."), + Arguments.of(symlinkAllowedResource, "/externalCombinedSymlinkFile/file", HttpStatus.OK_200, "This file is inside a sibling dir to webroot."), + Arguments.of(symlinkAllowedResource, "/combinedWebInfSymlink/web.xml", HttpStatus.OK_200, "This is the web.xml file.") + ); + return Stream.concat(testCases(_context2), combinedResourceTests); + } + @ParameterizedTest @MethodSource("testCases") public void test(AliasCheck aliasChecker, String path, int httpStatus, String responseContent) throws Exception { - setAliasChecker(aliasChecker); + setAliasChecker(_context1, aliasChecker); URI uri = URI.create("http://localhost:" + _connector.getLocalPort() + path); ContentResponse response = _client.GET(uri); assertThat(response.getStatus(), is(httpStatus)); if (responseContent != null) assertThat(response.getContentAsString(), is(responseContent)); } + + @ParameterizedTest + @MethodSource("combinedResourceTestCases") + public void testCombinedResource(AliasCheck aliasChecker, String path, int httpStatus, String responseContent) throws Exception + { + setAliasChecker(_context2, aliasChecker); + URI uri = URI.create("http://localhost:" + _connector.getLocalPort() + path); + ContentResponse response = _client.GET(uri); + assertThat(response.getStatus(), is(httpStatus)); + + if (responseContent != null) + { + if (responseContent.contains("|")) + { + for (String s : responseContent.split("\\|")) + { + assertThat("Could not find " + s, response.getContentAsString(), containsString(s)); + } + } + else + { + assertThat(response.getContentAsString(), equalTo(responseContent)); + } + } + } } diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/combined/WEB-INF/file b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-integration/src/test/resources/combined/WEB-INF/file similarity index 100% rename from jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/combined/WEB-INF/file rename to jetty-ee9/jetty-ee9-tests/jetty-ee9-test-integration/src/test/resources/combined/WEB-INF/file diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/combined/combinedFile b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-integration/src/test/resources/combined/combinedFile similarity index 100% rename from jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/combined/combinedFile rename to jetty-ee9/jetty-ee9-tests/jetty-ee9-test-integration/src/test/resources/combined/combinedFile diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/combined/files/file1 b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-integration/src/test/resources/combined/files/file1 similarity index 100% rename from jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/combined/files/file1 rename to jetty-ee9/jetty-ee9-tests/jetty-ee9-test-integration/src/test/resources/combined/files/file1 diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/webroot/files/file2 b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-integration/src/test/resources/webroot/files/file2 similarity index 100% rename from jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/webroot/files/file2 rename to jetty-ee9/jetty-ee9-tests/jetty-ee9-test-integration/src/test/resources/webroot/files/file2 diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/AliasCheckerSymlinkTest.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/AliasCheckerSymlinkTest.java index ac32d12ba69..7ecc03ffdd2 100644 --- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/AliasCheckerSymlinkTest.java +++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/AliasCheckerSymlinkTest.java @@ -14,10 +14,13 @@ package org.eclipse.jetty.test; import java.io.File; +import java.io.IOException; import java.net.URI; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; import java.util.stream.Stream; import org.eclipse.jetty.client.ContentResponse; @@ -29,9 +32,11 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.SymlinkAllowedResourceAliasChecker; import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.server.handler.HotSwapHandler; import org.eclipse.jetty.server.handler.ResourceHandler; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.resource.ResourceFactory; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.params.ParameterizedTest; @@ -39,6 +44,8 @@ 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.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -47,16 +54,11 @@ public class AliasCheckerSymlinkTest private static Server _server; private static ServerConnector _connector; private static HttpClient _client; - private static ContextHandler _context; + private static HotSwapHandler _hotSwapHandler; + private static ContextHandler _context1; + private static ContextHandler _context2; - private static Path _symlinkFile; - private static Path _symlinkExternalFile; - private static Path _symlinkDir; - private static Path _symlinkParentDir; - private static Path _symlinkSiblingDir; - private static Path _webInfSymlink; - private static Path _webrootSymlink; - private static Path _protectedFile; + private static final List _createdFiles = new ArrayList<>(); private static Path getResource(String path) throws Exception { @@ -70,73 +72,102 @@ public class AliasCheckerSymlinkTest IO.delete(path.toFile()); } - private static void setAliasChecker(AliasCheck aliasChecker) + private static void setAliasChecker(ContextHandler contextHandler, AliasCheck aliasChecker) throws Exception { - _context.clearAliasChecks(); + _hotSwapHandler.setHandler(contextHandler); + contextHandler.clearAliasChecks(); if (aliasChecker != null) - _context.addAliasCheck(aliasChecker); + contextHandler.addAliasCheck(aliasChecker); + } + + private static void createSymbolicLink(Path symlinkFile, Path target) throws IOException + { + delete(symlinkFile); + _createdFiles.add(symlinkFile); + Files.createSymbolicLink(symlinkFile, target).toFile().deleteOnExit(); } @BeforeAll public static void beforeAll() throws Exception { Path webRootPath = getResource("webroot"); - Path fileInWebroot = webRootPath.resolve("file"); + Path combinedPath = getResource("combined"); // Create symlink file that targets inside the webroot directory. - _symlinkFile = webRootPath.resolve("symlinkFile"); - delete(_symlinkFile); - Files.createSymbolicLink(_symlinkFile, fileInWebroot).toFile().deleteOnExit(); + createSymbolicLink( + webRootPath.resolve("symlinkFile"), + webRootPath.resolve("file")); // Create symlink file that targets outside the webroot directory. - _symlinkExternalFile = webRootPath.resolve("symlinkExternalFile"); - delete(_symlinkExternalFile); - Files.createSymbolicLink(_symlinkExternalFile, getResource("file")).toFile().deleteOnExit(); + createSymbolicLink( + webRootPath.resolve("symlinkExternalFile"), + getResource("file")); - // Symlink to a directory inside of the webroot. - _symlinkDir = webRootPath.resolve("symlinkDir"); - delete(_symlinkDir); - Files.createSymbolicLink(_symlinkDir, webRootPath.resolve("documents")).toFile().deleteOnExit(); + // Symlink to a directory inside the webroot. + createSymbolicLink( + webRootPath.resolve("symlinkDir"), + webRootPath.resolve("documents")); // Symlink to a directory parent of the webroot. - _symlinkParentDir = webRootPath.resolve("symlinkParentDir"); - delete(_symlinkParentDir); - Files.createSymbolicLink(_symlinkParentDir, webRootPath.resolve("..")).toFile().deleteOnExit(); + createSymbolicLink( + webRootPath.resolve("symlinkParentDir"), + webRootPath.resolve("..")); - // Symlink to a directory outside of the webroot. - _symlinkSiblingDir = webRootPath.resolve("symlinkSiblingDir"); - delete(_symlinkSiblingDir); - Files.createSymbolicLink(_symlinkSiblingDir, webRootPath.resolve("../sibling")).toFile().deleteOnExit(); + // Symlink to a directory outside the webroot. + createSymbolicLink( + webRootPath.resolve("symlinkSiblingDir"), + webRootPath.resolve("../sibling")); // Symlink to the WEB-INF directory. - _webInfSymlink = webRootPath.resolve("webInfSymlink"); - delete(_webInfSymlink); - Files.createSymbolicLink(_webInfSymlink, webRootPath.resolve("WEB-INF")).toFile().deleteOnExit(); + createSymbolicLink( + webRootPath.resolve("webInfSymlink"), + webRootPath.resolve("WEB-INF")); - // External symlink to webroot. - _webrootSymlink = webRootPath.resolve("../webrootSymlink"); - delete(_webrootSymlink); - Files.createSymbolicLink(_webrootSymlink, webRootPath).toFile().deleteOnExit(); + // Symlink file from the combined resource dir to the webroot. + createSymbolicLink( + combinedPath.resolve("combinedSymlinkFile"), + webRootPath.resolve("file")); + + // Symlink file from the combined resource dir to the webroot WEB-INF. + createSymbolicLink( + combinedPath.resolve("combinedWebInfSymlink"), + webRootPath.resolve("WEB-INF")); + + // Symlink file from the combined resource dir to outside the webroot. + createSymbolicLink( + combinedPath.resolve("externalCombinedSymlinkFile"), + webRootPath.resolve("../sibling")); - // PROTECTED is a symlink to a file outside webroot. - _protectedFile = webRootPath.resolve("PROTECTED"); - IO.delete(_protectedFile); - Files.createSymbolicLink(_protectedFile, webRootPath.resolve("../sibling/file")).toFile().deleteOnExit(); // Create and start Server and Client. _server = new Server(); + _server.setDynamic(true); _connector = new ServerConnector(_server); _server.addConnector(_connector); - _context = new ContextHandler(); - _context.setContextPath("/"); - _context.setBaseResourceAsPath(webRootPath); - _context.setProtectedTargets(new String[]{"/WEB-INF", "/META-INF", "/PROTECTED"}); - _context.setHandler(new ResourceHandler()); + _hotSwapHandler = new HotSwapHandler(); + _server.setHandler(_hotSwapHandler); + + // Standard tests. + _context1 = new ContextHandler(); + _context1.setContextPath("/"); + _context1.setBaseResourceAsPath(webRootPath); + _context1.setProtectedTargets(new String[]{"/WEB-INF", "/META-INF"}); + _context1.setHandler(new ResourceHandler()); + _context1.clearAliasChecks(); + + // CombinedResource tests. + ResourceFactory resourceFactory = ResourceFactory.of(_server); + Resource resource = ResourceFactory.combine( + resourceFactory.newResource(webRootPath), + resourceFactory.newResource(getResource("combined"))); + _context2 = new ContextHandler(); + _context2.setContextPath("/"); + _context2.setBaseResource(resource); + _context2.setProtectedTargets(new String[]{"/WEB-INF", "/META-INF"}); + _context2.setHandler(new ResourceHandler()); + _context2.clearAliasChecks(); - _server.setHandler(_context); - _context.clearAliasChecks(); _server.start(); - _client = new HttpClient(); _client.start(); } @@ -145,14 +176,18 @@ public class AliasCheckerSymlinkTest public static void afterAll() throws Exception { // Try to delete all files now so that the symlinks do not confuse other tests. - Files.delete(_symlinkFile); - Files.delete(_symlinkExternalFile); - Files.delete(_symlinkDir); - Files.delete(_symlinkParentDir); - Files.delete(_symlinkSiblingDir); - Files.delete(_webInfSymlink); - Files.delete(_webrootSymlink); - Files.delete(_protectedFile); + for (Path p : _createdFiles) + { + try + { + Files.delete(p); + } + catch (Throwable t) + { + // Ignored. + } + } + _createdFiles.clear(); _client.stop(); _server.stop(); @@ -169,62 +204,116 @@ public class AliasCheckerSymlinkTest public static Stream testCases() { - AllowedResourceAliasChecker allowedResource = new AllowedResourceAliasChecker(_context); - SymlinkAllowedResourceAliasChecker symlinkAllowedResource = new SymlinkAllowedResourceAliasChecker(_context); + return testCases(_context1); + } + + public static Stream testCases(ContextHandler context) + { + AllowedResourceAliasChecker allowedResource = new AllowedResourceAliasChecker(context); + SymlinkAllowedResourceAliasChecker symlinkAllowedResource = new SymlinkAllowedResourceAliasChecker(context); ApproveAliases approveAliases = new ApproveAliases(); return Stream.of( - // AllowedResourceAliasChecker that checks the target of symlinks. - Arguments.of(allowedResource, "/symlinkFile", HttpStatus.OK_200, "This file is inside webroot."), - Arguments.of(allowedResource, "/symlinkExternalFile", HttpStatus.NOT_FOUND_404, null), - Arguments.of(allowedResource, "/symlinkDir/file", HttpStatus.OK_200, "This file is inside webroot/documents."), - Arguments.of(allowedResource, "/symlinkParentDir/webroot/file", HttpStatus.OK_200, "This file is inside webroot."), - Arguments.of(allowedResource, "/symlinkParentDir/webroot/WEB-INF/web.xml", HttpStatus.NOT_FOUND_404, null), - Arguments.of(allowedResource, "/symlinkSiblingDir/file", HttpStatus.NOT_FOUND_404, null), - Arguments.of(allowedResource, "/webInfSymlink/web.xml", HttpStatus.NOT_FOUND_404, null), - Arguments.of(allowedResource, "/PROTECTED", HttpStatus.NOT_FOUND_404, null), + // AllowedResourceAliasChecker that checks the target of symlinks. + Arguments.of(allowedResource, "/symlinkFile", HttpStatus.OK_200, "This file is inside webroot."), + Arguments.of(allowedResource, "/symlinkExternalFile", HttpStatus.NOT_FOUND_404, null), + Arguments.of(allowedResource, "/symlinkDir/file", HttpStatus.OK_200, "This file is inside webroot/documents."), + Arguments.of(allowedResource, "/symlinkParentDir/webroot/file", HttpStatus.OK_200, "This file is inside webroot."), + Arguments.of(allowedResource, "/symlinkParentDir/webroot/WEB-INF/web.xml", HttpStatus.NOT_FOUND_404, null), + Arguments.of(allowedResource, "/symlinkSiblingDir/file", HttpStatus.NOT_FOUND_404, null), + Arguments.of(allowedResource, "/webInfSymlink/web.xml", HttpStatus.NOT_FOUND_404, null), - // SymlinkAllowedResourceAliasChecker that does not check the target of symlinks, but only approves files obtained through a symlink. - Arguments.of(symlinkAllowedResource, "/symlinkFile", HttpStatus.OK_200, "This file is inside webroot."), - Arguments.of(symlinkAllowedResource, "/symlinkExternalFile", HttpStatus.OK_200, "This file is outside webroot."), - Arguments.of(symlinkAllowedResource, "/symlinkDir/file", HttpStatus.OK_200, "This file is inside webroot/documents."), - Arguments.of(symlinkAllowedResource, "/symlinkParentDir/webroot/file", HttpStatus.OK_200, "This file is inside webroot."), - Arguments.of(symlinkAllowedResource, "/symlinkParentDir/webroot/WEB-INF/web.xml", HttpStatus.OK_200, "This is the web.xml file."), - Arguments.of(symlinkAllowedResource, "/symlinkSiblingDir/file", HttpStatus.OK_200, "This file is inside a sibling dir to webroot."), - Arguments.of(symlinkAllowedResource, "/webInfSymlink/web.xml", HttpStatus.OK_200, "This is the web.xml file."), - Arguments.of(symlinkAllowedResource, "/PROTECTED", HttpStatus.NOT_FOUND_404, null), + // SymlinkAllowedResourceAliasChecker that does not check the target of symlinks, but only approves files obtained through a symlink. + Arguments.of(symlinkAllowedResource, "/symlinkFile", HttpStatus.OK_200, "This file is inside webroot."), + Arguments.of(symlinkAllowedResource, "/symlinkExternalFile", HttpStatus.OK_200, "This file is outside webroot."), + Arguments.of(symlinkAllowedResource, "/symlinkDir/file", HttpStatus.OK_200, "This file is inside webroot/documents."), + Arguments.of(symlinkAllowedResource, "/symlinkParentDir/webroot/file", HttpStatus.OK_200, "This file is inside webroot."), + Arguments.of(symlinkAllowedResource, "/symlinkParentDir/webroot/WEB-INF/web.xml", HttpStatus.OK_200, "This is the web.xml file."), + Arguments.of(symlinkAllowedResource, "/symlinkSiblingDir/file", HttpStatus.OK_200, "This file is inside a sibling dir to webroot."), + Arguments.of(symlinkAllowedResource, "/webInfSymlink/web.xml", HttpStatus.OK_200, "This is the web.xml file."), - // The ApproveAliases (approves everything regardless). - Arguments.of(approveAliases, "/symlinkFile", HttpStatus.OK_200, "This file is inside webroot."), - Arguments.of(approveAliases, "/symlinkExternalFile", HttpStatus.OK_200, "This file is outside webroot."), - Arguments.of(approveAliases, "/symlinkDir/file", HttpStatus.OK_200, "This file is inside webroot/documents."), - Arguments.of(approveAliases, "/symlinkParentDir/webroot/file", HttpStatus.OK_200, "This file is inside webroot."), - Arguments.of(approveAliases, "/symlinkParentDir/webroot/WEB-INF/web.xml", HttpStatus.OK_200, "This is the web.xml file."), - Arguments.of(approveAliases, "/symlinkSiblingDir/file", HttpStatus.OK_200, "This file is inside a sibling dir to webroot."), - Arguments.of(approveAliases, "/webInfSymlink/web.xml", HttpStatus.OK_200, "This is the web.xml file."), - Arguments.of(approveAliases, "/PROTECTED", HttpStatus.OK_200, "This file is inside a sibling dir to webroot."), + // The ApproveAliases (approves everything regardless). + Arguments.of(approveAliases, "/symlinkFile", HttpStatus.OK_200, "This file is inside webroot."), + Arguments.of(approveAliases, "/symlinkExternalFile", HttpStatus.OK_200, "This file is outside webroot."), + Arguments.of(approveAliases, "/symlinkDir/file", HttpStatus.OK_200, "This file is inside webroot/documents."), + Arguments.of(approveAliases, "/symlinkParentDir/webroot/file", HttpStatus.OK_200, "This file is inside webroot."), + Arguments.of(approveAliases, "/symlinkParentDir/webroot/WEB-INF/web.xml", HttpStatus.OK_200, "This is the web.xml file."), + Arguments.of(approveAliases, "/symlinkSiblingDir/file", HttpStatus.OK_200, "This file is inside a sibling dir to webroot."), + Arguments.of(approveAliases, "/webInfSymlink/web.xml", HttpStatus.OK_200, "This is the web.xml file."), - // No alias checker (any symlink should be an alias). - Arguments.of(null, "/symlinkFile", HttpStatus.NOT_FOUND_404, null), - Arguments.of(null, "/symlinkExternalFile", HttpStatus.NOT_FOUND_404, null), - Arguments.of(null, "/symlinkDir/file", HttpStatus.NOT_FOUND_404, null), - Arguments.of(null, "/symlinkParentDir/webroot/file", HttpStatus.NOT_FOUND_404, null), - Arguments.of(null, "/symlinkParentDir/webroot/WEB-INF/web.xml", HttpStatus.NOT_FOUND_404, null), - Arguments.of(null, "/symlinkSiblingDir/file", HttpStatus.NOT_FOUND_404, null), - Arguments.of(null, "/webInfSymlink/web.xml", HttpStatus.NOT_FOUND_404, null), - Arguments.of(null, "/PROTECTED", HttpStatus.NOT_FOUND_404, null) - ); + // No alias checker (any symlink should be an alias). + Arguments.of(null, "/symlinkFile", HttpStatus.NOT_FOUND_404, null), + Arguments.of(null, "/symlinkExternalFile", HttpStatus.NOT_FOUND_404, null), + Arguments.of(null, "/symlinkDir/file", HttpStatus.NOT_FOUND_404, null), + Arguments.of(null, "/symlinkParentDir/webroot/file", HttpStatus.NOT_FOUND_404, null), + Arguments.of(null, "/symlinkParentDir/webroot/WEB-INF/web.xml", HttpStatus.NOT_FOUND_404, null), + Arguments.of(null, "/symlinkSiblingDir/file", HttpStatus.NOT_FOUND_404, null), + Arguments.of(null, "/webInfSymlink/web.xml", HttpStatus.NOT_FOUND_404, null), + + // We should only be able to list contents of a symlinked directory if the alias checker is installed. + Arguments.of(null, "/symlinkDir", HttpStatus.NOT_FOUND_404, null), + Arguments.of(allowedResource, "/symlinkDir", HttpStatus.OK_200, null) + ); + } + + public static Stream combinedResourceTestCases() + { + AllowedResourceAliasChecker allowedResource = new AllowedResourceAliasChecker(_context2); + SymlinkAllowedResourceAliasChecker symlinkAllowedResource = new SymlinkAllowedResourceAliasChecker(_context2); + + Stream combinedResourceTests = Stream.of( + Arguments.of(allowedResource, "/file", HttpStatus.OK_200, "This file is inside webroot."), + Arguments.of(allowedResource, "/combinedFile", HttpStatus.OK_200, "This is a file in the combined resource dir."), + Arguments.of(allowedResource, "/WEB-INF/file", HttpStatus.NOT_FOUND_404, null), + Arguments.of(allowedResource, "/files", HttpStatus.OK_200, "Directory: /files/|/files/file1|/files/file2"), + Arguments.of(allowedResource, "/files/file1", HttpStatus.OK_200, "file1 from combined dir"), + Arguments.of(allowedResource, "/files/file2", HttpStatus.OK_200, "file1 from webroot"), + + Arguments.of(allowedResource, "/combinedSymlinkFile", HttpStatus.OK_200, "This file is inside webroot."), + Arguments.of(allowedResource, "/externalCombinedSymlinkFile/file", HttpStatus.NOT_FOUND_404, null), + Arguments.of(allowedResource, "/combinedWebInfSymlink/web.xml", HttpStatus.NOT_FOUND_404, null), + + Arguments.of(symlinkAllowedResource, "/combinedSymlinkFile", HttpStatus.OK_200, "This file is inside webroot."), + Arguments.of(symlinkAllowedResource, "/externalCombinedSymlinkFile/file", HttpStatus.OK_200, "This file is inside a sibling dir to webroot."), + Arguments.of(symlinkAllowedResource, "/combinedWebInfSymlink/web.xml", HttpStatus.OK_200, "This is the web.xml file.") + ); + return Stream.concat(testCases(_context2), combinedResourceTests); } @ParameterizedTest @MethodSource("testCases") public void test(AliasCheck aliasChecker, String path, int httpStatus, String responseContent) throws Exception { - setAliasChecker(aliasChecker); + setAliasChecker(_context1, aliasChecker); URI uri = URI.create("http://localhost:" + _connector.getLocalPort() + path); ContentResponse response = _client.GET(uri); assertThat(response.getStatus(), is(httpStatus)); if (responseContent != null) assertThat(response.getContentAsString(), is(responseContent)); } + + @ParameterizedTest + @MethodSource("combinedResourceTestCases") + public void testCombinedResource(AliasCheck aliasChecker, String path, int httpStatus, String responseContent) throws Exception + { + setAliasChecker(_context2, aliasChecker); + URI uri = URI.create("http://localhost:" + _connector.getLocalPort() + path); + ContentResponse response = _client.GET(uri); + assertThat(response.getStatus(), is(httpStatus)); + + if (responseContent != null) + { + if (responseContent.contains("|")) + { + for (String s : responseContent.split("\\|")) + { + assertThat("Could not find " + s, response.getContentAsString(), containsString(s)); + } + } + else + { + assertThat(response.getContentAsString(), equalTo(responseContent)); + } + } + } } diff --git a/tests/test-integration/src/test/resources/combined/WEB-INF/file b/tests/test-integration/src/test/resources/combined/WEB-INF/file new file mode 100644 index 00000000000..e8dca46498f --- /dev/null +++ b/tests/test-integration/src/test/resources/combined/WEB-INF/file @@ -0,0 +1 @@ +This is in the second WEB-INF dir. \ No newline at end of file diff --git a/tests/test-integration/src/test/resources/combined/combinedFile b/tests/test-integration/src/test/resources/combined/combinedFile new file mode 100644 index 00000000000..7db0075b659 --- /dev/null +++ b/tests/test-integration/src/test/resources/combined/combinedFile @@ -0,0 +1 @@ +This is a file in the combined resource dir. \ No newline at end of file diff --git a/tests/test-integration/src/test/resources/combined/files/file1 b/tests/test-integration/src/test/resources/combined/files/file1 new file mode 100644 index 00000000000..18b5fd92d34 --- /dev/null +++ b/tests/test-integration/src/test/resources/combined/files/file1 @@ -0,0 +1 @@ +file1 from combined dir \ No newline at end of file diff --git a/tests/test-integration/src/test/resources/webroot/files/file2 b/tests/test-integration/src/test/resources/webroot/files/file2 new file mode 100644 index 00000000000..a8fb0948063 --- /dev/null +++ b/tests/test-integration/src/test/resources/webroot/files/file2 @@ -0,0 +1 @@ +file1 from webroot \ No newline at end of file