diff --git a/jetty-infinispan/infinispan-remote-query/pom.xml b/jetty-infinispan/infinispan-remote-query/pom.xml index 3d8b2221b1f..370439624df 100644 --- a/jetty-infinispan/infinispan-remote-query/pom.xml +++ b/jetty-infinispan/infinispan-remote-query/pom.xml @@ -138,5 +138,10 @@ testcontainers test + + org.testcontainers + junit-jupiter + test + diff --git a/jetty-infinispan/infinispan-remote-query/src/test/java/org/eclipse/jetty/server/session/infinispan/RemoteQueryManagerTest.java b/jetty-infinispan/infinispan-remote-query/src/test/java/org/eclipse/jetty/server/session/infinispan/RemoteQueryManagerTest.java index ca30fa5fb3d..d92aa2a7e11 100644 --- a/jetty-infinispan/infinispan-remote-query/src/test/java/org/eclipse/jetty/server/session/infinispan/RemoteQueryManagerTest.java +++ b/jetty-infinispan/infinispan-remote-query/src/test/java/org/eclipse/jetty/server/session/infinispan/RemoteQueryManagerTest.java @@ -46,11 +46,13 @@ import org.slf4j.LoggerFactory; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.output.Slf4jLogConsumer; import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy; +import org.testcontainers.junit.jupiter.Testcontainers; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; +@Testcontainers(disabledWithoutDocker = true) public class RemoteQueryManagerTest { public static final String DEFAULT_CACHE_NAME = "remote-session-test"; diff --git a/jetty-server/src/main/config/etc/jetty.xml b/jetty-server/src/main/config/etc/jetty.xml index 51f162c77ea..7a3f85cf65b 100644 --- a/jetty-server/src/main/config/etc/jetty.xml +++ b/jetty-server/src/main/config/etc/jetty.xml @@ -76,6 +76,8 @@ + + diff --git a/jetty-server/src/main/config/modules/logback-access.mod b/jetty-server/src/main/config/modules/logback-access.mod deleted file mode 100644 index 3f0e5aa066e..00000000000 --- a/jetty-server/src/main/config/modules/logback-access.mod +++ /dev/null @@ -1,30 +0,0 @@ -# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html - -[description] -Enables logback request log. - -[tags] -requestlog -logging -logback - -[depend] -server -logback-impl -resources - -[provide] -requestlog-impl - -[xml] -etc/jetty-logback-access.xml - -[files] -logs/ -basehome:modules/logback-access/jetty-logback-access.xml|etc/jetty-logback-access.xml -basehome:modules/logback-access/logback-access.xml|resources/logback-access.xml -maven://ch.qos.logback/logback-access/${logback.version}|lib/logback/logback-access-${logback.version}.jar - -[lib] -lib/logback/logback-access-${logback.version}.jar - diff --git a/jetty-server/src/main/config/modules/logback-access/jetty-logback-access.xml b/jetty-server/src/main/config/modules/logback-access/jetty-logback-access.xml deleted file mode 100644 index 2b8d2580288..00000000000 --- a/jetty-server/src/main/config/modules/logback-access/jetty-logback-access.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - logback-access - /logback-access.xml - - - - diff --git a/jetty-server/src/main/config/modules/logback-access/logback-access.xml b/jetty-server/src/main/config/modules/logback-access/logback-access.xml deleted file mode 100644 index 72e73b2e6d8..00000000000 --- a/jetty-server/src/main/config/modules/logback-access/logback-access.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - logs/access.log - - logs/access.%d{yyyy-MM-dd}.log.zip - - - combined - - - - - - diff --git a/jetty-server/src/main/config/modules/server.mod b/jetty-server/src/main/config/modules/server.mod index ec568b315a1..fb986de77f4 100644 --- a/jetty-server/src/main/config/modules/server.mod +++ b/jetty-server/src/main/config/modules/server.mod @@ -74,6 +74,10 @@ etc/jetty.xml ## Relative Redirect Locations allowed # jetty.httpConfig.relativeRedirectAllowed=false +## Whether to use direct ByteBuffers for reading or writing +# jetty.httpConfig.useInputDirectByteBuffers=true +# jetty.httpConfig.useOutputDirectByteBuffers=true + ### Server configuration ## Whether ctrl+c on the console gracefully stops the Jetty server # jetty.server.stopAtShutdown=true @@ -91,3 +95,4 @@ etc/jetty.xml # jetty.scheduler.name= # jetty.scheduler.deamon=false # jetty.scheduler.threads=-1 + diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java index 98d6f6092ae..9e1628927e6 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java @@ -1692,7 +1692,8 @@ public class Request implements HttpServletRequest _httpFields = request.getFields(); final HttpURI uri = request.getURI(); - if (uri.isAmbiguous()) + boolean ambiguous = uri.isAmbiguous(); + if (ambiguous) { UriCompliance compliance = _channel == null || _channel.getHttpConfiguration() == null ? null : _channel.getHttpConfiguration().getUriCompliance(); if (uri.hasAmbiguousSegment() && (compliance == null || !compliance.allows(UriCompliance.Violation.AMBIGUOUS_PATH_SEGMENT))) @@ -1741,7 +1742,15 @@ public class Request implements HttpServletRequest // TODO this is not really right for CONNECT path = _uri.isAbsolute() ? "/" : null; else if (encoded.startsWith("/")) + { path = (encoded.length() == 1) ? "/" : _uri.getDecodedPath(); + // Strictly speaking if a URI is legal and encodes ambiguous segments, then they should be + // reflected in the decoded string version. However, it can be ambiguous to provide a decoded path as + // a string, so we normalize again. If an application wishes to see ambiguous URIs, then they can look + // at the encoded form of the URI + if (ambiguous) + path = URIUtil.canonicalPath(path); + } else if ("*".equals(encoded) || HttpMethod.CONNECT.is(getMethod())) path = encoded; else diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/SecureRequestCustomizer.java b/jetty-server/src/main/java/org/eclipse/jetty/server/SecureRequestCustomizer.java index 270e088e082..c97c63897d8 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/SecureRequestCustomizer.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/SecureRequestCustomizer.java @@ -13,6 +13,7 @@ package org.eclipse.jetty.server; +import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.HashSet; import java.util.Set; @@ -48,10 +49,12 @@ import org.slf4j.LoggerFactory; public class SecureRequestCustomizer implements HttpConfiguration.Customizer { private static final Logger LOG = LoggerFactory.getLogger(SecureRequestCustomizer.class); + public static final String JAKARTA_SERVLET_REQUEST_X_509_CERTIFICATE = "jakarta.servlet.request.X509Certificate"; public static final String JAKARTA_SERVLET_REQUEST_CIPHER_SUITE = "jakarta.servlet.request.cipher_suite"; public static final String JAKARTA_SERVLET_REQUEST_KEY_SIZE = "jakarta.servlet.request.key_size"; public static final String JAKARTA_SERVLET_REQUEST_SSL_SESSION_ID = "jakarta.servlet.request.ssl_session_id"; + public static final String X509_CERT = "org.eclipse.jetty.server.x509_cert"; private String sslSessionAttribute = "org.eclipse.jetty.servlet.request.ssl_session"; @@ -244,24 +247,24 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer if (isSniRequired() || isSniHostCheck()) { String sniHost = (String)sslSession.getValue(SslContextFactory.Server.SNI_HOST); - X509 cert = new X509(null, (X509Certificate)sslSession.getLocalCertificates()[0]); + X509 x509 = (X509)sslSession.getValue(X509_CERT); + if (x509 == null) + { + Certificate[] certificates = sslSession.getLocalCertificates(); + if (certificates == null || certificates.length == 0 || !(certificates[0] instanceof X509Certificate)) + throw new BadMessageException(400, "Invalid SNI"); + x509 = new X509(null, (X509Certificate)certificates[0]); + sslSession.putValue(X509_CERT, x509); + } String serverName = request.getServerName(); if (LOG.isDebugEnabled()) - LOG.debug("Host={}, SNI={}, SNI Certificate={}", serverName, sniHost, cert); + LOG.debug("Host={}, SNI={}, SNI Certificate={}", serverName, sniHost, x509); - if (isSniRequired()) - { - if (sniHost == null) - throw new BadMessageException(400, "Invalid SNI"); - if (!cert.matches(sniHost)) - throw new BadMessageException(400, "Invalid SNI"); - } + if (isSniRequired() && (sniHost == null || !x509.matches(sniHost))) + throw new BadMessageException(400, "Invalid SNI"); - if (isSniHostCheck()) - { - if (!cert.matches(serverName)) - throw new BadMessageException(400, "Invalid SNI"); - } + if (isSniHostCheck() && !x509.matches(serverName)) + throw new BadMessageException(400, "Invalid SNI"); } request.setAttributes(new SslAttributes(request, sslSession)); diff --git a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppContextTest.java b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppContextTest.java index 5a60ce6201e..9b8130e3be9 100644 --- a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppContextTest.java +++ b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppContextTest.java @@ -28,8 +28,11 @@ import jakarta.servlet.GenericServlet; import jakarta.servlet.ServletContext; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; + import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpTester; +import org.eclipse.jetty.http.UriCompliance; +import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.LocalConnector; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; @@ -284,6 +287,43 @@ public class WebAppContextTest assertFalse(context.isProtectedTarget("/something-else/web-inf")); } + @Test + public void testProtectedTarget() throws Exception + { + Server server = newServer(); + + HandlerList handlers = new HandlerList(); + ContextHandlerCollection contexts = new ContextHandlerCollection(); + WebAppContext context = new WebAppContext(); + Path testWebapp = MavenTestingUtils.getProjectDirPath("src/test/webapp"); + context.setBaseResource(new PathResource(testWebapp)); + context.setContextPath("/"); + server.setHandler(handlers); + handlers.addHandler(contexts); + contexts.addHandler(context); + + LocalConnector connector = new LocalConnector(server); + server.addConnector(connector); + connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.RFC3986); + + server.start(); + + assertThat(HttpTester.parseResponse(connector.getResponse("GET /test.xml HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.OK_200)); + assertThat(HttpTester.parseResponse(connector.getResponse("GET /%2e/%2e/test.xml HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.OK_200)); + assertThat(HttpTester.parseResponse(connector.getResponse("GET /foo/%2e%2e/test.xml HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.OK_200)); + + assertThat(HttpTester.parseResponse(connector.getResponse("GET /WEB-INF HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.NOT_FOUND_404)); + assertThat(HttpTester.parseResponse(connector.getResponse("GET /WEB-INF/ HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.NOT_FOUND_404)); + assertThat(HttpTester.parseResponse(connector.getResponse("GET /WEB-INF/test.xml HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.NOT_FOUND_404)); + assertThat(HttpTester.parseResponse(connector.getResponse("GET /web-inf/test.xml HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.NOT_FOUND_404)); + assertThat(HttpTester.parseResponse(connector.getResponse("GET /%2e/WEB-INF/test.xml HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.NOT_FOUND_404)); + assertThat(HttpTester.parseResponse(connector.getResponse("GET /%2e/%2e/WEB-INF/test.xml HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.NOT_FOUND_404)); + assertThat(HttpTester.parseResponse(connector.getResponse("GET /foo/%2e%2e/WEB-INF/test.xml HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.NOT_FOUND_404)); + assertThat(HttpTester.parseResponse(connector.getResponse("GET /%2E/WEB-INF/test.xml HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.NOT_FOUND_404)); + assertThat(HttpTester.parseResponse(connector.getResponse("GET //WEB-INF/test.xml HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.NOT_FOUND_404)); + assertThat(HttpTester.parseResponse(connector.getResponse("GET /WEB-INF%2ftest.xml HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.NOT_FOUND_404)); + } + @Test public void testNullPath() throws Exception { diff --git a/jetty-webapp/src/test/webapp/WEB-INF/test.xml b/jetty-webapp/src/test/webapp/WEB-INF/test.xml new file mode 100644 index 00000000000..9daeafb9864 --- /dev/null +++ b/jetty-webapp/src/test/webapp/WEB-INF/test.xml @@ -0,0 +1 @@ +test diff --git a/jetty-webapp/src/test/webapp/test.xml b/jetty-webapp/src/test/webapp/test.xml new file mode 100644 index 00000000000..9daeafb9864 --- /dev/null +++ b/jetty-webapp/src/test/webapp/test.xml @@ -0,0 +1 @@ +test