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 132733ee8c2..b5be288a82b 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
@@ -27,7 +27,6 @@ import java.util.Map;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -46,6 +45,7 @@ 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.SettingsFrame;
+import org.eclipse.jetty.http2.parser.RateControl;
import org.eclipse.jetty.http2.parser.ServerParser;
import org.eclipse.jetty.http2.server.RawHTTP2ServerConnectionFactory;
import org.eclipse.jetty.server.Connector;
@@ -152,7 +152,7 @@ public class HTTP2Test extends AbstractTest
start(new HttpServlet()
{
@Override
- protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+ protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException
{
resp.getOutputStream().write(content);
}
@@ -200,7 +200,7 @@ public class HTTP2Test 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
{
IO.copy(request.getInputStream(), response.getOutputStream());
}
@@ -244,7 +244,7 @@ public class HTTP2Test 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
{
int download = request.getIntHeader(downloadBytes);
byte[] content = new byte[download];
@@ -287,7 +287,7 @@ public class HTTP2Test extends AbstractTest
start(new HttpServlet()
{
@Override
- protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+ protected void service(HttpServletRequest request, HttpServletResponse response)
{
response.setStatus(status);
}
@@ -322,7 +322,7 @@ public class HTTP2Test extends AbstractTest
start(new HttpServlet()
{
@Override
- protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+ protected void service(HttpServletRequest request, HttpServletResponse response)
{
assertEquals(host, request.getServerName());
assertEquals(port, request.getServerPort());
@@ -747,7 +747,7 @@ public class HTTP2Test extends AbstractTest
RawHTTP2ServerConnectionFactory connectionFactory = new RawHTTP2ServerConnectionFactory(new HttpConfiguration(), serverListener)
{
@Override
- protected ServerParser newServerParser(Connector connector, ServerParser.Listener listener)
+ protected ServerParser newServerParser(Connector connector, ServerParser.Listener listener, RateControl rateControl)
{
return super.newServerParser(connector, new ServerParser.Listener.Wrapper(listener)
{
@@ -757,7 +757,7 @@ public class HTTP2Test extends AbstractTest
super.onGoAway(frame);
goAwayLatch.countDown();
}
- });
+ }, rateControl);
}
};
prepareServer(connectionFactory);
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 770f1d07f28..a8fb651121a 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
@@ -57,6 +57,7 @@ import org.eclipse.jetty.util.AtomicBiInteger;
import org.eclipse.jetty.util.Atomics;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.CountingCallback;
+import org.eclipse.jetty.util.MathUtils;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.Retainable;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
@@ -460,47 +461,33 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
int windowDelta = frame.getWindowDelta();
if (streamId > 0)
{
- if (windowDelta == 0)
+ IStream stream = getStream(streamId);
+ if (stream != null)
{
- reset(new ResetFrame(streamId, ErrorCode.PROTOCOL_ERROR.code), Callback.NOOP);
- }
- else
- {
- IStream stream = getStream(streamId);
- if (stream != null)
+ int streamSendWindow = stream.updateSendWindow(0);
+ if (MathUtils.sumOverflows(streamSendWindow, windowDelta))
{
- 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);
- }
+ reset(new ResetFrame(streamId, ErrorCode.FLOW_CONTROL_ERROR.code), Callback.NOOP);
}
else
{
- if (!isRemoteStreamClosed(streamId))
- onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "unexpected_window_update_frame");
+ stream.process(frame, Callback.NOOP);
+ onWindowUpdate(stream, frame);
}
}
+ else
+ {
+ if (!isRemoteStreamClosed(streamId))
+ onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "unexpected_window_update_frame");
+ }
}
else
{
- if (windowDelta == 0)
- {
- onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "invalid_window_update_frame");
- }
+ int sessionSendWindow = updateSendWindow(0);
+ if (MathUtils.sumOverflows(sessionSendWindow, windowDelta))
+ onConnectionFailure(ErrorCode.FLOW_CONTROL_ERROR.code, "invalid_flow_control_window");
else
- {
- int sessionSendWindow = updateSendWindow(0);
- if (sumOverflows(sessionSendWindow, windowDelta))
- onConnectionFailure(ErrorCode.FLOW_CONTROL_ERROR.code, "invalid_flow_control_window");
- else
- onWindowUpdate(null, frame);
- }
+ onWindowUpdate(null, frame);
}
}
@@ -515,19 +502,6 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
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)
{
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/ContinuationFrame.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/ContinuationFrame.java
new file mode 100644
index 00000000000..39d69310ce8
--- /dev/null
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/ContinuationFrame.java
@@ -0,0 +1,48 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2019 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 ContinuationFrame extends Frame
+{
+ private final int streamId;
+ private final boolean endHeaders;
+
+ public ContinuationFrame(int streamId, boolean endHeaders)
+ {
+ super(FrameType.CONTINUATION);
+ this.streamId = streamId;
+ this.endHeaders = endHeaders;
+ }
+
+ public int getStreamId()
+ {
+ return streamId;
+ }
+
+ public boolean isEndHeaders()
+ {
+ return endHeaders;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s#%d{end=%b}", super.toString(), getStreamId(), isEndHeaders());
+ }
+}
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/UnknownFrame.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/UnknownFrame.java
new file mode 100644
index 00000000000..ffd88682b2a
--- /dev/null
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/UnknownFrame.java
@@ -0,0 +1,36 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2019 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 UnknownFrame extends Frame
+{
+ private final int frameType;
+
+ public UnknownFrame(int frameType)
+ {
+ super(null);
+ this.frameType = frameType;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s,t=%d", super.toString(), frameType);
+ }
+}
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 82d18f96a84..01c6d29e570 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
@@ -96,6 +96,11 @@ public abstract class BodyParser
return headerParser.getLength();
}
+ protected int getFrameType()
+ {
+ return headerParser.getFrameType();
+ }
+
protected void notifyData(DataFrame frame)
{
try
@@ -223,9 +228,10 @@ public abstract class BodyParser
}
}
- protected void streamFailure(int streamId, int error, String reason)
+ protected boolean streamFailure(int streamId, int error, String reason)
{
notifyStreamFailure(streamId, error, reason);
+ return false;
}
private void notifyStreamFailure(int streamId, int error, String reason)
@@ -239,4 +245,9 @@ public abstract class BodyParser
LOG.info("Failure while notifying listener " + listener, x);
}
}
+
+ protected boolean rateControlOnEvent(Object o)
+ {
+ return headerParser.getRateControl().onEvent(o);
+ }
}
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 65e47d1c827..fc2e03e97a0 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
@@ -23,6 +23,7 @@ import java.nio.ByteBuffer;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.ErrorCode;
import org.eclipse.jetty.http2.Flags;
+import org.eclipse.jetty.http2.frames.ContinuationFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
public class ContinuationBodyParser extends BodyParser
@@ -43,7 +44,15 @@ public class ContinuationBodyParser extends BodyParser
protected void emptyBody(ByteBuffer buffer)
{
if (hasFlag(Flags.END_HEADERS))
- onHeaders();
+ {
+ onHeaders(buffer);
+ }
+ else
+ {
+ ContinuationFrame frame = new ContinuationFrame(getStreamId(), hasFlag(Flags.END_HEADERS));
+ if (!rateControlOnEvent(frame))
+ connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_continuation_frame_rate");
+ }
}
@Override
@@ -81,7 +90,7 @@ public class ContinuationBodyParser extends BodyParser
headerBlockFragments.storeFragment(buffer, length, last);
reset();
if (last)
- return onHeaders();
+ return onHeaders(buffer);
return true;
}
}
@@ -94,15 +103,20 @@ public class ContinuationBodyParser extends BodyParser
return false;
}
- private boolean onHeaders()
+ private boolean onHeaders(ByteBuffer buffer)
{
ByteBuffer headerBlock = headerBlockFragments.complete();
MetaData metaData = headerBlockParser.parse(headerBlock, headerBlock.remaining());
+ if (metaData == null)
+ return true;
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());
+ if (metaData == HeaderBlockParser.STREAM_FAILURE)
+ {
+ if (!rateControlOnEvent(frame))
+ return connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_continuation_frame_rate");
+ }
notifyHeaders(frame);
return true;
}
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/DataBodyParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/DataBodyParser.java
index ac9e7bab991..be0ede0a5cb 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/DataBodyParser.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/DataBodyParser.java
@@ -48,9 +48,17 @@ public class DataBodyParser extends BodyParser
protected void emptyBody(ByteBuffer buffer)
{
if (isPadding())
+ {
connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_data_frame");
+ }
else
- onData(BufferUtil.EMPTY_BUFFER, false, 0);
+ {
+ DataFrame frame = new DataFrame(getStreamId(), BufferUtil.EMPTY_BUFFER, isEndStream());
+ if (!isEndStream() && !rateControlOnEvent(frame))
+ connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_data_frame_rate");
+ else
+ onData(frame);
+ }
}
@Override
@@ -134,7 +142,11 @@ public class DataBodyParser extends BodyParser
private void onData(ByteBuffer buffer, boolean fragment, int padding)
{
- DataFrame frame = new DataFrame(getStreamId(), buffer, !fragment && isEndStream(), padding);
+ onData(new DataFrame(getStreamId(), buffer, !fragment && isEndStream(), padding));
+ }
+
+ private void onData(DataFrame frame)
+ {
notifyData(frame);
}
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 0e4d2dc5e9e..e735d9ada3e 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
@@ -30,14 +30,24 @@ import org.eclipse.jetty.http2.frames.FrameType;
*/
public class HeaderParser
{
+ private final RateControl rateControl;
private State state = State.LENGTH;
private int cursor;
-
private int length;
private int type;
private int flags;
private int streamId;
+ public HeaderParser(RateControl rateControl)
+ {
+ this.rateControl = rateControl;
+ }
+
+ public RateControl getRateControl()
+ {
+ return rateControl;
+ }
+
protected void reset()
{
state = State.LENGTH;
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 febdefb6c25..3054d5ee439 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
@@ -61,17 +61,23 @@ public class HeadersBodyParser extends BodyParser
@Override
protected void emptyBody(ByteBuffer buffer)
{
- if (hasFlag(Flags.END_HEADERS))
+ if (hasFlag(Flags.PRIORITY))
+ {
+ connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_headers_priority_frame");
+ }
+ else if (hasFlag(Flags.END_HEADERS))
{
MetaData metaData = headerBlockParser.parse(BufferUtil.EMPTY_BUFFER, 0);
- onHeaders(0, 0, false, metaData);
+ HeadersFrame frame = new HeadersFrame(getStreamId(), metaData, null, isEndStream());
+ if (!rateControlOnEvent(frame))
+ connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_headers_frame_rate");
+ else
+ onHeaders(frame);
}
else
{
headerBlockFragments.setStreamId(getStreamId());
headerBlockFragments.setEndStream(isEndStream());
- if (hasFlag(Flags.PRIORITY))
- connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_headers_priority_frame");
}
}
@@ -179,7 +185,15 @@ public class HeadersBodyParser extends BodyParser
state = State.PADDING;
loop = paddingLength == 0;
if (metaData != HeaderBlockParser.STREAM_FAILURE)
+ {
onHeaders(parentStreamId, weight, exclusive, metaData);
+ }
+ else
+ {
+ HeadersFrame frame = new HeadersFrame(getStreamId(), metaData, null, isEndStream());
+ if (!rateControlOnEvent(frame))
+ connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_headers_frame_rate");
+ }
}
}
else
@@ -230,6 +244,11 @@ public class HeadersBodyParser extends BodyParser
if (hasFlag(Flags.PRIORITY))
priorityFrame = new PriorityFrame(getStreamId(), parentStreamId, weight, exclusive);
HeadersFrame frame = new HeadersFrame(getStreamId(), metaData, priorityFrame, isEndStream());
+ onHeaders(frame);
+ }
+
+ private void onHeaders(HeadersFrame frame)
+ {
notifyHeaders(frame);
}
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 15920bebcdd..7545b6fe159 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
@@ -54,18 +54,22 @@ public class Parser
private final HpackDecoder hpackDecoder;
private final BodyParser[] bodyParsers;
private UnknownBodyParser unknownBodyParser;
- private int maxFrameLength;
+ private int maxFrameLength = Frame.DEFAULT_MAX_LENGTH;
private int maxSettingsKeys = SettingsFrame.DEFAULT_MAX_KEYS;
private boolean continuation;
private State state = State.HEADER;
public Parser(ByteBufferPool byteBufferPool, Listener listener, int maxDynamicTableSize, int maxHeaderSize)
+ {
+ this(byteBufferPool, listener, maxDynamicTableSize, maxHeaderSize, RateControl.NO_RATE_CONTROL);
+ }
+
+ public Parser(ByteBufferPool byteBufferPool, Listener listener, int maxDynamicTableSize, int maxHeaderSize, RateControl rateControl)
{
this.byteBufferPool = byteBufferPool;
this.listener = listener;
- this.headerParser = new HeaderParser();
+ this.headerParser = new HeaderParser(rateControl == null ? RateControl.NO_RATE_CONTROL : rateControl);
this.hpackDecoder = new HpackDecoder(maxDynamicTableSize, maxHeaderSize);
- this.maxFrameLength = Frame.DEFAULT_MAX_LENGTH;
this.bodyParsers = new BodyParser[FrameType.values().length];
}
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/PingBodyParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/PingBodyParser.java
index 8cee350e91e..e56e573236e 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/PingBodyParser.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/PingBodyParser.java
@@ -66,7 +66,7 @@ public class PingBodyParser extends BodyParser
if (buffer.remaining() >= 8)
{
buffer.get(payload);
- return onPing(payload);
+ return onPing(buffer, payload);
}
else
{
@@ -80,7 +80,7 @@ public class PingBodyParser extends BodyParser
payload[8 - cursor] = buffer.get();
--cursor;
if (cursor == 0)
- return onPing(payload);
+ return onPing(buffer, payload);
break;
}
default:
@@ -92,9 +92,11 @@ public class PingBodyParser extends BodyParser
return false;
}
- private boolean onPing(byte[] payload)
+ private boolean onPing(ByteBuffer buffer, byte[] payload)
{
PingFrame frame = new PingFrame(payload, hasFlag(Flags.ACK));
+ if (!rateControlOnEvent(frame))
+ return connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_ping_frame_rate");
reset();
notifyPing(frame);
return true;
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 a9d11398987..914e9387b30 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
@@ -103,7 +103,7 @@ public class PriorityBodyParser extends BodyParser
if (getStreamId() == parentStreamId)
return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_priority_frame");
int weight = (buffer.get() & 0xFF) + 1;
- return onPriority(parentStreamId, weight, exclusive);
+ return onPriority(buffer, parentStreamId, weight, exclusive);
}
default:
{
@@ -114,9 +114,11 @@ public class PriorityBodyParser extends BodyParser
return false;
}
- private boolean onPriority(int parentStreamId, int weight, boolean exclusive)
+ private boolean onPriority(ByteBuffer buffer, int parentStreamId, int weight, boolean exclusive)
{
PriorityFrame frame = new PriorityFrame(getStreamId(), parentStreamId, weight, exclusive);
+ if (!rateControlOnEvent(frame))
+ return connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_priority_frame_rate");
reset();
notifyPriority(frame);
return true;
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/RateControl.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/RateControl.java
new file mode 100644
index 00000000000..be8f82a7cf2
--- /dev/null
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/RateControl.java
@@ -0,0 +1,39 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2019 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;
+
+/**
+ * Controls rate of events via {@link #onEvent(Object)}.
+ */
+public interface RateControl
+{
+ public static final RateControl NO_RATE_CONTROL = event -> true;
+
+ /**
+ *
Applications should call this method when they want to signal an
+ * event that is subject to rate control.
+ * Implementations should return true if the event does not exceed
+ * the desired rate, or false to signal that the event exceeded the
+ * desired rate.
+ *
+ * @param event the event subject to rate control.
+ * @return true IFF the rate is within limits
+ */
+ public boolean onEvent(Object event);
+}
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 49d40f8fd58..067e263cd33 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
@@ -37,9 +37,9 @@ public class ServerParser extends Parser
private State state = State.PREFACE;
private boolean notifyPreface = true;
- public ServerParser(ByteBufferPool byteBufferPool, Listener listener, int maxDynamicTableSize, int maxHeaderSize)
+ public ServerParser(ByteBufferPool byteBufferPool, Listener listener, int maxDynamicTableSize, int maxHeaderSize, RateControl rateControl)
{
- super(byteBufferPool, listener, maxDynamicTableSize, maxHeaderSize);
+ super(byteBufferPool, listener, maxDynamicTableSize, maxHeaderSize, rateControl);
this.listener = listener;
this.prefaceParser = new PrefaceParser(listener);
}
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 741dd95981d..b58fa8a8b51 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
@@ -19,6 +19,7 @@
package org.eclipse.jetty.http2.parser;
import java.nio.ByteBuffer;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
@@ -72,7 +73,12 @@ public class SettingsBodyParser extends BodyParser
@Override
protected void emptyBody(ByteBuffer buffer)
{
- onSettings(buffer, new HashMap<>());
+ boolean isReply = hasFlag(Flags.ACK);
+ SettingsFrame frame = new SettingsFrame(Collections.emptyMap(), isReply);
+ if (!isReply && !rateControlOnEvent(frame))
+ connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_settings_frame_rate");
+ else
+ onSettings(frame);
}
@Override
@@ -200,6 +206,11 @@ public class SettingsBodyParser extends BodyParser
return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_settings_max_frame_size");
SettingsFrame frame = new SettingsFrame(settings, hasFlag(Flags.ACK));
+ return onSettings(frame);
+ }
+
+ private boolean onSettings(SettingsFrame frame)
+ {
reset();
notifySettings(frame);
return true;
@@ -207,40 +218,25 @@ public class SettingsBodyParser extends BodyParser
public static SettingsFrame parseBody(final ByteBuffer buffer)
{
- final int bodyLength = buffer.remaining();
- final AtomicReference frameRef = new AtomicReference<>();
- SettingsBodyParser parser = new SettingsBodyParser(null, null)
+ AtomicReference frameRef = new AtomicReference<>();
+ SettingsBodyParser parser = new SettingsBodyParser(new HeaderParser(RateControl.NO_RATE_CONTROL), new Parser.Listener.Adapter()
{
@Override
- protected int getStreamId()
+ public void onSettings(SettingsFrame frame)
{
- return 0;
+ frameRef.set(frame);
}
@Override
- protected int getBodyLength()
- {
- return bodyLength;
- }
-
- @Override
- protected boolean onSettings(ByteBuffer buffer, Map settings)
- {
- frameRef.set(new SettingsFrame(settings, false));
- return true;
- }
-
- @Override
- protected boolean connectionFailure(ByteBuffer buffer, int error, String reason)
+ public void onConnectionFailure(int error, String reason)
{
frameRef.set(null);
- return false;
}
- };
- if (bodyLength == 0)
- parser.emptyBody(buffer);
- else
+ });
+ if (buffer.hasRemaining())
parser.parse(buffer);
+ else
+ parser.emptyBody(buffer);
return frameRef.get();
}
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
index dbb29bfede2..90293fee9db 100644
--- 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
@@ -20,6 +20,9 @@ package org.eclipse.jetty.http2.parser;
import java.nio.ByteBuffer;
+import org.eclipse.jetty.http2.ErrorCode;
+import org.eclipse.jetty.http2.frames.UnknownFrame;
+
public class UnknownBodyParser extends BodyParser
{
private int cursor;
@@ -34,7 +37,11 @@ public class UnknownBodyParser extends BodyParser
{
int length = cursor == 0 ? getBodyLength() : cursor;
cursor = consume(buffer, length);
- return cursor == 0;
+ boolean parsed = cursor == 0;
+ if (parsed && !rateControlOnEvent(new UnknownFrame(getFrameType())))
+ return connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_unknown_frame_rate");
+
+ return parsed;
}
private int consume(ByteBuffer buffer, int length)
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/WindowRateControl.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/WindowRateControl.java
new file mode 100644
index 00000000000..00a2c769737
--- /dev/null
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/WindowRateControl.java
@@ -0,0 +1,65 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2019 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.time.Duration;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * An implementation of {@link RateControl} that limits the number of
+ * events within a time period.
+ * Events are kept in a queue and for each event the queue is first
+ * drained of the old events outside the time window, and then the new
+ * event is added to the queue. The size of the queue is maintained
+ * separately in an AtomicInteger and if it exceeds the max
+ * number of events then {@link #onEvent(Object)} returns {@code false}.
+ */
+public class WindowRateControl implements RateControl
+{
+ private final Queue events = new ConcurrentLinkedQueue<>();
+ private final AtomicInteger size = new AtomicInteger();
+ private final int maxEvents;
+ private final long window;
+
+ public WindowRateControl(int maxEvents, Duration window)
+ {
+ this.maxEvents = maxEvents;
+ this.window = window.toNanos();
+ }
+
+ @Override
+ public boolean onEvent(Object event)
+ {
+ long now = System.nanoTime();
+ while (true)
+ {
+ Long time = events.peek();
+ if (time == null)
+ break;
+ if (now < time)
+ break;
+ if (events.remove(time))
+ size.decrementAndGet();
+ }
+ events.add(now + window);
+ return size.incrementAndGet() <= maxEvents;
+ }
+}
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/WindowUpdateBodyParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/WindowUpdateBodyParser.java
index 78505540939..2c3ec6b736c 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/WindowUpdateBodyParser.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/WindowUpdateBodyParser.java
@@ -61,7 +61,7 @@ public class WindowUpdateBodyParser extends BodyParser
if (buffer.remaining() >= 4)
{
windowDelta = buffer.getInt() & 0x7F_FF_FF_FF;
- return onWindowUpdate(windowDelta);
+ return onWindowUpdate(buffer, windowDelta);
}
else
{
@@ -78,7 +78,7 @@ public class WindowUpdateBodyParser extends BodyParser
if (cursor == 0)
{
windowDelta &= 0x7F_FF_FF_FF;
- return onWindowUpdate(windowDelta);
+ return onWindowUpdate(buffer, windowDelta);
}
break;
}
@@ -91,9 +91,17 @@ public class WindowUpdateBodyParser extends BodyParser
return false;
}
- private boolean onWindowUpdate(int windowDelta)
+ private boolean onWindowUpdate(ByteBuffer buffer, int windowDelta)
{
- WindowUpdateFrame frame = new WindowUpdateFrame(getStreamId(), windowDelta);
+ int streamId = getStreamId();
+ if (windowDelta == 0)
+ {
+ if (streamId == 0)
+ return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_window_update_frame");
+ else
+ return streamFailure(streamId, ErrorCode.PROTOCOL_ERROR.code, "invalid_window_update_frame");
+ }
+ WindowUpdateFrame frame = new WindowUpdateFrame(streamId, windowDelta);
reset();
notifyWindowUpdate(frame);
return true;
diff --git a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/FrameFloodTest.java b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/FrameFloodTest.java
new file mode 100644
index 00000000000..04685dcb743
--- /dev/null
+++ b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/FrameFloodTest.java
@@ -0,0 +1,161 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2019 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.time.Duration;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.UnaryOperator;
+
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.http.MetaData;
+import org.eclipse.jetty.http2.Flags;
+import org.eclipse.jetty.http2.hpack.HpackEncoder;
+import org.eclipse.jetty.http2.parser.Parser;
+import org.eclipse.jetty.http2.parser.WindowRateControl;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.MappedByteBufferPool;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.lessThan;
+
+public class FrameFloodTest
+{
+ private final ByteBufferPool byteBufferPool = new MappedByteBufferPool();
+
+ // Frame structure:
+ // | Len0 | Len1 | Len2 | Type | Flags | StreamID0 |StreamID1 |StreamID2 |StreamID3 | Payload... |
+
+ private byte[] frameFrom(int length, int frameType, int flags, int streamId, byte[] payload)
+ {
+ byte[] result = new byte[3 + 1 + 1 + 4 + payload.length];
+ result[0] = (byte)((length >>> 16) & 0xFF);
+ result[1] = (byte)((length >>> 8) & 0xFF);
+ result[2] = (byte)(length & 0xFF);
+ result[3] = (byte)frameType;
+ result[4] = (byte)flags;
+ result[5] = (byte)((streamId >>> 24) & 0xFF);
+ result[6] = (byte)((streamId >>> 16) & 0xFF);
+ result[7] = (byte)((streamId >>> 8) & 0xFF);
+ result[8] = (byte)(streamId & 0xFF);
+ System.arraycopy(payload, 0, result, 9, payload.length);
+ return result;
+ }
+
+ @Test
+ public void testDataFrameFlood()
+ {
+ byte[] payload = new byte[0];
+ testFrameFlood(null, frameFrom(payload.length, FrameType.DATA.getType(), 0, 13, payload));
+ }
+
+ @Test
+ public void testHeadersFrameFlood()
+ {
+ byte[] payload = new byte[0];
+ testFrameFlood(null, frameFrom(payload.length, FrameType.HEADERS.getType(), Flags.END_HEADERS, 13, payload));
+ }
+
+ @Test
+ public void testInvalidHeadersFrameFlood()
+ {
+ // Invalid MetaData (no method, no scheme, etc).
+ MetaData.Request metadata = new MetaData.Request(null, (String)null, null, null, HttpVersion.HTTP_2, null, -1);
+ HpackEncoder encoder = new HpackEncoder();
+ ByteBuffer buffer = ByteBuffer.allocate(1024);
+ encoder.encode(buffer, metadata);
+ buffer.flip();
+ byte[] payload = new byte[buffer.remaining()];
+ buffer.get(payload);
+ testFrameFlood(null, frameFrom(payload.length, FrameType.HEADERS.getType(), Flags.END_HEADERS, 13, payload));
+ }
+
+ @Test
+ public void testPriorityFrameFlood()
+ {
+ byte[] payload = new byte[]{0, 0, 0, 7, 0};
+ testFrameFlood(null, frameFrom(payload.length, FrameType.PRIORITY.getType(), 0, 13, payload));
+ }
+
+ @Test
+ public void testSettingsFrameFlood()
+ {
+ byte[] payload = new byte[0];
+ testFrameFlood(null, frameFrom(payload.length, FrameType.SETTINGS.getType(), 0, 0, payload));
+ }
+
+ @Test
+ public void testPingFrameFlood()
+ {
+ byte[] payload = {0, 0, 0, 0, 0, 0, 0, 0};
+ testFrameFlood(null, frameFrom(payload.length, FrameType.PING.getType(), 0, 0, payload));
+ }
+
+ @Test
+ public void testContinuationFrameFlood()
+ {
+ int streamId = 13;
+ byte[] headersPayload = new byte[0];
+ byte[] headersBytes = frameFrom(headersPayload.length, FrameType.HEADERS.getType(), 0, streamId, headersPayload);
+ byte[] continuationPayload = new byte[0];
+ testFrameFlood(headersBytes, frameFrom(continuationPayload.length, FrameType.CONTINUATION.getType(), 0, streamId, continuationPayload));
+ }
+
+ @Test
+ public void testUnknownFrameFlood()
+ {
+ byte[] payload = {0, 0, 0, 0};
+ testFrameFlood(null, frameFrom(payload.length, 64, 0, 0, payload));
+ }
+
+ private void testFrameFlood(byte[] preamble, byte[] bytes)
+ {
+ AtomicBoolean failed = new AtomicBoolean();
+ Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter()
+ {
+ @Override
+ public void onConnectionFailure(int error, String reason)
+ {
+ failed.set(true);
+ }
+ }, 4096, 8192, new WindowRateControl(8, Duration.ofSeconds(1)));
+ parser.init(UnaryOperator.identity());
+
+ if (preamble != null)
+ {
+ ByteBuffer buffer = ByteBuffer.wrap(preamble);
+ while (buffer.hasRemaining())
+ {
+ parser.parse(buffer);
+ }
+ }
+
+ int count = 0;
+ while (!failed.get())
+ {
+ ByteBuffer buffer = ByteBuffer.wrap(bytes);
+ while (buffer.hasRemaining())
+ {
+ parser.parse(buffer);
+ }
+ assertThat("too many frames allowed", ++count, lessThan(1024));
+ }
+ }
+}
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 34c47201932..bb1d3ede5fb 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
@@ -22,6 +22,7 @@ import java.nio.ByteBuffer;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpTokens;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.hpack.HpackContext.Entry;
import org.eclipse.jetty.util.TypeUtil;
@@ -175,14 +176,35 @@ public class HpackDecoder
name = Huffman.decode(buffer, length);
else
name = toASCIIString(buffer, length);
- for (int i = 0; i < name.length(); i++)
+ check:
+ for (int i = name.length(); i-- > 0;)
{
char c = name.charAt(i);
- if (c >= 'A' && c <= 'Z')
+ if (c > 0xff)
{
- _builder.streamException("Uppercase header name %s", name);
+ _builder.streamException("Illegal header name %s", name);
break;
}
+ HttpTokens.Token token = HttpTokens.TOKENS[0xFF & c];
+ switch (token.getType())
+ {
+ case ALPHA:
+ if (c >= 'A' && c <= 'Z')
+ {
+ _builder.streamException("Uppercase header name %s", name);
+ break check;
+ }
+ break;
+
+ case COLON:
+ case TCHAR:
+ case DIGIT:
+ break;
+
+ default:
+ _builder.streamException("Illegal header name %s", name);
+ break check;
+ }
}
header = HttpHeader.CACHE.get(name);
}
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 f10f792fb44..ce1f8b36cbe 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
@@ -74,11 +74,13 @@ public class MetaDataBuilder
{
HttpHeader header = field.getHeader();
String name = field.getName();
+ if (name == null || name.length() == 0)
+ throw new HpackException.SessionException("Header size 0");
String value = field.getValue();
int fieldSize = name.length() + (value == null ? 0 : value.length());
_size += fieldSize + 32;
if (_size > _maxSize)
- throw new HpackException.SessionException("Header Size %d > %d", _size, _maxSize);
+ throw new HpackException.SessionException("Header size %d > %d", _size, _maxSize);
if (field instanceof StaticTableHttpField)
{
@@ -113,7 +115,7 @@ public class MetaDataBuilder
{
case C_STATUS:
if (checkPseudoHeader(header, _status))
- _status = Integer.valueOf(field.getIntValue());
+ _status = field.getIntValue();
_response = true;
break;
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 3aa3da34e69..791ecd3d060 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
@@ -26,6 +26,7 @@ import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.hpack.HpackException.CompressionException;
+import org.eclipse.jetty.http2.hpack.HpackException.SessionException;
import org.eclipse.jetty.http2.hpack.HpackException.StreamException;
import org.eclipse.jetty.util.TypeUtil;
import org.hamcrest.Matchers;
@@ -43,6 +44,21 @@ import static org.junit.jupiter.api.Assertions.fail;
public class HpackDecoderTest
{
+ /*
+ 0 1 2 3 4 5 6 7
+ +---+---+---+---+---+---+---+---+
+ | 0 | 0 | 0 | 0 | 0 |
+ +---+---+-----------------------+
+ | H | Name Length (7+) |
+ +---+---------------------------+
+ | Name String (Length octets) |
+ +---+---------------------------+
+ | H | Value Length (7+) |
+ +---+---------------------------+
+ | Value String (Length octets) |
+ +-------------------------------+
+ */
+
@Test
public void testDecodeD_3() throws Exception
{
@@ -253,14 +269,14 @@ public class HpackDecoderTest
decoder.decode(buffer);
fail();
}
- catch (HpackException.SessionException e)
+ catch (SessionException e)
{
assertThat(e.getMessage(), Matchers.startsWith("Unknown index"));
}
}
/* 8.1.2.1. Pseudo-Header Fields */
- @Test()
+ @Test
public void test8_1_2_1_PsuedoHeaderFields() throws Exception
{
// 1:Sends a HEADERS frame that contains a unknown pseudo-header field
@@ -312,7 +328,7 @@ public class HpackDecoderTest
}
}
- @Test()
+ @Test
public void test8_1_2_2_ConnectionSpecificHeaderFields() throws Exception
{
MetaDataBuilder mdb;
@@ -349,7 +365,7 @@ public class HpackDecoderTest
assertNotNull(mdb.build());
}
- @Test()
+ @Test
public void test8_1_2_3_RequestPseudoHeaderFields() throws Exception
{
{
@@ -368,7 +384,7 @@ public class HpackDecoderTest
mdb.emit(new HttpField(HttpHeader.C_SCHEME, "http"));
mdb.emit(new HttpField(HttpHeader.C_AUTHORITY, "localhost:8080"));
mdb.emit(new HttpField(HttpHeader.C_PATH, ""));
- StreamException ex = assertThrows(StreamException.class, () -> mdb.build());
+ StreamException ex = assertThrows(StreamException.class, mdb::build);
assertThat(ex.getMessage(), Matchers.containsString("No Path"));
}
@@ -378,7 +394,7 @@ public class HpackDecoderTest
mdb.emit(new HttpField(HttpHeader.C_SCHEME, "http"));
mdb.emit(new HttpField(HttpHeader.C_AUTHORITY, "localhost:8080"));
mdb.emit(new HttpField(HttpHeader.C_PATH, "/"));
- StreamException ex = assertThrows(StreamException.class, () -> mdb.build());
+ StreamException ex = assertThrows(StreamException.class, mdb::build);
assertThat(ex.getMessage(), Matchers.containsString("No Method"));
}
@@ -388,7 +404,7 @@ public class HpackDecoderTest
mdb.emit(new HttpField(HttpHeader.C_METHOD, "GET"));
mdb.emit(new HttpField(HttpHeader.C_AUTHORITY, "localhost:8080"));
mdb.emit(new HttpField(HttpHeader.C_PATH, "/"));
- StreamException ex = assertThrows(StreamException.class, () -> mdb.build());
+ StreamException ex = assertThrows(StreamException.class, mdb::build);
assertThat(ex.getMessage(), Matchers.containsString("No Scheme"));
}
@@ -398,7 +414,7 @@ public class HpackDecoderTest
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"));
- StreamException ex = assertThrows(StreamException.class, () -> mdb.build());
+ StreamException ex = assertThrows(StreamException.class, mdb::build);
assertThat(ex.getMessage(), Matchers.containsString("No Path"));
}
@@ -410,7 +426,7 @@ public class HpackDecoderTest
mdb.emit(new HttpField(HttpHeader.C_SCHEME, "http"));
mdb.emit(new HttpField(HttpHeader.C_AUTHORITY, "localhost:8080"));
mdb.emit(new HttpField(HttpHeader.C_PATH, "/"));
- StreamException ex = assertThrows(StreamException.class, () -> mdb.build());
+ StreamException ex = assertThrows(StreamException.class, mdb::build);
assertThat(ex.getMessage(), Matchers.containsString("Duplicate"));
}
@@ -423,12 +439,12 @@ public class HpackDecoderTest
mdb.emit(new HttpField(HttpHeader.C_AUTHORITY, "localhost:8080"));
mdb.emit(new HttpField(HttpHeader.C_PATH, "/"));
- StreamException ex = assertThrows(StreamException.class, () -> mdb.build());
+ StreamException ex = assertThrows(StreamException.class, mdb::build);
assertThat(ex.getMessage(), Matchers.containsString("Duplicate"));
}
}
- @Test()
+ @Test
public void testHuffmanEncodedStandard() throws Exception
{
HpackDecoder decoder = new HpackDecoder(4096, 8192);
@@ -446,8 +462,8 @@ public class HpackDecoderTest
}
/* 5.2.1: Sends a Huffman-encoded string literal representation with padding longer than 7 bits */
- @Test()
- public void testHuffmanEncodedExtraPadding() throws Exception
+ @Test
+ public void testHuffmanEncodedExtraPadding()
{
HpackDecoder decoder = new HpackDecoder(4096, 8192);
@@ -458,8 +474,8 @@ public class HpackDecoderTest
}
/* 5.2.2: Sends a Huffman-encoded string literal representation padded by zero */
- @Test()
- public void testHuffmanEncodedZeroPadding() throws Exception
+ @Test
+ public void testHuffmanEncodedZeroPadding()
{
HpackDecoder decoder = new HpackDecoder(4096, 8192);
@@ -471,8 +487,8 @@ public class HpackDecoderTest
}
/* 5.2.3: Sends a Huffman-encoded string literal representation containing the EOS symbol */
- @Test()
- public void testHuffmanEncodedWithEOS() throws Exception
+ @Test
+ public void testHuffmanEncodedWithEOS()
{
HpackDecoder decoder = new HpackDecoder(4096, 8192);
@@ -483,8 +499,8 @@ public class HpackDecoderTest
assertThat(ex.getMessage(), Matchers.containsString("EOS in content"));
}
- @Test()
- public void testHuffmanEncodedOneIncompleteOctet() throws Exception
+ @Test
+ public void testHuffmanEncodedOneIncompleteOctet()
{
HpackDecoder decoder = new HpackDecoder(4096, 8192);
@@ -495,8 +511,8 @@ public class HpackDecoderTest
assertThat(ex.getMessage(), Matchers.containsString("Bad termination"));
}
- @Test()
- public void testHuffmanEncodedTwoIncompleteOctet() throws Exception
+ @Test
+ public void testHuffmanEncodedTwoIncompleteOctet()
{
HpackDecoder decoder = new HpackDecoder(4096, 8192);
@@ -506,4 +522,49 @@ public class HpackDecoderTest
CompressionException ex = assertThrows(CompressionException.class, () -> decoder.decode(buffer));
assertThat(ex.getMessage(), Matchers.containsString("Bad termination"));
}
+
+ @Test
+ public void testZeroLengthName()
+ {
+ HpackDecoder decoder = new HpackDecoder(4096, 8192);
+
+ String encoded = "00000130";
+ ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
+ SessionException ex = assertThrows(SessionException.class, () -> decoder.decode(buffer));
+ assertThat(ex.getMessage(), Matchers.containsString("Header size 0"));
+ }
+
+ @Test
+ public void testZeroLengthValue() throws Exception
+ {
+ HpackDecoder decoder = new HpackDecoder(4096, 8192);
+
+ String encoded = "00016800";
+ ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
+ MetaData metaData = decoder.decode(buffer);
+ assertThat(metaData.getFields().size(), is(1));
+ assertThat(metaData.getFields().get("h"), is(""));
+ }
+
+ @Test
+ public void testUpperCaseName()
+ {
+ HpackDecoder decoder = new HpackDecoder(4096, 8192);
+
+ String encoded = "0001480130";
+ ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
+ StreamException ex = assertThrows(StreamException.class, () -> decoder.decode(buffer));
+ assertThat(ex.getMessage(), Matchers.containsString("Uppercase header"));
+ }
+
+ @Test
+ public void testWhiteSpaceName()
+ {
+ HpackDecoder decoder = new HpackDecoder(4096, 8192);
+
+ String encoded = "0001200130";
+ ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
+ StreamException ex = assertThrows(StreamException.class, () -> decoder.decode(buffer));
+ assertThat(ex.getMessage(), Matchers.containsString("Illegal header"));
+ }
}
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 6e782a738b0..9304ae519c8 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
@@ -18,7 +18,6 @@
package org.eclipse.jetty.http2.client.http;
-import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
@@ -38,7 +37,6 @@ import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.UnaryOperator;
-import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -63,6 +61,7 @@ import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.frames.ResetFrame;
import org.eclipse.jetty.http2.frames.SettingsFrame;
import org.eclipse.jetty.http2.generator.Generator;
+import org.eclipse.jetty.http2.parser.RateControl;
import org.eclipse.jetty.http2.parser.ServerParser;
import org.eclipse.jetty.http2.server.RawHTTP2ServerConnectionFactory;
import org.eclipse.jetty.io.ByteBufferPool;
@@ -187,7 +186,7 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
start(new AbstractHandler()
{
@Override
- public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
{
baseRequest.setHandled(true);
HttpVersion version = HttpVersion.fromString(request.getProtocol());
@@ -313,7 +312,7 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
start(new AbstractHandler()
{
@Override
- public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
{
baseRequest.setHandled(true);
assertEquals(path, request.getRequestURI());
@@ -337,7 +336,7 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
start(new AbstractHandler()
{
@Override
- public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
{
baseRequest.setHandled(true);
assertEquals(path, request.getRequestURI());
@@ -501,7 +500,7 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
x.printStackTrace();
}
}
- }, 4096, 8192);
+ }, 4096, 8192, RateControl.NO_RATE_CONTROL);
parser.init(UnaryOperator.identity());
byte[] bytes = new byte[1024];
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 82312d11e17..7b5319efbc5 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
@@ -19,6 +19,7 @@
package org.eclipse.jetty.http2.server;
import java.io.IOException;
+import java.time.Duration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@@ -34,7 +35,9 @@ import org.eclipse.jetty.http2.api.server.ServerSessionListener;
import org.eclipse.jetty.http2.frames.Frame;
import org.eclipse.jetty.http2.frames.SettingsFrame;
import org.eclipse.jetty.http2.generator.Generator;
+import org.eclipse.jetty.http2.parser.RateControl;
import org.eclipse.jetty.http2.parser.ServerParser;
+import org.eclipse.jetty.http2.parser.WindowRateControl;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.server.AbstractConnectionFactory;
@@ -58,6 +61,7 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne
private int maxHeaderBlockFragment = 0;
private int maxFrameLength = Frame.DEFAULT_MAX_LENGTH;
private int maxSettingsKeys = SettingsFrame.DEFAULT_MAX_KEYS;
+ private RateControl rateControl = new WindowRateControl(20, Duration.ofSeconds(1));
private FlowControlStrategy.Factory flowControlStrategyFactory = () -> new BufferingFlowControlStrategy(0.5F);
private long streamIdleTimeout;
@@ -178,6 +182,16 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne
this.maxSettingsKeys = maxSettingsKeys;
}
+ public RateControl getRateControl()
+ {
+ return rateControl;
+ }
+
+ public void setRateControl(RateControl rateControl)
+ {
+ this.rateControl = rateControl;
+ }
+
/**
* @return -1
* @deprecated feature removed, no replacement
@@ -237,7 +251,7 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne
session.setInitialSessionRecvWindow(getInitialSessionRecvWindow());
session.setWriteThreshold(getHttpConfiguration().getOutputBufferSize());
- ServerParser parser = newServerParser(connector, session);
+ ServerParser parser = newServerParser(connector, session, getRateControl());
parser.setMaxFrameLength(getMaxFrameLength());
parser.setMaxSettingsKeys(getMaxSettingsKeys());
@@ -249,9 +263,9 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne
protected abstract ServerSessionListener newSessionListener(Connector connector, EndPoint endPoint);
- protected ServerParser newServerParser(Connector connector, ServerParser.Listener listener)
+ protected ServerParser newServerParser(Connector connector, ServerParser.Listener listener, RateControl rateControl)
{
- return new ServerParser(connector.getByteBufferPool(), listener, getMaxDynamicTableSize(), getHttpConfiguration().getRequestHeaderSize());
+ return new ServerParser(connector.getByteBufferPool(), listener, getMaxDynamicTableSize(), getHttpConfiguration().getRequestHeaderSize(), rateControl);
}
@ManagedObject("The container of HTTP/2 sessions")
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/MathUtils.java b/jetty-util/src/main/java/org/eclipse/jetty/util/MathUtils.java
new file mode 100644
index 00000000000..6c522299b21
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/MathUtils.java
@@ -0,0 +1,46 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2019 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;
+
+public class MathUtils
+{
+ private MathUtils()
+ {
+ }
+
+ /**
+ * Returns whether the sum of the arguments overflows an {@code int}.
+ *
+ * @param a the first value
+ * @param b the second value
+ * @return whether the sum of the arguments overflows an {@code int}
+ */
+ public static boolean sumOverflows(int a, int b)
+ {
+ try
+ {
+ Math.addExact(a, b);
+ return false;
+ }
+ catch (ArithmeticException x)
+ {
+ return true;
+ }
+ }
+}