Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,13 @@ interface Mode
Set<? extends ComplianceViolation> getAllowed();
}

record Event(ComplianceViolation.Mode mode, ComplianceViolation violation, String details)
record Event(ComplianceViolation.Mode mode, ComplianceViolation violation, String details, boolean allowed)
{
@Override
public String toString()
{
return String.format("%s (see %s) in mode %s for %s",
violation.getDescription(), violation.getURL(), mode, details);
return String.format("%s (see %s) in mode %s for %s during %s (%s)",
violation.getDescription(), violation.getURL(), mode, details, allowed ? "allowed" : "forbidden");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class ComplianceViolationException extends IllegalArgumentException

public ComplianceViolationException(ComplianceViolation.Mode mode, ComplianceViolation violation, String details)
{
this(new ComplianceViolation.Event(mode, violation, details));
this(new ComplianceViolation.Event(mode, violation, details, false));
}

public ComplianceViolationException(ComplianceViolation.Event event)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,10 @@ public List<HttpCookie> getCookies(HttpFields headers, ComplianceViolation.Liste
}

if (_violations != null && !_violations.isEmpty())
{
CookieCompliance cookieCompliance = _parser.getCookieCompliance();
_violations.forEach(complianceViolationListener::onComplianceViolation);
}

return _cookieList == null ? Collections.emptyList() : _cookieList;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -371,10 +371,17 @@ else if (tokenstart >= 0)
}
}

@Override
public CookieCompliance getCookieCompliance()
{
return _complianceMode;
}

protected void reportComplianceViolation(CookieCompliance.Violation violation, String reason)
{
boolean allows = _complianceMode.allows(violation);
if (_complianceListener != null)
_complianceListener.onComplianceViolation(new ComplianceViolation.Event(_complianceMode, violation, reason));
_complianceListener.onComplianceViolation(new ComplianceViolation.Event(_complianceMode, violation, reason, allows));
}

protected boolean isRFC6265RejectedCharacter(char c)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ default void parseFields(List<String> rawFields) throws InvalidCookieException
parseField(field);
}

CookieCompliance getCookieCompliance();

/**
* The handler of parsed cookies.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;

import org.eclipse.jetty.util.StringUtil;
import org.slf4j.Logger;
Expand Down Expand Up @@ -340,6 +341,42 @@ public HttpCompliance without(String name, Violation... violations)
return new HttpCompliance(name, remainder);
}

/**
* Assert that the specified Violation is allowed.
*
* @param violation the violation to check if allowed
* @param listener the listener to report to
* @param error the function to produce a Throwable if not allowed
* @param <T> the type of Throwable
* @throws T Throwable if not allowed
*/
public <T extends Throwable> void assertAllowed(HttpCompliance.Violation violation, ComplianceViolation.Listener listener, Function<String, T> error) throws T
{
assertAllowed(violation, listener, violation.getDescription(), error);
}

/**
* Assert that the specified Violation is allowed.
*
* @param violation the violation to check if allowed
* @param listener the listener to report to
* @param detail the detail on the listener event
* @param error the function to produce a Throwable if not allowed
* @param <T> the type of Throwable
* @throws T Throwable if not allowed
*/
public <T extends Throwable> void assertAllowed(HttpCompliance.Violation violation, ComplianceViolation.Listener listener, String detail, Function<String, T> error) throws T
{
boolean allowed = this.allows(violation);

// Always report violation to listeners
if (listener != null)
listener.onComplianceViolation(new ComplianceViolation.Event(this, violation, detail, allowed));

if (!allowed)
throw error.apply(violation.getDescription());
}

@Override
public String toString()
{
Expand All @@ -360,7 +397,7 @@ private static Set<Violation> copyOf(Set<Violation> violations)
return EnumSet.copyOf(violations);
}

