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