Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/jetty-12.0.x' into fix/jetty-12-…
Browse files Browse the repository at this point in the history
…10226-fcgi-HttpClientIdleTimeoutTest-leaks
  • Loading branch information
lorban committed Jan 25, 2024
2 parents 2b800a9 + b571c6a commit fa05eda
Show file tree
Hide file tree
Showing 114 changed files with 2,825 additions and 1,497 deletions.
2 changes: 1 addition & 1 deletion documentation/jetty-asciidoctor-extensions/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
</dependency>
<dependency>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>jetty-home-tester</artifactId>
<artifactId>jetty-testers</artifactId>
<scope>compile</scope>
</dependency>
</dependencies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
import org.asciidoctor.extension.IncludeProcessor;
import org.asciidoctor.extension.PreprocessorReader;
import org.asciidoctor.jruby.extension.spi.ExtensionRegistry;
import org.eclipse.jetty.tests.hometester.JettyHomeTester;
import org.eclipse.jetty.tests.testers.JettyHomeTester;

/**
* <p>Asciidoctor <em>include</em> extension that includes into
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,14 @@

package org.eclipse.jetty.http;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import org.eclipse.jetty.util.Attributes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* A Compliance Violation represents a requirement of an RFC, specification or Jetty implementation
* that may be allowed to be violated if it is included in a {@link ComplianceViolation.Mode}.
Expand Down Expand Up @@ -75,13 +81,119 @@ interface Mode
Set<? extends ComplianceViolation> getAllowed();
}

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

/**
* A listener that can be notified of violations.
*/
interface Listener
{
Listener NOOP = new Listener() {};

/**
* Initialize the listener in preparation for a new request life cycle.
* @return The Listener instance to use for the request life cycle.
*/
default Listener initialize()
{
return this;
}

/**
* A new Request has begun.
*
* @param request the request attributes, or null if the Request does not exist yet (eg: during parsing of HTTP/1.1 headers, before request is created)
*/
default void onRequestBegin(Attributes request)
{
}

/**
* A Request has ended.
*
* @param request the request attributes, or null if Request does not exist yet (eg: during handling of a {@link BadMessageException})
*/
default void onRequestEnd(Attributes request)
{
}

/**
* The compliance violation event.
*
* @param event the compliance violation event
*/
default void onComplianceViolation(Event event)
{
onComplianceViolation(event.mode, event.violation, event.details);
}

/**
* The compliance violation event.
*
* @param mode the mode
* @param violation the violation
* @param details the details
* @deprecated use {@link #onComplianceViolation(Event)} instead. Will be removed in Jetty 12.1.0
*/
@Deprecated(since = "12.0.6", forRemoval = true)
default void onComplianceViolation(Mode mode, ComplianceViolation violation, String details)
{
}
}

class LoggingListener implements Listener
{
private static final Logger LOG = LoggerFactory.getLogger(ComplianceViolation.class);

@Override
public void onComplianceViolation(Event event)
{
if (LOG.isDebugEnabled())
LOG.debug(event.toString());
}
}