public static void checkHttpCompliance(MetaData.Request request, HttpCompliance mode,
public static void checkHttpCompliance(MetaData.Request request, HttpCompliance httpCompliance,
ComplianceViolation.Listener listener)
{
boolean seenContentLength = false;
Expand All @@ -378,43 +415,33 @@ public static void checkHttpCompliance(MetaData.Request request, HttpCompliance
case CONTENT_LENGTH ->
{
if (seenContentLength)
assertAllowed(Violation.MULTIPLE_CONTENT_LENGTHS, mode, listener);
httpCompliance.assertAllowed(Violation.MULTIPLE_CONTENT_LENGTHS, listener, BadMessageException::new);
String[] lengths = httpField.getValues();
if (lengths.length > 1)
assertAllowed(Violation.MULTIPLE_CONTENT_LENGTHS, mode, listener);
httpCompliance.assertAllowed(Violation.MULTIPLE_CONTENT_LENGTHS, listener, BadMessageException::new);
if (seenTransferEncoding)
assertAllowed(Violation.TRANSFER_ENCODING_WITH_CONTENT_LENGTH, mode, listener);
httpCompliance.assertAllowed(Violation.TRANSFER_ENCODING_WITH_CONTENT_LENGTH, listener, BadMessageException::new);
seenContentLength = true;
}
case TRANSFER_ENCODING ->
{
if (seenContentLength)
assertAllowed(Violation.TRANSFER_ENCODING_WITH_CONTENT_LENGTH, mode, listener);
httpCompliance.assertAllowed(Violation.TRANSFER_ENCODING_WITH_CONTENT_LENGTH, listener, BadMessageException::new);
seenTransferEncoding = true;
}
case HOST ->
{
if (seenHostHeader)
assertAllowed(Violation.DUPLICATE_HOST_HEADERS, mode, listener);
httpCompliance.assertAllowed(Violation.DUPLICATE_HOST_HEADERS, listener, BadMessageException::new);
String[] hostValues = httpField.getValues();
if (hostValues.length > 1)
assertAllowed(Violation.DUPLICATE_HOST_HEADERS, mode, listener);
httpCompliance.assertAllowed(Violation.DUPLICATE_HOST_HEADERS, listener, BadMessageException::new);
for (String hostValue: hostValues)
if (StringUtil.isBlank(hostValue))
assertAllowed(Violation.UNSAFE_HOST_HEADER, mode, listener);
httpCompliance.assertAllowed(Violation.UNSAFE_HOST_HEADER, listener, BadMessageException::new);
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
Expand Up @@ -374,8 +374,9 @@ protected void reportComplianceViolation(Violation violation)

protected void reportComplianceViolation(Violation violation, String reason)
{
boolean allowed = _complianceMode.allows(violation);
if (_requestParser)
_requestHandler.onViolation(new ComplianceViolation.Event(_complianceMode, violation, reason));
_requestHandler.onViolation(new ComplianceViolation.Event(_complianceMode, violation, reason, allowed));
}

protected String caseInsensitiveHeader(String orig, String normative)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;

import static java.util.EnumSet.allOf;
import static java.util.EnumSet.noneOf;
Expand Down Expand Up @@ -209,6 +210,29 @@ public Set<Violation> getAllowed()
return violations;
}

/**
* Assert that the specified Violation is allowed.
*
* @param violation the violation to check if allowed
* @param listener the listener to report to
* @param error the function to produce a Throwable if not allowed
* @param <T> the type of Throwable
* @throws T Throwable if not allowed
*/
public <T extends Throwable> void assertAllowed(MultiPartCompliance.Violation violation, ComplianceViolation.Listener listener, String detail, Function<String, T> error) throws T
{
boolean allowed = this.allows(violation);

// Always report violation to listeners
if (listener != null)
listener.onComplianceViolation(new ComplianceViolation.Event(this, violation, detail, allowed));

if (!allowed)
{
throw error.apply(detail);
}
}

@Override
public String toString()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,14 @@ int getPartsSize()
return listener.getPartsSize();
}

private boolean complianceAllows(ComplianceViolation violation, String reason)
{
boolean allowed = compliance.allows(violation);
if (complianceListener != null)
complianceListener.onComplianceViolation(new ComplianceViolation.Event(compliance, violation, reason, allowed));
return allowed;
}

private class PartsListener extends MultiPart.AbstractPartsListener
{
private final AutoLock lock = new AutoLock();
Expand Down Expand Up @@ -825,31 +833,14 @@ public void onPart(String name, String fileName, HttpFields headers)
{
switch (StringUtil.asciiToLowerCase(value))
{
case "base64" ->
{
complianceListener.onComplianceViolation(
new ComplianceViolation.Event(compliance,
MultiPartCompliance.Violation.BASE64_TRANSFER_ENCODING,
value));
}
case "base64" -> complianceAllows(MultiPartCompliance.Violation.BASE64_TRANSFER_ENCODING, value);
case "quoted-printable" ->
{
complianceListener.onComplianceViolation(
new ComplianceViolation.Event(compliance,
MultiPartCompliance.Violation.QUOTED_PRINTABLE_TRANSFER_ENCODING,
value));
}
complianceAllows(MultiPartCompliance.Violation.QUOTED_PRINTABLE_TRANSFER_ENCODING, value);
case "8bit", "binary" ->
{
// ignore
}
default ->
{
complianceListener.onComplianceViolation(
new ComplianceViolation.Event(compliance,
MultiPartCompliance.Violation.CONTENT_TRANSFER_ENCODING,
value));
}
default -> complianceAllows(MultiPartCompliance.Violation.CONTENT_TRANSFER_ENCODING, value);
}
}

Expand Down Expand Up @@ -912,7 +903,8 @@ public void onViolation(MultiPartCompliance.Violation violation)
{
try
{
ComplianceViolation.Event event = new ComplianceViolation.Event(compliance, violation, "multipart spec violation");
boolean allowed = compliance.allows(violation);
ComplianceViolation.Event event = new ComplianceViolation.Event(compliance, violation, "multipart spec violation", allowed);
complianceListener.onComplianceViolation(event);
}
catch (Throwable x)
Expand Down
Loading