diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java index 73002187133..af2dcda012b 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java @@ -50,9 +50,29 @@ public interface HttpURI { enum Ambiguous { + /** + * URI contains ambiguous path segments e.g. {@code /foo/%2e%2e/bar} + */ SEGMENT, + + /** + * URI contains ambiguous empty segments e.g. {@code /foo//bar} or {@code /foo/;param/}, but not {@code /foo/} + */ + EMPTY, + + /** + * URI contains ambiguous path separator within a URI segment e.g. {@code /foo/b%2fr} + */ SEPARATOR, + + /** + * URI contains ambiguous path encoding within a URI segment e.g. {@code /%2557EB-INF} + */ ENCODING, + + /** + * URI contains ambiguous path parameters within a URI segment e.g. {@code /foo/..;/bar} + */ PARAM } @@ -150,6 +170,11 @@ public interface HttpURI */ boolean hasAmbiguousSegment(); + /** + * @return True if the URI empty segment that is ambiguous like '//' or '/;param/'. + */ + boolean hasAmbiguousEmptySegment(); + /** * @return True if the URI has a possibly ambiguous separator of %2f */ @@ -377,13 +402,19 @@ public interface HttpURI @Override public boolean hasAmbiguousSegment() { - return _ambiguous.contains(Mutable.Ambiguous.SEGMENT); + return _ambiguous.contains(Ambiguous.SEGMENT); + } + + @Override + public boolean hasAmbiguousEmptySegment() + { + return _ambiguous.contains(Ambiguous.EMPTY); } @Override public boolean hasAmbiguousSeparator() { - return _ambiguous.contains(Mutable.Ambiguous.SEPARATOR); + return _ambiguous.contains(Ambiguous.SEPARATOR); } @Override @@ -468,6 +499,7 @@ public interface HttpURI private String _uri; private String _decodedPath; private final EnumSet _ambiguous = EnumSet.noneOf(Ambiguous.class); + private boolean _emptySegment; private Mutable() { @@ -727,6 +759,12 @@ public interface HttpURI return _ambiguous.contains(Mutable.Ambiguous.SEGMENT); } + @Override + public boolean hasAmbiguousEmptySegment() + { + return _ambiguous.contains(Ambiguous.EMPTY); + } + @Override public boolean hasAmbiguousSeparator() { @@ -904,6 +942,7 @@ public interface HttpURI boolean dot = false; // set to true if the path containers . or .. segments int escapedTwo = 0; // state of parsing a %2 int end = uri.length(); + _emptySegment = false; for (int i = 0; i < end; i++) { char c = uri.charAt(i); @@ -919,16 +958,21 @@ public interface HttpURI state = State.HOST_OR_PATH; break; case ';': + checkSegment(uri, segment, i, true); mark = i + 1; state = State.PARAM; break; case '?': // assume empty path (if seen at start) + checkSegment(uri, segment, i, false); _path = ""; mark = i + 1; state = State.QUERY; break; case '#': + // assume empty path (if seen at start) + checkSegment(uri, segment, i, false); + _path = ""; mark = i + 1; state = State.FRAGMENT; break; @@ -1130,7 +1174,9 @@ public interface HttpURI state = State.FRAGMENT; break; case '/': - checkSegment(uri, segment, i, false); + // There is no leading segment when parsing only a path that starts with slash. + if (i != 0) + checkSegment(uri, segment, i, false); segment = i + 1; break; case '.': @@ -1218,6 +1264,9 @@ public interface HttpURI switch (state) { case START: + _path = ""; + checkSegment(uri, segment, end, false); + break; case ASTERISK: break; case SCHEME_OR_PATH: @@ -1279,13 +1328,45 @@ public interface HttpURI */ private void checkSegment(String uri, int segment, int end, boolean param) { - if (!_ambiguous.contains(Ambiguous.SEGMENT)) + // This method is called once for every segment parsed. + // A URI like "/foo/" has two segments: "foo" and an empty segment. + // Empty segments are only ambiguous if they are not the last segment + // So if this method is called for any segment and we have previously seen an empty segment, then it was ambiguous + if (_emptySegment) + _ambiguous.add(Ambiguous.EMPTY); + + if (end == segment) { - Boolean ambiguous = __ambiguousSegments.get(uri, segment, end - segment); - if (ambiguous == Boolean.TRUE) - _ambiguous.add(Ambiguous.SEGMENT); - else if (param && ambiguous == Boolean.FALSE) - _ambiguous.add(Ambiguous.PARAM); + // Empty segments are not ambiguous if followed by a '#', '?' or end of string. + if (end >= uri.length() || ("#?".indexOf(uri.charAt(end)) >= 0)) + return; + + // If this empty segment is the first segment then it is ambiguous. + if (segment == 0) + { + _ambiguous.add(Ambiguous.EMPTY); + return; + } + + // Otherwise remember we have seen an empty segment, which is check if we see a subsequent segment. + if (!_emptySegment) + { + _emptySegment = true; + return; + } + } + + // Look for segment in the ambiguous segment index. + Boolean ambiguous = __ambiguousSegments.get(uri, segment, end - segment); + if (ambiguous == Boolean.TRUE) + { + // The segment is always ambiguous. + _ambiguous.add(Ambiguous.SEGMENT); + } + else if (param && ambiguous == Boolean.FALSE) + { + // The segment is ambiguous only when followed by a parameter. + _ambiguous.add(Ambiguous.PARAM); } } } diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/UriCompliance.java b/jetty-http/src/main/java/org/eclipse/jetty/http/UriCompliance.java index cddb35dc3af..29b51dc9bff 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/UriCompliance.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/UriCompliance.java @@ -50,6 +50,10 @@ public final class UriCompliance implements ComplianceViolation.Mode * Allow ambiguous path segments e.g. /foo/%2e%2e/bar */ AMBIGUOUS_PATH_SEGMENT("https://tools.ietf.org/html/rfc3986#section-3.3", "Ambiguous URI path segment"), + /** + * Allow ambiguous empty segments e.g. // + */ + AMBIGUOUS_EMPTY_SEGMENT("https://tools.ietf.org/html/rfc3986#section-3.3", "Ambiguous URI empty segment"), /** * Allow ambiguous path separator within a URI segment e.g. /foo/b%2fr */ @@ -104,9 +108,10 @@ public final class UriCompliance implements ComplianceViolation.Mode public static final UriCompliance DEFAULT = new UriCompliance("DEFAULT", of(Violation.AMBIGUOUS_PATH_SEPARATOR)); /** - * LEGACY compliance mode that models Jetty-9.4 behavior by allowing {@link Violation#AMBIGUOUS_PATH_SEGMENT}, {@link Violation#AMBIGUOUS_PATH_SEPARATOR} and {@link Violation#AMBIGUOUS_PATH_ENCODING}. + * LEGACY compliance mode that models Jetty-9.4 behavior by allowing {@link Violation#AMBIGUOUS_PATH_SEGMENT}, + * {@link Violation#AMBIGUOUS_EMPTY_SEGMENT}, {@link Violation#AMBIGUOUS_PATH_SEPARATOR} and {@link Violation#AMBIGUOUS_PATH_ENCODING}. */ - public static final UriCompliance LEGACY = new UriCompliance("LEGACY", of(Violation.AMBIGUOUS_PATH_SEGMENT, Violation.AMBIGUOUS_PATH_SEPARATOR, Violation.AMBIGUOUS_PATH_ENCODING)); + public static final UriCompliance LEGACY = new UriCompliance("LEGACY", of(Violation.AMBIGUOUS_PATH_SEGMENT, Violation.AMBIGUOUS_PATH_SEPARATOR, Violation.AMBIGUOUS_PATH_ENCODING, Violation.AMBIGUOUS_EMPTY_SEGMENT)); /** * Compliance mode that exactly follows RFC3986, including allowing all additional ambiguous URI Violations, diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpURITest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpURITest.java index 7c23d38eb2e..09fcc5ff19a 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpURITest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpURITest.java @@ -28,6 +28,7 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -361,6 +362,21 @@ public class HttpURITest {".%2e", "..", EnumSet.of(Ambiguous.SEGMENT)}, {"%2e%2e", "..", EnumSet.of(Ambiguous.SEGMENT)}, + // empty segment treated as ambiguous + {"/foo//bar", "/foo//bar", EnumSet.of(Ambiguous.EMPTY)}, + {"/foo//../bar", "/foo/bar", EnumSet.of(Ambiguous.EMPTY)}, + {"/foo///../../../bar", "/bar", EnumSet.of(Ambiguous.EMPTY)}, + {"/foo/./../bar", "/bar", EnumSet.noneOf(Ambiguous.class)}, + {"/foo//./bar", "/foo//bar", EnumSet.of(Ambiguous.EMPTY)}, + {"foo/bar", "foo/bar", EnumSet.noneOf(Ambiguous.class)}, + {"foo;/bar", "foo/bar", EnumSet.noneOf(Ambiguous.class)}, + {";/bar", "/bar", EnumSet.of(Ambiguous.EMPTY)}, + {";?n=v", "", EnumSet.of(Ambiguous.EMPTY)}, + {"?n=v", "", EnumSet.noneOf(Ambiguous.class)}, + {"#n=v", "", EnumSet.noneOf(Ambiguous.class)}, + {"", "", EnumSet.noneOf(Ambiguous.class)}, + {"http:/foo", "/foo", EnumSet.noneOf(Ambiguous.class)}, + // ambiguous parameter inclusions {"/path/.;/info", "/path/./info", EnumSet.of(Ambiguous.PARAM)}, {"/path/.;param/info", "/path/./info", EnumSet.of(Ambiguous.PARAM)}, @@ -377,6 +393,11 @@ public class HttpURITest {"%2F/info", "//info", EnumSet.of(Ambiguous.SEPARATOR)}, {"/path/%2f../info", "/path//../info", EnumSet.of(Ambiguous.SEPARATOR)}, + // ambiguous encoding + {"/path/%25/info", "/path/%/info", EnumSet.of(Ambiguous.ENCODING)}, + {"%25/info", "%/info", EnumSet.of(Ambiguous.ENCODING)}, + {"/path/%25../info", "/path/%../info", EnumSet.of(Ambiguous.ENCODING)}, + // combinations {"/path/%2f/..;/info", "/path///../info", EnumSet.of(Ambiguous.SEPARATOR, Ambiguous.PARAM)}, {"/path/%2f/..;/%2e/info", "/path///.././info", EnumSet.of(Ambiguous.SEPARATOR, Ambiguous.PARAM, Ambiguous.SEGMENT)}, @@ -401,10 +422,125 @@ public class HttpURITest assertThat(uri.hasAmbiguousSegment(), is(expected.contains(Ambiguous.SEGMENT))); assertThat(uri.hasAmbiguousSeparator(), is(expected.contains(Ambiguous.SEPARATOR))); assertThat(uri.hasAmbiguousParameter(), is(expected.contains(Ambiguous.PARAM))); + assertThat(uri.hasAmbiguousEncoding(), is(expected.contains(Ambiguous.ENCODING))); } catch (Exception e) { assertThat(decodedPath, nullValue()); } } + + public static Stream testPathQueryTests() + { + return Arrays.stream(new Object[][] + { + // Simple path example + {"/path/info", "/path/info", EnumSet.noneOf(Ambiguous.class)}, + + // legal non ambiguous relative paths + {"/path/../info", "/info", EnumSet.noneOf(Ambiguous.class)}, + {"/path/./info", "/path/info", EnumSet.noneOf(Ambiguous.class)}, + {"path/../info", "info", EnumSet.noneOf(Ambiguous.class)}, + {"path/./info", "path/info", EnumSet.noneOf(Ambiguous.class)}, + + // illegal paths + {"/../path/info", null, null}, + {"../path/info", null, null}, + {"/path/%XX/info", null, null}, + {"/path/%2/F/info", null, null}, + + // ambiguous dot encodings + {"/path/%2e/info", "/path/./info", EnumSet.of(Ambiguous.SEGMENT)}, + {"path/%2e/info/", "path/./info/", EnumSet.of(Ambiguous.SEGMENT)}, + {"/path/%2e%2e/info", "/path/../info", EnumSet.of(Ambiguous.SEGMENT)}, + {"/path/%2e%2e;/info", "/path/../info", EnumSet.of(Ambiguous.SEGMENT)}, + {"/path/%2e%2e;param/info", "/path/../info", EnumSet.of(Ambiguous.SEGMENT)}, + {"/path/%2e%2e;param;other/info;other", "/path/../info", EnumSet.of(Ambiguous.SEGMENT)}, + {"%2e/info", "./info", EnumSet.of(Ambiguous.SEGMENT)}, + {"%2e%2e/info", "../info", EnumSet.of(Ambiguous.SEGMENT)}, + {"%2e%2e;/info", "../info", EnumSet.of(Ambiguous.SEGMENT)}, + {"%2e", ".", EnumSet.of(Ambiguous.SEGMENT)}, + {"%2e.", "..", EnumSet.of(Ambiguous.SEGMENT)}, + {".%2e", "..", EnumSet.of(Ambiguous.SEGMENT)}, + {"%2e%2e", "..", EnumSet.of(Ambiguous.SEGMENT)}, + + // empty segment treated as ambiguous + {"/", "/", EnumSet.noneOf(Ambiguous.class)}, + {"/#", "/", EnumSet.noneOf(Ambiguous.class)}, + {"/path", "/path", EnumSet.noneOf(Ambiguous.class)}, + {"/path/", "/path/", EnumSet.noneOf(Ambiguous.class)}, + {"//", "//", EnumSet.of(Ambiguous.EMPTY)}, + {"/foo//", "/foo//", EnumSet.of(Ambiguous.EMPTY)}, + {"/foo//bar", "/foo//bar", EnumSet.of(Ambiguous.EMPTY)}, + {"//foo/bar", "//foo/bar", EnumSet.of(Ambiguous.EMPTY)}, + {"/foo?bar", "/foo", EnumSet.noneOf(Ambiguous.class)}, + {"/foo#bar", "/foo", EnumSet.noneOf(Ambiguous.class)}, + {"/foo;bar", "/foo", EnumSet.noneOf(Ambiguous.class)}, + {"/foo/?bar", "/foo/", EnumSet.noneOf(Ambiguous.class)}, + {"/foo/#bar", "/foo/", EnumSet.noneOf(Ambiguous.class)}, + {"/foo/;param", "/foo/", EnumSet.noneOf(Ambiguous.class)}, + {"/foo/;param/bar", "/foo//bar", EnumSet.of(Ambiguous.EMPTY)}, + {"/foo//bar", "/foo//bar", EnumSet.of(Ambiguous.EMPTY)}, + {"/foo//bar//", "/foo//bar//", EnumSet.of(Ambiguous.EMPTY)}, + {"//foo//bar//", "//foo//bar//", EnumSet.of(Ambiguous.EMPTY)}, + {"/foo//../bar", "/foo/bar", EnumSet.of(Ambiguous.EMPTY)}, + {"/foo///../../../bar", "/bar", EnumSet.of(Ambiguous.EMPTY)}, + {"/foo/./../bar", "/bar", EnumSet.noneOf(Ambiguous.class)}, + {"/foo//./bar", "/foo//bar", EnumSet.of(Ambiguous.EMPTY)}, + {"foo/bar", "foo/bar", EnumSet.noneOf(Ambiguous.class)}, + {"foo;/bar", "foo/bar", EnumSet.noneOf(Ambiguous.class)}, + {";/bar", "/bar", EnumSet.of(Ambiguous.EMPTY)}, + {";?n=v", "", EnumSet.of(Ambiguous.EMPTY)}, + {"?n=v", "", EnumSet.noneOf(Ambiguous.class)}, + {"#n=v", "", EnumSet.noneOf(Ambiguous.class)}, + {"", "", EnumSet.noneOf(Ambiguous.class)}, + + // ambiguous parameter inclusions + {"/path/.;/info", "/path/./info", EnumSet.of(Ambiguous.PARAM)}, + {"/path/.;param/info", "/path/./info", EnumSet.of(Ambiguous.PARAM)}, + {"/path/..;/info", "/path/../info", EnumSet.of(Ambiguous.PARAM)}, + {"/path/..;param/info", "/path/../info", EnumSet.of(Ambiguous.PARAM)}, + {".;/info", "./info", EnumSet.of(Ambiguous.PARAM)}, + {".;param/info", "./info", EnumSet.of(Ambiguous.PARAM)}, + {"..;/info", "../info", EnumSet.of(Ambiguous.PARAM)}, + {"..;param/info", "../info", EnumSet.of(Ambiguous.PARAM)}, + + // ambiguous segment separators + {"/path/%2f/info", "/path///info", EnumSet.of(Ambiguous.SEPARATOR)}, + {"%2f/info", "//info", EnumSet.of(Ambiguous.SEPARATOR)}, + {"%2F/info", "//info", EnumSet.of(Ambiguous.SEPARATOR)}, + {"/path/%2f../info", "/path//../info", EnumSet.of(Ambiguous.SEPARATOR)}, + + // ambiguous encoding + {"/path/%25/info", "/path/%/info", EnumSet.of(Ambiguous.ENCODING)}, + {"%25/info", "%/info", EnumSet.of(Ambiguous.ENCODING)}, + {"/path/%25../info", "/path/%../info", EnumSet.of(Ambiguous.ENCODING)}, + + // combinations + {"/path/%2f/..;/info", "/path///../info", EnumSet.of(Ambiguous.SEPARATOR, Ambiguous.PARAM)}, + {"/path/%2f/..;/%2e/info", "/path///.././info", EnumSet.of(Ambiguous.SEPARATOR, Ambiguous.PARAM, Ambiguous.SEGMENT)}, + {"/path/%2f/%25/..;/%2e//info", "/path///%/.././/info", EnumSet.of(Ambiguous.SEPARATOR, Ambiguous.PARAM, Ambiguous.SEGMENT, Ambiguous.ENCODING, Ambiguous.EMPTY)}, + }).map(Arguments::of); + } + + @ParameterizedTest + @MethodSource("testPathQueryTests") + public void testPathQuery(String input, String decodedPath, EnumSet expected) + { + // If expected is null then it is a bad URI and should throw. + if (expected == null) + { + assertThrows(Throwable.class, () -> HttpURI.build().pathQuery(input)); + return; + } + + HttpURI uri = HttpURI.build().pathQuery(input); + assertThat(uri.getDecodedPath(), is(decodedPath)); + assertThat(uri.isAmbiguous(), is(!expected.isEmpty())); + assertThat(uri.hasAmbiguousEmptySegment(), is(expected.contains(Ambiguous.EMPTY))); + assertThat(uri.hasAmbiguousSegment(), is(expected.contains(Ambiguous.SEGMENT))); + assertThat(uri.hasAmbiguousSeparator(), is(expected.contains(Ambiguous.SEPARATOR))); + assertThat(uri.hasAmbiguousParameter(), is(expected.contains(Ambiguous.PARAM))); + assertThat(uri.hasAmbiguousEncoding(), is(expected.contains(Ambiguous.ENCODING))); + } } diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ByteBufferPool.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ByteBufferPool.java index e752453475e..2c67e29fdc6 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/ByteBufferPool.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ByteBufferPool.java @@ -15,9 +15,9 @@ package org.eclipse.jetty.io; import java.nio.ByteBuffer; import java.util.ArrayList; -import java.util.Deque; import java.util.List; -import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; @@ -149,7 +149,7 @@ public interface ByteBufferPool public static class Bucket { - private final Deque _queue = new ConcurrentLinkedDeque<>(); + private final Queue _queue = new ConcurrentLinkedQueue<>(); private final int _capacity; private final int _maxSize; private final AtomicInteger _size; @@ -209,7 +209,7 @@ public interface ByteBufferPool private void queueOffer(ByteBuffer buffer) { - _queue.offerFirst(buffer); + _queue.offer(buffer); } private ByteBuffer queuePoll() 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 ff854e1d375..b658ca8afae 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 @@ -1252,10 +1252,6 @@ public class Request implements HttpServletRequest @Override public RequestDispatcher getRequestDispatcher(String path) { - // path is encoded, potentially with query - - path = URIUtil.compactPath(path); - if (path == null || _context == null) return null; @@ -1699,6 +1695,8 @@ public class Request implements HttpServletRequest compliance = _channel == null || _channel.getHttpConfiguration() == null ? null : _channel.getHttpConfiguration().getUriCompliance(); if (uri.hasAmbiguousSegment() && (compliance == null || !compliance.allows(UriCompliance.Violation.AMBIGUOUS_PATH_SEGMENT))) throw new BadMessageException("Ambiguous segment in URI"); + if (uri.hasAmbiguousEmptySegment() && (compliance == null || !compliance.allows(UriCompliance.Violation.AMBIGUOUS_EMPTY_SEGMENT))) + throw new BadMessageException("Ambiguous empty segment in URI"); if (uri.hasAmbiguousSeparator() && (compliance == null || !compliance.allows(UriCompliance.Violation.AMBIGUOUS_PATH_SEPARATOR))) throw new BadMessageException("Ambiguous segment in URI"); if (uri.hasAmbiguousParameter() && (compliance == null || !compliance.allows(UriCompliance.Violation.AMBIGUOUS_PATH_PARAMETER))) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java index e9c7572c33c..44b2cf49bb0 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java @@ -1246,6 +1246,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu // check the target. if (DispatcherType.REQUEST.equals(dispatch) || DispatcherType.ASYNC.equals(dispatch)) { + // TODO: remove this once isCompact() has been deprecated for several releases. if (isCompactPath()) target = URIUtil.compactPath(target); if (!checkContext(target, baseRequest, response)) @@ -1804,7 +1805,9 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu /** * @return True if URLs are compacted to replace multiple '/'s with a single '/' + * @deprecated use {@code CompactPathRule} with {@code RewriteHandler} instead. */ + @Deprecated public boolean isCompactPath() { return _compactPath; @@ -1813,6 +1816,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu /** * @param compactPath True if URLs are compacted to replace multiple '/'s with a single '/' */ + @Deprecated public void setCompactPath(boolean compactPath) { _compactPath = compactPath; diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java index e4b3a49f231..33ea74c6115 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java @@ -1732,6 +1732,23 @@ public class RequestTest _connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.UNSAFE); assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 200")); } + + @Test + public void testAmbiguousDoubleSlash() throws Exception + { + _handler._checker = (request, response) -> true; + String request = "GET /ambiguous/doubleSlash// HTTP/1.0\r\n" + + "Host: whatever\r\n" + + "\r\n"; + _connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.DEFAULT); + assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 400")); + _connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.LEGACY); + assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 200")); + _connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.RFC3986); + assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 200")); + _connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.UNSAFE); + assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 200")); + } @Test public void testPushBuilder() diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/NcsaRequestLogTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/NcsaRequestLogTest.java index 30e25d1ef4c..cbbc3bf67d8 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/NcsaRequestLogTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/NcsaRequestLogTest.java @@ -136,21 +136,17 @@ public class NcsaRequestLogTest setup(logType); testHandlerServerStart(); - String log; - - /* _connector.getResponse("GET /foo?data=1 HTTP/1.0\nhost: host:80\n\n"); - log = _entries.poll(5, TimeUnit.SECONDS); + String log = _entries.poll(5, TimeUnit.SECONDS); assertThat(log, containsString("GET /foo?data=1 HTTP/1.0\" 200 ")); -*/ + _connector.getResponse("GET //bad/foo?data=1 HTTP/1.0\n\n"); log = _entries.poll(5, TimeUnit.SECONDS); - assertThat(log, containsString("GET //bad/foo?data=1 HTTP/1.0\" 200 ")); -/* + assertThat(log, containsString("GET //bad/foo?data=1 HTTP/1.0\" 400 ")); + _connector.getResponse("GET http://host:80/foo?data=1 HTTP/1.0\n\n"); log = _entries.poll(5, TimeUnit.SECONDS); assertThat(log, containsString("GET http://host:80/foo?data=1 HTTP/1.0\" 200 ")); - */ } @ParameterizedTest() diff --git a/tests/jetty-jmh/src/main/java/org/eclipse/jetty/util/ArrayByteBufferPoolBenchmark.java b/tests/jetty-jmh/src/main/java/org/eclipse/jetty/util/ArrayByteBufferPoolBenchmark.java new file mode 100644 index 00000000000..098e50f8a4d --- /dev/null +++ b/tests/jetty-jmh/src/main/java/org/eclipse/jetty/util/ArrayByteBufferPoolBenchmark.java @@ -0,0 +1,67 @@ +// +// ======================================================================== +// Copyright (c) 1995-2021 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.util; + +import java.nio.ByteBuffer; + +import org.eclipse.jetty.io.ArrayByteBufferPool; +import org.eclipse.jetty.io.ByteBufferPool; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +@State(Scope.Benchmark) +public class ArrayByteBufferPoolBenchmark +{ + private ByteBufferPool pool; + + @Setup + public void setUp() throws Exception + { + pool = new ArrayByteBufferPool(); + } + + @TearDown + public void tearDown() + { + pool = null; + } + + @Benchmark + public void testAcquireRelease() + { + ByteBuffer buffer = pool.acquire(2048, true); + pool.release(buffer); + } + + public static void main(String[] args) throws RunnerException + { + Options opt = new OptionsBuilder() + .include(ArrayByteBufferPoolBenchmark.class.getSimpleName()) + .warmupIterations(3) + .measurementIterations(3) + .forks(1) + .threads(8) + // .addProfiler(GCProfiler.class) + .build(); + + new Runner(opt).run(); + } +}