class CapturingListener implements Listener
{
public static final String VIOLATIONS_ATTR_KEY = "org.eclipse.jetty.http.compliance.violations";

private final List<Event> events;

public CapturingListener()
{
this(null);
}

private CapturingListener(List<Event> events)
{
this.events = events;
}

@Override
public Listener initialize()
{
return new CapturingListener(new ArrayList<>());
}

@Override
public void onRequestBegin(Attributes request)
{
if (request != null)
request.setAttribute(VIOLATIONS_ATTR_KEY, events);
}

@Override
public void onComplianceViolation(Event event)
{
events.add(event);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.http;

public class ComplianceViolationException extends IllegalArgumentException
{
private final ComplianceViolation.Event event;

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

public ComplianceViolationException(ComplianceViolation.Event event)
{
super(event.toString());
this.event = event;
}

public ComplianceViolation.Event getEvent()
{
return event;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,30 @@
* cookies are not re-parsed.
*
*/
public class CookieCache implements CookieParser.Handler
public class CookieCache implements CookieParser.Handler, ComplianceViolation.Listener
{
protected static final Logger LOG = LoggerFactory.getLogger(CookieCache.class);
protected final List<String> _rawFields = new ArrayList<>();
protected List<HttpCookie> _cookieList;
private final CookieParser _parser;
private List<ComplianceViolation.Event> _violations;

public CookieCache()
{
this(CookieCompliance.RFC6265, null);
this(CookieCompliance.RFC6265);
}

public CookieCache(CookieCompliance compliance, ComplianceViolation.Listener complianceListener)
public CookieCache(CookieCompliance compliance)
{
_parser = CookieParser.newParser(this, compliance, complianceListener);
_parser = CookieParser.newParser(this, compliance, this);
}

@Override
public void onComplianceViolation(ComplianceViolation.Event event)
{
if (_violations == null)
_violations = new ArrayList<>();
_violations.add(event);
}

@Override
Expand All @@ -67,6 +76,11 @@ public void addCookie(String cookieName, String cookieValue, int cookieVersion,
}

public List<HttpCookie> getCookies(HttpFields headers)
{
return getCookies(headers, ComplianceViolation.Listener.NOOP);
}

public List<HttpCookie> getCookies(HttpFields headers, ComplianceViolation.Listener complianceViolationListener)
{
boolean building = false;
ListIterator<String> raw = _rawFields.listIterator();
Expand Down Expand Up @@ -136,6 +150,8 @@ public List<HttpCookie> getCookies(HttpFields headers)
_cookieList = new ArrayList<>();
try
{
if (_violations != null)
_violations.clear();
_parser.parseFields(_rawFields);
}
catch (CookieParser.InvalidCookieException invalidCookieException)
Expand All @@ -144,6 +160,9 @@ public List<HttpCookie> getCookies(HttpFields headers)
}
}

if (_violations != null && !_violations.isEmpty())
_violations.forEach(complianceViolationListener::onComplianceViolation);

return _cookieList == null ? Collections.emptyList() : _cookieList;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ else if (tokenstart >= 0)
protected void reportComplianceViolation(CookieCompliance.Violation violation, String reason)
{
if (_complianceListener != null)
_complianceListener.onComplianceViolation(_complianceMode, violation, reason);
_complianceListener.onComplianceViolation(new ComplianceViolation.Event(_complianceMode, violation, reason));
}

protected boolean isRFC6265RejectedCharacter(char c)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -156,8 +157,14 @@ public String getDescription()

/**
* The request attribute which may be set to record any allowed HTTP violations.
* @deprecated use {@link ComplianceViolation.CapturingListener#VIOLATIONS_ATTR_KEY} instead.<br>
* (Note: new ATTR captures all Compliance violations, not just HTTP.<br>
* Make sure you have {@code HttpConnectionFactory.setRecordHttpComplianceViolations(true)}.<br>
* Also make sure that a {@link ComplianceViolation.CapturingListener} has been added as a bean to
* either the {@code Connector} or {@code Server} for the Attribute to be created.)
*/
public static final String VIOLATIONS_ATTR = "org.eclipse.jetty.http.compliance.violations";
@Deprecated(since = "12.0.6", forRemoval = true)
public static final String VIOLATIONS_ATTR = ComplianceViolation.CapturingListener.VIOLATIONS_ATTR_KEY;

/**
* The HttpCompliance mode that supports <a href="https://tools.ietf.org/html/rfc7230">RFC 7230</a>
Expand Down Expand Up @@ -210,7 +217,8 @@ public static HttpCompliance valueOf(String name)
if (compliance.getName().equals(name))
return compliance;
}
LOG.warn("Unknown HttpCompliance mode {}", name);
if (name.indexOf(',') == -1) // skip warning if delimited, will be handled by .from() properly as a CUSTOM mode.
LOG.warn("Unknown HttpCompliance mode {}", name);
return null;
}

Expand Down Expand Up @@ -351,4 +359,65 @@ 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,
ComplianceViolation.Listener listener)
{
boolean seenContentLength = false;
boolean seenTransferEncoding = false;
boolean seenHostHeader = false;

HttpFields fields = request.getHttpFields();
for (HttpField httpField: fields)
{
if (httpField.getHeader() == null)
continue;

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());
}
}
Loading

0 comments on commit fa05eda

Please sign in to comment.