diff --git a/.gitattributes b/.gitattributes index b9b0a86549a..3f0ee056c1d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,7 +3,9 @@ *.txt eol=lf *.properties eol=lf *.java eol=lf +*.mod eol=lf +*.adoc eol=lf *.xml eol=lf Jenkinsfile eol=lf *.js eol=lf -*.raw binary \ No newline at end of file +*.raw binary diff --git a/Jenkinsfile b/Jenkinsfile index 639528cf8a0..2ff5e914b5f 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -12,6 +12,7 @@ for (def os in oss) { parallel builds + def getFullBuild(jdk, os) { return { node(os) { @@ -164,6 +165,7 @@ def getFullBuild(jdk, os) { notifyBuild("Compact3 Failure", jdk) throw e } + } } } diff --git a/Jmh_Jenkinsfile b/Jmh_Jenkinsfile new file mode 100644 index 00000000000..deab122aca3 --- /dev/null +++ b/Jmh_Jenkinsfile @@ -0,0 +1,56 @@ +#!groovy + + +node("linux") { + // System Dependent Locations + def mvntool = tool name: 'maven3.5', type: 'hudson.tasks.Maven$MavenInstallation' + def jdktool = tool name: "jdk8", type: 'hudson.model.JDK' + def mvnName = 'maven3.5' + def localRepo = "${env.JENKINS_HOME}/${env.EXECUTOR_NUMBER}" + def settingsName = 'oss-settings.xml' + def mavenOpts = '-Xms1g -Xmx4g -Djava.awt.headless=true' + + // Environment + List mvnEnv = ["PATH+MVN=${mvntool}/bin", "PATH+JDK=${jdktool}/bin", "JAVA_HOME=${jdktool}/", "MAVEN_HOME=${mvntool}"] + mvnEnv.add("MAVEN_OPTS=$mavenOpts") + + + stage("Checkout") { + git url: 'https://github.com/eclipse/jetty.project.git', branch: 'jetty-9.4.x' + } + + stage("Compile") { + withEnv(mvnEnv) { + timeout(time: 15, unit: 'MINUTES') { + withMaven( + maven: mvnName, + jdk: "jdk8", + publisherStrategy: 'EXPLICIT', + globalMavenSettingsConfig: settingsName, + mavenOpts: mavenOpts, + mavenLocalRepo: localRepo) { + sh "mvn -V -B clean install -DskipTests -T6 -e" + } + + } + stash name: 'perf-tests', includes: 'jetty-jmh/target/benchmarks.jar' + } + } +} + +// jmh run + +stage("jmh-run") { + node( 'jmh-build-node' ) { + timeout( time: 120, unit: 'MINUTES' ) { + withEnv( ["JAVA_HOME=${tool "jdk8"}"] ) { + unstash name: 'perf-tests' + sh "${env.JAVA_HOME}/bin/java -jar jetty-jmh/target/benchmarks.jar -rff jetty-jmh/target/jmh_result.json -rf json" + jmhReport 'jetty-jmh/target/jmh_result.json' + } + } + } +} + + +// vim: et:ts=2:sw=2:ft=groovy diff --git a/VERSION.txt b/VERSION.txt index 53495ba2570..f73551a94f6 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1,31 +1,5 @@ jetty-10.0.0-SNAPSHOT -jetty-9.4.12.RC0 - 11 July 2018 - + 901 Overriding SSL context KeyStoreType requires explicit override of - TrustStoreType - + 2075 Deprecating MultiException - + 2342 File Descriptor Leak: Conscrypt: "Too many open files" - + 2349 HTTP/2 max streams enforcement - + 2398 MultiPartFormInputStream parsing should default to UTF-8, but allowed - to be overridden by Request.setCharacterEncoding() - + 2468 EWYK concurrent produce can fail SSL connections - + 2501 Include accepting connections in connection limit - + 2530 Client waits forever for cancelled large uploads - + 2560 Review PathResource exception handling - + 2565 HashLoginService silently ignores file:/ config paths from 9.3.x - + 2631 IllegalArgumentException: Buffering capacity exceeded, from HttpClient - HEAD Requests to resources referencing large body contents - + 2648 LdapLoginModule fails with forceBinding=true under Java 9 - + 2655 WebSocketClient not removing closed WebSocket Session's from managed - beans - + 2662 Remove unnecessary boxing conversions - + 2663 Guard Throwable.addSuppressed() calls - + 2675 Demo rewrite rules prevent URL Session tracking - + 2677 Decode URI before matching against "/favicon.ico" - + 2683 NPE in FrameFlusher toString() - + 2684 MimeTypes.getAssumedEncodings() does not work - + 2696 GcloudDataStore dependency generation broken - jetty-9.4.11.v20180605 - 05 June 2018 + 1785 Support for vhost@connectorname syntax of virtual hosts + 2346 Revert stack trace logging for HTTPChannel.onException diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-12.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-12.mod new file mode 100644 index 00000000000..689601a4197 --- /dev/null +++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-12.mod @@ -0,0 +1,4 @@ +DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html + +[depend] +alpn-impl/alpn-9 diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java b/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java index c6a160ea214..6d390f87c08 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java @@ -47,7 +47,7 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler public static final int DEFAULT_MAX_CONTENT_LENGTH = 16*1024; public static final Logger LOG = Log.getLogger(AuthenticationProtocolHandler.class); - private static final Pattern PARAM_PATTERN = Pattern.compile("([^=]+)=([^=]+)?"); + private static final Pattern PARAM_PATTERN = Pattern.compile("([^=]+)=(.*)"); private static final Pattern TYPE_PATTERN = Pattern.compile("([^\\s]+)(\\s+(.*))?"); private static final Pattern MULTIPLE_CHALLENGE_PATTERN = Pattern.compile("(.*?)\\s*,\\s*([^=\\s,]+(\\s+[^=\\s].*)?)"); private static final Pattern BASE64_PATTERN = Pattern.compile("[\\+\\-\\.\\/\\dA-Z_a-z~]+=*"); diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java index ccaacd51dbd..aa6249d33fe 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java @@ -732,4 +732,40 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest Assert.assertTrue(headerInfos.get(1).getType().equalsIgnoreCase("Negotiate")); Assert.assertTrue(headerInfos.get(1).getBase64().equals("YIIJvwYGKwYBBQUCoIIJszCCCa+gJDAi=")); } + + + + @Test + public void testEqualsInParam() + { + AuthenticationProtocolHandler aph = new WWWAuthenticationProtocolHandler(client); + HeaderInfo headerInfo; + + headerInfo = aph.getHeaderInfo("Digest realm=\"=the=rmo=stat=\", qop=\"=a=u=t=h=\", nonce=\"=1523430383=\"").get(0); + Assert.assertTrue(headerInfo.getType().equalsIgnoreCase("Digest")); + Assert.assertTrue(headerInfo.getParameter("qop").equals("=a=u=t=h=")); + Assert.assertTrue(headerInfo.getParameter("realm").equals("=the=rmo=stat=")); + Assert.assertTrue(headerInfo.getParameter("nonce").equals("=1523430383=")); + + + // test multiple authentications + List headerInfoList = aph.getHeaderInfo("Digest qop=\"=au=th=\", realm=\"=ther=mostat=\", nonce=\"=152343=0383=\", " + + "Digest realm=\"=thermostat2\", qop=\"=auth2\", nonce=\"=4522530354\", " + + "Digest qop=\"auth3=\", nonce=\"9523570528=\", realm=\"thermostat3=\", "); + + Assert.assertTrue(headerInfoList.get(0).getType().equalsIgnoreCase("Digest")); + Assert.assertTrue(headerInfoList.get(0).getParameter("qop").equals("=au=th=")); + Assert.assertTrue(headerInfoList.get(0).getParameter("realm").equals("=ther=mostat=")); + Assert.assertTrue(headerInfoList.get(0).getParameter("nonce").equals("=152343=0383=")); + + Assert.assertTrue(headerInfoList.get(1).getType().equalsIgnoreCase("Digest")); + Assert.assertTrue(headerInfoList.get(1).getParameter("qop").equals("=auth2")); + Assert.assertTrue(headerInfoList.get(1).getParameter("realm").equals("=thermostat2")); + Assert.assertTrue(headerInfoList.get(1).getParameter("nonce").equals("=4522530354")); + + Assert.assertTrue(headerInfoList.get(2).getType().equalsIgnoreCase("Digest")); + Assert.assertTrue(headerInfoList.get(2).getParameter("qop").equals("auth3=")); + Assert.assertTrue(headerInfoList.get(2).getParameter("realm").equals("thermostat3=")); + Assert.assertTrue(headerInfoList.get(2).getParameter("nonce").equals("9523570528=")); + } } diff --git a/jetty-http-spi/src/test/java/org/eclipse/jetty/http/spi/util/PrintTask.java b/jetty-http-spi/src/test/java/org/eclipse/jetty/http/spi/util/PrintTask.java index 48679194ff6..f340e659b31 100644 --- a/jetty-http-spi/src/test/java/org/eclipse/jetty/http/spi/util/PrintTask.java +++ b/jetty-http-spi/src/test/java/org/eclipse/jetty/http/spi/util/PrintTask.java @@ -19,7 +19,7 @@ package org.eclipse.jetty.http.spi.util; /** - * + * * This is a sample task. Test cases uses this for testing purpose * */ diff --git a/jetty-http/pom.xml b/jetty-http/pom.xml index 5157bd87067..005a72daca4 100644 --- a/jetty-http/pom.xml +++ b/jetty-http/pom.xml @@ -33,18 +33,6 @@ jetty-test-helper test - - org.openjdk.jmh - jmh-core - ${jmh.version} - test - - - org.openjdk.jmh - jmh-generator-annprocess - ${jmh.version} - test - 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 3d3a86c96c6..11f51d13863 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 @@ -257,7 +257,7 @@ public class HttpURI state=State.PATH; encoded=true; break; - + default: mark=i; if (_scheme==null) @@ -336,14 +336,14 @@ public class HttpURI path_mark=mark; state=State.PATH; break; - + case '.': // it is a path encoded=true; path_mark=mark; state=State.PATH; break; - + default: // it is a path path_mark=mark; @@ -425,7 +425,7 @@ public class HttpURI } break; } - + case PATH: { switch (c) @@ -453,9 +453,9 @@ public class HttpURI } break; } - - - + + + case PARAM: { @@ -747,7 +747,7 @@ public class HttpURI _port=port; _uri=null; } - + /* ------------------------------------------------------------ */ /** * @param path the path @@ -769,7 +769,7 @@ public class HttpURI _path=URIUtil.encodePath(path); _decodedPath=path; } - + /* ------------------------------------------------------------ */ public void setPathQuery(String path) { @@ -808,7 +808,7 @@ public class HttpURI { return _host!=null; } - + /* ------------------------------------------------------------ */ public String getAuthority() { diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/MetaData.java b/jetty-http/src/main/java/org/eclipse/jetty/http/MetaData.java index 75551f76ede..decab4d73bd 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/MetaData.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/MetaData.java @@ -163,17 +163,26 @@ public class MetaData implements Iterable public Request(String method, HttpScheme scheme, HostPortHttpField hostPort, String uri, HttpVersion version, HttpFields fields) { - this(method, new HttpURI(scheme == null ? null : scheme.asString(), hostPort.getHost(), hostPort.getPort(), uri), version, fields); + this(method, new HttpURI(scheme == null ? null : scheme.asString(), + hostPort==null?null:hostPort.getHost(), + hostPort==null?-1:hostPort.getPort(), + uri), version, fields); } public Request(String method, HttpScheme scheme, HostPortHttpField hostPort, String uri, HttpVersion version, HttpFields fields, long contentLength) { - this(method, new HttpURI(scheme == null ? null : scheme.asString(), hostPort.getHost(), hostPort.getPort(), uri), version, fields, contentLength); + this(method, new HttpURI(scheme==null?null:scheme.asString(), + hostPort==null?null:hostPort.getHost(), + hostPort==null?-1:hostPort.getPort(), + uri), version, fields, contentLength); } public Request(String method, String scheme, HostPortHttpField hostPort, String uri, HttpVersion version, HttpFields fields, long contentLength) { - this(method, new HttpURI(scheme, hostPort.getHost(), hostPort.getPort(), uri), version, fields, contentLength); + this(method, new HttpURI(scheme, + hostPort==null?null:hostPort.getHost(), + hostPort==null?-1:hostPort.getPort(), + uri), version, fields, contentLength); } public Request(Request request) diff --git a/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2Client.java b/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2Client.java index 865379c68ac..03bd0d3d006 100644 --- a/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2Client.java +++ b/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2Client.java @@ -34,6 +34,7 @@ import org.eclipse.jetty.alpn.client.ALPNClientConnectionFactory; import org.eclipse.jetty.http2.BufferingFlowControlStrategy; import org.eclipse.jetty.http2.FlowControlStrategy; import org.eclipse.jetty.http2.api.Session; +import org.eclipse.jetty.http2.frames.Frame; import org.eclipse.jetty.http2.frames.SettingsFrame; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.ClientConnectionFactory; @@ -130,6 +131,8 @@ public class HTTP2Client extends ContainerLifeCycle private List protocols = Arrays.asList("h2", "h2-17", "h2-16", "h2-15", "h2-14"); private int initialSessionRecvWindow = 16 * 1024 * 1024; private int initialStreamRecvWindow = 8 * 1024 * 1024; + private int maxFrameLength = Frame.DEFAULT_MAX_LENGTH; + private int maxConcurrentPushedStreams = 32; private int maxSettingsKeys = SettingsFrame.DEFAULT_MAX_KEYS; private FlowControlStrategy.Factory flowControlStrategyFactory = () -> new BufferingFlowControlStrategy(0.5F); @@ -336,6 +339,28 @@ public class HTTP2Client extends ContainerLifeCycle this.initialStreamRecvWindow = initialStreamRecvWindow; } + @ManagedAttribute("The max frame length in bytes") + public int getMaxFrameLength() + { + return maxFrameLength; + } + + public void setMaxFrameLength(int maxFrameLength) + { + this.maxFrameLength = maxFrameLength; + } + + @ManagedAttribute("The max number of concurrent pushed streams") + public int getMaxConcurrentPushedStreams() + { + return maxConcurrentPushedStreams; + } + + public void setMaxConcurrentPushedStreams(int maxConcurrentPushedStreams) + { + this.maxConcurrentPushedStreams = maxConcurrentPushedStreams; + } + @ManagedAttribute("The max number of keys in all SETTINGS frames") public int getMaxSettingsKeys() { diff --git a/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientConnectionFactory.java b/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientConnectionFactory.java index 87aaee0d837..803d95b586a 100644 --- a/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientConnectionFactory.java +++ b/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientConnectionFactory.java @@ -18,7 +18,6 @@ package org.eclipse.jetty.http2.client; -import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Executor; @@ -53,7 +52,7 @@ public class HTTP2ClientConnectionFactory implements ClientConnectionFactory private final Connection.Listener connectionListener = new ConnectionListener(); @Override - public Connection newConnection(EndPoint endPoint, Map context) throws IOException + public Connection newConnection(EndPoint endPoint, Map context) { HTTP2Client client = (HTTP2Client)context.get(CLIENT_CONTEXT_KEY); ByteBufferPool byteBufferPool = (ByteBufferPool)context.get(BYTE_BUFFER_POOL_CONTEXT_KEY); @@ -66,8 +65,10 @@ public class HTTP2ClientConnectionFactory implements ClientConnectionFactory Generator generator = new Generator(byteBufferPool); FlowControlStrategy flowControl = client.getFlowControlStrategyFactory().newFlowControlStrategy(); HTTP2ClientSession session = new HTTP2ClientSession(scheduler, endPoint, generator, listener, flowControl); + session.setMaxRemoteStreams(client.getMaxConcurrentPushedStreams()); Parser parser = new Parser(byteBufferPool, session, 4096, 8192); + parser.setMaxFrameLength(client.getMaxFrameLength()); parser.setMaxSettingsKeys(client.getMaxSettingsKeys()); HTTP2ClientConnection connection = new HTTP2ClientConnection(client, byteBufferPool, executor, endPoint, @@ -111,6 +112,11 @@ public class HTTP2ClientConnectionFactory implements ClientConnectionFactory if (settings == null) settings = new HashMap<>(); settings.computeIfAbsent(SettingsFrame.INITIAL_WINDOW_SIZE, k -> client.getInitialStreamRecvWindow()); + settings.computeIfAbsent(SettingsFrame.MAX_CONCURRENT_STREAMS, k -> client.getMaxConcurrentPushedStreams()); + + Integer maxFrameLength = settings.get(SettingsFrame.MAX_FRAME_SIZE); + if (maxFrameLength != null) + getParser().setMaxFrameLength(maxFrameLength); PrefaceFrame prefaceFrame = new PrefaceFrame(); SettingsFrame settingsFrame = new SettingsFrame(settings, false); diff --git a/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientSession.java b/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientSession.java index 617a13fbba4..3be563d093c 100644 --- a/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientSession.java +++ b/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientSession.java @@ -78,6 +78,7 @@ public class HTTP2ClientSession extends HTTP2Session if (LOG.isDebugEnabled()) LOG.debug("Received {}", frame); + // HEADERS can be received for normal and pushed responses. int streamId = frame.getStreamId(); IStream stream = getStream(streamId); if (stream != null) @@ -96,7 +97,23 @@ public class HTTP2ClientSession extends HTTP2Session else { if (LOG.isDebugEnabled()) - LOG.debug("Ignoring {}, stream #{} not found", frame, streamId); + LOG.debug("Stream #{} not found", streamId); + if (isClientStream(streamId)) + { + // Normal stream. + // Headers or trailers arriving after + // the stream has been reset are ignored. + if (!isLocalStreamClosed(streamId)) + onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "unexpected_headers_frame"); + } + else + { + // Pushed stream. + // Headers or trailers arriving after + // the stream has been reset are ignored. + if (!isRemoteStreamClosed(streamId)) + onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "unexpected_headers_frame"); + } } } @@ -117,9 +134,12 @@ public class HTTP2ClientSession extends HTTP2Session else { IStream pushStream = createRemoteStream(pushStreamId); - pushStream.process(frame, Callback.NOOP); - Stream.Listener listener = notifyPush(stream, pushStream, frame); - pushStream.setListener(listener); + if (pushStream != null) + { + pushStream.process(frame, Callback.NOOP); + Stream.Listener listener = notifyPush(stream, pushStream, frame); + pushStream.setListener(listener); + } } } diff --git a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/FlowControlStrategyTest.java b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/FlowControlStrategyTest.java index 28862a21795..d601f2d3b17 100644 --- a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/FlowControlStrategyTest.java +++ b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/FlowControlStrategyTest.java @@ -734,7 +734,21 @@ public abstract class FlowControlStrategyTest public void testClientExceedingSessionWindow() throws Exception { // On server, we don't consume the data. - start(new ServerSessionListener.Adapter()); + start(new ServerSessionListener.Adapter() + { + @Override + public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) + { + return new Stream.Listener.Adapter() + { + @Override + public void onData(Stream stream, DataFrame frame, Callback callback) + { + // Do not succeed the callback. + } + }; + } + }); final CountDownLatch closeLatch = new CountDownLatch(1); Session session = newClient(new Session.Listener.Adapter() @@ -805,6 +819,19 @@ public abstract class FlowControlStrategyTest ((ISession)session).updateRecvWindow(FlowControlStrategy.DEFAULT_WINDOW_SIZE); return super.onPreface(session); } + + @Override + public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) + { + return new Stream.Listener.Adapter() + { + @Override + public void onData(Stream stream, DataFrame frame, Callback callback) + { + // Do not succeed the callback. + } + }; + } }); final CountDownLatch closeLatch = new CountDownLatch(1); diff --git a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/HTTP2Test.java b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/HTTP2Test.java index 9961e799e80..1a3a315d095 100644 --- a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/HTTP2Test.java +++ b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/HTTP2Test.java @@ -46,12 +46,7 @@ import org.eclipse.jetty.http2.api.server.ServerSessionListener; import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.GoAwayFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; -import org.eclipse.jetty.http2.frames.PingFrame; -import org.eclipse.jetty.http2.frames.PriorityFrame; -import org.eclipse.jetty.http2.frames.PushPromiseFrame; -import org.eclipse.jetty.http2.frames.ResetFrame; import org.eclipse.jetty.http2.frames.SettingsFrame; -import org.eclipse.jetty.http2.frames.WindowUpdateFrame; import org.eclipse.jetty.http2.parser.ServerParser; import org.eclipse.jetty.http2.server.RawHTTP2ServerConnectionFactory; import org.eclipse.jetty.server.Connector; @@ -752,7 +747,7 @@ public class HTTP2Test extends AbstractTest @Override protected ServerParser newServerParser(Connector connector, ServerParser.Listener listener) { - return super.newServerParser(connector, new ServerParserListenerWrapper(listener) + return super.newServerParser(connector, new ServerParser.Listener.Wrapper(listener) { @Override public void onGoAway(GoAwayFrame frame) @@ -806,80 +801,4 @@ public class HTTP2Test extends AbstractTest throw new RuntimeException(); } } - - private static class ServerParserListenerWrapper implements ServerParser.Listener - { - private final ServerParser.Listener listener; - - private ServerParserListenerWrapper(ServerParser.Listener listener) - { - this.listener = listener; - } - - @Override - public void onPreface() - { - listener.onPreface(); - } - - @Override - public void onData(DataFrame frame) - { - listener.onData(frame); - } - - @Override - public void onHeaders(HeadersFrame frame) - { - listener.onHeaders(frame); - } - - @Override - public void onPriority(PriorityFrame frame) - { - listener.onPriority(frame); - } - - @Override - public void onReset(ResetFrame frame) - { - listener.onReset(frame); - } - - @Override - public void onSettings(SettingsFrame frame) - { - listener.onSettings(frame); - } - - @Override - public void onPushPromise(PushPromiseFrame frame) - { - listener.onPushPromise(frame); - } - - @Override - public void onPing(PingFrame frame) - { - listener.onPing(frame); - } - - @Override - public void onGoAway(GoAwayFrame frame) - { - listener.onGoAway(frame); - } - - @Override - public void onWindowUpdate(WindowUpdateFrame frame) - { - listener.onWindowUpdate(frame); - } - - @Override - public void onConnectionFailure(int error, String reason) - { - listener.onConnectionFailure(error, reason); - } - } } diff --git a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/MaxPushedStreamsTest.java b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/MaxPushedStreamsTest.java new file mode 100644 index 00000000000..be26c7a8955 --- /dev/null +++ b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/MaxPushedStreamsTest.java @@ -0,0 +1,130 @@ +// +// ======================================================================== +// Copyright (c) 1995-2018 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.http2.client; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.function.BiFunction; +import java.util.function.BinaryOperator; +import java.util.stream.IntStream; + +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.http.MetaData; +import org.eclipse.jetty.http2.ErrorCode; +import org.eclipse.jetty.http2.HTTP2Session; +import org.eclipse.jetty.http2.api.Session; +import org.eclipse.jetty.http2.api.Stream; +import org.eclipse.jetty.http2.api.server.ServerSessionListener; +import org.eclipse.jetty.http2.frames.DataFrame; +import org.eclipse.jetty.http2.frames.HeadersFrame; +import org.eclipse.jetty.http2.frames.PushPromiseFrame; +import org.eclipse.jetty.http2.frames.ResetFrame; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.FuturePromise; +import org.eclipse.jetty.util.Promise; +import org.junit.Assert; +import org.junit.Test; + +public class MaxPushedStreamsTest extends AbstractTest +{ + @Test + public void testMaxPushedStreams() throws Exception + { + int maxPushed = 2; + + CountDownLatch resetLatch = new CountDownLatch(1); + start(new ServerSessionListener.Adapter() + { + @Override + public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) + { + // Trick the server into thinking it can push unlimited streams. + ((HTTP2Session)stream.getSession()).setMaxLocalStreams(-1); + + BiFunction, Stream, List> add = (l, s) -> { l.add(s); return l; }; + BinaryOperator> addAll = (l1, l2) -> { l1.addAll(l2); return l1; }; + CompletableFuture> result = CompletableFuture.completedFuture(new ArrayList<>()); + // Push maxPushed resources... + IntStream.range(0, maxPushed) + .mapToObj(i -> new PushPromiseFrame(stream.getId(), 0, newRequest("GET", "/push_" + i, new HttpFields()))) + .map(pushFrame -> + { + Promise.Completable promise = new Promise.Completable<>(); + stream.push(pushFrame, promise, new Stream.Listener.Adapter()); + return promise; + }) + // ... wait for the pushed streams... + .reduce(result, (cfList, cfStream) -> cfList.thenCombine(cfStream, add), + (cfList1, cfList2) -> cfList1.thenCombine(cfList2, addAll)) + // ... then push one extra stream, the client must reject it... + .thenApply(streams -> + { + PushPromiseFrame extraPushFrame = new PushPromiseFrame(stream.getId(), 0, newRequest("GET", "/push_extra", new HttpFields())); + FuturePromise extraPromise = new FuturePromise<>(); + stream.push(extraPushFrame, extraPromise, new Stream.Listener.Adapter() + { + @Override + public void onReset(Stream stream, ResetFrame frame) + { + Assert.assertEquals(ErrorCode.REFUSED_STREAM_ERROR.code, frame.getError()); + resetLatch.countDown(); + } + }); + return streams; + }) + // ... then send the data for the valid pushed streams... + .thenAccept(streams -> streams.forEach(pushedStream -> + { + DataFrame data = new DataFrame(pushedStream.getId(), BufferUtil.EMPTY_BUFFER, true); + pushedStream.data(data, Callback.NOOP); + })) + // ... then send the response. + .thenRun(() -> + { + MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, new HttpFields()); + stream.headers(new HeadersFrame(stream.getId(), response, null, true), Callback.NOOP); + }); + return null; + } + }); + client.setMaxConcurrentPushedStreams(maxPushed); + + Session session = newClient(new Session.Listener.Adapter()); + MetaData.Request request = newRequest("GET", new HttpFields()); + CountDownLatch responseLatch = new CountDownLatch(1); + session.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() + { + @Override + public void onHeaders(Stream stream, HeadersFrame frame) + { + if (frame.isEndStream()) + responseLatch.countDown(); + } + }); + + Assert.assertTrue(resetLatch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(responseLatch.await(5, TimeUnit.SECONDS)); + } +} diff --git a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/TrailersTest.java b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/TrailersTest.java index f63d5f50498..f772b2df29e 100644 --- a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/TrailersTest.java +++ b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/TrailersTest.java @@ -26,13 +26,13 @@ import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import javax.servlet.ServletException; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; @@ -42,11 +42,13 @@ import org.eclipse.jetty.http2.api.server.ServerSessionListener; import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.Frame; import org.eclipse.jetty.http2.frames.HeadersFrame; +import org.eclipse.jetty.http2.frames.ResetFrame; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Response; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.FuturePromise; import org.eclipse.jetty.util.Promise; +import org.eclipse.jetty.util.StringUtil; import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Test; @@ -106,7 +108,7 @@ public class TrailersTest extends AbstractTest start(new HttpServlet() { @Override - protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException { Request jettyRequest = (Request)request; // No trailers yet. @@ -238,7 +240,7 @@ public class TrailersTest extends AbstractTest start(new EmptyHttpServlet() { @Override - protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException { Request jettyRequest = (Request)request; Response jettyResponse = jettyRequest.getResponse(); @@ -287,4 +289,63 @@ public class TrailersTest extends AbstractTest Assert.assertTrue(trailers.isEndStream()); Assert.assertThat(trailers.getMetaData().getFields().get(trailerName), Matchers.equalTo(trailerValue)); } + + @Test + public void testRequestTrailerInvalidHpack() throws Exception + { + CountDownLatch serverLatch = new CountDownLatch(1); + start(new HttpServlet() + { + @Override + protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException + { + try + { + // Read the content to read the trailers + ServletInputStream input = request.getInputStream(); + while (true) + { + int read = input.read(); + if (read < 0) + break; + } + } + catch (IOException x) + { + serverLatch.countDown(); + throw x; + } + } + }); + + CountDownLatch clientLatch = new CountDownLatch(1); + Session session = newClient(new Session.Listener.Adapter()); + MetaData.Request request = newRequest("POST", new HttpFields()); + HeadersFrame requestFrame = new HeadersFrame(request, null, false); + FuturePromise promise = new FuturePromise<>(); + session.newStream(requestFrame, promise, new Stream.Listener.Adapter() + { + @Override + public void onReset(Stream stream, ResetFrame frame) + { + clientLatch.countDown(); + } + }); + Stream stream = promise.get(5, TimeUnit.SECONDS); + ByteBuffer data = ByteBuffer.wrap(StringUtil.getUtf8Bytes("hello")); + Callback.Completable completable = new Callback.Completable(); + stream.data(new DataFrame(stream.getId(), data, false), completable); + completable.thenRun(() -> + { + // Invalid trailer: cannot contain pseudo headers. + HttpFields trailerFields = new HttpFields(); + trailerFields.put(HttpHeader.C_METHOD, "GET"); + MetaData trailer = new MetaData(HttpVersion.HTTP_2, trailerFields); + HeadersFrame trailerFrame = new HeadersFrame(stream.getId(), trailer, null, true); + stream.headers(trailerFrame, Callback.NOOP); + }); + + Assert.assertTrue(serverLatch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(clientLatch.await(5, TimeUnit.SECONDS)); + } } diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/ErrorCode.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/ErrorCode.java index 8d38a0e852b..aae6c082ccd 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/ErrorCode.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/ErrorCode.java @@ -19,6 +19,7 @@ package org.eclipse.jetty.http2; import java.util.HashMap; +import java.util.Locale; import java.util.Map; /** @@ -96,6 +97,19 @@ public enum ErrorCode return Codes.codes.get(error); } + public static String toString(int error, String dft) + { + ErrorCode errorCode = from(error); + String result; + if (errorCode != null) + result = errorCode.name().toLowerCase(Locale.ENGLISH); + else if (dft == null) + result = String.valueOf(error); + else + result = dft; + return result; + } + private static class Codes { private static final Map codes = new HashMap<>(); diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Connection.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Connection.java index 534359990f8..0cd45644a60 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Connection.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Connection.java @@ -27,14 +27,6 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import org.eclipse.jetty.http2.frames.DataFrame; -import org.eclipse.jetty.http2.frames.GoAwayFrame; -import org.eclipse.jetty.http2.frames.HeadersFrame; -import org.eclipse.jetty.http2.frames.PingFrame; -import org.eclipse.jetty.http2.frames.PriorityFrame; -import org.eclipse.jetty.http2.frames.PushPromiseFrame; -import org.eclipse.jetty.http2.frames.ResetFrame; -import org.eclipse.jetty.http2.frames.SettingsFrame; -import org.eclipse.jetty.http2.frames.WindowUpdateFrame; import org.eclipse.jetty.http2.parser.Parser; import org.eclipse.jetty.io.AbstractConnection; import org.eclipse.jetty.io.ByteBufferPool; @@ -220,6 +212,7 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher. private final Callback fillableCallback = new FillableCallback(); private NetworkBuffer buffer; private boolean shutdown; + private boolean failed; private void setInputBuffer(ByteBuffer byteBuffer) { @@ -237,7 +230,7 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher. if (task != null) return task; - if (isFillInterested() || shutdown) + if (isFillInterested() || shutdown || failed) return null; if (buffer == null) @@ -248,11 +241,22 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher. if (parse) { buffer.retain(); - - while (buffer.hasRemaining()) - parser.parse(buffer.buffer); - - boolean released = buffer.tryRelease(); + boolean released; + try + { + while (buffer.hasRemaining()) + { + parser.parse(buffer.buffer); + if (failed) + return null; + } + } + finally + { + released = buffer.release(); + if (failed && released) + releaseNetworkBuffer(); + } task = pollTask(); if (LOG.isDebugEnabled()) @@ -272,6 +276,9 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher. } } + // Here we know that this.buffer is not retained: + // either it has been released, or it's a new one. + int filled = fill(getEndPoint(), buffer.buffer); if (LOG.isDebugEnabled()) LOG.debug("Filled {} bytes in {}", filled, buffer); @@ -307,14 +314,10 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher. private void releaseNetworkBuffer() { - if (!buffer.hasRemaining()) - { - if (LOG.isDebugEnabled()) - LOG.debug("Released {}", buffer); - buffer.release(); - byteBufferPool.release(buffer.buffer); - buffer = null; - } + if (LOG.isDebugEnabled()) + LOG.debug("Released {}", buffer); + buffer.recycle(); + buffer = null; } @Override @@ -345,13 +348,11 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher. } } - private class ParserListener implements Parser.Listener + private class ParserListener extends Parser.Listener.Wrapper { - private final Parser.Listener listener; - private ParserListener(Parser.Listener listener) { - this.listener = listener; + super(listener); } @Override @@ -363,58 +364,11 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher. session.onData(frame, callback); } - @Override - public void onHeaders(HeadersFrame frame) - { - listener.onHeaders(frame); - } - - @Override - public void onPriority(PriorityFrame frame) - { - listener.onPriority(frame); - } - - @Override - public void onReset(ResetFrame frame) - { - listener.onReset(frame); - } - - @Override - public void onSettings(SettingsFrame frame) - { - listener.onSettings(frame); - } - - @Override - public void onPushPromise(PushPromiseFrame frame) - { - listener.onPushPromise(frame); - } - - @Override - public void onPing(PingFrame frame) - { - listener.onPing(frame); - } - - @Override - public void onGoAway(GoAwayFrame frame) - { - listener.onGoAway(frame); - } - - @Override - public void onWindowUpdate(WindowUpdateFrame frame) - { - listener.onWindowUpdate(frame); - } - @Override public void onConnectionFailure(int error, String reason) { - listener.onConnectionFailure(error, reason); + producer.failed = true; + super.onConnectionFailure(error, reason); } } @@ -444,16 +398,31 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher. refCount.incrementAndGet(); } - @Override - public void succeeded() + private boolean release() { - release(); + return refCount.decrementAndGet() == 0; } @Override - public void failed(Throwable x) + public void succeeded() { - release(); + if (release()) + { + if (LOG.isDebugEnabled()) + LOG.debug("Released retained {}", this); + recycle(); + } + } + + @Override + public void failed(Throwable failure) + { + if (release()) + { + if (LOG.isDebugEnabled()) + LOG.debug("Released retained " + this, failure); + recycle(); + } } @Override @@ -462,19 +431,9 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher. return InvocationType.NON_BLOCKING; } - private void release() + private void recycle() { - if (tryRelease()) - { - byteBufferPool.release(buffer); - if (LOG.isDebugEnabled()) - LOG.debug("Released retained {}", this); - } - } - - private boolean tryRelease() - { - return refCount.decrementAndGet() == 0; + byteBufferPool.release(buffer); } @Override diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java index 600c96b1c66..04279864c5d 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java @@ -37,6 +37,7 @@ import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.DisconnectFrame; +import org.eclipse.jetty.http2.frames.FailureFrame; import org.eclipse.jetty.http2.frames.Frame; import org.eclipse.jetty.http2.frames.FrameType; import org.eclipse.jetty.http2.frames.GoAwayFrame; @@ -72,8 +73,8 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio private static final Logger LOG = Log.getLogger(HTTP2Session.class); private final ConcurrentMap streams = new ConcurrentHashMap<>(); - private final AtomicInteger streamIds = new AtomicInteger(); - private final AtomicInteger lastStreamId = new AtomicInteger(); + private final AtomicInteger localStreamIds = new AtomicInteger(); + private final AtomicInteger lastRemoteStreamId = new AtomicInteger(); private final AtomicInteger localStreamCount = new AtomicInteger(); private final AtomicBiInteger remoteStreamCount = new AtomicBiInteger(); private final AtomicInteger sendWindow = new AtomicInteger(); @@ -105,7 +106,8 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio this.flusher = new HTTP2Flusher(this); this.maxLocalStreams = -1; this.maxRemoteStreams = -1; - this.streamIds.set(initialStreamId); + this.localStreamIds.set(initialStreamId); + this.lastRemoteStreamId.set(isClientStream(initialStreamId) ? 0 : -1); this.streamIdleTimeout = endPoint.getIdleTimeout(); this.sendWindow.set(FlowControlStrategy.DEFAULT_WINDOW_SIZE); this.recvWindow.set(FlowControlStrategy.DEFAULT_WINDOW_SIZE); @@ -229,35 +231,46 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio LOG.debug("Received {}", frame); int streamId = frame.getStreamId(); - final IStream stream = getStream(streamId); + IStream stream = getStream(streamId); // SPEC: the session window must be updated even if the stream is null. // The flow control length includes the padding bytes. - final int flowControlLength = frame.remaining() + frame.padding(); + int flowControlLength = frame.remaining() + frame.padding(); flowControl.onDataReceived(this, stream, flowControlLength); if (stream != null) { if (getRecvWindow() < 0) - { - close(ErrorCode.FLOW_CONTROL_ERROR.code, "session_window_exceeded", callback); - } + onConnectionFailure(ErrorCode.FLOW_CONTROL_ERROR.code, "session_window_exceeded", callback); else - { stream.process(frame, new DataCallback(callback, stream, flowControlLength)); - } } else { if (LOG.isDebugEnabled()) - LOG.debug("Ignoring {}, stream #{} not found", frame, streamId); + LOG.debug("Stream #{} not found", streamId); // We must enlarge the session flow control window, // otherwise other requests will be stalled. flowControl.onDataConsumed(this, null, flowControlLength); - callback.succeeded(); + boolean local = (streamId & 1) == (localStreamIds.get() & 1); + boolean closed = local ? isLocalStreamClosed(streamId) : isRemoteStreamClosed(streamId); + if (closed) + reset(new ResetFrame(streamId, ErrorCode.STREAM_CLOSED_ERROR.code), callback); + else + onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "unexpected_data_frame", callback); } } + protected boolean isLocalStreamClosed(int streamId) + { + return streamId <= localStreamIds.get(); + } + + protected boolean isRemoteStreamClosed(int streamId) + { + return streamId <= getLastRemoteStreamId(); + } + @Override public abstract void onHeaders(HeadersFrame frame); @@ -274,11 +287,19 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio if (LOG.isDebugEnabled()) LOG.debug("Received {}", frame); - IStream stream = getStream(frame.getStreamId()); + int streamId = frame.getStreamId(); + IStream stream = getStream(streamId); if (stream != null) - stream.process(frame, new ResetCallback()); + { + stream.process(frame, new OnResetCallback()); + } else - notifyReset(this, frame); + { + if (isRemoteStreamClosed(streamId)) + notifyReset(this, frame); + else + onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "unexpected_rst_stream_frame"); + } } @Override @@ -306,54 +327,42 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio case SettingsFrame.HEADER_TABLE_SIZE: { if (LOG.isDebugEnabled()) - LOG.debug("Update HPACK header table size to {} for {}", value, this); + LOG.debug("Updating HPACK header table size to {} for {}", value, this); generator.setHeaderTableSize(value); break; } case SettingsFrame.ENABLE_PUSH: { - // SPEC: check the value is sane. - if (value != 0 && value != 1) - { - onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "invalid_settings_enable_push"); - return; - } - pushEnabled = value == 1; if (LOG.isDebugEnabled()) - LOG.debug("{} push for {}", pushEnabled ? "Enable" : "Disable", this); + LOG.debug("{} push for {}", pushEnabled ? "Enabling" : "Disabling", this); + pushEnabled = value == 1; break; } case SettingsFrame.MAX_CONCURRENT_STREAMS: { - maxLocalStreams = value; if (LOG.isDebugEnabled()) - LOG.debug("Update max local concurrent streams to {} for {}", maxLocalStreams, this); + LOG.debug("Updating max local concurrent streams to {} for {}", maxLocalStreams, this); + maxLocalStreams = value; break; } case SettingsFrame.INITIAL_WINDOW_SIZE: { if (LOG.isDebugEnabled()) - LOG.debug("Update initial window size to {} for {}", value, this); + LOG.debug("Updating initial window size to {} for {}", value, this); flowControl.updateInitialStreamWindow(this, value, false); break; } case SettingsFrame.MAX_FRAME_SIZE: { if (LOG.isDebugEnabled()) - LOG.debug("Update max frame size to {} for {}", value, this); - // SPEC: check the max frame size is sane. - if (value < Frame.DEFAULT_MAX_LENGTH || value > Frame.MAX_MAX_LENGTH) - { - onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "invalid_settings_max_frame_size"); - return; - } + LOG.debug("Updating max frame size to {} for {}", value, this); generator.setMaxFrameSize(value); break; } case SettingsFrame.MAX_HEADER_LIST_SIZE: { if (LOG.isDebugEnabled()) - LOG.debug("Update max header list size to {} for {}", value, this); + LOG.debug("Updating max header list size to {} for {}", value, this); generator.setMaxHeaderListSize(value); break; } @@ -448,25 +457,86 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio LOG.debug("Received {}", frame); int streamId = frame.getStreamId(); + int windowDelta = frame.getWindowDelta(); if (streamId > 0) { - IStream stream = getStream(streamId); - if (stream != null) + if (windowDelta == 0) { - stream.process(frame, Callback.NOOP); - onWindowUpdate(stream, frame); + reset(new ResetFrame(streamId, ErrorCode.PROTOCOL_ERROR.code), Callback.NOOP); + } + else + { + IStream stream = getStream(streamId); + if (stream != null) + { + int streamSendWindow = stream.updateSendWindow(0); + if (sumOverflows(streamSendWindow, windowDelta)) + { + reset(new ResetFrame(streamId, ErrorCode.FLOW_CONTROL_ERROR.code), Callback.NOOP); + } + else + { + stream.process(frame, Callback.NOOP); + onWindowUpdate(stream, frame); + } + } + else + { + if (!isRemoteStreamClosed(streamId)) + onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "unexpected_window_update_frame"); + } } } else { - onWindowUpdate(null, frame); + if (windowDelta == 0) + { + onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "invalid_window_update_frame"); + } + else + { + int sessionSendWindow = updateSendWindow(0); + if (sumOverflows(sessionSendWindow, windowDelta)) + onConnectionFailure(ErrorCode.FLOW_CONTROL_ERROR.code, "invalid_flow_control_window"); + else + onWindowUpdate(null, frame); + } + } + } + + @Override + public void onStreamFailure(int streamId, int error, String reason) + { + Callback callback = new ResetCallback(streamId, error, Callback.NOOP); + IStream stream = getStream(streamId); + if (stream != null) + stream.process(new FailureFrame(error, reason), callback); + else + callback.succeeded(); + } + + private boolean sumOverflows(int a, int b) + { + try + { + Math.addExact(a, b); + return false; + } + catch (ArithmeticException x) + { + return true; } } @Override public void onConnectionFailure(int error, String reason) { - notifyFailure(this, new IOException(String.format("%d/%s", error, reason)), new CloseCallback(error, reason)); + onConnectionFailure(error, reason, Callback.NOOP); + } + + protected void onConnectionFailure(int error, String reason, Callback callback) + { + notifyFailure(this, new IOException(String.format("%d/%s", error, reason)), new CloseCallback(error, reason, callback)); } @Override @@ -482,7 +552,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio int streamId = frame.getStreamId(); if (streamId <= 0) { - streamId = streamIds.getAndAdd(2); + streamId = localStreamIds.getAndAdd(2); PriorityFrame priority = frame.getPriority(); priority = priority == null ? null : new PriorityFrame(streamId, priority.getParentStreamId(), priority.getWeight(), priority.isExclusive()); @@ -511,7 +581,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio IStream stream = streams.get(streamId); if (stream == null) { - streamId = streamIds.getAndAdd(2); + streamId = localStreamIds.getAndAdd(2); frame = new PriorityFrame(streamId, frame.getParentStreamId(), frame.getWeight(), frame.isExclusive()); } @@ -529,7 +599,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio boolean queued; synchronized (this) { - int streamId = streamIds.getAndAdd(2); + int streamId = localStreamIds.getAndAdd(2); frame = new PushPromiseFrame(frame.getStreamId(), streamId, frame.getMetaData()); IStream pushStream = createLocalStream(streamId); @@ -629,7 +699,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio reason = reason.substring(0, Math.min(reason.length(), 32)); payload = reason.getBytes(StandardCharsets.UTF_8); } - return new GoAwayFrame(closeState, lastStreamId.get(), error, payload); + return new GoAwayFrame(closeState, getLastRemoteStreamId(), error, payload); } @Override @@ -736,7 +806,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio // SPEC: duplicate stream is treated as connection error. if (streams.putIfAbsent(streamId, stream) == null) { - updateLastStreamId(streamId); + updateLastRemoteStreamId(streamId); stream.setIdleTimeout(getStreamIdleTimeout()); flowControl.onStreamCreated(stream); if (LOG.isDebugEnabled()) @@ -745,7 +815,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio } else { - close(ErrorCode.PROTOCOL_ERROR.code, "duplicate_stream", Callback.NOOP); + onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "duplicate_stream"); return null; } } @@ -1014,9 +1084,14 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio return !endPoint.isOpen(); } - private void updateLastStreamId(int streamId) + protected int getLastRemoteStreamId() { - Atomics.updateMax(lastStreamId, streamId); + return lastRemoteStreamId.get(); + } + + private void updateLastRemoteStreamId(int streamId) + { + Atomics.updateMax(lastRemoteStreamId, streamId); } protected Stream.Listener notifyNewStream(Stream stream, HeadersFrame frame) @@ -1120,6 +1195,12 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio } } + protected static boolean isClientStream(int streamId) + { + // Client-initiated stream ids are odd. + return (streamId & 1) == 1; + } + @Override public void dump(Appendable out, String indent) throws IOException { @@ -1448,7 +1529,37 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio } } - private class ResetCallback implements Callback + private class ResetCallback extends Callback.Nested + { + private final int streamId; + private final int error; + + private ResetCallback(int streamId, int error, Callback callback) + { + super(callback); + this.streamId = streamId; + this.error = error; + } + + @Override + public void succeeded() + { + complete(); + } + + @Override + public void failed(Throwable x) + { + complete(); + } + + private void complete() + { + reset(new ResetFrame(streamId, error), getCallback()); + } + } + + private class OnResetCallback implements Callback { @Override public void succeeded() @@ -1474,13 +1585,14 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio } } - private class CloseCallback implements Callback + private class CloseCallback extends Callback.Nested { private final int error; private final String reason; - private CloseCallback(int error, String reason) + private CloseCallback(int error, String reason, Callback callback) { + super(callback); this.error = error; this.reason = reason; } @@ -1497,15 +1609,9 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio complete(); } - @Override - public InvocationType getInvocationType() - { - return InvocationType.NON_BLOCKING; - } - private void complete() { - close(error, reason, Callback.NOOP); + close(error, reason, getCallback()); } } diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java index 6320b9efab1..b9609ad4903 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java @@ -28,8 +28,12 @@ import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.frames.DataFrame; +import org.eclipse.jetty.http2.frames.FailureFrame; import org.eclipse.jetty.http2.frames.Frame; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.PushPromiseFrame; @@ -58,9 +62,10 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa private final ISession session; private final int streamId; private final boolean local; - private volatile Listener listener; - private volatile boolean localReset; - private volatile boolean remoteReset; + private boolean localReset; + private Listener listener; + private boolean remoteReset; + private long dataLength; public HTTP2Stream(Scheduler scheduler, ISession session, int streamId, boolean local) { @@ -68,6 +73,7 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa this.session = session; this.streamId = streamId; this.local = local; + this.dataLength = Long.MIN_VALUE; } @Override @@ -255,6 +261,11 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa onWindowUpdate((WindowUpdateFrame)frame, callback); break; } + case FAILURE: + { + onFailure((FailureFrame)frame, callback); + break; + } default: { throw new UnsupportedOperationException(); @@ -266,6 +277,15 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa { if (updateClose(frame.isEndStream(), CloseState.Event.RECEIVED)) session.removeStream(this); + MetaData metaData = frame.getMetaData(); + if (metaData.isRequest() || metaData.isResponse()) + { + HttpFields fields = metaData.getFields(); + long length = -1; + if (fields != null) + length = fields.getLongField(HttpHeader.CONTENT_LENGTH.asString()); + dataLength = length >= 0 ? length : Long.MIN_VALUE; + } callback.succeeded(); } @@ -295,8 +315,20 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa return; } + if (dataLength != Long.MIN_VALUE) + { + dataLength -= frame.remaining(); + if (frame.isEndStream() && dataLength != 0) + { + reset(new ResetFrame(streamId, ErrorCode.PROTOCOL_ERROR.code), Callback.NOOP); + callback.failed(new IOException("invalid_data_length")); + return; + } + } + if (updateClose(frame.isEndStream(), CloseState.Event.RECEIVED)) session.removeStream(this); + notifyData(this, frame, callback); } @@ -321,6 +353,11 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa callback.succeeded(); } + private void onFailure(FailureFrame frame, Callback callback) + { + notifyFailure(this, frame, callback); + } + @Override public boolean updateClose(boolean update, CloseState.Event event) { @@ -498,31 +535,43 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa private void notifyData(Stream stream, DataFrame frame, Callback callback) { - final Listener listener = this.listener; - if (listener == null) - return; - try + Listener listener = this.listener; + if (listener != null) { - listener.onData(stream, frame, callback); + try + { + listener.onData(stream, frame, callback); + } + catch (Throwable x) + { + LOG.info("Failure while notifying listener " + listener, x); + callback.failed(x); + } } - catch (Throwable x) + else { - LOG.info("Failure while notifying listener " + listener, x); + callback.succeeded(); } } private void notifyReset(Stream stream, ResetFrame frame, Callback callback) { - final Listener listener = this.listener; - if (listener == null) - return; - try + Listener listener = this.listener; + if (listener != null) { - listener.onReset(stream, frame, callback); + try + { + listener.onReset(stream, frame, callback); + } + catch (Throwable x) + { + LOG.info("Failure while notifying listener " + listener, x); + callback.failed(x); + } } - catch (Throwable x) + else { - LOG.info("Failure while notifying listener " + listener, x); + callback.succeeded(); } } @@ -542,6 +591,27 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa } } + private void notifyFailure(Stream stream, FailureFrame frame, Callback callback) + { + Listener listener = this.listener; + if (listener != null) + { + try + { + listener.onFailure(stream, frame.getError(), frame.getReason(), callback); + } + catch (Throwable x) + { + LOG.info("Failure while notifying listener " + listener, x); + callback.failed(x); + } + } + else + { + callback.succeeded(); + } + } + @Override public String dump() { diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/api/Stream.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/api/Stream.java index 9987ef546d9..808d01a3034 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/api/Stream.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/api/Stream.java @@ -197,6 +197,11 @@ public interface Stream */ public boolean onIdleTimeout(Stream stream, Throwable x); + public default void onFailure(Stream stream, int error, String reason, Callback callback) + { + callback.succeeded(); + } + /** *

Empty implementation of {@link Listener}

*/ diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/FailureFrame.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/FailureFrame.java new file mode 100644 index 00000000000..ea16526d15d --- /dev/null +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/FailureFrame.java @@ -0,0 +1,42 @@ +// +// ======================================================================== +// Copyright (c) 1995-2018 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.http2.frames; + +public class FailureFrame extends Frame +{ + private final int error; + private final String reason; + + public FailureFrame(int error, String reason) + { + super(FrameType.FAILURE); + this.error = error; + this.reason = reason; + } + + public int getError() + { + return error; + } + + public String getReason() + { + return reason; + } +} diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/FrameType.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/FrameType.java index 917aa99cc4a..3353c85a5b2 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/FrameType.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/FrameType.java @@ -35,7 +35,8 @@ public enum FrameType CONTINUATION(9), // Synthetic frames only needed by the implementation. PREFACE(10), - DISCONNECT(11); + DISCONNECT(11), + FAILURE(12); public static FrameType from(int type) { diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/GoAwayFrame.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/GoAwayFrame.java index e603771dee9..57a2dd5c5d0 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/GoAwayFrame.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/GoAwayFrame.java @@ -76,11 +76,10 @@ public class GoAwayFrame extends Frame @Override public String toString() { - ErrorCode errorCode = ErrorCode.from(error); return String.format("%s,%d/%s/%s/%s", super.toString(), lastStreamId, - errorCode != null ? errorCode.toString() : String.valueOf(error), + ErrorCode.toString(error, null), tryConvertPayload(), closeState); } diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/HeadersFrame.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/HeadersFrame.java index 709e28f0f44..478dae889e9 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/HeadersFrame.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/HeadersFrame.java @@ -83,6 +83,7 @@ public class HeadersFrame extends Frame @Override public String toString() { - return String.format("%s#%d{end=%b}", super.toString(), streamId, endStream); + return String.format("%s#%d{end=%b}%s", super.toString(), streamId, endStream, + priority == null ? "" : String.format("+%s", priority)); } } diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/ResetFrame.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/ResetFrame.java index d86c8ca9713..b05a6812ca6 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/ResetFrame.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/ResetFrame.java @@ -18,8 +18,6 @@ package org.eclipse.jetty.http2.frames; -import java.util.Locale; - import org.eclipse.jetty.http2.ErrorCode; public class ResetFrame extends Frame @@ -49,8 +47,6 @@ public class ResetFrame extends Frame @Override public String toString() { - ErrorCode errorCode = ErrorCode.from(error); - String reason = errorCode == null ? "error=" + error : errorCode.name().toLowerCase(Locale.ENGLISH); - return String.format("%s#%d{%s}", super.toString(), streamId, reason); + return String.format("%s#%d{%s}", super.toString(), streamId, ErrorCode.toString(error, null)); } } diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/generator/GoAwayGenerator.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/generator/GoAwayGenerator.java index 73f211d81c2..1b6e1735358 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/generator/GoAwayGenerator.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/generator/GoAwayGenerator.java @@ -45,7 +45,7 @@ public class GoAwayGenerator extends FrameGenerator public int generateGoAway(ByteBufferPool.Lease lease, int lastStreamId, int error, byte[] payload) { if (lastStreamId < 0) - throw new IllegalArgumentException("Invalid last stream id: " + lastStreamId); + lastStreamId = 0; // The last streamId + the error code. int fixedLength = 4 + 4; diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/BodyParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/BodyParser.java index 68b655ce694..369f312618e 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/BodyParser.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/BodyParser.java @@ -222,4 +222,21 @@ public abstract class BodyParser LOG.info("Failure while notifying listener " + listener, x); } } + + protected void streamFailure(int streamId, int error, String reason) + { + notifyStreamFailure(streamId, error, reason); + } + + private void notifyStreamFailure(int streamId, int error, String reason) + { + try + { + listener.onStreamFailure(streamId, error, reason); + } + catch (Throwable x) + { + LOG.info("Failure while notifying listener " + listener, x); + } + } } diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/ContinuationBodyParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/ContinuationBodyParser.java index ca997feba70..db52392be3b 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/ContinuationBodyParser.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/ContinuationBodyParser.java @@ -81,7 +81,7 @@ public class ContinuationBodyParser extends BodyParser headerBlockFragments.storeFragment(buffer, length, last); reset(); if (last) - onHeaders(); + return onHeaders(); return true; } } @@ -94,12 +94,17 @@ public class ContinuationBodyParser extends BodyParser return false; } - private void onHeaders() + private boolean onHeaders() { ByteBuffer headerBlock = headerBlockFragments.complete(); MetaData metaData = headerBlockParser.parse(headerBlock, headerBlock.remaining()); + if (metaData == HeaderBlockParser.SESSION_FAILURE) + return false; + if (metaData == null || metaData == HeaderBlockParser.STREAM_FAILURE) + return true; HeadersFrame frame = new HeadersFrame(getStreamId(), metaData, headerBlockFragments.getPriorityFrame(), headerBlockFragments.isEndStream()); notifyHeaders(frame); + return true; } private void reset() diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeaderBlockParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeaderBlockParser.java index f9a048d8471..7dc2e2c357c 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeaderBlockParser.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeaderBlockParser.java @@ -20,23 +20,46 @@ package org.eclipse.jetty.http2.parser; import java.nio.ByteBuffer; +import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; +import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.hpack.HpackDecoder; +import org.eclipse.jetty.http2.hpack.HpackException; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; public class HeaderBlockParser { + public static final MetaData STREAM_FAILURE = new MetaData(HttpVersion.HTTP_2, null); + public static final MetaData SESSION_FAILURE = new MetaData(HttpVersion.HTTP_2, null); + private static final Logger LOG = Log.getLogger(HeaderBlockParser.class); + + private final HeaderParser headerParser; private final ByteBufferPool byteBufferPool; private final HpackDecoder hpackDecoder; + private final BodyParser notifier; private ByteBuffer blockBuffer; - public HeaderBlockParser(ByteBufferPool byteBufferPool, HpackDecoder hpackDecoder) + public HeaderBlockParser(HeaderParser headerParser, ByteBufferPool byteBufferPool, HpackDecoder hpackDecoder, BodyParser notifier) { + this.headerParser = headerParser; this.byteBufferPool = byteBufferPool; this.hpackDecoder = hpackDecoder; + this.notifier = notifier; } + /** + * Parses @{code blockLength} HPACK bytes from the given {@code buffer}. + * + * @param buffer the buffer to parse + * @param blockLength the length of the HPACK block + * @return null, if the buffer contains less than {@code blockLength} bytes; + * {@link #STREAM_FAILURE} if parsing the HPACK block produced a stream failure; + * {@link #SESSION_FAILURE} if parsing the HPACK block produced a session failure; + * a valid MetaData object if the parsing was successful. + */ public MetaData parse(ByteBuffer buffer, int blockLength) { // We must wait for the all the bytes of the header block to arrive. @@ -72,17 +95,41 @@ public class HeaderBlockParser toDecode = buffer; } - MetaData result = hpackDecoder.decode(toDecode); - - buffer.limit(limit); - - if (blockBuffer != null) + try { - byteBufferPool.release(blockBuffer); - blockBuffer = null; + return hpackDecoder.decode(toDecode); } + catch (HpackException.StreamException x) + { + if (LOG.isDebugEnabled()) + LOG.debug(x); + notifier.streamFailure(headerParser.getStreamId(), ErrorCode.PROTOCOL_ERROR.code, "invalid_hpack_block"); + return STREAM_FAILURE; + } + catch (HpackException.CompressionException x) + { + if (LOG.isDebugEnabled()) + LOG.debug(x); + notifier.connectionFailure(buffer, ErrorCode.COMPRESSION_ERROR.code, "invalid_hpack_block"); + return SESSION_FAILURE; + } + catch (HpackException.SessionException x) + { + if (LOG.isDebugEnabled()) + LOG.debug(x); + notifier.connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_hpack_block"); + return SESSION_FAILURE; + } + finally + { + buffer.limit(limit); - return result; + if (blockBuffer != null) + { + byteBufferPool.release(blockBuffer); + blockBuffer = null; + } + } } } } diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeaderParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeaderParser.java index a9d3b9e2771..df717084674 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeaderParser.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeaderParser.java @@ -21,6 +21,7 @@ package org.eclipse.jetty.http2.parser; import java.nio.ByteBuffer; import org.eclipse.jetty.http2.frames.Frame; +import org.eclipse.jetty.http2.frames.FrameType; /** *

The parser for the frame header of HTTP/2 frames.

@@ -144,6 +145,12 @@ public class HeaderParser return streamId; } + @Override + public String toString() + { + return String.format("[%s|%d|%d|%d]", FrameType.from(getFrameType()), getLength(), flags, getStreamId()); + } + private enum State { LENGTH, TYPE, FLAGS, STREAM_ID, STREAM_ID_BYTES diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeadersBodyParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeadersBodyParser.java index 784c2369d71..2a1df70a4cd 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeadersBodyParser.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeadersBodyParser.java @@ -92,17 +92,11 @@ public class HeadersBodyParser extends BodyParser length = getBodyLength(); if (isPadding()) - { state = State.PADDING_LENGTH; - } else if (hasFlag(Flags.PRIORITY)) - { state = State.EXCLUSIVE; - } else - { state = State.HEADERS; - } break; } case PADDING_LENGTH: @@ -162,6 +156,9 @@ public class HeadersBodyParser extends BodyParser } case WEIGHT: { + // SPEC: stream cannot depend on itself. + if (getStreamId() == parentStreamId) + return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_priority_frame"); weight = (buffer.get() & 0xFF) + 1; --length; state = State.HEADERS; @@ -173,13 +170,16 @@ public class HeadersBodyParser extends BodyParser if (hasFlag(Flags.END_HEADERS)) { MetaData metaData = headerBlockParser.parse(buffer, length); + if (metaData == HeaderBlockParser.SESSION_FAILURE) + return false; if (metaData != null) { if (LOG.isDebugEnabled()) LOG.debug("Parsed {} frame hpack from {}", FrameType.HEADERS, buffer); state = State.PADDING; loop = paddingLength == 0; - onHeaders(parentStreamId, weight, exclusive, metaData); + if (metaData != HeaderBlockParser.STREAM_FAILURE) + onHeaders(parentStreamId, weight, exclusive, metaData); } } else diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/Parser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/Parser.java index 016bd0ef749..4b45c7320fd 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/Parser.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/Parser.java @@ -24,6 +24,7 @@ import java.util.function.UnaryOperator; import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.Flags; import org.eclipse.jetty.http2.frames.DataFrame; +import org.eclipse.jetty.http2.frames.Frame; import org.eclipse.jetty.http2.frames.FrameType; import org.eclipse.jetty.http2.frames.GoAwayFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; @@ -35,7 +36,6 @@ import org.eclipse.jetty.http2.frames.SettingsFrame; import org.eclipse.jetty.http2.frames.WindowUpdateFrame; import org.eclipse.jetty.http2.hpack.HpackDecoder; import org.eclipse.jetty.io.ByteBufferPool; -import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -52,6 +52,8 @@ public class Parser private final HeaderParser headerParser; private final HeaderBlockParser headerBlockParser; private final BodyParser[] bodyParsers; + private final UnknownBodyParser unknownBodyParser; + private int maxFrameLength; private int maxSettingsKeys = SettingsFrame.DEFAULT_MAX_KEYS; private boolean continuation; private State state = State.HEADER; @@ -60,7 +62,9 @@ public class Parser { this.listener = listener; this.headerParser = new HeaderParser(); - this.headerBlockParser = new HeaderBlockParser(byteBufferPool, new HpackDecoder(maxDynamicTableSize, maxHeaderSize)); + this.unknownBodyParser = new UnknownBodyParser(headerParser, listener); + this.headerBlockParser = new HeaderBlockParser(headerParser, byteBufferPool, new HpackDecoder(maxDynamicTableSize, maxHeaderSize), unknownBodyParser); + this.maxFrameLength = Frame.DEFAULT_MAX_LENGTH; this.bodyParsers = new BodyParser[FrameType.values().length]; } @@ -128,8 +132,7 @@ public class Parser { if (LOG.isDebugEnabled()) LOG.debug(x); - BufferUtil.clear(buffer); - notifyConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "parser_error"); + connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR, "parser_error"); } } @@ -138,31 +141,27 @@ public class Parser if (!headerParser.parse(buffer)) return false; - FrameType frameType = FrameType.from(getFrameType()); if (LOG.isDebugEnabled()) - LOG.debug("Parsed {} frame header from {}", frameType, buffer); + LOG.debug("Parsed {} frame header from {}", headerParser, buffer); + if (headerParser.getLength() > getMaxFrameLength()) + return connectionFailure(buffer, ErrorCode.FRAME_SIZE_ERROR, "invalid_frame_length"); + + FrameType frameType = FrameType.from(getFrameType()); if (continuation) { + // SPEC: CONTINUATION frames must be consecutive. if (frameType != FrameType.CONTINUATION) - { - // SPEC: CONTINUATION frames must be consecutive. - BufferUtil.clear(buffer); - notifyConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "continuation_frame_expected"); - return false; - } + return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR, "expected_continuation_frame"); if (headerParser.hasFlag(Flags.END_HEADERS)) - { continuation = false; - } } else { - if (frameType == FrameType.HEADERS && - !headerParser.hasFlag(Flags.END_HEADERS)) - { - continuation = true; - } + if (frameType == FrameType.HEADERS) + continuation = !headerParser.hasFlag(Flags.END_HEADERS); + else if (frameType == FrameType.CONTINUATION) + return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR, "unexpected_continuation_frame"); } state = State.BODY; return true; @@ -173,9 +172,13 @@ public class Parser int type = getFrameType(); if (type < 0 || type >= bodyParsers.length) { - BufferUtil.clear(buffer); - notifyConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "unknown_frame_type_" + type); + // Unknown frame types must be ignored. + if (LOG.isDebugEnabled()) + LOG.debug("Ignoring unknown frame type {}", Integer.toHexString(type)); + if (!unknownBodyParser.parse(buffer)) return false; + reset(); + return true; } BodyParser bodyParser = bodyParsers[type]; @@ -194,6 +197,11 @@ public class Parser return true; } + private boolean connectionFailure(ByteBuffer buffer, ErrorCode error, String reason) + { + return unknownBodyParser.connectionFailure(buffer, error.code, reason); + } + protected int getFrameType() { return headerParser.getFrameType(); @@ -204,6 +212,16 @@ public class Parser return headerParser.hasFlag(bit); } + public int getMaxFrameLength() + { + return maxFrameLength; + } + + public void setMaxFrameLength(int maxFrameLength) + { + this.maxFrameLength = maxFrameLength; + } + public int getMaxSettingsKeys() { return maxSettingsKeys; @@ -246,6 +264,8 @@ public class Parser public void onWindowUpdate(WindowUpdateFrame frame); + public void onStreamFailure(int streamId, int error, String reason); + public void onConnectionFailure(int error, String reason); public static class Adapter implements Listener @@ -295,12 +315,98 @@ public class Parser { } + @Override + public void onStreamFailure(int streamId, int error, String reason) + { + } + @Override public void onConnectionFailure(int error, String reason) { LOG.warn("Connection failure: {}/{}", error, reason); } } + + public static class Wrapper implements Listener + { + private final Parser.Listener listener; + + public Wrapper(Parser.Listener listener) + { + this.listener = listener; + } + + public Listener getParserListener() + { + return listener; + } + + @Override + public void onData(DataFrame frame) + { + listener.onData(frame); + } + + @Override + public void onHeaders(HeadersFrame frame) + { + listener.onHeaders(frame); + } + + @Override + public void onPriority(PriorityFrame frame) + { + listener.onPriority(frame); + } + + @Override + public void onReset(ResetFrame frame) + { + listener.onReset(frame); + } + + @Override + public void onSettings(SettingsFrame frame) + { + listener.onSettings(frame); + } + + @Override + public void onPushPromise(PushPromiseFrame frame) + { + listener.onPushPromise(frame); + } + + @Override + public void onPing(PingFrame frame) + { + listener.onPing(frame); + } + + @Override + public void onGoAway(GoAwayFrame frame) + { + listener.onGoAway(frame); + } + + @Override + public void onWindowUpdate(WindowUpdateFrame frame) + { + listener.onWindowUpdate(frame); + } + + @Override + public void onStreamFailure(int streamId, int error, String reason) + { + listener.onStreamFailure(streamId, error, reason); + } + + @Override + public void onConnectionFailure(int error, String reason) + { + listener.onConnectionFailure(error, reason); + } + } } private enum State diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/PriorityBodyParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/PriorityBodyParser.java index daa76602e20..6c1f94d956c 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/PriorityBodyParser.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/PriorityBodyParser.java @@ -102,7 +102,6 @@ public class PriorityBodyParser extends BodyParser // SPEC: stream cannot depend on itself. if (getStreamId() == parentStreamId) return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_priority_frame"); - int weight = (buffer.get() & 0xFF) + 1; return onPriority(parentStreamId, weight, exclusive); } diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/PushPromiseBodyParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/PushPromiseBodyParser.java index 508936b2c02..c46da3dcb39 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/PushPromiseBodyParser.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/PushPromiseBodyParser.java @@ -125,11 +125,14 @@ public class PushPromiseBodyParser extends BodyParser case HEADERS: { MetaData metaData = headerBlockParser.parse(buffer, length); + if (metaData == HeaderBlockParser.SESSION_FAILURE) + return false; if (metaData != null) { state = State.PADDING; loop = paddingLength == 0; - onPushPromise(streamId, metaData); + if (metaData != HeaderBlockParser.STREAM_FAILURE) + onPushPromise(streamId, metaData); } break; } diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/ServerParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/ServerParser.java index 2b2c3a1dfd1..2f688b1161c 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/ServerParser.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/ServerParser.java @@ -158,6 +158,26 @@ public class ServerParser extends Parser { } } + + public static class Wrapper extends Parser.Listener.Wrapper implements Listener + { + public Wrapper(ServerParser.Listener listener) + { + super(listener); + } + + @Override + public ServerParser.Listener getParserListener() + { + return (Listener)super.getParserListener(); + } + + @Override + public void onPreface() + { + getParserListener().onPreface(); + } + } } private enum State diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/SettingsBodyParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/SettingsBodyParser.java index 1d2cf892abc..6c9ede766f1 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/SettingsBodyParser.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/SettingsBodyParser.java @@ -25,6 +25,7 @@ import java.util.concurrent.atomic.AtomicReference; import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.Flags; +import org.eclipse.jetty.http2.frames.Frame; import org.eclipse.jetty.http2.frames.SettingsFrame; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -71,7 +72,7 @@ public class SettingsBodyParser extends BodyParser @Override protected void emptyBody(ByteBuffer buffer) { - onSettings(new HashMap<>()); + onSettings(buffer, new HashMap<>()); } @Override @@ -135,7 +136,7 @@ public class SettingsBodyParser extends BodyParser state = State.SETTING_ID; length -= 4; if (length == 0) - return onSettings(settings); + return onSettings(buffer, settings); } else { @@ -161,7 +162,7 @@ public class SettingsBodyParser extends BodyParser return false; state = State.SETTING_ID; if (length == 0) - return onSettings(settings); + return onSettings(buffer, settings); } break; } @@ -183,8 +184,21 @@ public class SettingsBodyParser extends BodyParser return true; } - protected boolean onSettings(Map settings) + protected boolean onSettings(ByteBuffer buffer, Map settings) { + Integer enablePush = settings.get(SettingsFrame.ENABLE_PUSH); + if (enablePush != null && enablePush != 0 && enablePush != 1) + return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_settings_enable_push"); + + Integer initialWindowSize = settings.get(SettingsFrame.INITIAL_WINDOW_SIZE); + // Values greater than Integer.MAX_VALUE will overflow to negative. + if (initialWindowSize != null && initialWindowSize < 0) + return connectionFailure(buffer, ErrorCode.FLOW_CONTROL_ERROR.code, "invalid_settings_initial_window_size"); + + Integer maxFrameLength = settings.get(SettingsFrame.MAX_FRAME_SIZE); + if (maxFrameLength != null && (maxFrameLength < Frame.DEFAULT_MAX_LENGTH || maxFrameLength > Frame.MAX_MAX_LENGTH)) + return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_settings_max_frame_size"); + SettingsFrame frame = new SettingsFrame(settings, hasFlag(Flags.ACK)); reset(); notifySettings(frame); @@ -210,7 +224,7 @@ public class SettingsBodyParser extends BodyParser } @Override - protected boolean onSettings(Map settings) + protected boolean onSettings(ByteBuffer buffer, Map settings) { frameRef.set(new SettingsFrame(settings, false)); return true; diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/UnknownBodyParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/UnknownBodyParser.java new file mode 100644 index 00000000000..97a418c2ec4 --- /dev/null +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/UnknownBodyParser.java @@ -0,0 +1,54 @@ +// +// ======================================================================== +// Copyright (c) 1995-2018 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.http2.parser; + +import java.nio.ByteBuffer; + +public class UnknownBodyParser extends BodyParser +{ + private int cursor; + + public UnknownBodyParser(HeaderParser headerParser, Parser.Listener listener) + { + super(headerParser, listener); + } + + @Override + public boolean parse(ByteBuffer buffer) + { + int length = cursor == 0 ? getBodyLength() : cursor; + cursor = consume(buffer, length); + return cursor == 0; + } + + private int consume(ByteBuffer buffer, int length) + { + int remaining = buffer.remaining(); + if (remaining >= length) + { + buffer.position(buffer.position() + length); + return 0; + } + else + { + buffer.position(buffer.limit()); + return length - remaining; + } + } +} diff --git a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/MaxFrameSizeParseTest.java b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/MaxFrameSizeParseTest.java new file mode 100644 index 00000000000..b902c1d925c --- /dev/null +++ b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/MaxFrameSizeParseTest.java @@ -0,0 +1,66 @@ +// +// ======================================================================== +// Copyright (c) 1995-2018 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.http2.frames; + +import java.nio.ByteBuffer; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.UnaryOperator; + +import org.eclipse.jetty.http2.ErrorCode; +import org.eclipse.jetty.http2.parser.Parser; +import org.eclipse.jetty.io.ByteBufferPool; +import org.eclipse.jetty.io.MappedByteBufferPool; +import org.junit.Assert; +import org.junit.Test; + +public class MaxFrameSizeParseTest +{ + private final ByteBufferPool byteBufferPool = new MappedByteBufferPool(); + + @Test + public void testMaxFrameSize() + { + int maxFrameLength = Frame.DEFAULT_MAX_LENGTH + 16; + + AtomicInteger failure = new AtomicInteger(); + Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + { + @Override + public void onConnectionFailure(int error, String reason) + { + failure.set(error); + } + }, 4096, 8192); + parser.setMaxFrameLength(maxFrameLength); + parser.init(UnaryOperator.identity()); + + // Iterate a few times to be sure the parser is properly reset. + for (int i = 0; i < 2; ++i) + { + byte[] bytes = new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0}; + ByteBuffer buffer = ByteBuffer.wrap(bytes); + buffer.putInt(0, maxFrameLength + 1); + buffer.position(1); + while (buffer.hasRemaining()) + parser.parse(buffer); + } + + Assert.assertEquals(ErrorCode.FRAME_SIZE_ERROR.code, failure.get()); + } +} diff --git a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/UnknownParseTest.java b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/UnknownParseTest.java new file mode 100644 index 00000000000..bbaa6c43347 --- /dev/null +++ b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/UnknownParseTest.java @@ -0,0 +1,72 @@ +// +// ======================================================================== +// Copyright (c) 1995-2018 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.http2.frames; + +import java.nio.ByteBuffer; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Function; +import java.util.function.UnaryOperator; + +import org.eclipse.jetty.http2.parser.Parser; +import org.eclipse.jetty.io.ByteBufferPool; +import org.eclipse.jetty.io.MappedByteBufferPool; +import org.junit.Assert; +import org.junit.Test; + +public class UnknownParseTest +{ + private final ByteBufferPool byteBufferPool = new MappedByteBufferPool(); + + @Test + public void testParse() + { + testParse(Function.identity()); + } + + @Test + public void testParseOneByteAtATime() + { + testParse(buffer -> ByteBuffer.wrap(new byte[]{buffer.get()})); + } + + private void testParse(Function fn) + { + AtomicBoolean failure = new AtomicBoolean(); + Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + { + @Override + public void onConnectionFailure(int error, String reason) + { + failure.set(true); + } + }, 4096, 8192); + parser.init(UnaryOperator.identity()); + + // Iterate a few times to be sure the parser is properly reset. + for (int i = 0; i < 2; ++i) + { + byte[] bytes = new byte[]{0, 0, 4, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + ByteBuffer buffer = ByteBuffer.wrap(bytes); + while (buffer.hasRemaining()) + parser.parse(fn.apply(buffer)); + } + + Assert.assertFalse(failure.get()); + } +} diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackDecoder.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackDecoder.java index 28cb913c34f..df7b2a6e971 100644 --- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackDecoder.java +++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackDecoder.java @@ -21,10 +21,8 @@ package org.eclipse.jetty.http2.hpack; import java.nio.ByteBuffer; -import org.eclipse.jetty.http.BadMessageException; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpHeader; -import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.hpack.HpackContext.Entry; import org.eclipse.jetty.util.TypeUtil; @@ -66,14 +64,16 @@ public class HpackDecoder _localMaxDynamicTableSize=localMaxdynamciTableSize; } - public MetaData decode(ByteBuffer buffer) + public MetaData decode(ByteBuffer buffer) throws HpackException.SessionException, HpackException.StreamException { if (LOG.isDebugEnabled()) LOG.debug(String.format("CtxTbl[%x] decoding %d octets",_context.hashCode(),buffer.remaining())); // If the buffer is big, don't even think about decoding it if (buffer.remaining()>_builder.getMaxSize()) - throw new BadMessageException(HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE_431,"Header frame size "+buffer.remaining()+">"+_builder.getMaxSize()); + throw new HpackException.SessionException("431 Request Header Fields too large"); + + boolean emitted = false; while(buffer.hasRemaining()) { @@ -92,14 +92,14 @@ public class HpackDecoder int index = NBitInteger.decode(buffer,7); Entry entry=_context.get(index); if (entry==null) - { - throw new BadMessageException(HttpStatus.BAD_REQUEST_400, "Unknown index "+index); - } - else if (entry.isStatic()) + throw new HpackException.SessionException("Unknown index %d",index); + + if (entry.isStatic()) { if (LOG.isDebugEnabled()) LOG.debug("decode IdxStatic {}",entry); // emit field + emitted = true; _builder.emit(entry.getHttpField()); // TODO copy and add to reference set if there is room @@ -110,6 +110,7 @@ public class HpackDecoder if (LOG.isDebugEnabled()) LOG.debug("decode Idx {}",entry); // emit + emitted = true; _builder.emit(entry.getHttpField()); } } @@ -134,6 +135,8 @@ public class HpackDecoder LOG.debug("decode resize="+size); if (size>_localMaxDynamicTableSize) throw new IllegalArgumentException(); + if (emitted) + throw new HpackException.CompressionException("Dynamic table resize after fields"); _context.resize(size); continue; @@ -178,7 +181,8 @@ public class HpackDecoder char c=name.charAt(i); if (c>='A'&&c<='Z') { - throw new BadMessageException(400,"Uppercase header name"); + _builder.streamException("Uppercase header name %s",name); + break; } } header=HttpHeader.CACHE.get(name); @@ -240,6 +244,7 @@ public class HpackDecoder } // emit the field + emitted = true; _builder.emit(field); // if indexed add to dynamic table diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java index 0bb5bb70fe3..9f9ee924f97 100644 --- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java +++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java @@ -21,8 +21,11 @@ package org.eclipse.jetty.http2.hpack; import java.nio.ByteBuffer; import java.util.EnumSet; +import java.util.Set; +import java.util.stream.Collectors; import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpStatus; @@ -31,6 +34,9 @@ import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http.PreEncodedHttpField; import org.eclipse.jetty.http2.hpack.HpackContext.Entry; import org.eclipse.jetty.http2.hpack.HpackContext.StaticEntry; +import org.eclipse.jetty.util.ArrayTrie; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.Trie; import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -38,17 +44,13 @@ import org.eclipse.jetty.util.log.Logger; public class HpackEncoder { public static final Logger LOG = Log.getLogger(HpackEncoder.class); - private final static HttpField[] __status= new HttpField[599]; - - final static EnumSet __DO_NOT_HUFFMAN = EnumSet.of( HttpHeader.AUTHORIZATION, HttpHeader.CONTENT_MD5, HttpHeader.PROXY_AUTHENTICATE, HttpHeader.PROXY_AUTHORIZATION); - final static EnumSet __DO_NOT_INDEX = EnumSet.of( // HttpHeader.C_PATH, // TODO more data needed @@ -69,18 +71,21 @@ public class HpackEncoder HttpHeader.LAST_MODIFIED, HttpHeader.SET_COOKIE, HttpHeader.SET_COOKIE2); - - final static EnumSet __NEVER_INDEX = EnumSet.of( HttpHeader.AUTHORIZATION, HttpHeader.SET_COOKIE, HttpHeader.SET_COOKIE2); + private static final PreEncodedHttpField CONNECTION_TE = new PreEncodedHttpField(HttpHeader.CONNECTION, "te"); + private static final PreEncodedHttpField TE_TRAILERS = new PreEncodedHttpField(HttpHeader.TE, "trailers"); + private static final Trie specialHopHeaders = new ArrayTrie<>(6); static { for (HttpStatus.Code code : HttpStatus.Code.values()) __status[code.getCode()]=new PreEncodedHttpField(HttpHeader.C_STATUS,Integer.toString(code.getCode())); + specialHopHeaders.put("close", true); + specialHopHeaders.put("te", true); } private final HpackContext _context; @@ -174,9 +179,30 @@ public class HpackEncoder encode(buffer,status); } - // Add all the other fields - for (HttpField field : metadata) - encode(buffer,field); + // Add all non-connection fields. + HttpFields fields = metadata.getFields(); + if (fields != null) + { + Set hopHeaders = fields.getCSV(HttpHeader.CONNECTION, false).stream() + .filter(v -> specialHopHeaders.get(v) == Boolean.TRUE) + .map(StringUtil::asciiToLowerCase) + .collect(Collectors.toSet()); + for (HttpField field : fields) + { + if (field.getHeader() == HttpHeader.CONNECTION) + continue; + if (!hopHeaders.isEmpty() && hopHeaders.contains(StringUtil.asciiToLowerCase(field.getName()))) + continue; + if (field.getHeader() == HttpHeader.TE) + { + if (!field.contains("trailers")) + continue; + encode(buffer, CONNECTION_TE); + encode(buffer, TE_TRAILERS); + } + encode(buffer,field); + } + } // Check size if (_maxHeaderListSize>0 && _headerListSize>_maxHeaderListSize) @@ -305,7 +331,7 @@ public class HpackEncoder encoding="Lit"+ ((name==null)?"HuffN":("IdxN"+(name.isStatic()?"S":"")+(1+NBitInteger.octectsNeeded(4,_context.index(name)))))+ (huffman?"HuffV":"LitV")+ - (indexed?"Idx":(never_index?"!!Idx":"!Idx")); + (never_index?"!!Idx":"!Idx"); } else if (field_size>=_context.getMaxDynamicTableSize() || header==HttpHeader.CONTENT_LENGTH && field.getValue().length()>2) { diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackException.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackException.java new file mode 100644 index 00000000000..78516c73bd2 --- /dev/null +++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackException.java @@ -0,0 +1,65 @@ +// +// ======================================================================== +// Copyright (c) 1995-2018 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.http2.hpack; + + +@SuppressWarnings("serial") +public abstract class HpackException extends Exception +{ + HpackException(String messageFormat, Object... args) + { + super(String.format(messageFormat, args)); + } + + /** + * A Stream HPACK exception. + *

Stream exceptions are not fatal to the connection and the + * hpack state is complete and able to continue handling other + * decoding/encoding for the session. + *

+ */ + public static class StreamException extends HpackException + { + StreamException(String messageFormat, Object... args) + { + super(messageFormat,args); + } + } + + /** + * A Session HPACK Exception. + *

Session exceptions are fatal for the stream and the HPACK + * state is unable to decode/encode further.

+ */ + public static class SessionException extends HpackException + { + SessionException(String messageFormat, Object... args) + { + super(messageFormat,args); + } + } + + public static class CompressionException extends SessionException + { + public CompressionException(String messageFormat, Object... args) + { + super(messageFormat,args); + } + } +} diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/Huffman.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/Huffman.java index 67c58ee0a98..ddb2c4a97df 100644 --- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/Huffman.java +++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/Huffman.java @@ -287,6 +287,7 @@ public class Huffman }; static final int[][] LCCODES = new int[CODES.length][]; + static final char EOS = 256; // Huffman decode tree stored in a flattened char array for good // locality of reference. @@ -344,24 +345,25 @@ public class Huffman } } - public static String decode(ByteBuffer buffer) + public static String decode(ByteBuffer buffer) throws HpackException.CompressionException { return decode(buffer,buffer.remaining()); } - public static String decode(ByteBuffer buffer,int length) + public static String decode(ByteBuffer buffer,int length) throws HpackException.CompressionException { StringBuilder out = new StringBuilder(length*2); int node = 0; int current = 0; int bits = 0; - + byte[] array = buffer.array(); int position=buffer.position(); int start=buffer.arrayOffset()+position; int end=start+length; buffer.position(position+length); - + + for (int i=start; i 0) { int c = (current << (8 - bits)) & 0xFF; + int lastNode = node; node = tree[node*256+c]; - if (rowbits[node]==0 || rowbits[node] > bits) + + if (rowbits[node]==0 || rowbits[node] > bits) + { + int requiredPadding = 0; + for(int i=0; i>(8-bits)) != requiredPadding) + throw new HpackException.CompressionException("Incorrect padding"); + + node = lastNode; break; - - if (rowbits[node]==0) - throw new IllegalStateException(); - + } + out.append(rowsym[node]); bits -= rowbits[node]; node = 0; } + if(node != 0) + throw new HpackException.CompressionException("Bad termination"); + return out.toString(); } diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/MetaDataBuilder.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/MetaDataBuilder.java index b9e72e7352f..d891ab3dfe5 100644 --- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/MetaDataBuilder.java +++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/MetaDataBuilder.java @@ -20,27 +20,29 @@ package org.eclipse.jetty.http2.hpack; -import org.eclipse.jetty.http.BadMessageException; import org.eclipse.jetty.http.HostPortHttpField; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpScheme; -import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; +import org.eclipse.jetty.http2.hpack.HpackException.SessionException; public class MetaDataBuilder { private final int _maxSize; private int _size; - private int _status; + private int _status=-1; private String _method; private HttpScheme _scheme; private HostPortHttpField _authority; private String _path; private long _contentLength=Long.MIN_VALUE; private HttpFields _fields = new HttpFields(10); + private HpackException.StreamException _streamException; + private boolean _request; + private boolean _response; /** * @param maxHeadersSize The maximum size of the headers, expressed as total name and value characters. @@ -66,7 +68,7 @@ public class MetaDataBuilder return _size; } - public void emit(HttpField field) + public void emit(HttpField field) throws HpackException.SessionException { HttpHeader header = field.getHeader(); String name = field.getName(); @@ -74,7 +76,7 @@ public class MetaDataBuilder int field_size = name.length() + (value == null ? 0 : value.length()); _size+=field_size+32; if (_size>_maxSize) - throw new BadMessageException(HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE_431,"Header size "+_size+">"+_maxSize); + throw new HpackException.SessionException("Header Size %d > %d",_size,_maxSize); if (field instanceof StaticTableHttpField) { @@ -82,15 +84,21 @@ public class MetaDataBuilder switch(header) { case C_STATUS: - _status=(Integer)staticField.getStaticValue(); + if(checkHeader(header, _status)) + _status = (Integer)staticField.getStaticValue(); + _response = true; break; case C_METHOD: - _method=value; + if(checkPseudoHeader(header, _method)) + _method = value; + _request = true; break; case C_SCHEME: - _scheme = (HttpScheme)staticField.getStaticValue(); + if(checkPseudoHeader(header, _scheme)) + _scheme = (HttpScheme)staticField.getStaticValue(); + _request = true; break; default: @@ -102,23 +110,32 @@ public class MetaDataBuilder switch(header) { case C_STATUS: - _status=field.getIntValue(); + if(checkHeader(header, _status)) + _status = field.getIntValue(); + _response = true; break; case C_METHOD: - _method=value; + if(checkPseudoHeader(header, _method)) + _method = value; + _request = true; break; case C_SCHEME: - if (value != null) + if(checkPseudoHeader(header, _scheme) && value != null) _scheme = HttpScheme.CACHE.get(value); + _request = true; break; case C_AUTHORITY: - if (field instanceof HostPortHttpField) - _authority = (HostPortHttpField)field; - else if (value != null) - _authority = new AuthorityHttpField(value); + if(checkPseudoHeader(header, _authority)) + { + if (field instanceof HostPortHttpField) + _authority = (HostPortHttpField)field; + else if (value != null) + _authority = new AuthorityHttpField(value); + } + _request = true; break; case HOST: @@ -134,54 +151,129 @@ public class MetaDataBuilder break; case C_PATH: - _path = value; + if(checkPseudoHeader(header, _path)) + { + if (value!=null && value.length()>0) + _path = value; + else + streamException("No Path"); + } + _request = true; break; case CONTENT_LENGTH: _contentLength = field.getLongValue(); _fields.add(field); break; + + case TE: + if ("trailers".equalsIgnoreCase(value)) + _fields.add(field); + else + streamException("Unsupported TE value '%s'", value); + break; + + case CONNECTION: + if ("TE".equalsIgnoreCase(value)) + _fields.add(field); + else + streamException("Connection specific field '%s'", header); + break; - default: - if (name.charAt(0)!=':') + default: + if (name.charAt(0)==':') + streamException("Unknown pseudo header '%s'", name); + else _fields.add(field); break; } } else { - if (name.charAt(0)!=':') + if (name.charAt(0)==':') + streamException("Unknown pseudo header '%s'",name); + else _fields.add(field); } } - public MetaData build() + void streamException(String messageFormat, Object... args) { + HpackException.StreamException stream = new HpackException.StreamException(messageFormat, args); + if (_streamException==null) + _streamException = stream; + else + _streamException.addSuppressed(stream); + } + + private boolean checkHeader(HttpHeader header, int value) + { + if (_fields.size()>0) + { + streamException("Pseudo header %s after fields", header.asString()); + return false; + } + if (value==-1) + return true; + streamException("Duplicate pseudo header %s", header.asString()); + return false; + } + + private boolean checkPseudoHeader(HttpHeader header, Object value) + { + if (_fields.size()>0) + { + streamException("Pseudo header %s after fields", header.asString()); + return false; + } + if (value==null) + return true; + streamException("Duplicate pseudo header %s", header.asString()); + return false; + } + + public MetaData build() throws HpackException.StreamException + { + if (_streamException!=null) + { + _streamException.addSuppressed(new Throwable()); + throw _streamException; + } + + if (_request && _response) + throw new HpackException.StreamException("Request and Response headers"); + + + HttpFields fields = _fields; try { - HttpFields fields = _fields; - _fields = new HttpFields(Math.max(10,fields.size()+5)); - - if (_method!=null) + if (_request) + { + if (_method==null) + throw new HpackException.StreamException("No Method"); + if (_scheme==null) + throw new HpackException.StreamException("No Scheme"); + if (_path==null) + throw new HpackException.StreamException("No Path"); return new MetaData.Request(_method,_scheme,_authority,_path,HttpVersion.HTTP_2,fields,_contentLength); - if (_status!=0) + } + if (_response) return new MetaData.Response(HttpVersion.HTTP_2,_status,fields,_contentLength); - if (_path!=null) - fields.put(HttpHeader.C_PATH,_path); - if (_authority!=null) - fields.put(HttpHeader.HOST,_authority.getValue()); return new MetaData(HttpVersion.HTTP_2,fields,_contentLength); } finally { - _status=0; + _fields = new HttpFields(Math.max(10,fields.size()+5)); + _request=false; + _response=false; + _status=-1; _method=null; _scheme=null; _authority=null; _path=null; _size=0; - _contentLength=Long.MIN_VALUE; + _contentLength=Long.MIN_VALUE; } } @@ -189,13 +281,14 @@ public class MetaDataBuilder * Check that the max size will not be exceeded. * @param length the length * @param huffman the huffman name + * @throws SessionException in case of size errors */ - public void checkSize(int length, boolean huffman) + public void checkSize(int length, boolean huffman) throws SessionException { // Apply a huffman fudge factor if (huffman) length=(length*4)/3; if ((_size+length)>_maxSize) - throw new BadMessageException(HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE_431,"Header size "+(_size+length)+">"+_maxSize); + throw new HpackException.SessionException("Header too large %d > %d", _size+length, _maxSize); } } diff --git a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackContextTest.java b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackContextTest.java index 8af7b0e11e5..d91caff99cf 100644 --- a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackContextTest.java +++ b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackContextTest.java @@ -411,7 +411,7 @@ public class HpackContextTest } @Test - public void testStaticHuffmanValues() + public void testStaticHuffmanValues() throws Exception { HpackContext ctx = new HpackContext(4096); for (int i=2;i<=14;i++) diff --git a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java index d6cb341aca9..f912cf8d9d9 100644 --- a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java +++ b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java @@ -22,22 +22,17 @@ package org.eclipse.jetty.http2.hpack; import java.nio.ByteBuffer; import java.util.Iterator; -import org.eclipse.jetty.http.BadMessageException; import org.eclipse.jetty.http.HttpField; -import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpScheme; -import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.http2.hpack.HpackException.CompressionException; +import org.eclipse.jetty.http2.hpack.HpackException.StreamException; import org.eclipse.jetty.util.TypeUtil; -import org.eclipse.jetty.util.log.Log; import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Test; -import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -47,7 +42,7 @@ import static org.junit.Assert.assertTrue; public class HpackDecoderTest { @Test - public void testDecodeD_3() + public void testDecodeD_3() throws Exception { HpackDecoder decoder = new HpackDecoder(4096,8192); @@ -95,7 +90,7 @@ public class HpackDecoderTest } @Test - public void testDecodeD_4() + public void testDecodeD_4() throws Exception { HpackDecoder decoder = new HpackDecoder(4096,8192); @@ -128,7 +123,7 @@ public class HpackDecoderTest } @Test - public void testDecodeWithArrayOffset() + public void testDecodeWithArrayOffset() throws Exception { String value = "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="; @@ -152,7 +147,7 @@ public class HpackDecoderTest } @Test - public void testDecodeHuffmanWithArrayOffset() + public void testDecodeHuffmanWithArrayOffset() throws Exception { HpackDecoder decoder = new HpackDecoder(4096,8192); @@ -172,7 +167,7 @@ public class HpackDecoderTest } @Test - public void testNghttpx() + public void testNghttpx() throws Exception { // Response encoded by nghttpx String encoded="886196C361Be940b6a65B6850400B8A00571972e080a62D1Bf5f87497cA589D34d1f9a0f0d0234327690Aa69D29aFcA954D3A5358980Ae112e0f7c880aE152A9A74a6bF3"; @@ -194,30 +189,59 @@ public class HpackDecoderTest @Test public void testResize() throws Exception { - String encoded = "3f6166871e33A13a47497f205f8841E92b043d492d49"; + String encoded = "203f136687A0E41d139d090760881c6490B2Cd39Ba7f"; ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); HpackDecoder decoder = new HpackDecoder(4096, 8192); MetaData metaData = decoder.decode(buffer); - assertThat(metaData.getFields().get(HttpHeader.HOST),is("aHostName")); - assertThat(metaData.getFields().get(HttpHeader.CONTENT_TYPE),is("some/content")); - assertThat(decoder.getHpackContext().getDynamicTableSize(),is(0)); + assertThat(metaData.getFields().get(HttpHeader.HOST),is( "localhost0")); + assertThat(metaData.getFields().get(HttpHeader.COOKIE),is("abcdefghij")); + assertThat(decoder.getHpackContext().getMaxDynamicTableSize(),is(50)); + assertThat(decoder.getHpackContext().size(),is(1)); + + + } + + @Test + public void testBadResize() throws Exception + { + /* + 4. Dynamic Table Management + 4.2. Maximum Table Size + × 1: Sends a dynamic table size update at the end of header block + -> The endpoint MUST treat this as a decoding error. + Expected: GOAWAY Frame (Error Code: COMPRESSION_ERROR) + Connection closed + */ + + String encoded = "203f136687A0E41d139d090760881c6490B2Cd39Ba7f20"; + ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + HpackDecoder decoder = new HpackDecoder(4096, 8192); + try + { + decoder.decode(buffer); + Assert.fail(); + } + catch(CompressionException e) + { + Assert.assertThat(e.getMessage(),Matchers.containsString("Dynamic table resize after fields")); + } } @Test - public void testTooBigToIndex() + public void testTooBigToIndex() throws Exception { - String encoded = "44FfEc02Df3990A190A0D4Ee5b3d2940Ec98Aa4a62D127D29e273a0aA20dEcAa190a503b262d8a2671D4A2672a927aA874988a2471D05510750c951139EdA2452a3a548cAa1aA90bE4B228342864A9E0D450A5474a92992a1aA513395448E3A0Aa17B96cFe3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f14E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F353F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F54f"; + String encoded = "3f610f17FfEc02Df3990A190A0D4Ee5b3d2940Ec98Aa4a62D127D29e273a0aA20dEcAa190a503b262d8a2671D4A2672a927aA874988a2471D05510750c951139EdA2452a3a548cAa1aA90bE4B228342864A9E0D450A5474a92992a1aA513395448E3A0Aa17B96cFe3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f14E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F353F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F54f"; ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); HpackDecoder decoder = new HpackDecoder(128,8192); MetaData metaData = decoder.decode(buffer); assertThat(decoder.getHpackContext().getDynamicTableSize(),is(0)); - assertThat(metaData.getFields().get(HttpHeader.C_PATH),Matchers.startsWith("This is a very large field")); + assertThat(metaData.getFields().get("host"),Matchers.startsWith("This is a very large field")); } @Test - public void testUnknownIndex() + public void testUnknownIndex() throws Exception { String encoded = "BE"; ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); @@ -228,11 +252,334 @@ public class HpackDecoderTest decoder.decode(buffer); Assert.fail(); } - catch (BadMessageException e) + catch (HpackException.SessionException e) { - assertThat(e.getCode(),equalTo(HttpStatus.BAD_REQUEST_400)); - assertThat(e.getReason(),Matchers.startsWith("Unknown index")); + assertThat(e.getMessage(),Matchers.startsWith("Unknown index")); } } + + /* 8.1.2.1. Pseudo-Header Fields */ + @Test() + public void test8_1_2_1_PsuedoHeaderFields() throws Exception + { + // 1:Sends a HEADERS frame that contains a unknown pseudo-header field + MetaDataBuilder mdb = new MetaDataBuilder(4096); + mdb.emit(new HttpField(":unknown","value")); + try + { + mdb.build(); + Assert.fail(); + } + catch(StreamException ex) + { + Assert.assertThat(ex.getMessage(),Matchers.containsString("Unknown pseudo header")); + } + + // 2: Sends a HEADERS frame that contains the pseudo-header field defined for response + mdb = new MetaDataBuilder(4096); + mdb.emit(new HttpField(HttpHeader.C_SCHEME,"http")); + mdb.emit(new HttpField(HttpHeader.C_METHOD,"GET")); + mdb.emit(new HttpField(HttpHeader.C_PATH,"/path")); + mdb.emit(new HttpField(HttpHeader.C_STATUS,"100")); + try + { + mdb.build(); + Assert.fail(); + } + catch(StreamException ex) + { + Assert.assertThat(ex.getMessage(),Matchers.containsString("Request and Response headers")); + } + + // 3: Sends a HEADERS frame that contains a pseudo-header field as trailers + + // 4: Sends a HEADERS frame that contains a pseudo-header field that appears in a header block after a regular header field + mdb = new MetaDataBuilder(4096); + mdb.emit(new HttpField(HttpHeader.C_SCHEME,"http")); + mdb.emit(new HttpField(HttpHeader.C_METHOD,"GET")); + mdb.emit(new HttpField(HttpHeader.C_PATH,"/path")); + mdb.emit(new HttpField("Accept","No Compromise")); + mdb.emit(new HttpField(HttpHeader.C_AUTHORITY,"localhost")); + try + { + mdb.build(); + Assert.fail(); + } + catch(StreamException ex) + { + Assert.assertThat(ex.getMessage(),Matchers.containsString("Pseudo header :authority after fields")); + } + } + + @Test() + public void test8_1_2_2_ConnectionSpecificHeaderFields() throws Exception + { + MetaDataBuilder mdb; + + // 1: Sends a HEADERS frame that contains the connection-specific header field + mdb = new MetaDataBuilder(4096); + mdb.emit(new HttpField(HttpHeader.CONNECTION,"value")); + try + { + mdb.build(); + Assert.fail(); + } + catch(StreamException ex) + { + Assert.assertThat(ex.getMessage(),Matchers.containsString("Connection specific field 'Connection'")); + } + + // 2: Sends a HEADERS frame that contains the TE header field with any value other than "trailers" + mdb = new MetaDataBuilder(4096); + mdb.emit(new HttpField(HttpHeader.TE,"not_trailers")); + try + { + mdb.build(); + Assert.fail(); + } + catch(StreamException ex) + { + Assert.assertThat(ex.getMessage(),Matchers.containsString("Unsupported TE value 'not_trailers'")); + } + + + mdb = new MetaDataBuilder(4096); + mdb.emit(new HttpField(HttpHeader.CONNECTION,"TE")); + mdb.emit(new HttpField(HttpHeader.TE,"trailers")); + Assert.assertNotNull(mdb.build()); + } + + + @Test() + public void test8_1_2_3_RequestPseudoHeaderFields() throws Exception + { + MetaDataBuilder mdb; + + mdb = new MetaDataBuilder(4096); + mdb.emit(new HttpField(HttpHeader.C_METHOD,"GET")); + mdb.emit(new HttpField(HttpHeader.C_SCHEME,"http")); + mdb.emit(new HttpField(HttpHeader.C_AUTHORITY,"localhost:8080")); + mdb.emit(new HttpField(HttpHeader.C_PATH,"/")); + Assert.assertThat(mdb.build(),Matchers.instanceOf(MetaData.Request.class)); + + + // 1: Sends a HEADERS frame with empty ":path" pseudo-header field + mdb = new MetaDataBuilder(4096); + mdb = new MetaDataBuilder(4096); + mdb.emit(new HttpField(HttpHeader.C_METHOD,"GET")); + mdb.emit(new HttpField(HttpHeader.C_SCHEME,"http")); + mdb.emit(new HttpField(HttpHeader.C_AUTHORITY,"localhost:8080")); + mdb.emit(new HttpField(HttpHeader.C_PATH,"")); + try + { + mdb.build(); + Assert.fail(); + } + catch(StreamException ex) + { + Assert.assertThat(ex.getMessage(),Matchers.containsString("No Path")); + } + + // 2: Sends a HEADERS frame that omits ":method" pseudo-header field + mdb = new MetaDataBuilder(4096); + mdb.emit(new HttpField(HttpHeader.C_SCHEME,"http")); + mdb.emit(new HttpField(HttpHeader.C_AUTHORITY,"localhost:8080")); + mdb.emit(new HttpField(HttpHeader.C_PATH,"/")); + try + { + mdb.build(); + Assert.fail(); + } + catch(StreamException ex) + { + Assert.assertThat(ex.getMessage(),Matchers.containsString("No Method")); + } + + + // 3: Sends a HEADERS frame that omits ":scheme" pseudo-header field + mdb = new MetaDataBuilder(4096); + mdb.emit(new HttpField(HttpHeader.C_METHOD,"GET")); + mdb.emit(new HttpField(HttpHeader.C_AUTHORITY,"localhost:8080")); + mdb.emit(new HttpField(HttpHeader.C_PATH,"/")); + try + { + mdb.build(); + Assert.fail(); + } + catch(StreamException ex) + { + Assert.assertThat(ex.getMessage(),Matchers.containsString("No Scheme")); + } + + // 4: Sends a HEADERS frame that omits ":path" pseudo-header field + mdb = new MetaDataBuilder(4096); + mdb.emit(new HttpField(HttpHeader.C_METHOD,"GET")); + mdb.emit(new HttpField(HttpHeader.C_SCHEME,"http")); + mdb.emit(new HttpField(HttpHeader.C_AUTHORITY,"localhost:8080")); + try + { + mdb.build(); + Assert.fail(); + } + catch(StreamException ex) + { + Assert.assertThat(ex.getMessage(),Matchers.containsString("No Path")); + } + + // 5: Sends a HEADERS frame with duplicated ":method" pseudo-header field + mdb = new MetaDataBuilder(4096); + mdb.emit(new HttpField(HttpHeader.C_METHOD,"GET")); + mdb.emit(new HttpField(HttpHeader.C_METHOD,"GET")); + mdb.emit(new HttpField(HttpHeader.C_SCHEME,"http")); + mdb.emit(new HttpField(HttpHeader.C_AUTHORITY,"localhost:8080")); + mdb.emit(new HttpField(HttpHeader.C_PATH,"/")); + try + { + mdb.build(); + Assert.fail(); + } + catch(StreamException ex) + { + Assert.assertThat(ex.getMessage(),Matchers.containsString("Duplicate")); + } + + // 6: Sends a HEADERS frame with duplicated ":scheme" pseudo-header field + mdb = new MetaDataBuilder(4096); + mdb.emit(new HttpField(HttpHeader.C_METHOD,"GET")); + mdb.emit(new HttpField(HttpHeader.C_SCHEME,"http")); + mdb.emit(new HttpField(HttpHeader.C_SCHEME,"http")); + mdb.emit(new HttpField(HttpHeader.C_AUTHORITY,"localhost:8080")); + mdb.emit(new HttpField(HttpHeader.C_PATH,"/")); + try + { + mdb.build(); + Assert.fail(); + } + catch(StreamException ex) + { + Assert.assertThat(ex.getMessage(),Matchers.containsString("Duplicate")); + } + } + + + @Test() + public void testHuffmanEncodedStandard() throws Exception + { + HpackDecoder decoder = new HpackDecoder(4096, 8192); + + String encoded = "82868441" + "83" + "49509F"; + ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + + MetaData.Request request = (MetaData.Request)decoder.decode(buffer); + + assertEquals("GET", request.getMethod()); + assertEquals(HttpScheme.HTTP.asString(), request.getURI().getScheme()); + assertEquals("/", request.getURI().getPath()); + assertEquals("test", request.getURI().getHost()); + assertFalse(request.iterator().hasNext()); + } + + + /* 5.2.1: Sends a Huffman-encoded string literal representation with padding longer than 7 bits */ + @Test() + public void testHuffmanEncodedExtraPadding() throws Exception + { + HpackDecoder decoder = new HpackDecoder(4096, 8192); + + String encoded = "82868441" + "84" + "49509FFF"; + ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + + try + { + decoder.decode(buffer); + Assert.fail(); + } + catch (CompressionException ex) + { + Assert.assertThat(ex.getMessage(), Matchers.containsString("Bad termination")); + } + } + + + /* 5.2.2: Sends a Huffman-encoded string literal representation padded by zero */ + @Test() + public void testHuffmanEncodedZeroPadding() throws Exception + { + HpackDecoder decoder = new HpackDecoder(4096, 8192); + + String encoded = "82868441" + "83" + "495090"; + ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + + try + { + decoder.decode(buffer); + Assert.fail(); + } + catch (CompressionException ex) + { + Assert.assertThat(ex.getMessage(), Matchers.containsString("Incorrect padding")); + } + } + + + /* 5.2.3: Sends a Huffman-encoded string literal representation containing the EOS symbol */ + @Test() + public void testHuffmanEncodedWithEOS() throws Exception + { + HpackDecoder decoder = new HpackDecoder(4096, 8192); + + String encoded = "82868441" + "87" + "497FFFFFFF427F"; + ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + + try + { + decoder.decode(buffer); + Assert.fail(); + } + catch (CompressionException ex) + { + Assert.assertThat(ex.getMessage(), Matchers.containsString("EOS in content")); + } + } + + + @Test() + public void testHuffmanEncodedOneIncompleteOctet() throws Exception + { + HpackDecoder decoder = new HpackDecoder(4096, 8192); + + String encoded = "82868441" + "81" + "FE"; + ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + + try + { + decoder.decode(buffer); + Assert.fail(); + } + catch (CompressionException ex) + { + Assert.assertThat(ex.getMessage(), Matchers.containsString("Bad termination")); + } + } + + + @Test() + public void testHuffmanEncodedTwoIncompleteOctet() throws Exception + { + HpackDecoder decoder = new HpackDecoder(4096, 8192); + + String encoded = "82868441" + "82" + "FFFE"; + ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + + try + { + decoder.decode(buffer); + Assert.fail(); + } + catch (CompressionException ex) + { + Assert.assertThat(ex.getMessage(), Matchers.containsString("Bad termination")); + } + } } diff --git a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackEncoderTest.java b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackEncoderTest.java index 00349c46017..66381b69c35 100644 --- a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackEncoderTest.java +++ b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackEncoderTest.java @@ -249,4 +249,28 @@ public class HpackEncoderTest context.get(HpackContext.STATIC_SIZE+1).getSize()+context.get(HpackContext.STATIC_SIZE+2).getSize())); } + + @Test + public void testResize() + { + HttpFields fields = new HttpFields(); + fields.add("host", "localhost0"); + fields.add("cookie","abcdefghij"); + + HpackEncoder encoder = new HpackEncoder(4096); + + ByteBuffer buffer = BufferUtil.allocate(4096); + int pos = BufferUtil.flipToFill(buffer); + encoder.encodeMaxDynamicTableSize(buffer,0); + encoder.setRemoteMaxDynamicTableSize(50); + encoder.encode(buffer,new MetaData(HttpVersion.HTTP_2,fields)); + BufferUtil.flipToFlush(buffer,pos); + + HpackContext context = encoder.getHpackContext(); + + Assert.assertThat(context.getMaxDynamicTableSize(),Matchers.is(50)); + Assert.assertThat(context.size(),Matchers.is(1)); + + + } } diff --git a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackTest.java b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackTest.java index 31c1ee21d8f..2f3f461de31 100644 --- a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackTest.java +++ b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackTest.java @@ -18,15 +18,18 @@ package org.eclipse.jetty.http2.hpack; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + import java.nio.ByteBuffer; import java.util.concurrent.TimeUnit; -import org.eclipse.jetty.http.BadMessageException; import org.eclipse.jetty.http.DateGenerator; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; -import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http.MetaData.Response; @@ -35,10 +38,6 @@ import org.eclipse.jetty.util.BufferUtil; import org.junit.Assert; import org.junit.Test; -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; - public class HpackTest { final static HttpField ServerJetty = new PreEncodedHttpField(HttpHeader.SERVER,"jetty"); @@ -46,7 +45,7 @@ public class HpackTest final static HttpField Date = new PreEncodedHttpField(HttpHeader.DATE,DateGenerator.formatDate(TimeUnit.NANOSECONDS.toMillis(System.nanoTime()))); @Test - public void encodeDecodeResponseTest() + public void encodeDecodeResponseTest() throws Exception { HpackEncoder encoder = new HpackEncoder(); HpackDecoder decoder = new HpackDecoder(4096,8192); @@ -99,7 +98,7 @@ public class HpackTest } @Test - public void encodeDecodeTooLargeTest() + public void encodeDecodeTooLargeTest() throws Exception { HpackEncoder encoder = new HpackEncoder(); HpackDecoder decoder = new HpackDecoder(4096,164); @@ -131,14 +130,14 @@ public class HpackTest decoder.decode(buffer); Assert.fail(); } - catch(BadMessageException e) + catch(HpackException.SessionException e) { - assertEquals(HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE_431,e.getCode()); + assertThat(e.getMessage(),containsString("Header too large")); } } @Test - public void evictReferencedFieldTest() + public void evictReferencedFieldTest() throws Exception { HpackEncoder encoder = new HpackEncoder(200,200); HpackDecoder decoder = new HpackDecoder(200,1024); diff --git a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HuffmanTest.java b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HuffmanTest.java index 61a6f42e184..3b697ac0fe5 100644 --- a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HuffmanTest.java +++ b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HuffmanTest.java @@ -50,17 +50,6 @@ public class HuffmanTest } } - @Test - public void testDecodeTrailingFF() throws Exception - { - for (String[] test:tests) - { - byte[] encoded=TypeUtil.fromHexString(test[1]+"FF"); - String decoded=Huffman.decode(ByteBuffer.wrap(encoded)); - Assert.assertEquals(test[0],test[2],decoded); - } - } - @Test public void testEncode() throws Exception { diff --git a/jetty-http2/http2-hpack/src/test/resources/jetty-logging.properties b/jetty-http2/http2-hpack/src/test/resources/jetty-logging.properties index e40e8e43ce1..f4e33644f4c 100644 --- a/jetty-http2/http2-hpack/src/test/resources/jetty-logging.properties +++ b/jetty-http2/http2-hpack/src/test/resources/jetty-logging.properties @@ -1,3 +1,3 @@ org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog -org.eclipse.jetty.http2.LEVEL=INFO -org.eclipse.jetty.http2.hpack.LEVEL=INFO +#org.eclipse.jetty.http2.LEVEL=DEBUG +#org.eclipse.jetty.http2.hpack.LEVEL=DEBUG diff --git a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpReceiverOverHTTP2.java b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpReceiverOverHTTP2.java index 354e19f5e45..a0f1817a6bb 100644 --- a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpReceiverOverHTTP2.java +++ b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpReceiverOverHTTP2.java @@ -23,7 +23,6 @@ import java.nio.ByteBuffer; import java.util.ArrayDeque; import java.util.Collections; import java.util.List; -import java.util.Locale; import java.util.Queue; import java.util.function.BiFunction; @@ -165,10 +164,8 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements Stream.Listen HttpExchange exchange = getHttpExchange(); if (exchange == null) return; - - ErrorCode error = ErrorCode.from(frame.getError()); - String reason = error == null ? "reset" : error.name().toLowerCase(Locale.ENGLISH); - exchange.getRequest().abort(new IOException(reason)); + int error = frame.getError(); + exchange.getRequest().abort(new IOException(ErrorCode.toString(error, "reset_code_" + error))); } @Override @@ -178,6 +175,13 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements Stream.Listen return true; } + @Override + public void onFailure(Stream stream, int error, String reason, Callback callback) + { + responseFailure(new IOException(String.format("%s/%s", ErrorCode.toString(error, null), reason))); + callback.succeeded(); + } + private void notifyContent(HttpExchange exchange, DataFrame frame, Callback callback) { contentNotifier.offer(new DataInfo(exchange, frame, callback)); diff --git a/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2Test.java b/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2Test.java index e668fc20056..a6eb2acb7a8 100644 --- a/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2Test.java +++ b/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2Test.java @@ -576,6 +576,40 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest Assert.assertArrayEquals(bytes, response.getContent()); } + @Test + public void testInvalidResponseHPack() throws Exception + { + start(new ServerSessionListener.Adapter() + { + @Override + public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) + { + // Produce an invalid HPACK block by adding a request pseudo-header to the response. + HttpFields fields = new HttpFields(); + fields.put(":method", "get"); + MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, fields, 0); + int streamId = stream.getId(); + HeadersFrame responseFrame = new HeadersFrame(streamId, response, null, false); + Callback.Completable callback = new Callback.Completable(); + stream.headers(responseFrame, callback); + byte[] bytes = "hello".getBytes(StandardCharsets.US_ASCII); + callback.thenRun(() -> stream.data(new DataFrame(streamId, ByteBuffer.wrap(bytes), true), Callback.NOOP)); + return null; + } + }); + + CountDownLatch latch = new CountDownLatch(1); + client.newRequest("localhost", connector.getLocalPort()) + .timeout(5, TimeUnit.SECONDS) + .send(result -> + { + if (result.isFailed()) + latch.countDown(); + }); + + Assert.assertTrue(latch.await(5, TimeUnit.SECONDS)); + } + @Ignore @Test public void testExternalServer() throws Exception diff --git a/jetty-http2/http2-server/pom.xml b/jetty-http2/http2-server/pom.xml index 522ab2e9115..9e7196363c4 100644 --- a/jetty-http2/http2-server/pom.xml +++ b/jetty-http2/http2-server/pom.xml @@ -1,5 +1,6 @@ - + org.eclipse.jetty.http2 http2-parent @@ -10,55 +11,76 @@ http2-server Jetty :: HTTP2 :: Server - + ${project.groupId}.server + + com.github.madgnome + h2spec-maven-plugin + + org.eclipse.jetty.http2.server.H2SpecServer + ${skipTests} + + + 5.1 - closed: Sends a DATA frame + + + + + h2spec + test + + h2spec + + + + - - - org.eclipse.jetty.http2 - http2-common - ${project.version} - - - org.eclipse.jetty - jetty-server - ${project.version} - - - org.eclipse.jetty.alpn - alpn-api - ${alpn.api.version} - provided - - - org.eclipse.jetty - jetty-servlet - ${project.version} - test - - - org.eclipse.jetty - jetty-servlets - ${project.version} - test - - - org.eclipse.jetty - jetty-alpn-server - ${project.version} - test - - - org.eclipse.jetty.toolchain - jetty-test-helper - test - - + + + org.eclipse.jetty.http2 + http2-common + ${project.version} + + + org.eclipse.jetty + jetty-server + ${project.version} + + + org.eclipse.jetty.alpn + alpn-api + ${alpn.api.version} + provided + + + org.eclipse.jetty + jetty-servlet + ${project.version} + test + + + org.eclipse.jetty + jetty-servlets + ${project.version} + test + + + org.eclipse.jetty + jetty-alpn-server + ${project.version} + test + + + org.eclipse.jetty.toolchain + jetty-test-helper + test + + diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java index 317b7db7b92..853ab0ef0ad 100644 --- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java +++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java @@ -50,6 +50,7 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne private int initialStreamRecvWindow = 512 * 1024; private int maxConcurrentStreams = 128; private int maxHeaderBlockFragment = 0; + private int maxFrameLength = Frame.DEFAULT_MAX_LENGTH; private int maxSettingsKeys = SettingsFrame.DEFAULT_MAX_KEYS; private FlowControlStrategy.Factory flowControlStrategyFactory = () -> new BufferingFlowControlStrategy(0.5F); private long streamIdleTimeout; @@ -146,6 +147,17 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne this.streamIdleTimeout = streamIdleTimeout; } + @ManagedAttribute("The max frame length in bytes") + public int getMaxFrameLength() + { + return maxFrameLength; + } + + public void setMaxFrameLength(int maxFrameLength) + { + this.maxFrameLength = maxFrameLength; + } + @ManagedAttribute("The max number of keys in all SETTINGS frames") public int getMaxSettingsKeys() { @@ -217,6 +229,7 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne session.setWriteThreshold(getHttpConfiguration().getOutputBufferSize()); ServerParser parser = newServerParser(connector, session); + parser.setMaxFrameLength(getMaxFrameLength()); parser.setMaxSettingsKeys(getMaxSettingsKeys()); HTTP2Connection connection = new HTTP2ServerConnection(connector.getByteBufferPool(), connector.getExecutor(), diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java index eee89bd9b48..3feb1f12fad 100644 --- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java +++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java @@ -115,13 +115,10 @@ public class HTTP2ServerConnectionFactory extends AbstractHTTP2ServerConnectionF @Override public void onClose(Session session, GoAwayFrame frame, Callback callback) { - ErrorCode error = ErrorCode.from(frame.getError()); - if (error == null) - error = ErrorCode.STREAM_CLOSED_ERROR; String reason = frame.tryConvertPayload(); if (reason != null && !reason.isEmpty()) reason = " (" + reason + ")"; - getConnection().onSessionFailure(new EofException("HTTP/2 " + error + reason), callback); + getConnection().onSessionFailure(new EofException(String.format("Close %s/%s", ErrorCode.toString(frame.getError(), null), reason)), callback); } @Override @@ -156,10 +153,13 @@ public class HTTP2ServerConnectionFactory extends AbstractHTTP2ServerConnectionF @Override public void onReset(Stream stream, ResetFrame frame, Callback callback) { - ErrorCode error = ErrorCode.from(frame.getError()); - if (error == null) - error = ErrorCode.CANCEL_STREAM_ERROR; - getConnection().onStreamFailure((IStream)stream, new EofException("HTTP/2 " + error), callback); + getConnection().onStreamFailure((IStream)stream, new EofException("Reset " + ErrorCode.toString(frame.getError(), null)), callback); + } + + @Override + public void onFailure(Stream stream, int error, String reason, Callback callback) + { + getConnection().onStreamFailure((IStream)stream, new EofException(String.format("Failure %s/%s", ErrorCode.toString(error, null), reason)), callback); } @Override diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerSession.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerSession.java index dfe5c828039..c89c72ed9ca 100644 --- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerSession.java +++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerSession.java @@ -83,16 +83,39 @@ public class HTTP2ServerSession extends HTTP2Session implements ServerParser.Lis if (LOG.isDebugEnabled()) LOG.debug("Received {}", frame); + int streamId = frame.getStreamId(); + if (!isClientStream(streamId)) + { + onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "invalid_stream_id"); + return; + } + + IStream stream = getStream(streamId); + MetaData metaData = frame.getMetaData(); if (metaData.isRequest()) { - IStream stream = createRemoteStream(frame.getStreamId()); - if (stream != null) + if (stream == null) { - onStreamOpened(stream); - stream.process(frame, Callback.NOOP); - Stream.Listener listener = notifyNewStream(stream, frame); - stream.setListener(listener); + if (isRemoteStreamClosed(streamId)) + { + onConnectionFailure(ErrorCode.STREAM_CLOSED_ERROR.code, "unexpected_headers_frame"); + } + else + { + stream = createRemoteStream(streamId); + if (stream != null) + { + onStreamOpened(stream); + stream.process(frame, Callback.NOOP); + Stream.Listener listener = notifyNewStream(stream, frame); + stream.setListener(listener); + } + } + } + else + { + onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "duplicate_stream"); } } else if (metaData.isResponse()) @@ -102,8 +125,6 @@ public class HTTP2ServerSession extends HTTP2Session implements ServerParser.Lis else { // Trailers. - int streamId = frame.getStreamId(); - IStream stream = getStream(streamId); if (stream != null) { stream.process(frame, Callback.NOOP); @@ -112,7 +133,8 @@ public class HTTP2ServerSession extends HTTP2Session implements ServerParser.Lis else { if (LOG.isDebugEnabled()) - LOG.debug("Ignoring {}, stream #{} not found", frame, streamId); + LOG.debug("Stream #{} not found", streamId); + onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "unexpected_headers_frame"); } } } diff --git a/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/H2SpecServer.java b/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/H2SpecServer.java new file mode 100644 index 00000000000..de99425b90b --- /dev/null +++ b/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/H2SpecServer.java @@ -0,0 +1,48 @@ +// +// ======================================================================== +// Copyright (c) 1995-2018 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.http2.server; + +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; + +/** + * HTTP/2 server to run the 'h2spec' tool against. + */ +public class H2SpecServer +{ + public static void main(String[] args) throws Exception + { + int port = Integer.parseInt(args[0]); + + Server server = new Server(); + + HttpConfiguration http_config = new HttpConfiguration(); + http_config.setRequestHeaderSize(16 * 1024); + + HttpConnectionFactory http = new HttpConnectionFactory(http_config); + HTTP2CServerConnectionFactory h2c = new HTTP2CServerConnectionFactory(http_config); + ServerConnector connector = new ServerConnector(server, http, h2c); + connector.setPort(port); + server.addConnector(connector); + + server.start(); + } +} diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java index cac08240b57..801ce7bda87 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java @@ -23,6 +23,7 @@ import java.io.IOException; import java.net.ConnectException; import java.net.SocketTimeoutException; import java.nio.channels.CancelledKeyException; +import java.nio.channels.ClosedSelectorException; import java.nio.channels.SelectableChannel; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; @@ -101,7 +102,9 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable _selectorManager.execute(_strategy::produce); // Set started only if we really are started - submit(s->_started.set(true)); + Start start = new Start(); + submit(start); + start._started.await(); } public int size() @@ -421,6 +424,10 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable { if (LOG.isDebugEnabled()) LOG.debug("Selector {} woken with none selected", selector); + + if (Thread.interrupted() && !isRunning()) + throw new ClosedSelectorException(); + selected = selector.selectNow(); } if (LOG.isDebugEnabled()) @@ -536,6 +543,18 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable public void update(Selector selector); } + private class Start implements SelectorUpdate + { + private final CountDownLatch _started = new CountDownLatch(1); + + @Override + public void update(Selector selector) + { + ManagedSelector.this._started.set(true); + _started.countDown(); + } + } + private static class DumpKeys implements SelectorUpdate { private CountDownLatch latch = new CountDownLatch(1); diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java index 7078c01635b..bb16309a48b 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java @@ -546,7 +546,7 @@ public class SslConnection extends AbstractConnection if (LOG.isDebugEnabled()) LOG.debug("net filled={}", net_filled); - if (net_filled > 0 && _handshake.get() == Handshake.INITIAL && _sslEngine.isOutboundDone()) + if (net_filled > 0 && _handshake.get() == Handshake.INITIAL && isOutboundDone()) throw new SSLHandshakeException("Closed during handshake"); // Let's unwrap even if we have no net data because in that @@ -783,6 +783,10 @@ public class SslConnection extends AbstractConnection else LOG.ignore(x); } + catch (Throwable x) + { + LOG.ignore(x); + } } @Override @@ -1036,7 +1040,7 @@ public class SslConnection extends AbstractConnection if (!_closedOutbound) { _closedOutbound=true; // Only attempt this once - _sslEngine.closeOutbound(); + closeOutbound(); flush = true; } @@ -1059,6 +1063,18 @@ public class SslConnection extends AbstractConnection } } + private void closeOutbound() + { + try + { + _sslEngine.closeOutbound(); + } + catch (Throwable x) + { + LOG.ignore(x); + } + } + private void ensureFillInterested() { if (LOG.isDebugEnabled()) @@ -1069,7 +1085,20 @@ public class SslConnection extends AbstractConnection @Override public boolean isOutputShutdown() { - return _sslEngine.isOutboundDone() || getEndPoint().isOutputShutdown(); + return isOutboundDone() || getEndPoint().isOutputShutdown(); + } + + private boolean isOutboundDone() + { + try + { + return _sslEngine.isOutboundDone(); + } + catch (Throwable x) + { + LOG.ignore(x); + return true; + } } @Override @@ -1090,7 +1119,20 @@ public class SslConnection extends AbstractConnection @Override public boolean isInputShutdown() { - return getEndPoint().isInputShutdown() || _sslEngine.isInboundDone(); + return getEndPoint().isInputShutdown() || isInboundDone(); + } + + private boolean isInboundDone() + { + try + { + return _sslEngine.isInboundDone(); + } + catch (Throwable x) + { + LOG.ignore(x); + return true; + } } private void notifyHandshakeSucceeded(SSLEngine sslEngine) diff --git a/jetty-jmh/pom.xml b/jetty-jmh/pom.xml new file mode 100644 index 00000000000..71ebaccfc56 --- /dev/null +++ b/jetty-jmh/pom.xml @@ -0,0 +1,111 @@ + + + org.eclipse.jetty + jetty-project + 9.4.12-SNAPSHOT + + 4.0.0 + jetty-jmh + Jetty :: Jmh + Jmh classes for Jetty + http://www.eclipse.org/jetty + + + + org.codehaus.mojo + findbugs-maven-plugin + + true + + + + org.apache.felix + maven-bundle-plugin + true + + true + + + + org.apache.maven.plugins + maven-shade-plugin + + + package + + shade + + + ${jmhjar.name} + true + + + org.openjdk.jmh.Main + + + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + + + + + + + org.apache.maven.plugins + maven-deploy-plugin + + + true + + + + + + + + org.eclipse.jetty + jetty-util + ${project.version} + + + org.eclipse.jetty + jetty-http + ${project.version} + + + org.eclipse.jetty + jetty-http + ${project.version} + tests + + + javax.servlet + javax.servlet-api + + + org.eclipse.jetty.toolchain + jetty-test-helper + + + org.openjdk.jmh + jmh-core + ${jmh.version} + + + org.openjdk.jmh + jmh-generator-annprocess + ${jmh.version} + + + diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/jmh/MultiPartBenchmark.java b/jetty-jmh/src/main/java/org/eclipse/jetty/http/jmh/MultiPartBenchmark.java similarity index 87% rename from jetty-http/src/test/java/org/eclipse/jetty/http/jmh/MultiPartBenchmark.java rename to jetty-jmh/src/main/java/org/eclipse/jetty/http/jmh/MultiPartBenchmark.java index 7bf56f04e09..8c985728875 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/jmh/MultiPartBenchmark.java +++ b/jetty-jmh/src/main/java/org/eclipse/jetty/http/jmh/MultiPartBenchmark.java @@ -20,34 +20,42 @@ package org.eclipse.jetty.http.jmh; import java.io.File; import java.io.InputStream; +import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.List; import java.util.Random; +import java.util.concurrent.TimeUnit; import javax.servlet.MultipartConfigElement; import javax.servlet.http.Part; import org.eclipse.jetty.http.MultiPartFormInputStream; import org.eclipse.jetty.http.MultiPartCaptureTest.MultipartExpectations; -import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.toolchain.test.IO; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.Param; 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.annotations.Threads; +import org.openjdk.jmh.annotations.Warmup; 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) +@Threads(4) +@Warmup(iterations = 7, time = 500, timeUnit = TimeUnit.MILLISECONDS) +@Measurement(iterations = 7, time = 500, timeUnit = TimeUnit.MILLISECONDS) public class MultiPartBenchmark { @@ -212,15 +220,23 @@ public class MultiPartBenchmark { for (String multiPart : data) { - Path multipartRawFile = MavenTestingUtils.getTestResourcePathFile("multipart/" + multiPart + ".raw"); - Path expectationPath = MavenTestingUtils.getTestResourcePathFile("multipart/" + multiPart + ".expected.txt"); + //Path multipartRawFile = MavenTestingUtils.getTestResourcePathFile("multipart/" + multiPart + ".raw"); + String expectationPath = "multipart/" + multiPart + ".expected.txt"; + //Path expectationPath = MavenTestingUtils.getTestResourcePathFile("multipart/" + multiPart + ".expected.txt"); + + File expectationFile = File.createTempFile( expectationPath, ".tmp" ); + + try(InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(expectationPath); + OutputStream os = Files.newOutputStream( expectationFile.toPath() )) { + IO.copy( inputStream, os ); + } + + Path outputDir = Files.createTempDirectory( "expected_output_jmh_jetty" );// new File("/tmp").toPath(); - Path outputDir = new File("/tmp").toPath(); - - MultipartExpectations multipartExpectations = new MultipartExpectations(expectationPath); + MultipartExpectations multipartExpectations = new MultipartExpectations(expectationFile.toPath()); MultipartConfigElement config = newMultipartConfigElement(outputDir); - try (InputStream in = Files.newInputStream(multipartRawFile)) + try (InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream( "multipart/" + multiPart + ".raw" )) { switch (parserType) { diff --git a/jetty-jmh/src/main/java/org/eclipse/jetty/util/jmh/DateCacheBenchmark.java b/jetty-jmh/src/main/java/org/eclipse/jetty/util/jmh/DateCacheBenchmark.java new file mode 100644 index 00000000000..aa11e93a3cc --- /dev/null +++ b/jetty-jmh/src/main/java/org/eclipse/jetty/util/jmh/DateCacheBenchmark.java @@ -0,0 +1,91 @@ +// +// ======================================================================== +// Copyright (c) 1995-2018 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.util.jmh; + +import org.eclipse.jetty.util.DateCache; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Threads; +import org.openjdk.jmh.annotations.Warmup; +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; +import org.openjdk.jmh.runner.options.TimeValue; + +import java.time.Instant; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +@State(Scope.Benchmark) +@Threads(4) +@Warmup(iterations = 7, time = 500, timeUnit = TimeUnit.MILLISECONDS) +@Measurement(iterations = 7, time = 500, timeUnit = TimeUnit.MILLISECONDS) +public class DateCacheBenchmark +{ + + DateCache dateCache = new DateCache(); + long timestamp = Instant.now().toEpochMilli(); + + @Benchmark + @BenchmarkMode(Mode.Throughput) + public void testDateCacheTimestamp() + { + dateCache.format(timestamp); + } + + @Benchmark + @BenchmarkMode(Mode.Throughput) + public void testDateCacheNow() + { + dateCache.format(new Date()); + } + + @Benchmark + @BenchmarkMode(Mode.Throughput) + public void testDateCacheFormatNow() + { + dateCache.formatNow(System.currentTimeMillis()); + } + + public static void main(String[] args) throws RunnerException + { + Options opt = new OptionsBuilder() + .include(DateCacheBenchmark.class.getSimpleName()) + .warmupIterations(2) + .measurementIterations(3) + .forks(1) + .threads(400) + // .syncIterations(true) // Don't start all threads at same time + .warmupTime(new TimeValue(10000,TimeUnit.MILLISECONDS)) + .measurementTime(new TimeValue(10000,TimeUnit.MILLISECONDS)) + // .addProfiler(CompilerProfiler.class) + // .addProfiler(LinuxPerfProfiler.class) + // .addProfiler(LinuxPerfNormProfiler.class) + // .addProfiler(LinuxPerfAsmProfiler.class) + // .resultFormat(ResultFormatType.CSV) + .build(); + + new Runner(opt).run(); + } +} \ No newline at end of file diff --git a/jetty-jmh/src/main/java/org/eclipse/jetty/util/jmh/DateCacheNoTick.java b/jetty-jmh/src/main/java/org/eclipse/jetty/util/jmh/DateCacheNoTick.java new file mode 100644 index 00000000000..0b224055f39 --- /dev/null +++ b/jetty-jmh/src/main/java/org/eclipse/jetty/util/jmh/DateCacheNoTick.java @@ -0,0 +1,195 @@ +// +// ======================================================================== +// Copyright (c) 1995-2018 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.util.jmh; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +/** + * Date Format Cache. + * Computes String representations of Dates and caches + * the results so that subsequent requests within the same second + * will be fast. + * + * Only format strings that contain either "ss". Sub second formatting is + * not handled. + * + * The timezone of the date may be included as an ID with the "zzz" + * format string or as an offset with the "ZZZ" format string. + * + * If consecutive calls are frequently very different, then this + * may be a little slower than a normal DateFormat. + */ +public class DateCacheNoTick +{ + public static final String DEFAULT_FORMAT="EEE MMM dd HH:mm:ss zzz yyyy"; + + private final String _formatString; + private final String _tzFormatString; + private final DateTimeFormatter _tzFormat; + private final Locale _locale; + private final ZoneId _zoneId; + + /* ------------------------------------------------------------ */ + /** Constructor. + * Make a DateCache that will use a default format. The default format + * generates the same results as Date.toString(). + */ + public DateCacheNoTick() + { + this(DEFAULT_FORMAT); + } + + /* ------------------------------------------------------------ */ + /** Constructor. + * Make a DateCache that will use the given format + * @param format the format to use + */ + public DateCacheNoTick( String format) + { + this(format,null,TimeZone.getDefault()); + } + + /* ------------------------------------------------------------ */ + public DateCacheNoTick( String format, Locale l) + { + this(format,l,TimeZone.getDefault()); + } + + /* ------------------------------------------------------------ */ + public DateCacheNoTick( String format, Locale l, String tz) + { + this(format,l,TimeZone.getTimeZone(tz)); + } + + /* ------------------------------------------------------------ */ + public DateCacheNoTick( String format, Locale l, TimeZone tz) + { + _formatString=format; + _locale = l; + + + int zIndex = _formatString.indexOf( "ZZZ" ); + if( zIndex >= 0 ) + { + String ss1 = _formatString.substring( 0, zIndex ); + String ss2 = _formatString.substring( zIndex+3 ); + int tzOffset = tz.getRawOffset(); + + StringBuilder sb = new StringBuilder(_formatString.length()+10); + sb.append(ss1); + sb.append("'"); + if( tzOffset >= 0 ) + sb.append( '+' ); + else + { + tzOffset = -tzOffset; + sb.append( '-' ); + } + + int raw = tzOffset / (1000*60); // Convert to seconds + int hr = raw / 60; + int min = raw % 60; + + if( hr < 10 ) + sb.append( '0' ); + sb.append( hr ); + if( min < 10 ) + sb.append( '0' ); + sb.append( min ); + sb.append( '\'' ); + + sb.append(ss2); + _tzFormatString=sb.toString(); + } + else + _tzFormatString=_formatString; + + if( _locale != null ) + { + _tzFormat=DateTimeFormatter.ofPattern(_tzFormatString,_locale); + } + else + { + _tzFormat=DateTimeFormatter.ofPattern(_tzFormatString); + } + _zoneId = tz.toZoneId(); + _tzFormat.withZone(_zoneId); + } + + + /* ------------------------------------------------------------ */ + public TimeZone getTimeZone() + { + return TimeZone.getTimeZone(_zoneId); + } + + + /* ------------------------------------------------------------ */ + /** Format a date according to our stored formatter. + * @param inDate the Date + * @return Formatted date + */ + public String format(Date inDate) + { + return ZonedDateTime.ofInstant(inDate.toInstant(),_zoneId).format( _tzFormat ); + } + + /* ------------------------------------------------------------ */ + /** Format a date according to our stored formatter. + * If it happens to be in the same second as the last formatNow + * call, then the format is reused. + * @param inDate the date in milliseconds since unix epoch + * @return Formatted date + */ + public String format(long inDate) + { + return ZonedDateTime.ofInstant(Instant.ofEpochMilli(inDate),_zoneId).format( _tzFormat ); + } + + /* ------------------------------------------------------------ */ + /** Format a date according to our stored formatter. + * The passed time is expected to be close to the current time, so it is + * compared to the last value passed and if it is within the same second, + * the format is reused. Otherwise a new cached format is created. + * @param now the milliseconds since unix epoch + * @return Formatted date + */ + public String formatNow(long now) + { + return format(now); + } + + /* ------------------------------------------------------------ */ + public String now() + { + return formatNow(System.currentTimeMillis()); + } + + /* ------------------------------------------------------------ */ + public String getFormatString() + { + return _formatString; + } +} diff --git a/jetty-jmh/src/main/java/org/eclipse/jetty/util/jmh/DateCacheNoTickBenchmark.java b/jetty-jmh/src/main/java/org/eclipse/jetty/util/jmh/DateCacheNoTickBenchmark.java new file mode 100644 index 00000000000..6fb8781ca93 --- /dev/null +++ b/jetty-jmh/src/main/java/org/eclipse/jetty/util/jmh/DateCacheNoTickBenchmark.java @@ -0,0 +1,90 @@ +// +// ======================================================================== +// Copyright (c) 1995-2018 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.util.jmh; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Threads; +import org.openjdk.jmh.annotations.Warmup; +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; +import org.openjdk.jmh.runner.options.TimeValue; + +import java.time.Instant; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +@State(Scope.Benchmark) +@Threads(4) +@Warmup(iterations = 7, time = 500, timeUnit = TimeUnit.MILLISECONDS) +@Measurement(iterations = 7, time = 500, timeUnit = TimeUnit.MILLISECONDS) +public class DateCacheNoTickBenchmark +{ + + DateCacheNoTick dateCache = new DateCacheNoTick(); + long timestamp = Instant.now().toEpochMilli(); + + @Benchmark + @BenchmarkMode(Mode.Throughput) + public void testDateCacheTimestamp() + { + dateCache.format(timestamp); + } + + @Benchmark + @BenchmarkMode(Mode.Throughput) + public void testDateCacheNow() + { + dateCache.format(new Date()); + } + + @Benchmark + @BenchmarkMode(Mode.Throughput) + public void testDateCacheFormatNow() + { + dateCache.formatNow(System.currentTimeMillis()); + } + + public static void main(String[] args) throws RunnerException + { + Options opt = new OptionsBuilder() + .include(DateCacheNoTickBenchmark.class.getSimpleName()) + .warmupIterations(2) + .measurementIterations(3) + .forks(1) + .threads(400) + // .syncIterations(true) // Don't start all threads at same time + .warmupTime(new TimeValue(10000,TimeUnit.MILLISECONDS)) + .measurementTime(new TimeValue(10000,TimeUnit.MILLISECONDS)) + // .addProfiler(CompilerProfiler.class) + // .addProfiler(LinuxPerfProfiler.class) + // .addProfiler(LinuxPerfNormProfiler.class) + // .addProfiler(LinuxPerfAsmProfiler.class) + // .resultFormat(ResultFormatType.CSV) + .build(); + + new Runner(opt).run(); + } +} \ No newline at end of file diff --git a/jetty-jmh/src/main/java/org/eclipse/jetty/util/jmh/DateCacheSimpleDateFormat.java b/jetty-jmh/src/main/java/org/eclipse/jetty/util/jmh/DateCacheSimpleDateFormat.java new file mode 100644 index 00000000000..8c9826e442a --- /dev/null +++ b/jetty-jmh/src/main/java/org/eclipse/jetty/util/jmh/DateCacheSimpleDateFormat.java @@ -0,0 +1,268 @@ +// +// ======================================================================== +// Copyright (c) 1995-2018 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.util.jmh; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +/** Date Format Cache. + * Computes String representations of Dates and caches + * the results so that subsequent requests within the same second + * will be fast. + * + * Only format strings that contain either "ss". Sub second formatting is + * not handled. + * + * The timezone of the date may be included as an ID with the "zzz" + * format string or as an offset with the "ZZZ" format string. + * + * If consecutive calls are frequently very different, then this + * may be a little slower than a normal DateFormat. + */ +public class DateCacheSimpleDateFormat +{ + + public static final String DEFAULT_FORMAT="EEE MMM dd HH:mm:ss zzz yyyy"; + + private final String _formatString; + private final String _tzFormatString; + private final SimpleDateFormat _tzFormat; + private final Locale _locale ; + + private volatile Tick _tick; + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + public static class Tick + { + final long _seconds; + final String _string; + public Tick(long seconds, String string) + { + _seconds = seconds; + _string = string; + } + } + + /* ------------------------------------------------------------ */ + /** Constructor. + * Make a DateCache that will use a default format. The default format + * generates the same results as Date.toString(). + */ + public DateCacheSimpleDateFormat() + { + this(DEFAULT_FORMAT); + } + + /* ------------------------------------------------------------ */ + /** Constructor. + * Make a DateCache that will use the given format + * @param format the format to use + */ + public DateCacheSimpleDateFormat(String format) + { + this( format, null, TimeZone.getDefault()); + } + + /* ------------------------------------------------------------ */ + public DateCacheSimpleDateFormat(String format,Locale l) + { + this(format,l,TimeZone.getDefault()); + } + + /* ------------------------------------------------------------ */ + public DateCacheSimpleDateFormat(String format,Locale l,String tz) + { + this(format,l,TimeZone.getTimeZone(tz)); + } + + /* ------------------------------------------------------------ */ + public DateCacheSimpleDateFormat(String format,Locale l,TimeZone tz) + { + _formatString=format; + _locale = l; + + + int zIndex = _formatString.indexOf( "ZZZ" ); + if( zIndex >= 0 ) + { + String ss1 = _formatString.substring( 0, zIndex ); + String ss2 = _formatString.substring( zIndex+3 ); + int tzOffset = tz.getRawOffset(); + + StringBuilder sb = new StringBuilder(_formatString.length()+10); + sb.append(ss1); + sb.append("'"); + if( tzOffset >= 0 ) + sb.append( '+' ); + else + { + tzOffset = -tzOffset; + sb.append( '-' ); + } + + int raw = tzOffset / (1000*60); // Convert to seconds + int hr = raw / 60; + int min = raw % 60; + + if( hr < 10 ) + sb.append( '0' ); + sb.append( hr ); + if( min < 10 ) + sb.append( '0' ); + sb.append( min ); + sb.append( '\'' ); + + sb.append(ss2); + _tzFormatString=sb.toString(); + } + else + _tzFormatString=_formatString; + + if( _locale != null ) + { + _tzFormat=new SimpleDateFormat(_tzFormatString,_locale); + } + else + { + _tzFormat=new SimpleDateFormat(_tzFormatString); + } + _tzFormat.setTimeZone(tz); + + _tick=null; + } + + + /* ------------------------------------------------------------ */ + public TimeZone getTimeZone() + { + return _tzFormat.getTimeZone(); + } + + + /* ------------------------------------------------------------ */ + /** Format a date according to our stored formatter. + * @param inDate the Date + * @return Formatted date + */ + public String format(Date inDate) + { + long seconds = inDate.getTime() / 1000; + + Tick tick=_tick; + + // Is this the cached time + if (tick==null || seconds!=tick._seconds) + { + // It's a cache miss + synchronized (this) + { + return _tzFormat.format(inDate); + } + } + + return tick._string; + } + + /* ------------------------------------------------------------ */ + /** Format a date according to our stored formatter. + * If it happens to be in the same second as the last formatNow + * call, then the format is reused. + * @param inDate the date in milliseconds since unix epoch + * @return Formatted date + */ + public String format(long inDate) + { + long seconds = inDate / 1000; + + Tick tick=_tick; + + // Is this the cached time + if (tick==null || seconds!=tick._seconds) + { + // It's a cache miss + Date d = new Date(inDate); + synchronized (this) + { + return _tzFormat.format(d); + } + } + + return tick._string; + } + + /* ------------------------------------------------------------ */ + /** Format a date according to our stored formatter. + * The passed time is expected to be close to the current time, so it is + * compared to the last value passed and if it is within the same second, + * the format is reused. Otherwise a new cached format is created. + * @param now the milliseconds since unix epoch + * @return Formatted date + */ + public String formatNow(long now) + { + long seconds = now / 1000; + + Tick tick=_tick; + + // Is this the cached time + if (tick!=null && tick._seconds==seconds) + return tick._string; + return formatTick(now)._string; + } + + /* ------------------------------------------------------------ */ + public String now() + { + return formatNow(System.currentTimeMillis()); + } + + /* ------------------------------------------------------------ */ + public Tick tick() + { + return formatTick(System.currentTimeMillis()); + } + + /* ------------------------------------------------------------ */ + protected Tick formatTick(long now) + { + long seconds = now / 1000; + + // Synchronize to protect _tzFormat + synchronized (this) + { + // recheck the tick, to save multiple formats + if (_tick==null || _tick._seconds!=seconds) + { + String s= _tzFormat.format(new Date(now)); + return _tick=new Tick(seconds,s); + } + return _tick; + } + } + + /* ------------------------------------------------------------ */ + public String getFormatString() + { + return _formatString; + } + +} diff --git a/jetty-jmh/src/main/java/org/eclipse/jetty/util/jmh/DateCacheSimpleDateFormatBenchmark.java b/jetty-jmh/src/main/java/org/eclipse/jetty/util/jmh/DateCacheSimpleDateFormatBenchmark.java new file mode 100644 index 00000000000..3006bddd892 --- /dev/null +++ b/jetty-jmh/src/main/java/org/eclipse/jetty/util/jmh/DateCacheSimpleDateFormatBenchmark.java @@ -0,0 +1,90 @@ +// +// ======================================================================== +// Copyright (c) 1995-2018 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.util.jmh; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Threads; +import org.openjdk.jmh.annotations.Warmup; +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; +import org.openjdk.jmh.runner.options.TimeValue; + +import java.time.Instant; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +@State(Scope.Benchmark) +@Threads(4) +@Warmup(iterations = 7, time = 500, timeUnit = TimeUnit.MILLISECONDS) +@Measurement(iterations = 7, time = 500, timeUnit = TimeUnit.MILLISECONDS) +public class DateCacheSimpleDateFormatBenchmark +{ + + DateCacheSimpleDateFormat dateCache = new DateCacheSimpleDateFormat(); + long timestamp = Instant.now().toEpochMilli(); + + @Benchmark + @BenchmarkMode(Mode.Throughput) + public void testDateCacheTimestamp() + { + dateCache.format(timestamp); + } + + @Benchmark + @BenchmarkMode(Mode.Throughput) + public void testDateCacheNow() + { + dateCache.format(new Date()); + } + + @Benchmark + @BenchmarkMode(Mode.Throughput) + public void testDateCacheFormatNow() + { + dateCache.formatNow(System.currentTimeMillis()); + } + + public static void main(String[] args) throws RunnerException + { + Options opt = new OptionsBuilder() + .include(DateCacheSimpleDateFormatBenchmark.class.getSimpleName()) + .warmupIterations(2) + .measurementIterations(3) + .forks(1) + .threads(400) + // .syncIterations(true) // Don't start all threads at same time + .warmupTime(new TimeValue(10000,TimeUnit.MILLISECONDS)) + .measurementTime(new TimeValue(10000,TimeUnit.MILLISECONDS)) + // .addProfiler(CompilerProfiler.class) + // .addProfiler(LinuxPerfProfiler.class) + // .addProfiler(LinuxPerfNormProfiler.class) + // .addProfiler(LinuxPerfAsmProfiler.class) + // .resultFormat(ResultFormatType.CSV) + .build(); + + new Runner(opt).run(); + } +} \ No newline at end of file diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/thread/jmh/ThreadPoolBenchmark.java b/jetty-jmh/src/main/java/org/eclipse/jetty/util/thread/jmh/ThreadPoolBenchmark.java similarity index 100% rename from jetty-util/src/test/java/org/eclipse/jetty/util/thread/jmh/ThreadPoolBenchmark.java rename to jetty-jmh/src/main/java/org/eclipse/jetty/util/thread/jmh/ThreadPoolBenchmark.java diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/thread/strategy/jmh/EWYKBenchmark.java b/jetty-jmh/src/main/java/org/eclipse/jetty/util/thread/strategy/jmh/EWYKBenchmark.java similarity index 100% rename from jetty-util/src/test/java/org/eclipse/jetty/util/thread/strategy/jmh/EWYKBenchmark.java rename to jetty-jmh/src/main/java/org/eclipse/jetty/util/thread/strategy/jmh/EWYKBenchmark.java diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/thread/strategy/jmh/TestConnection.java b/jetty-jmh/src/main/java/org/eclipse/jetty/util/thread/strategy/jmh/TestConnection.java similarity index 100% rename from jetty-util/src/test/java/org/eclipse/jetty/util/thread/strategy/jmh/TestConnection.java rename to jetty-jmh/src/main/java/org/eclipse/jetty/util/thread/strategy/jmh/TestConnection.java diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/thread/strategy/jmh/TestServer.java b/jetty-jmh/src/main/java/org/eclipse/jetty/util/thread/strategy/jmh/TestServer.java similarity index 100% rename from jetty-util/src/test/java/org/eclipse/jetty/util/thread/strategy/jmh/TestServer.java rename to jetty-jmh/src/main/java/org/eclipse/jetty/util/thread/strategy/jmh/TestServer.java diff --git a/jetty-jmh/src/main/resources/jetty-logging.properties b/jetty-jmh/src/main/resources/jetty-logging.properties new file mode 100644 index 00000000000..799aa62aed3 --- /dev/null +++ b/jetty-jmh/src/main/resources/jetty-logging.properties @@ -0,0 +1,4 @@ +org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog +#org.eclipse.jetty.LEVEL=DEBUG +#org.eclipse.jetty.server.LEVEL=DEBUG +#org.eclipse.jetty.http.LEVEL=DEBUG diff --git a/jetty-jmh/src/main/resources/keystore b/jetty-jmh/src/main/resources/keystore new file mode 100644 index 00000000000..b727bd0fb77 Binary files /dev/null and b/jetty-jmh/src/main/resources/keystore differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-company-urlencoded-apache-httpcomp.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-company-urlencoded-apache-httpcomp.expected.txt new file mode 100644 index 00000000000..1eed1a48357 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-company-urlencoded-apache-httpcomp.expected.txt @@ -0,0 +1,9 @@ +Request-Header|Accept-Encoding|gzip,deflate +Request-Header|Connection|keep-alive +Request-Header|Content-Length|248 +Request-Header|Content-Type|multipart/form-data; boundary=DHbU6ChASebwm4iE8z9Lakv4ybMmkp +Request-Header|Host|localhost:9090 +Request-Header|User-Agent|Apache-HttpClient/4.5.5 (Java/1.8.0_162) +Request-Header|X-BrowserId|apache-httpcomp +Parts-Count|1 +Part-ContainsContents|company|bob+%26+frank%27s+shoe+repair diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-company-urlencoded-apache-httpcomp.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-company-urlencoded-apache-httpcomp.raw new file mode 100644 index 00000000000..7059e4760c7 Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-company-urlencoded-apache-httpcomp.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-complex-apache-httpcomp.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-complex-apache-httpcomp.expected.txt new file mode 100644 index 00000000000..fde7344fec7 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-complex-apache-httpcomp.expected.txt @@ -0,0 +1,15 @@ +Request-Header|Accept-Encoding|gzip,deflate +Request-Header|Connection|keep-alive +Request-Header|Content-Length|22940 +Request-Header|Content-Type|multipart/form-data; boundary=owr6UQGvVNunA_sx2AsizBtyq_uK-OjsQXrF +Request-Header|Host|localhost:9090 +Request-Header|User-Agent|Apache-HttpClient/4.5.5 (Java/1.8.0_162) +Request-Header|X-BrowserId|apache-httpcomp +Parts-Count|6 +Part-ContainsContents|pi|3.14159265358979323846264338327950288419716939937510 +Part-ContainsContents|company|bob & frank's shoe repair +Part-ContainsContents|power|ꬵо𝗋ⲥ𝖾 +Part-ContainsContents|japanese|オープンソース +Part-ContainsContents|hello|日食桟橋 +Part-Filename|upload_file|filename +Part-Sha1sum|upload_file|e75b73644afe9b234d70da9ff225229de68cdff8 diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-complex-apache-httpcomp.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-complex-apache-httpcomp.raw new file mode 100644 index 00000000000..87f46ff9258 Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-complex-apache-httpcomp.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-complex-jetty-client.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-complex-jetty-client.expected.txt new file mode 100644 index 00000000000..df6340cc7da --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-complex-jetty-client.expected.txt @@ -0,0 +1,15 @@ +Request-Header|Accept-Encoding|gzip +Request-Header|Connection|close +Request-Header|Content-Type|multipart/form-data; boundary=JettyHttpClientBoundary1275gffetpxz8o0q +Request-Header|Host|localhost:9090 +Request-Header|Transfer-Encoding|chunked +Request-Header|User-Agent|Jetty/9.4.9.v20180320 +Request-Header|X-BrowserId|jetty-client +Parts-Count|6 +Part-ContainsContents|pi|3.14159265358979323846264338327950288419716939937510 +Part-ContainsContents|company|bob & frank's shoe repair +Part-ContainsContents|power|ꬵо𝗋ⲥ𝖾 +Part-ContainsContents|japanese|オープンソース +Part-ContainsContents|hello|日食桟橋 +Part-Filename|upload_file|filename +Part-Sha1sum|upload_file|e75b73644afe9b234d70da9ff225229de68cdff8 diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-complex-jetty-client.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-complex-jetty-client.raw new file mode 100644 index 00000000000..04514a19dcb Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-complex-jetty-client.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-duplicate-names-apache-httpcomp.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-duplicate-names-apache-httpcomp.expected.txt new file mode 100644 index 00000000000..796af8952ce --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-duplicate-names-apache-httpcomp.expected.txt @@ -0,0 +1,8 @@ +Request-Header|Accept-Encoding|gzip,deflate +Request-Header|Connection|keep-alive +Request-Header|Content-Length|1815 +Request-Header|Content-Type|multipart/form-data; boundary=QW3F8Fg64P2J2dpfEKGKlX0Q9QF2a8SK_7YH +Request-Header|Host|localhost:9090 +Request-Header|User-Agent|Apache-HttpClient/4.5.5 (Java/1.8.0_162) +Request-Header|X-BrowserId|apache-httpcomp +Parts-Count|10 diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-duplicate-names-apache-httpcomp.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-duplicate-names-apache-httpcomp.raw new file mode 100644 index 00000000000..e48b5a62856 Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-duplicate-names-apache-httpcomp.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-duplicate-names-jetty-client.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-duplicate-names-jetty-client.expected.txt new file mode 100644 index 00000000000..bc73cca6fc6 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-duplicate-names-jetty-client.expected.txt @@ -0,0 +1,8 @@ +Request-Header|Accept-Encoding|gzip +Request-Header|Connection|close +Request-Header|Content-Type|multipart/form-data; boundary=JettyHttpClientBoundary14beb4to333d91v8 +Request-Header|Host|localhost:9090 +Request-Header|Transfer-Encoding|chunked +Request-Header|User-Agent|Jetty/9.4.9.v20180320 +Request-Header|X-BrowserId|jetty-client +Parts-Count|10 diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-duplicate-names-jetty-client.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-duplicate-names-jetty-client.raw new file mode 100644 index 00000000000..44646fda28c Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-duplicate-names-jetty-client.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-encoding-mess-apache-httpcomp.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-encoding-mess-apache-httpcomp.expected.txt new file mode 100644 index 00000000000..5769e300b10 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-encoding-mess-apache-httpcomp.expected.txt @@ -0,0 +1,11 @@ +Request-Header|Accept-Encoding|gzip,deflate +Request-Header|Connection|keep-alive +Request-Header|Content-Length|31148 +Request-Header|Content-Type|multipart/form-data; boundary=qqr2YBBR31U4xVib4vaVuIsrwNY1iw +Request-Header|Host|localhost:9090 +Request-Header|User-Agent|Apache-HttpClient/4.5.5 (Java/1.8.0_162) +Request-Header|X-BrowserId|apache-httpcomp +Parts-Count|169 +Part-ContainsContents|count|168 +Part-ContainsContents|persian-UTF-8|برج بابل +Part-ContainsContents|persian-CESU-8|برج بابل diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-encoding-mess-apache-httpcomp.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-encoding-mess-apache-httpcomp.raw new file mode 100644 index 00000000000..17948f0419e Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-encoding-mess-apache-httpcomp.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-encoding-mess-jetty-client.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-encoding-mess-jetty-client.expected.txt new file mode 100644 index 00000000000..473743d99e6 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-encoding-mess-jetty-client.expected.txt @@ -0,0 +1,11 @@ +Request-Header|Accept-Encoding|gzip +Request-Header|Connection|close +Request-Header|Content-Type|multipart/form-data; boundary=JettyHttpClientBoundary1jcfdl0zps9nf362 +Request-Header|Host|localhost:9090 +Request-Header|Transfer-Encoding|chunked +Request-Header|User-Agent|Jetty/9.4.9.v20180320 +Request-Header|X-BrowserId|jetty-client +Parts-Count|169 +Part-ContainsContents|count|168 +Part-ContainsContents|persian-UTF-8|برج بابل +Part-ContainsContents|persian-CESU-8|برج بابل diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-encoding-mess-jetty-client.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-encoding-mess-jetty-client.raw new file mode 100644 index 00000000000..7f374670a10 Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-encoding-mess-jetty-client.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-alt-chrome.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-alt-chrome.expected.txt new file mode 100644 index 00000000000..7b689768bb9 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-alt-chrome.expected.txt @@ -0,0 +1,21 @@ +Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 +Request-Header|Accept-Encoding|gzip, deflate, br +Request-Header|Accept-Language|en-US,en;q=0.9 +Request-Header|Cache-Control|max-age=0 +Request-Header|Connection|keep-alive +Request-Header|Content-Length|22759 +Request-Header|Content-Type|multipart/form-data; boundary=----WebKitFormBoundaryafpkbdzB5Ciqre2z +Request-Header|Cookie|visited=yes +Request-Header|DNT|1 +Request-Header|Host|localhost:9090 +Request-Header|Origin|http://localhost:9090 +Request-Header|Referer|http://localhost:9090/form-fileupload-multi.html +Request-Header|Upgrade-Insecure-Requests|1 +Request-Header|User-Agent|Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36 +Parts-Count|4 +Part-ContainsContents|description|the larger icon +Part-ContainsContents|alternate|text.raw +Part-Filename|file|jetty-avatar-256.png +Part-Sha1sum|file|e75b73644afe9b234d70da9ff225229de68cdff8 +Part-Filename|file-alt|text.raw +Part-Sha1sum|file-alt|5fb031816a27d80cc88c390819addab0ec3c189b diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-alt-chrome.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-alt-chrome.raw new file mode 100644 index 00000000000..cb7809cf0d4 Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-alt-chrome.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-alt-edge.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-alt-edge.expected.txt new file mode 100644 index 00000000000..9c3e2549453 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-alt-edge.expected.txt @@ -0,0 +1,17 @@ +Request-Header|Accept|text/html, application/xhtml+xml, image/jxr, */* +Request-Header|Accept-Encoding|gzip, deflate +Request-Header|Accept-Language|en-US +Request-Header|Cache-Control|no-cache +Request-Header|Connection|keep-alive +Request-Header|Content-Length|22824 +Request-Header|Content-Type|multipart/form-data; boundary=---------------------------7e21c038151054 +Request-Header|Host|localhost:9090 +Request-Header|Referer|http://localhost:9090/form-fileupload-multi.html +Request-Header|User-Agent|Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299 +Parts-Count|4 +Part-ContainsContents|description|the larger icon +Part-ContainsContents|alternate|text.raw +Part-Filename|file|C:\Users\joakim\Pictures\jetty-avatar-256.png +Part-Sha1sum|file|e75b73644afe9b234d70da9ff225229de68cdff8 +Part-Filename|file-alt|C:\Users\joakim\Pictures\text.raw +Part-Sha1sum|file-alt|5fb031816a27d80cc88c390819addab0ec3c189b diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-alt-edge.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-alt-edge.raw new file mode 100644 index 00000000000..13fa9572fdd Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-alt-edge.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-alt-firefox.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-alt-firefox.expected.txt new file mode 100644 index 00000000000..f918d12a4f6 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-alt-firefox.expected.txt @@ -0,0 +1,17 @@ +Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 +Request-Header|Accept-Encoding|gzip, deflate +Request-Header|Accept-Language|en-US,en;q=0.5 +Request-Header|Connection|keep-alive +Request-Header|Content-Length|22774 +Request-Header|Content-Type|multipart/form-data; boundary=---------------------------23281168279961 +Request-Header|Host|localhost:9090 +Request-Header|Referer|http://localhost:9090/form-fileupload-multi.html +Request-Header|Upgrade-Insecure-Requests|1 +Request-Header|User-Agent|Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:58.0) Gecko/20100101 Firefox/58.0 +Parts-Count|4 +Part-ContainsContents|description|the larger icon +Part-ContainsContents|alternate|text.raw +Part-Filename|file|jetty-avatar-256.png +Part-Sha1sum|file|e75b73644afe9b234d70da9ff225229de68cdff8 +Part-Filename|file-alt|text.raw +Part-Sha1sum|file-alt|5fb031816a27d80cc88c390819addab0ec3c189b diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-alt-firefox.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-alt-firefox.raw new file mode 100644 index 00000000000..ca094b5a9fc Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-alt-firefox.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-alt-msie.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-alt-msie.expected.txt new file mode 100644 index 00000000000..b1534812a61 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-alt-msie.expected.txt @@ -0,0 +1,17 @@ +Request-Header|Accept|text/html, application/xhtml+xml, image/jxr, */* +Request-Header|Accept-Encoding|gzip, deflate +Request-Header|Accept-Language|en-US +Request-Header|Cache-Control|no-cache +Request-Header|Connection|keep-alive +Request-Header|Content-Length|22814 +Request-Header|Content-Type|multipart/form-data; boundary=---------------------------7e226692109c +Request-Header|Host|localhost:9090 +Request-Header|Referer|http://localhost:9090/form-fileupload-multi.html +Request-Header|User-Agent|Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; Touch; rv:11.0) like Gecko +Parts-Count|4 +Part-ContainsContents|description|the larger icon +Part-ContainsContents|alternate|text.raw +Part-Filename|file|C:\Users\joakim\Pictures\jetty-avatar-256.png +Part-Sha1sum|file|e75b73644afe9b234d70da9ff225229de68cdff8 +Part-Filename|file-alt|C:\Users\joakim\Pictures\text.raw +Part-Sha1sum|file-alt|5fb031816a27d80cc88c390819addab0ec3c189b diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-alt-msie.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-alt-msie.raw new file mode 100644 index 00000000000..786215f3d90 Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-alt-msie.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-alt-safari.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-alt-safari.expected.txt new file mode 100644 index 00000000000..12c657e8258 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-alt-safari.expected.txt @@ -0,0 +1,18 @@ +Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 +Request-Header|Accept-Encoding|gzip, deflate +Request-Header|Accept-Language|en-us +Request-Header|Connection|keep-alive +Request-Header|Content-Length|22774 +Request-Header|Content-Type|multipart/form-data; boundary=----WebKitFormBoundaryEQhxWUv9r38x3LyB +Request-Header|Host|192.168.0.119:9090 +Request-Header|Origin|http://192.168.0.119:9090 +Request-Header|Referer|http://192.168.0.119:9090/form-fileupload-multi.html +Request-Header|Upgrade-Insecure-Requests|1 +Request-Header|User-Agent|Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/604.5.6 (KHTML, like Gecko) Version/11.0.3 Safari/604.5.6 +Parts-Count|4 +Part-ContainsContents|description|the larger icon +Part-ContainsContents|alternate|text.raw +Part-Filename|file|jetty-avatar-256.png +Part-Sha1sum|file|e75b73644afe9b234d70da9ff225229de68cdff8 +Part-Filename|file-alt|text.raw +Part-Sha1sum|file-alt|5fb031816a27d80cc88c390819addab0ec3c189b diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-alt-safari.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-alt-safari.raw new file mode 100644 index 00000000000..125321970b1 Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-alt-safari.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-android-chrome.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-android-chrome.expected.txt new file mode 100644 index 00000000000..ef15f1eba25 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-android-chrome.expected.txt @@ -0,0 +1,17 @@ +Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 +Request-Header|Accept-Encoding|gzip, deflate +Request-Header|Accept-Language|en-US,en;q=0.9 +Request-Header|Cache-Control|max-age=0 +Request-Header|Connection|keep-alive +Request-Header|Content-Length|22054 +Request-Header|Content-Type|multipart/form-data; boundary=----WebKitFormBoundary2oBNepLIldUG8YwL +Request-Header|DNT|1 +Request-Header|Host|192.168.0.119:9090 +Request-Header|Origin|http://192.168.0.119:9090 +Request-Header|Referer|http://192.168.0.119:9090/form-fileupload.html +Request-Header|Upgrade-Insecure-Requests|1 +Request-Header|User-Agent|Mozilla/5.0 (Linux; Android 8.1.0; Pixel 2 XL Build/OPM1.171019.021) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.109 Mobile Safari/537.36 +Parts-Count|2 +Part-ContainsContents|description|the larger icon +Part-Filename|file|jetty-avatar-256.png +Part-Sha1sum|file|e75b73644afe9b234d70da9ff225229de68cdff8 diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-android-chrome.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-android-chrome.raw new file mode 100644 index 00000000000..2263dfdaf68 Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-android-chrome.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-android-firefox.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-android-firefox.expected.txt new file mode 100644 index 00000000000..fceb88d1000 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-android-firefox.expected.txt @@ -0,0 +1,14 @@ +Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 +Request-Header|Accept-Encoding|gzip, deflate +Request-Header|Accept-Language|en-US,en;q=0.5 +Request-Header|Connection|keep-alive +Request-Header|Content-Length|22105 +Request-Header|Content-Type|multipart/form-data; boundary=---------------------------2117751712556306154183865432 +Request-Header|Host|192.168.0.119:9090 +Request-Header|Referer|http://192.168.0.119:9090/form-fileupload.html +Request-Header|Upgrade-Insecure-Requests|1 +Request-Header|User-Agent|Mozilla/5.0 (Android 8.1.0; Mobile; rv:59.0) Gecko/59.0 Firefox/59.0 +Parts-Count|2 +Part-ContainsContents|description|the larger icon +Part-Filename|file|jetty-avatar-256.png +Part-Sha1sum|file|e75b73644afe9b234d70da9ff225229de68cdff8 diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-android-firefox.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-android-firefox.raw new file mode 100644 index 00000000000..3492fddbe80 Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-android-firefox.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-chrome.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-chrome.expected.txt new file mode 100644 index 00000000000..491fe431b37 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-chrome.expected.txt @@ -0,0 +1,18 @@ +Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 +Request-Header|Accept-Encoding|gzip, deflate, br +Request-Header|Accept-Language|en-US,en;q=0.9 +Request-Header|Cache-Control|max-age=0 +Request-Header|Connection|keep-alive +Request-Header|Content-Length|22054 +Request-Header|Content-Type|multipart/form-data; boundary=----WebKitFormBoundarylxcKjAyTlRs3jNP2 +Request-Header|Cookie|visited=yes +Request-Header|DNT|1 +Request-Header|Host|localhost:9090 +Request-Header|Origin|http://localhost:9090 +Request-Header|Referer|http://localhost:9090/form-fileupload.html +Request-Header|Upgrade-Insecure-Requests|1 +Request-Header|User-Agent|Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36 +Parts-Count|2 +Part-ContainsContents|description|the larger icon +Part-Filename|file|jetty-avatar-256.png +Part-Sha1sum|file|e75b73644afe9b234d70da9ff225229de68cdff8 diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-chrome.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-chrome.raw new file mode 100644 index 00000000000..b31c8858989 Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-chrome.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-edge.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-edge.expected.txt new file mode 100644 index 00000000000..3086e324bc9 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-edge.expected.txt @@ -0,0 +1,14 @@ +Request-Header|Accept|text/html, application/xhtml+xml, image/jxr, */* +Request-Header|Accept-Encoding|gzip, deflate +Request-Header|Accept-Language|en-US +Request-Header|Cache-Control|no-cache +Request-Header|Connection|keep-alive +Request-Header|Content-Length|22085 +Request-Header|Content-Type|multipart/form-data; boundary=---------------------------7e225f6151054 +Request-Header|Host|localhost:9090 +Request-Header|Referer|http://localhost:9090/form-fileupload.html +Request-Header|User-Agent|Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299 +Parts-Count|2 +Part-ContainsContents|description|the larger icon +Part-Filename|file|C:\Users\joakim\Pictures\jetty-avatar-256.png +Part-Sha1sum|file|e75b73644afe9b234d70da9ff225229de68cdff8 diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-edge.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-edge.raw new file mode 100644 index 00000000000..6f60b77cd3e Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-edge.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-firefox.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-firefox.expected.txt new file mode 100644 index 00000000000..6b138ba5bb6 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-firefox.expected.txt @@ -0,0 +1,14 @@ +Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 +Request-Header|Accept-Encoding|gzip, deflate +Request-Header|Accept-Language|en-US,en;q=0.5 +Request-Header|Connection|keep-alive +Request-Header|Content-Length|22063 +Request-Header|Content-Type|multipart/form-data; boundary=---------------------------24464570528145 +Request-Header|Host|localhost:9090 +Request-Header|Referer|http://localhost:9090/form-fileupload.html +Request-Header|Upgrade-Insecure-Requests|1 +Request-Header|User-Agent|Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:58.0) Gecko/20100101 Firefox/58.0 +Parts-Count|2 +Part-ContainsContents|description|the larger icon +Part-Filename|file|jetty-avatar-256.png +Part-Sha1sum|file|e75b73644afe9b234d70da9ff225229de68cdff8 \ No newline at end of file diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-firefox.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-firefox.raw new file mode 100644 index 00000000000..cb14119752f Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-firefox.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-ios-safari.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-ios-safari.expected.txt new file mode 100644 index 00000000000..3620ce269c0 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-ios-safari.expected.txt @@ -0,0 +1,15 @@ +Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 +Request-Header|Accept-Encoding|gzip, deflate +Request-Header|Accept-Language|en-us +Request-Header|Connection|keep-alive +Request-Header|Content-Length|22074 +Request-Header|Content-Type|multipart/form-data; boundary=----WebKitFormBoundary5trdx3OwYr8uMtbA +Request-Header|Host|192.168.0.119:9090 +Request-Header|Origin|http://192.168.0.119:9090 +Request-Header|Referer|http://192.168.0.119:9090/form-fileupload.html +Request-Header|Upgrade-Insecure-Requests|1 +Request-Header|User-Agent|Mozilla/5.0 (iPad; CPU OS 11_2_6 like Mac OS X) AppleWebKit/604.5.6 (KHTML, like Gecko) Version/11.0 Mobile/15D100 Safari/604.1 +Parts-Count|2 +Part-ContainsContents|description|the larger icon +Part-Filename|file|66A4F66B-9B37-4F69-86A7-456547EBF079.png +Part-Sha1sum|file|e75b73644afe9b234d70da9ff225229de68cdff8 \ No newline at end of file diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-ios-safari.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-ios-safari.raw new file mode 100644 index 00000000000..24fbac72d3e Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-ios-safari.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-msie.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-msie.expected.txt new file mode 100644 index 00000000000..e2f6482cadb --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-msie.expected.txt @@ -0,0 +1,14 @@ +Request-Header|Accept|text/html, application/xhtml+xml, image/jxr, */* +Request-Header|Accept-Encoding|gzip, deflate +Request-Header|Accept-Language|en-US +Request-Header|Cache-Control|no-cache +Request-Header|Connection|keep-alive +Request-Header|Content-Length|22082 +Request-Header|Content-Type|multipart/form-data; boundary=---------------------------7e223ef2109c +Request-Header|Host|localhost:9090 +Request-Header|Referer|http://localhost:9090/form-fileupload.html +Request-Header|User-Agent|Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; Touch; rv:11.0) like Gecko +Parts-Count|2 +Part-ContainsContents|description|the larger icon +Part-Filename|file|C:\Users\joakim\Pictures\jetty-avatar-256.png +Part-Sha1sum|file|e75b73644afe9b234d70da9ff225229de68cdff8 \ No newline at end of file diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-msie.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-msie.raw new file mode 100644 index 00000000000..9b27e7677b4 Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-msie.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-safari.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-safari.expected.txt new file mode 100644 index 00000000000..9dd87813730 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-safari.expected.txt @@ -0,0 +1,15 @@ +Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 +Request-Header|Accept-Encoding|gzip, deflate +Request-Header|Accept-Language|en-us +Request-Header|Connection|keep-alive +Request-Header|Content-Length|22054 +Request-Header|Content-Type|multipart/form-data; boundary=----WebKitFormBoundaryWl9yEX5Fas0SI2xc +Request-Header|Host|192.168.0.119:9090 +Request-Header|Origin|http://192.168.0.119:9090 +Request-Header|Referer|http://192.168.0.119:9090/form-fileupload.html +Request-Header|Upgrade-Insecure-Requests|1 +Request-Header|User-Agent|Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/604.5.6 (KHTML, like Gecko) Version/11.0.3 Safari/604.5.6 +Parts-Count|2 +Part-ContainsContents|description|the larger icon +Part-Filename|file|jetty-avatar-256.png +Part-Sha1sum|file|e75b73644afe9b234d70da9ff225229de68cdff8 diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-safari.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-safari.raw new file mode 100644 index 00000000000..3b6922522eb Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-form-fileupload-safari.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form1-android-chrome.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-form1-android-chrome.expected.txt new file mode 100644 index 00000000000..271e31b1181 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-form1-android-chrome.expected.txt @@ -0,0 +1,16 @@ +Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 +Request-Header|Accept-Encoding|gzip, deflate +Request-Header|Accept-Language|en-US,en;q=0.9 +Request-Header|Cache-Control|max-age=0 +Request-Header|Connection|keep-alive +Request-Header|Content-Length|245 +Request-Header|Content-Type|multipart/form-data; boundary=----WebKitFormBoundaryD4GyXQgjBRmK3aBz +Request-Header|DNT|1 +Request-Header|Host|192.168.0.119:9090 +Request-Header|Origin|http://192.168.0.119:9090 +Request-Header|Referer|http://192.168.0.119:9090/form.html +Request-Header|Upgrade-Insecure-Requests|1 +Request-Header|User-Agent|Mozilla/5.0 (Linux; Android 8.1.0; Pixel 2 XL Build/OPM1.171019.021) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.109 Mobile Safari/537.36 +Parts-Count|2 +Part-ContainsContents|user|Androiduser +Part-ContainsContents|comment|Dyac! \ No newline at end of file diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form1-android-chrome.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-form1-android-chrome.raw new file mode 100644 index 00000000000..f5ce1cab07d Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-form1-android-chrome.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form1-android-firefox.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-form1-android-firefox.expected.txt new file mode 100644 index 00000000000..9f7d2307e87 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-form1-android-firefox.expected.txt @@ -0,0 +1,13 @@ +Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 +Request-Header|Accept-Encoding|gzip, deflate +Request-Header|Accept-Language|en-US,en;q=0.5 +Request-Header|Connection|keep-alive +Request-Header|Content-Length|306 +Request-Header|Content-Type|multipart/form-data; boundary=---------------------------6390283156237600831344307695 +Request-Header|Host|192.168.0.119:9090 +Request-Header|Referer|http://192.168.0.119:9090/form.html +Request-Header|Upgrade-Insecure-Requests|1 +Request-Header|User-Agent|Mozilla/5.0 (Android 8.1.0; Mobile; rv:59.0) Gecko/59.0 Firefox/59.0 +Parts-Count|2 +Part-ContainsContents|user|androidfireuser +Part-ContainsContents|comment|More to say \ No newline at end of file diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form1-android-firefox.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-form1-android-firefox.raw new file mode 100644 index 00000000000..75dbbde1a6f Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-form1-android-firefox.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form1-chrome.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-form1-chrome.expected.txt new file mode 100644 index 00000000000..d36342fb640 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-form1-chrome.expected.txt @@ -0,0 +1,17 @@ +Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 +Request-Header|Accept-Encoding|gzip, deflate, br +Request-Header|Accept-Language|en-US,en;q=0.9 +Request-Header|Cache-Control|max-age=0 +Request-Header|Connection|keep-alive +Request-Header|Content-Length|256 +Request-Header|Content-Type|multipart/form-data; boundary=----WebKitFormBoundary46EP6zTN86hbbaJC +Request-Header|Cookie|visited=yes +Request-Header|DNT|1 +Request-Header|Host|localhost:9090 +Request-Header|Origin|http://localhost:9090 +Request-Header|Referer|http://localhost:9090/form.html +Request-Header|Upgrade-Insecure-Requests|1 +Request-Header|User-Agent|Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36 +Parts-Count|2 +Part-ContainsContents|user|joe +Part-ContainsContents|comment|this is a simple comment \ No newline at end of file diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form1-chrome.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-form1-chrome.raw new file mode 100644 index 00000000000..7f8bfc267d8 Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-form1-chrome.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form1-edge.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-form1-edge.expected.txt new file mode 100644 index 00000000000..0b7f887ddc6 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-form1-edge.expected.txt @@ -0,0 +1,13 @@ +Request-Header|Accept|text/html, application/xhtml+xml, image/jxr, */* +Request-Header|Accept-Encoding|gzip, deflate +Request-Header|Accept-Language|en-US +Request-Header|Cache-Control|no-cache +Request-Header|Connection|keep-alive +Request-Header|Content-Length|267 +Request-Header|Content-Type|multipart/form-data; boundary=---------------------------7e25e1e151054 +Request-Header|Host|localhost:9090 +Request-Header|Referer|http://localhost:9090/form.html +Request-Header|User-Agent|Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299 +Parts-Count|2 +Part-ContainsContents|user|anotheruser +Part-ContainsContents|comment|with something to say \ No newline at end of file diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form1-edge.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-form1-edge.raw new file mode 100644 index 00000000000..48aa4e73f1c Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-form1-edge.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form1-firefox.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-form1-firefox.expected.txt new file mode 100644 index 00000000000..9f0e4ee9899 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-form1-firefox.expected.txt @@ -0,0 +1,13 @@ +Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 +Request-Header|Accept-Encoding|gzip, deflate +Request-Header|Accept-Language|en-US,en;q=0.5 +Request-Header|Connection|keep-alive +Request-Header|Content-Length|258 +Request-Header|Content-Type|multipart/form-data; boundary=---------------------------41184676334 +Request-Header|Host|localhost:9090 +Request-Header|Referer|http://localhost:9090/form.html +Request-Header|Upgrade-Insecure-Requests|1 +Request-Header|User-Agent|Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:58.0) Gecko/20100101 Firefox/58.0 +Parts-Count|2 +Part-ContainsContents|user|fireuser +Part-ContainsContents|comment|with detailed message \ No newline at end of file diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form1-firefox.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-form1-firefox.raw new file mode 100644 index 00000000000..a7c65315450 Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-form1-firefox.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form1-ios-safari.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-form1-ios-safari.expected.txt new file mode 100644 index 00000000000..4d0533dfb0f --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-form1-ios-safari.expected.txt @@ -0,0 +1,14 @@ +Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 +Request-Header|Accept-Encoding|gzip, deflate +Request-Header|Accept-Language|en-us +Request-Header|Connection|keep-alive +Request-Header|Content-Length|268 +Request-Header|Content-Type|multipart/form-data; boundary=----WebKitFormBoundary56m5uMm4gNcn4rL1 +Request-Header|Host|192.168.0.119:9090 +Request-Header|Origin|http://192.168.0.119:9090 +Request-Header|Referer|http://192.168.0.119:9090/form.html +Request-Header|Upgrade-Insecure-Requests|1 +Request-Header|User-Agent|Mozilla/5.0 (iPad; CPU OS 11_2_6 like Mac OS X) AppleWebKit/604.5.6 (KHTML, like Gecko) Version/11.0 Mobile/15D100 Safari/604.1 +Parts-Count|2 +Part-ContainsContents|user|UseriPad +Part-ContainsContents|comment|This form isn’t pretty \ No newline at end of file diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form1-ios-safari.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-form1-ios-safari.raw new file mode 100644 index 00000000000..9664c903572 Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-form1-ios-safari.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form1-msie.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-form1-msie.expected.txt new file mode 100644 index 00000000000..60cbe9e5956 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-form1-msie.expected.txt @@ -0,0 +1,13 @@ +Request-Header|Accept|text/html, application/xhtml+xml, image/jxr, */* +Request-Header|Accept-Encoding|gzip, deflate +Request-Header|Accept-Language|en-US +Request-Header|Cache-Control|no-cache +Request-Header|Connection|keep-alive +Request-Header|Content-Length|285 +Request-Header|Content-Type|multipart/form-data; boundary=---------------------------7e21b6f2109c +Request-Header|Host|localhost:9090 +Request-Header|Referer|http://localhost:9090/form.html +Request-Header|User-Agent|Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; Touch; rv:11.0) like Gecko +Parts-Count|2 +Part-ContainsContents|user|msieuser +Part-ContainsContents|comment|with information that they think is important \ No newline at end of file diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form1-msie.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-form1-msie.raw new file mode 100644 index 00000000000..e562e721397 Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-form1-msie.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form1-osx-safari.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-form1-osx-safari.expected.txt new file mode 100644 index 00000000000..236c06f15e8 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-form1-osx-safari.expected.txt @@ -0,0 +1,14 @@ +Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 +Request-Header|Accept-Encoding|gzip, deflate +Request-Header|Accept-Language|en-us +Request-Header|Connection|keep-alive +Request-Header|Content-Length|284 +Request-Header|Content-Type|multipart/form-data; boundary=----WebKitFormBoundaryjwqONTsAFgubfMZc +Request-Header|Host|192.168.0.119:9090 +Request-Header|Origin|http://192.168.0.119:9090 +Request-Header|Referer|http://192.168.0.119:9090/form.html +Request-Header|Upgrade-Insecure-Requests|1 +Request-Header|User-Agent|Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/604.5.6 (KHTML, like Gecko) Version/11.0.3 Safari/604.5.6 +Parts-Count|2 +Part-ContainsContents|user|safariuser +Part-ContainsContents|comment|with rambling thoughts about bellybutton lint \ No newline at end of file diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-form1-osx-safari.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-form1-osx-safari.raw new file mode 100644 index 00000000000..0e6b82ffd18 Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-form1-osx-safari.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-nested-apache-httpcomp.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-nested-apache-httpcomp.expected.txt new file mode 100644 index 00000000000..fc44839cac2 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-nested-apache-httpcomp.expected.txt @@ -0,0 +1,12 @@ +Request-Header|Accept-Encoding|gzip,deflate +Request-Header|Connection|keep-alive +Request-Header|Content-Length|1203 +Request-Header|Content-Type|multipart/form-data; boundary=Cku4UvJrPFCXkXjge2a2Y2sgq1bbOa +Request-Header|Host|localhost:9090 +Request-Header|User-Agent|Apache-HttpClient/4.5.5 (Java/1.8.0_162) +Request-Header|X-BrowserId|apache-httpcomp +Parts-Count|4 +Part-ContainsContents|reporter| +Part-ContainsContents|timestamp|2018-03-21T18:52:18+00:00 +Part-ContainsContents|comments|this couldn't be parsed +Part-ContainsContents|attachment|banana \ No newline at end of file diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-nested-apache-httpcomp.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-nested-apache-httpcomp.raw new file mode 100644 index 00000000000..12ce1761a03 Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-nested-apache-httpcomp.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-nested-binary-apache-httpcomp.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-nested-binary-apache-httpcomp.expected.txt new file mode 100644 index 00000000000..12e9da8703e --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-nested-binary-apache-httpcomp.expected.txt @@ -0,0 +1,12 @@ +Request-Header|Accept-Encoding|gzip,deflate +Request-Header|Connection|keep-alive +Request-Header|Content-Length|1577 +Request-Header|Content-Type|multipart/form-data; boundary=xDeLGHDDsXrlJSXfqDmg5IRop7auqTTBXuI +Request-Header|Host|localhost:9090 +Request-Header|User-Agent|Apache-HttpClient/4.5.5 (Java/1.8.0_162) +Request-Header|X-BrowserId|apache-httpcomp +Parts-Count|4 +Part-ContainsContents|reporter| +Part-ContainsContents|timestamp|2018-03-21T19:00:18+00:00 +Part-ContainsContents|comments|this also couldn't be parsed +Part-ContainsContents|attachment|cherry \ No newline at end of file diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-nested-binary-apache-httpcomp.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-nested-binary-apache-httpcomp.raw new file mode 100644 index 00000000000..bf8c06a969d Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-nested-binary-apache-httpcomp.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-nested-jetty-client.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-nested-jetty-client.expected.txt new file mode 100644 index 00000000000..294f1eefd47 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-nested-jetty-client.expected.txt @@ -0,0 +1,12 @@ +Request-Header|Accept-Encoding|gzip +Request-Header|Connection|close +Request-Header|Content-Type|multipart/form-data; boundary=JettyHttpClientBoundary1uz60vid2bq7x1t9 +Request-Header|Host|localhost:9090 +Request-Header|Transfer-Encoding|chunked +Request-Header|User-Agent|Jetty/9.4.9.v20180320 +Request-Header|X-BrowserId|jetty-client +Parts-Count|4 +Part-ContainsContents|reporter| +Part-ContainsContents|timestamp|2018-03-21T18:52:18+00:00 +Part-ContainsContents|comments|this couldn't be parsed +Part-ContainsContents|attachment|banana \ No newline at end of file diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-nested-jetty-client.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-nested-jetty-client.raw new file mode 100644 index 00000000000..38f849cc89b Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-nested-jetty-client.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-number-only-apache-httpcomp.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-number-only-apache-httpcomp.expected.txt new file mode 100644 index 00000000000..e090188dbfd --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-number-only-apache-httpcomp.expected.txt @@ -0,0 +1,9 @@ +Request-Header|Accept-Encoding|gzip,deflate +Request-Header|Connection|keep-alive +Request-Header|Content-Length|173 +Request-Header|Content-Type|multipart/form-data; boundary=xE8WoYDcbqAfj08bxPk669iK22hMMlZL +Request-Header|Host|localhost:9090 +Request-Header|User-Agent|Apache-HttpClient/4.5.5 (Java/1.8.0_162) +Request-Header|X-BrowserId|apache-httpcomp +Parts-Count|1 +Part-ContainsContents|pi|3.14159265358979323846264338327950288419716939937510 diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-number-only-apache-httpcomp.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-number-only-apache-httpcomp.raw new file mode 100644 index 00000000000..0af35a6be1f Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-number-only-apache-httpcomp.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-number-only-jetty-client.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-number-only-jetty-client.expected.txt new file mode 100644 index 00000000000..a9f21f2f65f --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-number-only-jetty-client.expected.txt @@ -0,0 +1,12 @@ +Request-Header|Accept-Encoding|gzip +Request-Header|Connection|close +Request-Header|Content-Type|multipart/form-data; boundary=JettyHttpClientBoundary1shlqpw2yahae6jf +Request-Header|Host|localhost:9090 +Request-Header|Transfer-Encoding|chunked +Request-Header|User-Agent|Jetty/9.4.9.v20180320 +Request-Header|X-BrowserId|jetty-client +Parts-Count|1 +# Start of sequence +Part-ContainsContents|pi|3.14159 26535 89793 23846 26433 83279 50288 +# End of sequence +Part-ContainsContents|pi|81592 05600 10165 52563 7567 diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-number-only-jetty-client.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-number-only-jetty-client.raw new file mode 100644 index 00000000000..ab78f173b7c Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-number-only-jetty-client.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-number-only2-apache-httpcomp.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-number-only2-apache-httpcomp.expected.txt new file mode 100644 index 00000000000..aa49e327f1c --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-number-only2-apache-httpcomp.expected.txt @@ -0,0 +1,9 @@ +Request-Header|Accept-Encoding|gzip,deflate +Request-Header|Connection|keep-alive +Request-Header|Content-Length|240 +Request-Header|Content-Type|multipart/form-data; boundary=L8vdau8TpP0o-AYJDjCuYFQYnjB5gcHIFyap +Request-Header|Host|localhost:9090 +Request-Header|User-Agent|Apache-HttpClient/4.5.5 (Java/1.8.0_162) +Request-Header|X-BrowserId|apache-httpcomp +Parts-Count|1 +Part-ContainsContents|pi|3.14159265358979323846264338327950288419716939937510 diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-number-only2-apache-httpcomp.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-number-only2-apache-httpcomp.raw new file mode 100644 index 00000000000..641c1a14837 Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-number-only2-apache-httpcomp.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-apache-httpcomp.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-apache-httpcomp.expected.txt new file mode 100644 index 00000000000..77ad18f6899 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-apache-httpcomp.expected.txt @@ -0,0 +1,10 @@ +Request-Header|Accept-Encoding|gzip,deflate +Request-Header|Connection|keep-alive +Request-Header|Content-Length|406 +Request-Header|Content-Type|multipart/form-data; boundary=u7tfLQaHJEHHUJjnVDbFdc_Oqz4jmkA25mgWd +Request-Header|Host|localhost:9090 +Request-Header|User-Agent|Apache-HttpClient/4.5.5 (Java/1.8.0_162) +Request-Header|X-BrowserId|apache-httpcomp +Parts-Count|2 +Part-ContainsContents|japanese|オープンソース +Part-ContainsContents|hello|日食桟橋 \ No newline at end of file diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-apache-httpcomp.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-apache-httpcomp.raw new file mode 100644 index 00000000000..dfe4e57ab49 Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-apache-httpcomp.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-android-chrome.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-android-chrome.expected.txt new file mode 100644 index 00000000000..1ce9ca18e50 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-android-chrome.expected.txt @@ -0,0 +1,17 @@ +Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 +Request-Header|Accept-Encoding|gzip, deflate +Request-Header|Accept-Language|en-US,en;q=0.9 +Request-Header|Cache-Control|max-age=0 +Request-Header|Connection|keep-alive +Request-Header|Content-Length|354 +Request-Header|Content-Type|multipart/form-data; boundary=----WebKitFormBoundaryN7pYBoDaXhEcUl13 +Request-Header|DNT|1 +Request-Header|Host|192.168.0.119:9090 +Request-Header|Origin|http://192.168.0.119:9090 +Request-Header|Referer|http://192.168.0.119:9090/sjis-form-charset.html +Request-Header|Upgrade-Insecure-Requests|1 +Request-Header|User-Agent|Mozilla/5.0 (Linux; Android 8.1.0; Pixel 2 XL Build/OPM1.171019.021) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.109 Mobile Safari/537.36 +Parts-Count|3 +Part-ContainsContents|_charset_|Shift_JIS +Part-ContainsContents|japanese|健治 +Part-ContainsContents|hello|ャユ戆タ \ No newline at end of file diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-android-chrome.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-android-chrome.raw new file mode 100644 index 00000000000..5c77075588e Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-android-chrome.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-android-firefox.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-android-firefox.expected.txt new file mode 100644 index 00000000000..0a05edeeeff --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-android-firefox.expected.txt @@ -0,0 +1,14 @@ +Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 +Request-Header|Accept-Encoding|gzip, deflate +Request-Header|Accept-Language|en-US,en;q=0.5 +Request-Header|Connection|keep-alive +Request-Header|Content-Length|430 +Request-Header|Content-Type|multipart/form-data; boundary=---------------------------117031256520586657911714164254 +Request-Header|Host|192.168.0.119:9090 +Request-Header|Referer|http://192.168.0.119:9090/sjis-form-charset.html +Request-Header|Upgrade-Insecure-Requests|1 +Request-Header|User-Agent|Mozilla/5.0 (Android 8.1.0; Mobile; rv:59.0) Gecko/59.0 Firefox/59.0 +Parts-Count|3 +Part-ContainsContents|_charset_|Shift_JIS +Part-ContainsContents|japanese|健治 +Part-ContainsContents|hello|ャユ戆タ \ No newline at end of file diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-android-firefox.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-android-firefox.raw new file mode 100644 index 00000000000..b3c4ae8f72e Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-android-firefox.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-chrome.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-chrome.expected.txt new file mode 100644 index 00000000000..0d91a3d3545 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-chrome.expected.txt @@ -0,0 +1,18 @@ +Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 +Request-Header|Accept-Encoding|gzip, deflate, br +Request-Header|Accept-Language|en-US,en;q=0.9 +Request-Header|Cache-Control|max-age=0 +Request-Header|Connection|keep-alive +Request-Header|Content-Length|354 +Request-Header|Content-Type|multipart/form-data; boundary=----WebKitFormBoundaryDHtjXxgNUcgLjcKs +Request-Header|Cookie|visited=yes +Request-Header|DNT|1 +Request-Header|Host|localhost:9090 +Request-Header|Origin|http://localhost:9090 +Request-Header|Referer|http://localhost:9090/sjis-form-charset.html +Request-Header|Upgrade-Insecure-Requests|1 +Request-Header|User-Agent|Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36 +Parts-Count|3 +Part-ContainsContents|_charset_|Shift_JIS +Part-ContainsContents|japanese|健治 +Part-ContainsContents|hello|ャユ戆タ \ No newline at end of file diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-chrome.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-chrome.raw new file mode 100644 index 00000000000..64314612ecc Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-chrome.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-edge.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-edge.expected.txt new file mode 100644 index 00000000000..4b4cc724c95 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-edge.expected.txt @@ -0,0 +1,14 @@ +Request-Header|Accept|text/html, application/xhtml+xml, image/jxr, */* +Request-Header|Accept-Encoding|gzip, deflate +Request-Header|Accept-Language|en-US +Request-Header|Cache-Control|no-cache +Request-Header|Connection|keep-alive +Request-Header|Content-Length|362 +Request-Header|Content-Type|multipart/form-data; boundary=---------------------------7e227e17151054 +Request-Header|Host|localhost:9090 +Request-Header|Referer|http://localhost:9090/sjis-form-charset.html +Request-Header|User-Agent|Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299 +Parts-Count|3 +Part-ContainsContents|_charset_|utf-8 +Part-ContainsContents|japanese|健治 +Part-ContainsContents|hello|ャユ戆タ \ No newline at end of file diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-edge.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-edge.raw new file mode 100644 index 00000000000..71dac77ca76 Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-edge.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-firefox.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-firefox.expected.txt new file mode 100644 index 00000000000..f085e29368d --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-firefox.expected.txt @@ -0,0 +1,14 @@ +Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 +Request-Header|Accept-Encoding|gzip, deflate +Request-Header|Accept-Language|en-US,en;q=0.5 +Request-Header|Connection|keep-alive +Request-Header|Content-Length|370 +Request-Header|Content-Type|multipart/form-data; boundary=---------------------------114782935826962 +Request-Header|Host|localhost:9090 +Request-Header|Referer|http://localhost:9090/sjis-form-charset.html +Request-Header|Upgrade-Insecure-Requests|1 +Request-Header|User-Agent|Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:58.0) Gecko/20100101 Firefox/58.0 +Parts-Count|3 +Part-ContainsContents|_charset_|Shift_JIS +Part-ContainsContents|japanese|健治 +Part-ContainsContents|hello|ャユ戆タ \ No newline at end of file diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-firefox.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-firefox.raw new file mode 100644 index 00000000000..921df609d8a Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-firefox.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-ios-safari.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-ios-safari.expected.txt new file mode 100644 index 00000000000..2d6fbab768c --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-ios-safari.expected.txt @@ -0,0 +1,15 @@ +Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 +Request-Header|Accept-Encoding|gzip, deflate +Request-Header|Accept-Language|en-us +Request-Header|Connection|keep-alive +Request-Header|Content-Length|354 +Request-Header|Content-Type|multipart/form-data; boundary=----WebKitFormBoundaryvshQXGBfIsRjfMBN +Request-Header|Host|192.168.0.119:9090 +Request-Header|Origin|http://192.168.0.119:9090 +Request-Header|Referer|http://192.168.0.119:9090/sjis-form-charset.html +Request-Header|Upgrade-Insecure-Requests|1 +Request-Header|User-Agent|Mozilla/5.0 (iPad; CPU OS 11_2_6 like Mac OS X) AppleWebKit/604.5.6 (KHTML, like Gecko) Version/11.0 Mobile/15D100 Safari/604.1 +Parts-Count|3 +Part-ContainsContents|_charset_|Shift_JIS +Part-ContainsContents|japanese|健治 +Part-ContainsContents|hello|ャユ戆タ \ No newline at end of file diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-ios-safari.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-ios-safari.raw new file mode 100644 index 00000000000..9892c9c05fe Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-ios-safari.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-msie.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-msie.expected.txt new file mode 100644 index 00000000000..5d84aa6eb75 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-msie.expected.txt @@ -0,0 +1,14 @@ +Request-Header|Accept|text/html, application/xhtml+xml, image/jxr, */* +Request-Header|Accept-Encoding|gzip, deflate +Request-Header|Accept-Language|en-US +Request-Header|Cache-Control|no-cache +Request-Header|Connection|keep-alive +Request-Header|Content-Length|358 +Request-Header|Content-Type|multipart/form-data; boundary=---------------------------7e226e1b2109c +Request-Header|Host|localhost:9090 +Request-Header|Referer|http://localhost:9090/sjis-form-charset.html +Request-Header|User-Agent|Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; Touch; rv:11.0) like Gecko +Parts-Count|3 +Part-ContainsContents|_charset_|utf-8 +Part-ContainsContents|japanese|健治 +Part-ContainsContents|hello|ャユ戆タ \ No newline at end of file diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-msie.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-msie.raw new file mode 100644 index 00000000000..9a043e69d64 Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-msie.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-safari.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-safari.expected.txt new file mode 100644 index 00000000000..18452b29e95 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-safari.expected.txt @@ -0,0 +1,15 @@ +Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 +Request-Header|Accept-Encoding|gzip, deflate +Request-Header|Accept-Language|en-us +Request-Header|Connection|keep-alive +Request-Header|Content-Length|354 +Request-Header|Content-Type|multipart/form-data; boundary=----WebKitFormBoundaryHFCTTESrC7sXQ2Gf +Request-Header|Host|192.168.0.119:9090 +Request-Header|Origin|http://192.168.0.119:9090 +Request-Header|Referer|http://192.168.0.119:9090/sjis-form-charset.html +Request-Header|Upgrade-Insecure-Requests|1 +Request-Header|User-Agent|Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/604.5.6 (KHTML, like Gecko) Version/11.0.3 Safari/604.5.6 +Parts-Count|3 +Part-ContainsContents|_charset_|Shift_JIS +Part-ContainsContents|japanese|健治 +Part-ContainsContents|hello|ャユ戆タ \ No newline at end of file diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-safari.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-safari.raw new file mode 100644 index 00000000000..ce14357da86 Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-charset-form-safari.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-android-chrome.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-android-chrome.expected.txt new file mode 100644 index 00000000000..f5e2236ce9b --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-android-chrome.expected.txt @@ -0,0 +1,16 @@ +Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 +Request-Header|Accept-Encoding|gzip, deflate +Request-Header|Accept-Language|en-US,en;q=0.9 +Request-Header|Cache-Control|max-age=0 +Request-Header|Connection|keep-alive +Request-Header|Content-Length|249 +Request-Header|Content-Type|multipart/form-data; boundary=----WebKitFormBoundaryjJR29nbr1TDUu2yh +Request-Header|DNT|1 +Request-Header|Host|192.168.0.119:9090 +Request-Header|Origin|http://192.168.0.119:9090 +Request-Header|Referer|http://192.168.0.119:9090/sjis-form.html +Request-Header|Upgrade-Insecure-Requests|1 +Request-Header|User-Agent|Mozilla/5.0 (Linux; Android 8.1.0; Pixel 2 XL Build/OPM1.171019.021) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.109 Mobile Safari/537.36 +Parts-Count|2 +Part-ContainsContents|japanese|健治 +Part-ContainsContents|hello|ャユ戆タ \ No newline at end of file diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-android-chrome.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-android-chrome.raw new file mode 100644 index 00000000000..618c30e3c98 Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-android-chrome.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-android-firefox.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-android-firefox.expected.txt new file mode 100644 index 00000000000..b3baf194689 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-android-firefox.expected.txt @@ -0,0 +1,13 @@ +Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 +Request-Header|Accept-Encoding|gzip, deflate +Request-Header|Accept-Language|en-US,en;q=0.5 +Request-Header|Connection|keep-alive +Request-Header|Content-Length|303 +Request-Header|Content-Type|multipart/form-data; boundary=---------------------------18591390852002031541755421242 +Request-Header|Host|192.168.0.119:9090 +Request-Header|Referer|http://192.168.0.119:9090/sjis-form.html +Request-Header|Upgrade-Insecure-Requests|1 +Request-Header|User-Agent|Mozilla/5.0 (Android 8.1.0; Mobile; rv:59.0) Gecko/59.0 Firefox/59.0 +Parts-Count|2 +Part-ContainsContents|japanese|健治 +Part-ContainsContents|hello|ャユ戆タ \ No newline at end of file diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-android-firefox.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-android-firefox.raw new file mode 100644 index 00000000000..5c8d5b47190 Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-android-firefox.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-chrome.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-chrome.expected.txt new file mode 100644 index 00000000000..6cba2d9e365 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-chrome.expected.txt @@ -0,0 +1,17 @@ +Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 +Request-Header|Accept-Encoding|gzip, deflate, br +Request-Header|Accept-Language|en-US,en;q=0.9 +Request-Header|Cache-Control|max-age=0 +Request-Header|Connection|keep-alive +Request-Header|Content-Length|249 +Request-Header|Content-Type|multipart/form-data; boundary=----WebKitFormBoundarysKD6As9BBil2g6Fc +Request-Header|Cookie|visited=yes +Request-Header|DNT|1 +Request-Header|Host|localhost:9090 +Request-Header|Origin|http://localhost:9090 +Request-Header|Referer|http://localhost:9090/sjis-form.html +Request-Header|Upgrade-Insecure-Requests|1 +Request-Header|User-Agent|Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36 +Parts-Count|2 +Part-ContainsContents|japanese|健治 +Part-ContainsContents|hello|ャユ戆タ \ No newline at end of file diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-chrome.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-chrome.raw new file mode 100644 index 00000000000..02e44b0913e Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-chrome.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-edge.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-edge.expected.txt new file mode 100644 index 00000000000..f51c4cc1399 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-edge.expected.txt @@ -0,0 +1,13 @@ +Request-Header|Accept|text/html, application/xhtml+xml, image/jxr, */* +Request-Header|Accept-Encoding|gzip, deflate +Request-Header|Accept-Language|en-US +Request-Header|Cache-Control|no-cache +Request-Header|Connection|keep-alive +Request-Header|Content-Length|255 +Request-Header|Content-Type|multipart/form-data; boundary=---------------------------7e28636151054 +Request-Header|Host|localhost:9090 +Request-Header|Referer|http://localhost:9090/sjis-form.html +Request-Header|User-Agent|Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299 +Parts-Count|2 +Part-ContainsContents|japanese|健治 +Part-ContainsContents|hello|ャユ戆タ \ No newline at end of file diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-edge.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-edge.raw new file mode 100644 index 00000000000..b6a9a545c58 Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-edge.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-firefox.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-firefox.expected.txt new file mode 100644 index 00000000000..ad25c45b321 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-firefox.expected.txt @@ -0,0 +1,13 @@ +Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 +Request-Header|Accept-Encoding|gzip, deflate +Request-Header|Accept-Language|en-US,en;q=0.5 +Request-Header|Connection|keep-alive +Request-Header|Content-Length|261 +Request-Header|Content-Type|multipart/form-data; boundary=---------------------------265001916915724 +Request-Header|Host|localhost:9090 +Request-Header|Referer|http://localhost:9090/sjis-form.html +Request-Header|Upgrade-Insecure-Requests|1 +Request-Header|User-Agent|Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:58.0) Gecko/20100101 Firefox/58.0 +Parts-Count|2 +Part-ContainsContents|japanese|健治 +Part-ContainsContents|hello|ャユ戆タ \ No newline at end of file diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-firefox.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-firefox.raw new file mode 100644 index 00000000000..5c8def3fffc Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-firefox.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-ios-safari.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-ios-safari.expected.txt new file mode 100644 index 00000000000..e4b4d8168e7 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-ios-safari.expected.txt @@ -0,0 +1,14 @@ +Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 +Request-Header|Accept-Encoding|gzip, deflate +Request-Header|Accept-Language|en-us +Request-Header|Connection|keep-alive +Request-Header|Content-Length|249 +Request-Header|Content-Type|multipart/form-data; boundary=----WebKitFormBoundaryj1Xj6oPRT7sp3VPE +Request-Header|Host|192.168.0.119:9090 +Request-Header|Origin|http://192.168.0.119:9090 +Request-Header|Referer|http://192.168.0.119:9090/sjis-form.html +Request-Header|Upgrade-Insecure-Requests|1 +Request-Header|User-Agent|Mozilla/5.0 (iPad; CPU OS 11_2_6 like Mac OS X) AppleWebKit/604.5.6 (KHTML, like Gecko) Version/11.0 Mobile/15D100 Safari/604.1 +Parts-Count|2 +Part-ContainsContents|japanese|健治 +Part-ContainsContents|hello|ャユ戆タ \ No newline at end of file diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-ios-safari.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-ios-safari.raw new file mode 100644 index 00000000000..f0d39757789 Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-ios-safari.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-msie.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-msie.expected.txt new file mode 100644 index 00000000000..d8ddc61a07e --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-msie.expected.txt @@ -0,0 +1,13 @@ +Request-Header|Accept|text/html, application/xhtml+xml, image/jxr, */* +Request-Header|Accept-Encoding|gzip, deflate +Request-Header|Accept-Language|en-US +Request-Header|Cache-Control|no-cache +Request-Header|Connection|keep-alive +Request-Header|Content-Length|255 +Request-Header|Content-Type|multipart/form-data; boundary=---------------------------7e21df392109c +Request-Header|Host|localhost:9090 +Request-Header|Referer|http://localhost:9090/sjis-form.html +Request-Header|User-Agent|Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; Touch; rv:11.0) like Gecko +Parts-Count|2 +Part-ContainsContents|japanese|健治 +Part-ContainsContents|hello|ャユ戆タ \ No newline at end of file diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-msie.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-msie.raw new file mode 100644 index 00000000000..b60882cec82 Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-msie.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-safari.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-safari.expected.txt new file mode 100644 index 00000000000..2acbd52718b --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-safari.expected.txt @@ -0,0 +1,14 @@ +Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 +Request-Header|Accept-Encoding|gzip, deflate +Request-Header|Accept-Language|en-us +Request-Header|Connection|keep-alive +Request-Header|Content-Length|249 +Request-Header|Content-Type|multipart/form-data; boundary=----WebKitFormBoundarytsFILMzOBBWaETUj +Request-Header|Host|192.168.0.119:9090 +Request-Header|Origin|http://192.168.0.119:9090 +Request-Header|Referer|http://192.168.0.119:9090/sjis-form.html +Request-Header|Upgrade-Insecure-Requests|1 +Request-Header|User-Agent|Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/604.5.6 (KHTML, like Gecko) Version/11.0.3 Safari/604.5.6 +Parts-Count|2 +Part-ContainsContents|japanese|健治 +Part-ContainsContents|hello|ャユ戆タ \ No newline at end of file diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-safari.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-safari.raw new file mode 100644 index 00000000000..82475faa9e7 Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-form-safari.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-jetty-client.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-jetty-client.expected.txt new file mode 100644 index 00000000000..a0ec6edc7ed --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-jetty-client.expected.txt @@ -0,0 +1,10 @@ +Request-Header|Accept-Encoding|gzip +Request-Header|Connection|close +Request-Header|Content-Type|multipart/form-data; boundary=JettyHttpClientBoundaryny8fndkswj5ot6hx +Request-Header|Host|localhost:9090 +Request-Header|Transfer-Encoding|chunked +Request-Header|User-Agent|Jetty/9.4.9.v20180320 +Request-Header|X-BrowserId|jetty-client +Parts-Count|2 +Part-ContainsContents|japanese|オープンソース +Part-ContainsContents|hello|日食桟橋 \ No newline at end of file diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-jetty-client.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-jetty-client.raw new file mode 100644 index 00000000000..eb7b56fcc39 Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-sjis-jetty-client.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-strange-quoting-apache-httpcomp.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-strange-quoting-apache-httpcomp.expected.txt new file mode 100644 index 00000000000..42010b4fa30 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-strange-quoting-apache-httpcomp.expected.txt @@ -0,0 +1,11 @@ +Request-Header|Accept-Encoding|gzip,deflate +Request-Header|Connection|keep-alive +Request-Header|Content-Length|798 +Request-Header|Content-Type|multipart/form-data; boundary=z5xWs05oeiE0TAdFlrrlAX5RSgHrHzVcgskrru +Request-Header|Host|localhost:9090 +Request-Header|User-Agent|Apache-HttpClient/4.5.5 (Java/1.8.0_162) +Request-Header|X-BrowserId|apache-httpcomp +Parts-Count|4 +Part-ContainsContents|and "I" quote|Value 1 +Part-ContainsContents|and+%22I%22+quote|Value 2 +Part-ContainsContents|value"; what="whoa"|Value 3 diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-strange-quoting-apache-httpcomp.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-strange-quoting-apache-httpcomp.raw new file mode 100644 index 00000000000..487ea39b98d Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-strange-quoting-apache-httpcomp.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-text-files-apache-httpcomp.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-text-files-apache-httpcomp.expected.txt new file mode 100644 index 00000000000..c246ce67c29 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-text-files-apache-httpcomp.expected.txt @@ -0,0 +1,15 @@ +Request-Header|Accept-Encoding|gzip,deflate +Request-Header|Connection|keep-alive +Request-Header|Content-Length|737 +Request-Header|Content-Type|multipart/form-data; boundary=B8x_673_DRSeYGTpUMgof-qN1nircWQA +Request-Header|Host|localhost:9090 +Request-Header|User-Agent|Apache-HttpClient/4.5.5 (Java/1.8.0_162) +Request-Header|X-BrowserId|apache-httpcomp +Parts-Count|3 +Part-ContainsContents|text|text default +Part-ContainsContents|file1|Content of a.txt +Part-ContainsContents|file2|Content of a.html +Part-Filename|file1|a.txt +Part-Filename|file2|a.html +Part-Sha1sum|file1|588A0F273CB5AFE9C8D76DD081812E672F2061E2 +Part-Sha1sum|file2|9A9005159AB90A6D2D9BACB5414EFE932F0CED85 \ No newline at end of file diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-text-files-apache-httpcomp.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-text-files-apache-httpcomp.raw new file mode 100644 index 00000000000..b8fee37ca7e Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-text-files-apache-httpcomp.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-text-files-jetty-client.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-text-files-jetty-client.expected.txt new file mode 100644 index 00000000000..7e0b528d284 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-text-files-jetty-client.expected.txt @@ -0,0 +1,15 @@ +Request-Header|Accept-Encoding|gzip +Request-Header|Connection|close +Request-Header|Content-Type|multipart/form-data; boundary=JettyHttpClientBoundary1e87p8a551psw1al +Request-Header|Host|localhost:9090 +Request-Header|Transfer-Encoding|chunked +Request-Header|User-Agent|Jetty/9.4.9.v20180320 +Request-Header|X-BrowserId|jetty-client +Parts-Count|3 +Part-ContainsContents|text|text default +Part-ContainsContents|file1|Content of a.txt +Part-ContainsContents|file2|<!DOCTYPE html><title>Content of a.html +Part-Filename|file1|a.txt +Part-Filename|file2|a.html +Part-Sha1sum|file1|588A0F273CB5AFE9C8D76DD081812E672F2061E2 +Part-Sha1sum|file2|9A9005159AB90A6D2D9BACB5414EFE932F0CED85 \ No newline at end of file diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-text-files-jetty-client.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-text-files-jetty-client.raw new file mode 100644 index 00000000000..53405425431 Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-text-files-jetty-client.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-unicode-names-apache-httpcomp.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-unicode-names-apache-httpcomp.expected.txt new file mode 100644 index 00000000000..446b69d34fc --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-unicode-names-apache-httpcomp.expected.txt @@ -0,0 +1,11 @@ +Request-Header|Accept-Encoding|gzip,deflate +Request-Header|Connection|keep-alive +Request-Header|Content-Length|475 +Request-Header|Content-Type|multipart/form-data; boundary=yRxfRSltW63lJPc9oHOOVyCn-SmDG6i4Ts9M4E6 +Request-Header|Host|localhost:9090 +Request-Header|User-Agent|Apache-HttpClient/4.5.5 (Java/1.8.0_162) +Request-Header|X-BrowserId|apache-httpcomp +Parts-Count|2 +Part-ContainsContents|こんにちは世界|Greetings 1 +Part-ContainsContents|%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF%E4%B8%96%E7%95%8C|Greetings 2 + diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-unicode-names-apache-httpcomp.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-unicode-names-apache-httpcomp.raw new file mode 100644 index 00000000000..938bdbdce6b Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-unicode-names-apache-httpcomp.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-unicode-names-jetty-client.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-unicode-names-jetty-client.expected.txt new file mode 100644 index 00000000000..bc9e8e69dab --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-unicode-names-jetty-client.expected.txt @@ -0,0 +1,11 @@ +Request-Header|Accept-Encoding|gzip +Request-Header|Connection|close +Request-Header|Content-Type|multipart/form-data; boundary=JettyHttpClientBoundary9iv9jofnq5dkzmgl +Request-Header|Host|localhost:9090 +Request-Header|Transfer-Encoding|chunked +Request-Header|User-Agent|Jetty/9.4.9.v20180320 +Request-Header|X-BrowserId|jetty-client +Parts-Count|2 +Part-ContainsContents|こんにちは世界|Greetings 1 +Part-ContainsContents|%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF%E4%B8%96%E7%95%8C|Greetings 2 + diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-unicode-names-jetty-client.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-unicode-names-jetty-client.raw new file mode 100644 index 00000000000..4188d3e81d2 Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-unicode-names-jetty-client.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-whitespace-only-jetty-client.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-whitespace-only-jetty-client.expected.txt new file mode 100644 index 00000000000..8b7f3212c67 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-whitespace-only-jetty-client.expected.txt @@ -0,0 +1,10 @@ +Request-Header|Accept-Encoding|gzip +Request-Header|Connection|close +Request-Header|Content-Type|multipart/form-data; boundary=JettyHttpClientBoundary1evz7ehqg8tvo10h +Request-Header|Host|localhost:9090 +Request-Header|Transfer-Encoding|chunked +Request-Header|User-Agent|Jetty/9.4.9.v20180320 +Request-Header|X-BrowserId|jetty-client +Parts-Count|1 +Part-Filename|whitespace|whitespace.txt +Part-Sha1sum|whitespace|353E2CCDDE1069706B950414B230B6C047F98491 \ No newline at end of file diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-whitespace-only-jetty-client.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-whitespace-only-jetty-client.raw new file mode 100644 index 00000000000..64f39f6ee0f Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-whitespace-only-jetty-client.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-zalgo-text-plain-apache-httpcomp.expected.txt b/jetty-jmh/src/main/resources/multipart/browser-capture-zalgo-text-plain-apache-httpcomp.expected.txt new file mode 100644 index 00000000000..9fecae5916c --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/browser-capture-zalgo-text-plain-apache-httpcomp.expected.txt @@ -0,0 +1,12 @@ +Request-Header|Accept-Encoding|gzip,deflate +Request-Header|Connection|keep-alive +Request-Header|Content-Length|1870 +Request-Header|Content-Type|multipart/form-data; boundary=V9oY7Ug2J-n4sFXLWdb7yd2LtU0hdK36ejhKYh +Request-Header|Host|localhost:9090 +Request-Header|User-Agent|Apache-HttpClient/4.5.5 (Java/1.8.0_162) +Request-Header|X-BrowserId|apache-httpcomp +Parts-Count|4 +Part-ContainsContents|zalgo-8|y͔͕͍o̪̞͎̥͇̤̕u'̛̰̫̳̰v̧̘̪̠̟̟e̥͈̱ ̥̠͇͎͕̜s̤e̺e̙ͅņ̜ ̲̟͝za̴͖̱̲͈̘l͖̖͓̙̮͔g͕̞͖͘o͕̤͈̗ ̯̲̹̲͓b͙͟e̞͎̜̗͈͉̭͞f̸or̰̩e̡̝̺,̸͕̙̥̼͇̜ ̪͇̹r̘̪ͅị͔̥͈ͅg̠̟̯͖̦͇ht͖̪͍͚̖͡?͙̝͖̞ +Part-ContainsContents|zalgo-16|y͔͕͍o̪̞͎̥͇̤̕u'̛̰̫̳̰v̧̘̪̠̟̟e̥͈̱ ̥̠͇͎͕̜s̤e̺e̙ͅņ̜ ̲̟͝za̴͖̱̲͈̘l͖̖͓̙̮͔g͕̞͖͘o͕̤͈̗ ̯̲̹̲͓b͙͟e̞͎̜̗͈͉̭͞f̸or̰̩e̡̝̺,̸͕̙̥̼͇̜ ̪͇̹r̘̪ͅị͔̥͈ͅg̠̟̯͖̦͇ht͖̪͍͚̖͡?͙̝͖̞ +Part-ContainsContents|zalgo-16-be|y͔͕͍o̪̞͎̥͇̤̕u'̛̰̫̳̰v̧̘̪̠̟̟e̥͈̱ ̥̠͇͎͕̜s̤e̺e̙ͅņ̜ ̲̟͝za̴͖̱̲͈̘l͖̖͓̙̮͔g͕̞͖͘o͕̤͈̗ ̯̲̹̲͓b͙͟e̞͎̜̗͈͉̭͞f̸or̰̩e̡̝̺,̸͕̙̥̼͇̜ ̪͇̹r̘̪ͅị͔̥͈ͅg̠̟̯͖̦͇ht͖̪͍͚̖͡?͙̝͖̞ +Part-ContainsContents|zalgo-16-le|y͔͕͍o̪̞͎̥͇̤̕u'̛̰̫̳̰v̧̘̪̠̟̟e̥͈̱ ̥̠͇͎͕̜s̤e̺e̙ͅņ̜ ̲̟͝za̴͖̱̲͈̘l͖̖͓̙̮͔g͕̞͖͘o͕̤͈̗ ̯̲̹̲͓b͙͟e̞͎̜̗͈͉̭͞f̸or̰̩e̡̝̺,̸͕̙̥̼͇̜ ̪͇̹r̘̪ͅị͔̥͈ͅg̠̟̯͖̦͇ht͖̪͍͚̖͡?͙̝͖̞ diff --git a/jetty-jmh/src/main/resources/multipart/browser-capture-zalgo-text-plain-apache-httpcomp.raw b/jetty-jmh/src/main/resources/multipart/browser-capture-zalgo-text-plain-apache-httpcomp.raw new file mode 100644 index 00000000000..2cd8c76db09 Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/browser-capture-zalgo-text-plain-apache-httpcomp.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/multipart-base64-long.expected.txt b/jetty-jmh/src/main/resources/multipart/multipart-base64-long.expected.txt new file mode 100644 index 00000000000..2b1d5ded060 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/multipart-base64-long.expected.txt @@ -0,0 +1,4 @@ +Content-Type|multipart/form-data; boundary="JuH4rALGPJfmAquncS_U1du8s59GjKKiG9a8" +Parts-Count|1 +Part-Filename|png|jetty-avatar-256.png +Part-Sha1sum|png|e75b73644afe9b234d70da9ff225229de68cdff8 \ No newline at end of file diff --git a/jetty-jmh/src/main/resources/multipart/multipart-base64-long.raw b/jetty-jmh/src/main/resources/multipart/multipart-base64-long.raw new file mode 100644 index 00000000000..2e2b4991568 Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/multipart-base64-long.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/multipart-base64.expected.txt b/jetty-jmh/src/main/resources/multipart/multipart-base64.expected.txt new file mode 100644 index 00000000000..5d4a189b8dc --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/multipart-base64.expected.txt @@ -0,0 +1,4 @@ +Content-Type|multipart/form-data; boundary="8GbcZNTauFWYMt7GeM9BxFMdlNBJ6aLJhGdXp" +Parts-Count|1 +Part-Filename|png|jetty-avatar-256.png +Part-Sha1sum|png|e75b73644afe9b234d70da9ff225229de68cdff8 \ No newline at end of file diff --git a/jetty-jmh/src/main/resources/multipart/multipart-base64.raw b/jetty-jmh/src/main/resources/multipart/multipart-base64.raw new file mode 100644 index 00000000000..514a6a1ed3c Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/multipart-base64.raw differ diff --git a/jetty-jmh/src/main/resources/multipart/multipart-uppercase.expected.txt b/jetty-jmh/src/main/resources/multipart/multipart-uppercase.expected.txt new file mode 100644 index 00000000000..ef8470f4cc7 --- /dev/null +++ b/jetty-jmh/src/main/resources/multipart/multipart-uppercase.expected.txt @@ -0,0 +1,5 @@ +Content-Type|multipart/form-data; boundary="8Q4MHJ3LWIQEQQ_OXYU5U9ZLYEH60_CFZQYANCZ" +Parts-Count|2 +Part-ContainsContents|STATE|TEXAS +Part-ContainsContents|CITY|AUSTIN + diff --git a/jetty-jmh/src/main/resources/multipart/multipart-uppercase.raw b/jetty-jmh/src/main/resources/multipart/multipart-uppercase.raw new file mode 100644 index 00000000000..3aecb111bc7 Binary files /dev/null and b/jetty-jmh/src/main/resources/multipart/multipart-uppercase.raw differ diff --git a/jetty-runner/src/main/java/org/eclipse/jetty/runner/Runner.java b/jetty-runner/src/main/java/org/eclipse/jetty/runner/Runner.java index 41e803cbbe7..12fe3a27ca6 100644 --- a/jetty-runner/src/main/java/org/eclipse/jetty/runner/Runner.java +++ b/jetty-runner/src/main/java/org/eclipse/jetty/runner/Runner.java @@ -63,7 +63,9 @@ import org.eclipse.jetty.xml.XmlConfiguration; * Runner * <p> * Combine jetty classes into a single executable jar and run webapps based on the args to it. + * @deprecated No replacement provided or available. Migrate to jetty-home (and use {@code ${jetty.base}} directory). */ +@Deprecated public class Runner { private static final Logger LOG = Log.getLogger(Runner.class); @@ -553,6 +555,10 @@ public class Runner public static void main(String[] args) { + System.err.println("WARNING: jetty-runner is deprecated."); + System.err.println(" See Jetty Documentation for startup options"); + System.err.println(" https://www.eclipse.org/jetty/documentation/"); + Runner runner = new Runner(); try 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 87e823846db..c6215c46908 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 @@ -451,6 +451,10 @@ public class Request implements HttpServletRequest /* ------------------------------------------------------------ */ private void extractContentParameters() { + // Content cannot be encoded + if (_metaData!=null && getHttpFields().contains(HttpHeader.CONTENT_ENCODING)) + throw new BadMessageException(HttpStatus.NOT_IMPLEMENTED_501,"Unsupported Content-Encoding"); + String contentType = getContentType(); if (contentType == null || contentType.isEmpty()) _contentParameters=NO_PARAMS; diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java index 73c6b1be12e..4a3dde749a7 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java @@ -32,6 +32,7 @@ import java.util.Enumeration; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Supplier; import javax.servlet.AsyncContext; import javax.servlet.RequestDispatcher; @@ -195,7 +196,7 @@ public class ResourceService } /* ------------------------------------------------------------ */ - public void doGet(HttpServletRequest request, HttpServletResponse response) + public boolean doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String servletPath=null; @@ -243,16 +244,16 @@ public class ResourceService if (included) throw new FileNotFoundException("!" + pathInContext); notFound(request,response); - return; + return response.isCommitted(); } - + // Directory? if (content.getResource().isDirectory()) { sendWelcome(content,pathInContext,endsWithSlash,included,request,response); - return; + return true; } - + // Strip slash? if (!included && endsWithSlash && pathInContext.length()>1) { @@ -261,12 +262,12 @@ public class ResourceService if (q!=null&&q.length()!=0) pathInContext+="?"+q; response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),pathInContext))); - return; + return true; } // Conditional response? if (!included && !passConditionalHeaders(request,response,content)) - return; + return true; // Precompressed variant available? Map<CompressedContentFormat,? extends HttpContent> precompressedContents = checkPrecompressedVariants?content.getPrecompressedContents():null; @@ -309,6 +310,8 @@ public class ResourceService content.release(); } } + + return true; } private List<String> getPreferredEncodingOrder(HttpServletRequest request) @@ -462,6 +465,14 @@ public class ResourceService response.sendError(HttpServletResponse.SC_NOT_FOUND); } + protected void sendStatus(HttpServletResponse response, int status, Supplier<String> etag) throws IOException + { + response.setStatus(status); + if (_etags && etag!=null) + response.setHeader(HttpHeader.ETAG.asString(),etag.get()); + response.flushBuffer(); + } + /* ------------------------------------------------------------ */ /* Check modification date headers. */ @@ -510,95 +521,84 @@ public class ResourceService ifms=request.getHeader(HttpHeader.IF_MODIFIED_SINCE.asString()); ifums=request.getDateHeader(HttpHeader.IF_UNMODIFIED_SINCE.asString()); } - - if (!HttpMethod.HEAD.is(request.getMethod())) - { - if (_etags) - { - String etag=content.getETagValue(); - if (ifm!=null) - { - boolean match=false; - if (etag!=null) - { - QuotedCSV quoted = new QuotedCSV(true,ifm); - for (String tag : quoted) - { - if (CompressedContentFormat.tagEquals(etag, tag)) - { - match=true; - break; - } - } - } - if (!match) - { - response.setStatus(HttpServletResponse.SC_PRECONDITION_FAILED); - return false; - } - } - - if (ifnm!=null && etag!=null) + if (_etags) + { + String etag=content.getETagValue(); + if (ifm!=null) + { + boolean match=false; + if (etag!=null) { - // Handle special case of exact match OR gzip exact match - if (CompressedContentFormat.tagEquals(etag, ifnm) && ifnm.indexOf(',')<0) - { - response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); - response.setHeader(HttpHeader.ETAG.asString(),ifnm); - return false; - } - - // Handle list of tags - QuotedCSV quoted = new QuotedCSV(true,ifnm); + QuotedCSV quoted = new QuotedCSV(true,ifm); for (String tag : quoted) { if (CompressedContentFormat.tagEquals(etag, tag)) { - response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); - response.setHeader(HttpHeader.ETAG.asString(),tag); - return false; + match=true; + break; } } - - // If etag requires content to be served, then do not check if-modified-since - return true; } - } - - // Handle if modified since - if (ifms!=null) - { - //Get jetty's Response impl - String mdlm=content.getLastModifiedValue(); - if (mdlm!=null && ifms.equals(mdlm)) + + if (!match) { - response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); - if (_etags) - response.setHeader(HttpHeader.ETAG.asString(),content.getETagValue()); - response.flushBuffer(); - return false; - } - - long ifmsl=request.getDateHeader(HttpHeader.IF_MODIFIED_SINCE.asString()); - if (ifmsl!=-1 && content.getResource().lastModified()/1000 <= ifmsl/1000) - { - response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); - if (_etags) - response.setHeader(HttpHeader.ETAG.asString(),content.getETagValue()); - response.flushBuffer(); + sendStatus(response,HttpServletResponse.SC_PRECONDITION_FAILED,null); return false; } } - // Parse the if[un]modified dates and compare to resource - if (ifums!=-1 && content.getResource().lastModified()/1000 > ifums/1000) + if (ifnm!=null && etag!=null) { - response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED); + // Handle special case of exact match OR gzip exact match + if (CompressedContentFormat.tagEquals(etag, ifnm) && ifnm.indexOf(',')<0) + { + sendStatus(response,HttpServletResponse.SC_NOT_MODIFIED,ifnm::toString); + return false; + } + + // Handle list of tags + QuotedCSV quoted = new QuotedCSV(true,ifnm); + for (String tag : quoted) + { + if (CompressedContentFormat.tagEquals(etag, tag)) + { + sendStatus(response,HttpServletResponse.SC_NOT_MODIFIED,tag::toString); + return false; + } + } + + // If etag requires content to be served, then do not check if-modified-since + return true; + } + } + + // Handle if modified since + if (ifms!=null) + { + //Get jetty's Response impl + String mdlm=content.getLastModifiedValue(); + if (mdlm!=null && ifms.equals(mdlm)) + { + sendStatus(response,HttpServletResponse.SC_NOT_MODIFIED,content::getETagValue); return false; } + long ifmsl=request.getDateHeader(HttpHeader.IF_MODIFIED_SINCE.asString()); + if (ifmsl!=-1 && content.getResource().lastModified()/1000 <= ifmsl/1000) + { + sendStatus(response,HttpServletResponse.SC_NOT_MODIFIED,content::getETagValue); + return false; + } } + + // Parse the if[un]modified dates and compare to resource + if (ifums!=-1 && content.getResource().lastModified()/1000 > ifums/1000) + { + response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED); + return false; + } + } catch(IllegalArgumentException iae) { @@ -606,6 +606,7 @@ public class ResourceService response.sendError(400, iae.getMessage()); throw iae; } + return true; } @@ -649,7 +650,7 @@ public class ResourceService final long content_length = content.getContentLengthValue(); // Get the output stream (or writer) - OutputStream out =null; + OutputStream out; boolean written; try { @@ -741,10 +742,9 @@ public class ResourceService if (ranges==null || ranges.size()==0) { putHeaders(response,content,0); - response.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE); response.setHeader(HttpHeader.CONTENT_RANGE.asString(), InclusiveByteRange.to416HeaderRangeString(content_length)); - content.getResource().writeTo(out,0,content_length); + sendStatus(response,HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE,null); return true; } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java index 3b82750588b..b568b3defbb 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java @@ -269,19 +269,14 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory,W if (baseRequest.isHandled()) return; - if (!HttpMethod.GET.is(request.getMethod())) + if (!HttpMethod.GET.is(request.getMethod()) && !HttpMethod.HEAD.is(request.getMethod())) { - if (!HttpMethod.HEAD.is(request.getMethod())) - { - // try another handler - super.handle(target,baseRequest,request,response); - return; - } + // try another handler + super.handle(target,baseRequest,request,response); + return; } - _resourceService.doGet(request,response); - - if (response.isCommitted()) + if (_resourceService.doGet(request,response)) baseRequest.setHandled(true); else // no resource - try other handlers diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java index cbcce396e1b..c6a6b74a6db 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java @@ -23,6 +23,7 @@ import java.util.Arrays; import java.util.EnumSet; import java.util.ListIterator; import java.util.Set; +import java.util.regex.Pattern; import java.util.zip.Deflater; import javax.servlet.DispatcherType; @@ -36,6 +37,8 @@ import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.http.PreEncodedHttpField; +import org.eclipse.jetty.http.QuotedCSV; import org.eclipse.jetty.http.pathmap.PathSpecSet; import org.eclipse.jetty.server.HttpOutput; import org.eclipse.jetty.server.Request; @@ -153,6 +156,8 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory public static final int DEFAULT_MIN_GZIP_SIZE=16; public static final int COMPRESSION_LEVEL = Deflater.DEFAULT_COMPRESSION; private static final Logger LOG = Log.getLogger(GzipHandler.class); + private static final HttpField X_CE_GZIP = new PreEncodedHttpField("X-Content-Encoding","gzip"); + private static final Pattern COMMA_GZIP = Pattern.compile(".*, *gzip"); private int _minGzipSize=DEFAULT_MIN_GZIP_SIZE; private boolean _syncFlush = false; @@ -601,16 +606,31 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory // Handle request inflation if (_inflateBufferSize>0) { - HttpField ce = baseRequest.getHttpFields().getField(HttpHeader.CONTENT_ENCODING); - if (ce!=null && "gzip".equalsIgnoreCase(ce.getValue())) + for (ListIterator<HttpField> i = baseRequest.getHttpFields().listIterator(); i.hasNext();) { - // TODO should check ce.contains and then remove just the gzip encoding - baseRequest.getHttpFields().remove(HttpHeader.CONTENT_ENCODING); - baseRequest.getHttpFields().add(new HttpField("X-Content-Encoding",ce.getValue())); - baseRequest.getHttpInput().addInterceptor(new GzipHttpInputInterceptor(baseRequest.getHttpChannel().getByteBufferPool(),_inflateBufferSize)); - } + HttpField field = i.next(); + if (field.getHeader()!=HttpHeader.CONTENT_ENCODING) + continue; + + if (field.getValue().equalsIgnoreCase("gzip")) + { + i.set(X_CE_GZIP); + baseRequest.getHttpInput().addInterceptor(new GzipHttpInputInterceptor(baseRequest.getHttpChannel().getByteBufferPool(),_inflateBufferSize)); + break; + } + + if (COMMA_GZIP.matcher(field.getValue()).matches()) + { + String v = field.getValue(); + v = v.substring(0,v.lastIndexOf(',')); + i.set(new HttpField(HttpHeader.CONTENT_ENCODING,v)); + i.add(X_CE_GZIP); + baseRequest.getHttpInput().addInterceptor(new GzipHttpInputInterceptor(baseRequest.getHttpChannel().getByteBufferPool(),_inflateBufferSize)); + break; + } + } } - + // Are we already being gzipped? HttpOutput out = baseRequest.getResponse().getHttpOutput(); HttpOutput.Interceptor interceptor = out.getInterceptor(); diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestFixture.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestFixture.java index f7d16a619ba..09fe0438ad7 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestFixture.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestFixture.java @@ -115,7 +115,7 @@ public class HttpServerTestFixture @Override public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { - Log.getRootLogger().info("handle "+target); + Log.getRootLogger().debug("handle "+target); baseRequest.setHandled(true); if (request.getContentType()!=null) @@ -157,7 +157,7 @@ public class HttpServerTestFixture if (reader.read()>=0) throw new IllegalStateException("Not closed"); - Log.getRootLogger().info("handled "+target); + Log.getRootLogger().debug("handled "+target); } } 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 45fef312d4b..de89cc020cf 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 @@ -611,6 +611,42 @@ public class RequestTest String responses=_connector.getResponse(request); assertThat(responses,startsWith("HTTP/1.1 200")); } + + + @Test + public void testEncodedParamExtraction() throws Exception + { + _handler._checker = new RequestTester() + { + @Override + public boolean check(HttpServletRequest request,HttpServletResponse response) + { + try + { + // This throws an exception if attempted + request.getParameter("param"); + return false; + } + catch(BadMessageException e) + { + return e.getCode()==501; + } + } + }; + + //Send a request with encoded form content + String request="GET / HTTP/1.1\r\n"+ + "Host: whatever\r\n"+ + "Content-Type: application/x-www-form-urlencoded; charset=utf-8\n"+ + "Content-Length: 10\n"+ + "Content-Encoding: gzip\n"+ + "Connection: close\n"+ + "\n"+ + "0123456789\n"; + + String responses=_connector.getResponse(request); + assertThat(responses,startsWith("HTTP/1.1 200")); + } @Test public void testInvalidHostHeader() throws Exception diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerTest.java index 91d0ededdd1..cac9454a6a3 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerTest.java @@ -40,6 +40,7 @@ import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpTester; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.HttpConfiguration; @@ -185,6 +186,24 @@ public class ResourceHandlerTest assertThat(response.get(SERVER),containsString("Jetty")); assertThat(response.getContent(),containsString("simple text")); } + + @Test + public void testIfModifiedSince() throws Exception + { + HttpTester.Response response = HttpTester.parseResponse( + _local.getResponse("GET /resource/simple.txt HTTP/1.0\r\n\r\n")); + assertThat(response.getStatus(),equalTo(200)); + assertThat(response.get(LAST_MODIFIED),Matchers.notNullValue()); + assertThat(response.getContent(),containsString("simple text")); + String last_modified = response.get(LAST_MODIFIED); + + response = HttpTester.parseResponse(_local.getResponse( + "GET /resource/simple.txt HTTP/1.0\r\n" + + "If-Modified-Since: " + last_modified + "\r\n" + + "\r\n")); + + assertThat(response.getStatus(),equalTo(304)); + } @Test public void testBigFile() throws Exception @@ -266,7 +285,7 @@ public class ResourceHandlerTest @Slow public void testSlowBiggest() throws Exception { - _connector.setIdleTimeout(10000); + _connector.setIdleTimeout(9000); File dir = MavenTestingUtils.getTargetFile("test-classes/simple"); File biggest = new File(dir,"biggest.txt"); @@ -291,7 +310,7 @@ public class ResourceHandlerTest ByteBuffer buffer=null; while(true) { - Thread.sleep(100); + Thread.sleep(25); int len=in.read(array); if (len<0) break; @@ -305,4 +324,32 @@ public class ResourceHandlerTest } } + + + @Test + public void testConditionalGetResponseCommitted() throws Exception + { + _config.setOutputBufferSize(8); + _resourceHandler.setEtags(true); + + HttpTester.Response response = HttpTester.parseResponse(_local.getResponse("GET /resource/big.txt HTTP/1.0\r\n" + + "If-Match: \"NO_MATCH\"\r\n" + + "\r\n")); + + assertThat(response.getStatus(),equalTo(HttpStatus.PRECONDITION_FAILED_412)); + } + + + @Test + public void testConditionalHeadResponseCommitted() throws Exception + { + _config.setOutputBufferSize(8); + _resourceHandler.setEtags(true); + + HttpTester.Response response = HttpTester.parseResponse(_local.getResponse("HEAD /resource/big.txt HTTP/1.0\r\n" + + "If-Match: \"NO_MATCH\"\r\n" + + "\r\n")); + + assertThat(response.getStatus(),equalTo(HttpStatus.PRECONDITION_FAILED_412)); + } } diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java index 8ea9740d50c..d004d838460 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java @@ -455,7 +455,8 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory, Welc protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - _resourceService.doGet(request,response); + if(!_resourceService.doGet(request,response)) + response.sendError(404); } /* ------------------------------------------------------------ */ diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/GzipHandlerTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/GzipHandlerTest.java index fd67f91eaa3..c09a43a050c 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/GzipHandlerTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/GzipHandlerTest.java @@ -35,6 +35,7 @@ import java.io.PrintWriter; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.Enumeration; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; @@ -102,6 +103,7 @@ public class GzipHandlerTest servlets.addServletWithMapping(ForwardServlet.class,"/forward"); servlets.addServletWithMapping(IncludeServlet.class,"/include"); servlets.addServletWithMapping(EchoServlet.class,"/echo/*"); + servlets.addServletWithMapping(DumpServlet.class,"/dump/*"); _server.start(); } @@ -178,6 +180,20 @@ public class GzipHandlerTest doGet(req,response); } } + + public static class DumpServlet extends HttpServlet + { + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException + { + response.setContentType("text/plain"); + for (Enumeration<String> e = req.getParameterNames(); e.hasMoreElements(); ) + { + String n = e.nextElement(); + response.getWriter().printf("%s: %s%n",n,req.getParameter(n)); + } + } + } public static class ForwardServlet extends HttpServlet { @@ -493,6 +509,35 @@ public class GzipHandlerTest } + + @Test + public void testGzipFormRequest() throws Exception + { + String data = "name=value"; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + GZIPOutputStream output = new GZIPOutputStream(baos); + output.write(data.getBytes(StandardCharsets.UTF_8)); + output.close(); + byte[] bytes = baos.toByteArray(); + + // generated and parsed test + HttpTester.Request request = HttpTester.newRequest(); + HttpTester.Response response; + + request.setMethod("POST"); + request.setURI("/ctx/dump"); + request.setVersion("HTTP/1.0"); + request.setHeader("Host","tester"); + request.setHeader("Content-Type","application/x-www-form-urlencoded; charset=utf-8"); + request.setHeader("Content-Encoding","gzip"); + request.setContent(bytes); + + response = HttpTester.parseResponse(_connector.getResponse(request.generate())); + + assertThat(response.getStatus(),is(200)); + assertThat(response.getContent(),is("name: value\n")); + } + @Test public void testGzipBomb() throws Exception { diff --git a/jetty-util/pom.xml b/jetty-util/pom.xml index a19d389b926..73804406ebc 100644 --- a/jetty-util/pom.xml +++ b/jetty-util/pom.xml @@ -54,49 +54,6 @@ </instructions> </configuration> </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-shade-plugin</artifactId> - <executions> - <execution> - <phase>package</phase> - <goals> - <goal>shade</goal> - </goals> - <configuration> - <finalName>${jmhjar.name}</finalName> - <shadeTestJar>true</shadeTestJar> - <artifactSet> - <includes> - <include>org.openjdk.jmh:jmh-core</include> - </includes> - </artifactSet> - <transformers> - <transformer - implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> - <mainClass>org.openjdk.jmh.Main</mainClass> - </transformer> - </transformers> - <filters> - <filter> - <artifact>org.openjdk.jmh:jmh-core</artifact> - <includes> - <include>**</include> - </includes> - </filter> - <filter> - <artifact>*:*</artifact> - <excludes> - <exclude>META-INF/*.SF</exclude> - <exclude>META-INF/*.DSA</exclude> - <exclude>META-INF/*.RSA</exclude> - </excludes> - </filter> - </filters> - </configuration> - </execution> - </executions> - </plugin> </plugins> <pluginManagement> <plugins> @@ -145,17 +102,5 @@ <version>${slf4j.version}</version> <scope>test</scope> </dependency> - <dependency> - <groupId>org.openjdk.jmh</groupId> - <artifactId>jmh-core</artifactId> - <version>${jmh.version}</version> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.openjdk.jmh</groupId> - <artifactId>jmh-generator-annprocess</artifactId> - <version>${jmh.version}</version> - <scope>test</scope> - </dependency> </dependencies> </project> diff --git a/jetty-util/src/main/assembly/perf-tests.xml b/jetty-util/src/main/assembly/perf-tests.xml new file mode 100644 index 00000000000..0ac6e21b341 --- /dev/null +++ b/jetty-util/src/main/assembly/perf-tests.xml @@ -0,0 +1,28 @@ +<assembly + xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd"> + <id>perf-tests</id> + <formats> + <format>jar</format> + </formats> + <includeBaseDirectory>false</includeBaseDirectory> + <dependencySets> + <dependencySet> + <outputDirectory>/</outputDirectory> + <useProjectArtifact>true</useProjectArtifact> + <unpack>true</unpack> + <scope>test</scope> + </dependencySet> + </dependencySets> + <fileSets> + <fileSet> + <directory>${project.build.directory}/test-classes</directory> + <outputDirectory>/</outputDirectory> + <includes> + <include>**/*</include> + </includes> + <useDefaultExcludes>true</useDefaultExcludes> + </fileSet> + </fileSets> +</assembly> \ No newline at end of file diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/DateCache.java b/jetty-util/src/main/java/org/eclipse/jetty/util/DateCache.java index 79a8561e92c..9f7fc960c41 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/DateCache.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/DateCache.java @@ -18,12 +18,16 @@ package org.eclipse.jetty.util; -import java.text.SimpleDateFormat; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.util.Date; import java.util.Locale; import java.util.TimeZone; -/** Date Format Cache. +/** + * Date Format Cache. * Computes String representations of Dates and caches * the results so that subsequent requests within the same second * will be fast. @@ -43,8 +47,9 @@ public class DateCache private final String _formatString; private final String _tzFormatString; - private final SimpleDateFormat _tzFormat; - private final Locale _locale ; + private final DateTimeFormatter _tzFormat; + private final Locale _locale; + private final ZoneId _zoneId; private volatile Tick _tick; @@ -138,14 +143,14 @@ public class DateCache if( _locale != null ) { - _tzFormat=new SimpleDateFormat(_tzFormatString,_locale); + _tzFormat=DateTimeFormatter.ofPattern(_tzFormatString,_locale); } else { - _tzFormat=new SimpleDateFormat(_tzFormatString); + _tzFormat=DateTimeFormatter.ofPattern(_tzFormatString); } - _tzFormat.setTimeZone(tz); - + _zoneId = tz.toZoneId(); + _tzFormat.withZone(_zoneId); _tick=null; } @@ -153,7 +158,7 @@ public class DateCache /* ------------------------------------------------------------ */ public TimeZone getTimeZone() { - return _tzFormat.getTimeZone(); + return TimeZone.getTimeZone(_zoneId); } @@ -171,11 +176,7 @@ public class DateCache // Is this the cached time if (tick==null || seconds!=tick._seconds) { - // It's a cache miss - synchronized (this) - { - return _tzFormat.format(inDate); - } + return ZonedDateTime.ofInstant(inDate.toInstant(),_zoneId).format( _tzFormat ); } return tick._string; @@ -198,11 +199,7 @@ public class DateCache if (tick==null || seconds!=tick._seconds) { // It's a cache miss - Date d = new Date(inDate); - synchronized (this) - { - return _tzFormat.format(d); - } + return ZonedDateTime.ofInstant(Instant.ofEpochMilli(inDate),_zoneId).format( _tzFormat ); } return tick._string; @@ -245,17 +242,16 @@ public class DateCache { long seconds = now / 1000; - // Synchronize to protect _tzFormat - synchronized (this) + Tick tick=_tick; + // recheck the tick, to save multiple formats + if (tick==null || tick._seconds!=seconds) { - // recheck the tick, to save multiple formats - if (_tick==null || _tick._seconds!=seconds) - { - String s= _tzFormat.format(new Date(now)); - return _tick=new Tick(seconds,s); - } - return _tick; + String s = ZonedDateTime.ofInstant(Instant.now(),_zoneId).format(_tzFormat); + _tick=new Tick(seconds,s); + tick=_tick; } + return tick; + } /* ------------------------------------------------------------ */ diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/DateCacheTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/DateCacheTest.java index 80e06435aab..2149cfd6827 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/DateCacheTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/DateCacheTest.java @@ -19,6 +19,8 @@ package org.eclipse.jetty.util; +import java.time.Instant; +import java.util.Date; import java.util.Locale; import java.util.TimeZone; import java.util.concurrent.TimeUnit; @@ -72,4 +74,32 @@ public class DateCacheTest } Assert.assertThat(hits,Matchers.greaterThan(misses)); } + + @Test + public void test_all_methods() { + // we simply check we do not have any exception + DateCache dateCache = new DateCache(); + Assert.assertNotNull(dateCache.formatNow(System.currentTimeMillis())); + Assert.assertNotNull(dateCache.formatNow(new Date().getTime())); + Assert.assertNotNull(dateCache.formatNow(Instant.now().toEpochMilli())); + + Assert.assertNotNull(dateCache.format(new Date())); + Assert.assertNotNull(dateCache.format(new Date(System.currentTimeMillis()))); + + Assert.assertNotNull(dateCache.format(System.currentTimeMillis())); + Assert.assertNotNull(dateCache.format(new Date().getTime())); + Assert.assertNotNull(dateCache.format(Instant.now().toEpochMilli())); + + Assert.assertNotNull(dateCache.formatTick(System.currentTimeMillis())); + Assert.assertNotNull(dateCache.formatTick(new Date().getTime())); + Assert.assertNotNull(dateCache.formatTick(Instant.now().toEpochMilli())); + + Assert.assertNotNull(dateCache.getFormatString()); + + Assert.assertNotNull(dateCache.getTimeZone()); + + Assert.assertNotNull(dateCache.now()); + + Assert.assertNotNull(dateCache.tick()); + } } diff --git a/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/jsr356/ConfiguratorTest.java b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/jsr356/ConfiguratorTest.java index dc5b6555b14..799d2257994 100644 --- a/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/jsr356/ConfiguratorTest.java +++ b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/jsr356/ConfiguratorTest.java @@ -18,9 +18,9 @@ package org.eclipse.jetty.websocket.tests.client.jsr356; +import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.notNullValue; -import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertThat; +import static org.hamcrest.Matchers.is; import java.util.List; import java.util.Map; @@ -63,7 +63,7 @@ public class ConfiguratorTest } private static SimpleServletServer server; - + @BeforeClass public static void startServer() throws Exception { diff --git a/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/jsr356/endpoints/EchoStringEndpoint.java b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/jsr356/endpoints/EchoStringEndpoint.java index 89101c1b4c4..4030e8c9d46 100644 --- a/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/jsr356/endpoints/EchoStringEndpoint.java +++ b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/jsr356/endpoints/EchoStringEndpoint.java @@ -18,20 +18,29 @@ package org.eclipse.jetty.websocket.tests.jsr356.endpoints; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingDeque; +import javax.websocket.EndpointConfig; +import javax.websocket.Session; + +import org.eclipse.jetty.util.BlockingArrayQueue; /** * Legitimate structure for an Endpoint */ public class EchoStringEndpoint extends AbstractStringEndpoint { - public BlockingQueue<String> messageQueue = new LinkedBlockingDeque<>(); + public BlockingArrayQueue<String> messages = new BlockingArrayQueue<>(); + + @Override + public void onOpen(Session session, EndpointConfig config) + { + super.onOpen(session, config); + this.session.getUserProperties().put("endpoint", this); + } @Override public void onMessage(String message) { - messageQueue.offer(message); - session.getAsyncRemote().sendText(message); + this.messages.offer(message); + this.session.getAsyncRemote().sendText(message); } } diff --git a/pom.xml b/pom.xml index b937bb704ea..6c6f3909284 100644 --- a/pom.xml +++ b/pom.xml @@ -29,7 +29,7 @@ <alpn.version>undefined</alpn.version> <conscrypt.version>1.1.4</conscrypt.version> <asm.version>6.2</asm.version> - <jmh.version>1.20</jmh.version> + <jmh.version>1.21</jmh.version> <jmhjar.name>benchmarks</jmhjar.name> <surefireVersion>2.21.0</surefireVersion> @@ -93,6 +93,7 @@ <module>jetty-memcached</module> <module>jetty-hazelcast</module> <module>jetty-unixsocket</module> + <module>jetty-jmh</module> <module>tests</module> <module>examples</module> <module>jetty-quickstart</module> @@ -591,7 +592,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> - <version>3.0.0</version> + <version>3.1.1</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> @@ -889,6 +890,11 @@ <artifactId>buildnumber-maven-plugin</artifactId> <version>1.4</version> </plugin> + <plugin> + <groupId>com.github.madgnome</groupId> + <artifactId>h2spec-maven-plugin</artifactId> + <version>0.3</version> + </plugin> </plugins> </pluginManagement> </build> diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/stylesheet.css~ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/stylesheet.css~ deleted file mode 100644 index def6847d14c..00000000000 --- a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/stylesheet.css~ +++ /dev/null @@ -1,7 +0,0 @@ -body {color: #2E2E2E; font-family:sans-serif; font-size:90%;} -h1 {font-variant: small-caps; font-size:130%; letter-spacing: 0.1em;} -h2 {font-variant: small-caps; font-size:100%; letter-spacing: 0.1em; margin-top:2em} -h3 {font-size:100%; letter-spacing: 0.1em;} - -span.pass { color: green; } -span.fail { color:red; }