-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Compliance Violation Listener cleanup of alternate proposal #11301
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,6 +20,7 @@ | |
import java.util.Set; | ||
import java.util.concurrent.atomic.AtomicInteger; | ||
|
||
import org.eclipse.jetty.util.StringUtil; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
|
@@ -82,15 +83,13 @@ public enum Violation implements ComplianceViolation | |
* as invalid even if the multiple values are the same. A deployment may include this violation to allow multiple | ||
* {@code Content-Length} values to be received, but only if they are identical. | ||
*/ | ||
// TODO: see if this is a violation in HTTP/2 & HTTP/3 as well | ||
MULTIPLE_CONTENT_LENGTHS("https://tools.ietf.org/html/rfc7230#section-3.3.2", "Multiple Content-Lengths"), | ||
|
||
/** | ||
* Since <a href="https://tools.ietf.org/html/rfc7230#section-3.3.1">RFC 7230</a>, the HTTP protocol has required that | ||
* a request is invalid if it contains both a {@code Transfer-Encoding} field and {@code Content-Length} field. | ||
* A deployment may include this violation to allow both fields to be in a received request. | ||
*/ | ||
// TODO: see if this is a violation in HTTP/2 & HTTP/3 as well | ||
TRANSFER_ENCODING_WITH_CONTENT_LENGTH("https://tools.ietf.org/html/rfc7230#section-3.3.1", "Transfer-Encoding and Content-Length"), | ||
|
||
/** | ||
|
@@ -112,23 +111,20 @@ public enum Violation implements ComplianceViolation | |
* says that a Server must reject a request duplicate host headers. | ||
* A deployment may include this violation to allow duplicate host headers on a received request. | ||
*/ | ||
// TODO: see if this is a violation in HTTP/2 & HTTP/3 as well | ||
DUPLICATE_HOST_HEADERS("https://www.rfc-editor.org/rfc/rfc7230#section-5.4", "Duplicate Host Header"), | ||
|
||
/** | ||
* Since <a href="https://www.rfc-editor.org/rfc/rfc7230#section-2.7.1">RFC 7230</a>, the HTTP protocol | ||
* 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. | ||
*/ | ||
// TODO: see if this is a violation in HTTP/2 & HTTP/3 as well | ||
UNSAFE_HOST_HEADER("https://www.rfc-editor.org/rfc/rfc7230#section-2.7.1", "Invalid Authority"), | ||
|
||
/** | ||
* Since <a href="https://www.rfc-editor.org/rfc/rfc7230#section-5.4">RFC 7230: Section 5.4</a>, 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. | ||
*/ | ||
// TODO: see if this is a violation in HTTP/2 & HTTP/3 as well | ||
MISMATCHED_AUTHORITY("https://www.rfc-editor.org/rfc/rfc7230#section-5.4", "Mismatched Authority"); | ||
|
||
private final String url; | ||
|
@@ -363,4 +359,62 @@ private static Set<Violation> copyOf(Set<Violation> violations) | |
return EnumSet.noneOf(Violation.class); | ||
return EnumSet.copyOf(violations); | ||
} | ||
|
||
public static void checkHttpCompliance(MetaData.Request request, HttpCompliance mode, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Used by HTTP/2 and HTTP/3 |
||
ComplianceViolation.Listener listener) | ||
{ | ||
boolean seenContentLength = false; | ||
boolean seenTransferEncoding = false; | ||
boolean seenHostHeader = false; | ||
|
||
HttpFields fields = request.getHttpFields(); | ||
for (HttpField httpField: fields) | ||
{ | ||
switch (httpField.getHeader()) | ||
{ | ||
case CONTENT_LENGTH -> | ||
{ | ||
if (seenContentLength) | ||
assertAllowed(Violation.MULTIPLE_CONTENT_LENGTHS, mode, listener); | ||
String[] lengths = httpField.getValues(); | ||
if (lengths.length > 1) | ||
assertAllowed(Violation.MULTIPLE_CONTENT_LENGTHS, mode, listener); | ||
if (seenTransferEncoding) | ||
assertAllowed(Violation.TRANSFER_ENCODING_WITH_CONTENT_LENGTH, mode, listener); | ||
seenContentLength = true; | ||
} | ||
case TRANSFER_ENCODING -> | ||
{ | ||
if (seenContentLength) | ||
assertAllowed(Violation.TRANSFER_ENCODING_WITH_CONTENT_LENGTH, mode, listener); | ||
seenTransferEncoding = true; | ||
} | ||
case HOST -> | ||
{ | ||
if (seenHostHeader) | ||
assertAllowed(Violation.DUPLICATE_HOST_HEADERS, mode, listener); | ||
String[] hostValues = httpField.getValues(); | ||
if (hostValues.length > 1) | ||
assertAllowed(Violation.DUPLICATE_HOST_HEADERS, mode, listener); | ||
for (String hostValue: hostValues) | ||
if (StringUtil.isBlank(hostValue)) | ||
assertAllowed(Violation.UNSAFE_HOST_HEADER, mode, listener); | ||
String authority = request.getHttpURI().getHost(); | ||
if (StringUtil.isBlank(authority)) | ||
assertAllowed(Violation.UNSAFE_HOST_HEADER, mode, listener); | ||
seenHostHeader = true; | ||
} | ||
} | ||
} | ||
} | ||
|
||
private static void assertAllowed(Violation violation, HttpCompliance mode, ComplianceViolation.Listener listener) | ||
{ | ||
if (mode.allows(violation)) | ||
listener.onComplianceViolation(new ComplianceViolation.Event( | ||
mode, violation, violation.getDescription() | ||
)); | ||
else | ||
throw new BadMessageException(violation.getDescription()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -200,6 +200,10 @@ public static class Parser | |
{ | ||
private final PartsListener listener = new PartsListener(); | ||
private final MultiPart.Parser parser; | ||
// TODO: it's a bit odd to hang onto a ComplianceViolation.Listener here. | ||
// as that would require the user to pass it in. Maybe a List<Event> violations instead? | ||
// that the ee10 layer could use? | ||
private ComplianceViolation.Listener complianceViolationListener; | ||
Comment on lines
+203
to
+206
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm -1 on keeping lists of violations just in case later on there is a violation listener we need to pass it too. Just have this field with an optional constructor. If it is constructed without the listener, then there is no listener. |
||
private boolean useFilesForPartsWithoutFileName; | ||
private Path filesDirectory; | ||
private long maxFileSize = -1; | ||
|
@@ -517,6 +521,13 @@ public void onPart(String name, String fileName, HttpFields headers) | |
memoryFileSize = 0; | ||
try (AutoLock ignored = lock.lock()) | ||
{ | ||
if (headers.contains("content-transfer-encoding")) | ||
{ | ||
String value = headers.get("content-transfer-encoding"); | ||
if (!"8bit".equalsIgnoreCase(value) && !"binary".equalsIgnoreCase(value)) | ||
complianceViolationListener.onComplianceViolation(new ComplianceViolation.Event(MultiPartCompliance.RFC7578, MultiPartCompliance.Violation.CONTENT_TRANSFER_ENCODING, value)); | ||
} | ||
|
||
MultiPart.Part part; | ||
if (fileChannel != null) | ||
part = new MultiPart.PathPart(name, fileName, headers, filePath); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -258,6 +258,7 @@ private HttpChannel pollHttpChannel() | |
httpChannel = httpChannels.poll(); | ||
if (httpChannel == null) | ||
httpChannel = httpChannelFactory.newHttpChannel(this); | ||
httpChannel.init(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know I first used the "initialize" verb... but is it really the right verb? perhaps Anyway, failing a better name, can this at least be |
||
return httpChannel; | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,7 @@ | |
import java.util.function.Supplier; | ||
|
||
import org.eclipse.jetty.http.ComplianceViolation; | ||
import org.eclipse.jetty.http.HttpCompliance; | ||
import org.eclipse.jetty.http.HttpException; | ||
import org.eclipse.jetty.http.HttpFields; | ||
import org.eclipse.jetty.http.HttpMethod; | ||
|
@@ -40,7 +41,6 @@ | |
import org.eclipse.jetty.server.HttpChannel; | ||
import org.eclipse.jetty.server.HttpStream; | ||
import org.eclipse.jetty.server.Request; | ||
import org.eclipse.jetty.server.Server; | ||
import org.eclipse.jetty.server.TunnelSupport; | ||
import org.eclipse.jetty.util.BufferUtil; | ||
import org.eclipse.jetty.util.Callback; | ||
|
@@ -85,12 +85,14 @@ public Runnable onRequest(HeadersFrame frame) | |
{ | ||
_requestMetaData = (MetaData.Request)frame.getMetaData(); | ||
|
||
ComplianceViolation.Listener listener = Server.getComplianceViolationListener(_httpChannel.getConnectionMetaData().getConnector()).initialize(); | ||
// Grab freshly initialized ComplianceViolation.Listener here, no need to reinitialize. | ||
ComplianceViolation.Listener listener = _httpChannel.getComponents().getComplianceViolationListener(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @sbordet This is the exposed violations listeners in components. I agree it is kind of funny... but is it really any different to the exposed The What if we moved the compliance listeners to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we move the configuration of We could have methods:
The existence of any added listener would be the equivalent of This however, wouldn't get rid of, or change, the existing storage and management from |
||
Runnable handler = _httpChannel.onRequest(_requestMetaData); | ||
Request request = _httpChannel.getRequest(); | ||
listener.onRequestBegin(request); | ||
// Note UriCompliance is done by HandlerInvoker | ||
// TODO: perform HttpCompliance violation checks? | ||
HttpCompliance httpCompliance = _httpChannel.getConnectionMetaData().getHttpConfiguration().getHttpCompliance(); | ||
HttpCompliance.checkHttpCompliance(_requestMetaData, httpCompliance, listener); | ||
|
||
if (frame.isEndStream()) | ||
{ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved to AbstractConnector (as it was the only place using it)