diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpCompliance.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpCompliance.java index 8e0a47d6924..1b9a20b9846 100644 --- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpCompliance.java +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpCompliance.java @@ -117,7 +117,14 @@ public final class HttpCompliance implements ComplianceViolation.Mode * should reject a request if the Host headers contains an invalid / unsafe authority. * A deployment may include this violation to allow unsafe host headesr on a received request. */ - UNSAFE_HOST_HEADER("https://www.rfc-editor.org/rfc/rfc7230#section-2.7.1", "Invalid Authority"); + UNSAFE_HOST_HEADER("https://www.rfc-editor.org/rfc/rfc7230#section-2.7.1", "Invalid Authority"), + + /** + * Since RFC 7230: Section 5.4, the HTTP protocol + * must reject a request if the target URI has an authority that is different than a provided Host header. + * A deployment may include this violation to allow different values on the target URI and the Host header on a received request. + */ + MISMATCHED_AUTHORITY("https://www.rfc-editor.org/rfc/rfc7230#section-5.4", "Mismatched Authority"); private final String url; private final String description; @@ -162,7 +169,11 @@ public final class HttpCompliance implements ComplianceViolation.Mode * The HttpCompliance mode that supports RFC 7230 * with only the violations that differ from {@link #RFC7230}. */ - public static final HttpCompliance RFC2616 = new HttpCompliance("RFC2616", of(Violation.HTTP_0_9, Violation.MULTILINE_FIELD_VALUE)); + public static final HttpCompliance RFC2616 = new HttpCompliance("RFC2616", of( + Violation.HTTP_0_9, + Violation.MULTILINE_FIELD_VALUE, + Violation.MISMATCHED_AUTHORITY + )); /** * A legacy HttpCompliance mode that allows all violations except case-insensitive methods. diff --git a/jetty-core/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java b/jetty-core/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java index c97d5cbb798..ad48badb429 100644 --- a/jetty-core/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java +++ b/jetty-core/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java @@ -42,7 +42,6 @@ import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.startsWith; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpConnection.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpConnection.java index f1a86e1e774..41c1ed65c02 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpConnection.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpConnection.java @@ -75,6 +75,7 @@ import org.eclipse.jetty.util.thread.Invocable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static org.eclipse.jetty.http.HttpCompliance.Violation.MISMATCHED_AUTHORITY; import static org.eclipse.jetty.http.HttpStatus.INTERNAL_SERVER_ERROR_500; /** @@ -359,6 +360,30 @@ public class HttpConnection extends AbstractConnection implements Runnable, Writ _useOutputDirectByteBuffers = useOutputDirectByteBuffers; } + protected void onComplianceViolation(ComplianceViolation.Mode mode, ComplianceViolation violation, String details) + { + //TODO configure this somewhere else + //TODO what about cookie compliance + //TODO what about http2 & 3 + //TODO test this in core + if (isRecordHttpComplianceViolations()) + { + HttpStreamOverHTTP1 stream = _stream.get(); + if (stream != null) + { + if (stream._complianceViolations == null) + { + stream._complianceViolations = new ArrayList<>(); + } + String record = String.format("%s (see %s) in mode %s for %s in %s", + violation.getDescription(), violation.getURL(), mode, details, HttpConnection.this); + stream._complianceViolations.add(record); + if (LOG.isDebugEnabled()) + LOG.debug(record); + } + } + } + @Override public ByteBuffer onUpgradeFrom() { @@ -1086,26 +1111,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Writ @Override public void onComplianceViolation(ComplianceViolation.Mode mode, ComplianceViolation violation, String details) { - //TODO configure this somewhere else - //TODO what about cookie compliance - //TODO what about http2 & 3 - //TODO test this in core - if (isRecordHttpComplianceViolations()) - { - HttpStreamOverHTTP1 stream = _stream.get(); - if (stream != null) - { - if (stream._complianceViolations == null) - { - stream._complianceViolations = new ArrayList<>(); - } - String record = String.format("%s (see %s) in mode %s for %s in %s", - violation.getDescription(), violation.getURL(), mode, details, HttpConnection.this); - stream._complianceViolations.add(record); - if (LOG.isDebugEnabled()) - LOG.debug(record); - } - } + HttpConnection.this.onComplianceViolation(mode, violation, details); } } @@ -1222,7 +1228,13 @@ public class HttpConnection extends AbstractConnection implements Runnable, Writ if (_uri.isAbsolute()) { if (!_hostField.getValue().equals(_uri.getAuthority())) - throw new BadMessageException("Authority!=Host"); + { + HttpCompliance httpCompliance = getHttpConfiguration().getHttpCompliance(); + if (httpCompliance.allows(MISMATCHED_AUTHORITY)) + onComplianceViolation(httpCompliance, MISMATCHED_AUTHORITY, _uri.asString()); + else + throw new BadMessageException("Authority!=Host"); + } } else { diff --git a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ForwardedRequestCustomizerTest.java b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ForwardedRequestCustomizerTest.java index 7a5b24606a4..d8ddbd24a6d 100644 --- a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ForwardedRequestCustomizerTest.java +++ b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ForwardedRequestCustomizerTest.java @@ -20,6 +20,7 @@ import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Stream; +import org.eclipse.jetty.http.HttpCompliance; import org.eclipse.jetty.http.HttpTester; import org.eclipse.jetty.util.Callback; import org.junit.jupiter.api.AfterEach; @@ -64,7 +65,10 @@ public class ForwardedRequestCustomizerTest // Default behavior Connector HttpConnectionFactory http = new HttpConnectionFactory(); + HttpCompliance mismatchedAuthorityHttpCompliance = HttpCompliance.RFC7230.with("Mismatched_Authority", HttpCompliance.Violation.MISMATCHED_AUTHORITY); + http.getHttpConfiguration().setHttpCompliance(mismatchedAuthorityHttpCompliance); http.getHttpConfiguration().setSecurePort(443); + customizer = new ForwardedRequestCustomizer(); http.getHttpConfiguration().addCustomizer(customizer); connector = new LocalConnector(server, http); @@ -73,6 +77,7 @@ public class ForwardedRequestCustomizerTest // Alternate behavior Connector HttpConnectionFactory httpAlt = new HttpConnectionFactory(); httpAlt.getHttpConfiguration().setSecurePort(8443); + httpAlt.getHttpConfiguration().setHttpCompliance(mismatchedAuthorityHttpCompliance); customizerAlt = new ForwardedRequestCustomizer(); httpAlt.getHttpConfiguration().addCustomizer(customizerAlt); connectorAlt = new LocalConnector(server, httpAlt); @@ -93,6 +98,7 @@ public class ForwardedRequestCustomizerTest http.getHttpConfiguration().addCustomizer(customizerConfigured); connectorConfigured = new LocalConnector(server, http); + connectorConfigured.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setHttpCompliance(mismatchedAuthorityHttpCompliance); server.addConnector(connectorConfigured); RequestHandler handler = new RequestHandler(); diff --git a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/PartialRFC2616Test.java b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/PartialRFC2616Test.java index 5e1cb473a16..a296dd716e6 100644 --- a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/PartialRFC2616Test.java +++ b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/PartialRFC2616Test.java @@ -17,6 +17,7 @@ import java.util.Date; import java.util.List; import java.util.concurrent.TimeUnit; +import org.eclipse.jetty.http.HttpCompliance; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpParser; @@ -48,6 +49,7 @@ public class PartialRFC2616Test { server = new Server(); connector = new LocalConnector(server); + connector.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration().setHttpCompliance(HttpCompliance.RFC2616); connector.setIdleTimeout(10000); server.addConnector(connector); @@ -403,13 +405,20 @@ public class PartialRFC2616Test } + /** + * @throws Exception + * @see RFC 2616 - Section 5.2 - The Resource Identified by a Request + */ @Test @Disabled // TODO public void test521() throws Exception { // Default Host int offset = 0; - String response = connector.getResponse("GET http://VirtualHost:8888/path/R1 HTTP/1.1\n" + "Host: wronghost\n" + "Connection: close\n" + "\n"); + String response = connector.getResponse( + "GET http://VirtualHost:8888/path/R1 HTTP/1.1\n" + + "Host: wronghost\n" + + "Connection: close\n" + "\n"); offset = checkContains(response, offset, "HTTP/1.1 200", "Virtual host") + 1; offset = checkContains(response, offset, "Virtual Dump", "Virtual host") + 1; offset = checkContains(response, offset, "pathInContext=/path/R1", "Virtual host") + 1; diff --git a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/NcsaRequestLogTest.java b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/NcsaRequestLogTest.java index c98b162aff6..c15f165703c 100644 --- a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/NcsaRequestLogTest.java +++ b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/NcsaRequestLogTest.java @@ -163,7 +163,7 @@ public class NcsaRequestLogTest _connector.getResponse( "GET http://hostname:8888/foo?name=value HTTP/1.1\n" + - "Host: servername\n" + + "Host: hostname:8888\n" + "\n"); String log = _entries.poll(5, TimeUnit.SECONDS); assertThat(log, containsString("GET http://hostname:8888/foo?name=value")); diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/RFC2616Base.xml b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/RFC2616Base.xml index 9f6af74dd8e..b80ae305b73 100644 --- a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/RFC2616Base.xml +++ b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/RFC2616Base.xml @@ -22,6 +22,12 @@ false 1024 + + + RFC2616 + + +