Issue #3978 HTTP2 Vulnerabilities
Reduce the number of RateControl fields, instead using common field in HeaderParser. Avoid null checking rateControl by having a NO_RATE_CONTROL static HPack does not emit field with empty header name. Apply rate control to any header parsing issue resulting in session/stream failure Signed-off-by: Greg Wilkins <gregw@webtide.com>
This commit is contained in:
parent
47fb8f4dea
commit
5fc83c3d0c
|
@ -46,6 +46,7 @@ import org.eclipse.jetty.http2.frames.DataFrame;
|
||||||
import org.eclipse.jetty.http2.frames.GoAwayFrame;
|
import org.eclipse.jetty.http2.frames.GoAwayFrame;
|
||||||
import org.eclipse.jetty.http2.frames.HeadersFrame;
|
import org.eclipse.jetty.http2.frames.HeadersFrame;
|
||||||
import org.eclipse.jetty.http2.frames.SettingsFrame;
|
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.parser.ServerParser;
|
||||||
import org.eclipse.jetty.http2.server.RawHTTP2ServerConnectionFactory;
|
import org.eclipse.jetty.http2.server.RawHTTP2ServerConnectionFactory;
|
||||||
import org.eclipse.jetty.server.Connector;
|
import org.eclipse.jetty.server.Connector;
|
||||||
|
@ -747,7 +748,7 @@ public class HTTP2Test extends AbstractTest
|
||||||
RawHTTP2ServerConnectionFactory connectionFactory = new RawHTTP2ServerConnectionFactory(new HttpConfiguration(), serverListener)
|
RawHTTP2ServerConnectionFactory connectionFactory = new RawHTTP2ServerConnectionFactory(new HttpConfiguration(), serverListener)
|
||||||
{
|
{
|
||||||
@Override
|
@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)
|
return super.newServerParser(connector, new ServerParser.Listener.Wrapper(listener)
|
||||||
{
|
{
|
||||||
|
@ -757,7 +758,7 @@ public class HTTP2Test extends AbstractTest
|
||||||
super.onGoAway(frame);
|
super.onGoAway(frame);
|
||||||
goAwayLatch.countDown();
|
goAwayLatch.countDown();
|
||||||
}
|
}
|
||||||
});
|
}, rateControl);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
prepareServer(connectionFactory);
|
prepareServer(connectionFactory);
|
||||||
|
|
|
@ -245,4 +245,9 @@ public abstract class BodyParser
|
||||||
LOG.info("Failure while notifying listener " + listener, x);
|
LOG.info("Failure while notifying listener " + listener, x);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected boolean rateControlOnEvent(Object o)
|
||||||
|
{
|
||||||
|
return headerParser.getRateControl().onEvent(o);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,16 +30,14 @@ public class ContinuationBodyParser extends BodyParser
|
||||||
{
|
{
|
||||||
private final HeaderBlockParser headerBlockParser;
|
private final HeaderBlockParser headerBlockParser;
|
||||||
private final HeaderBlockFragments headerBlockFragments;
|
private final HeaderBlockFragments headerBlockFragments;
|
||||||
private final RateControl rateControl;
|
|
||||||
private State state = State.PREPARE;
|
private State state = State.PREPARE;
|
||||||
private int length;
|
private int length;
|
||||||
|
|
||||||
public ContinuationBodyParser(HeaderParser headerParser, Parser.Listener listener, HeaderBlockParser headerBlockParser, HeaderBlockFragments headerBlockFragments, RateControl rateControl)
|
public ContinuationBodyParser(HeaderParser headerParser, Parser.Listener listener, HeaderBlockParser headerBlockParser, HeaderBlockFragments headerBlockFragments)
|
||||||
{
|
{
|
||||||
super(headerParser, listener);
|
super(headerParser, listener);
|
||||||
this.headerBlockParser = headerBlockParser;
|
this.headerBlockParser = headerBlockParser;
|
||||||
this.headerBlockFragments = headerBlockFragments;
|
this.headerBlockFragments = headerBlockFragments;
|
||||||
this.rateControl = rateControl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -52,7 +50,7 @@ public class ContinuationBodyParser extends BodyParser
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ContinuationFrame frame = new ContinuationFrame(getStreamId(), hasFlag(Flags.END_HEADERS));
|
ContinuationFrame frame = new ContinuationFrame(getStreamId(), hasFlag(Flags.END_HEADERS));
|
||||||
if (rateControl != null && !rateControl.onEvent(frame))
|
if (!rateControlOnEvent(frame))
|
||||||
connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_continuation_frame_rate");
|
connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_continuation_frame_rate");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,16 +26,14 @@ import org.eclipse.jetty.util.BufferUtil;
|
||||||
|
|
||||||
public class DataBodyParser extends BodyParser
|
public class DataBodyParser extends BodyParser
|
||||||
{
|
{
|
||||||
private final RateControl rateControl;
|
|
||||||
private State state = State.PREPARE;
|
private State state = State.PREPARE;
|
||||||
private int padding;
|
private int padding;
|
||||||
private int paddingLength;
|
private int paddingLength;
|
||||||
private int length;
|
private int length;
|
||||||
|
|
||||||
public DataBodyParser(HeaderParser headerParser, Parser.Listener listener, RateControl rateControl)
|
public DataBodyParser(HeaderParser headerParser, Parser.Listener listener)
|
||||||
{
|
{
|
||||||
super(headerParser, listener);
|
super(headerParser, listener);
|
||||||
this.rateControl = rateControl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reset()
|
private void reset()
|
||||||
|
@ -56,7 +54,7 @@ public class DataBodyParser extends BodyParser
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DataFrame frame = new DataFrame(getStreamId(), BufferUtil.EMPTY_BUFFER, isEndStream());
|
DataFrame frame = new DataFrame(getStreamId(), BufferUtil.EMPTY_BUFFER, isEndStream());
|
||||||
if (!isEndStream() && rateControl != null && !rateControl.onEvent(frame))
|
if (!isEndStream() && !rateControlOnEvent(frame))
|
||||||
connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_data_frame_rate");
|
connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_data_frame_rate");
|
||||||
else
|
else
|
||||||
onData(frame);
|
onData(frame);
|
||||||
|
|
|
@ -103,6 +103,11 @@ public class HeaderBlockParser
|
||||||
{
|
{
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug(x);
|
LOG.debug(x);
|
||||||
|
if (!headerParser.getRateControl().onEvent(x))
|
||||||
|
{
|
||||||
|
notifier.connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_header_frame_rate");
|
||||||
|
return SESSION_FAILURE;
|
||||||
|
}
|
||||||
notifier.streamFailure(headerParser.getStreamId(), ErrorCode.PROTOCOL_ERROR.code, "invalid_hpack_block");
|
notifier.streamFailure(headerParser.getStreamId(), ErrorCode.PROTOCOL_ERROR.code, "invalid_hpack_block");
|
||||||
return STREAM_FAILURE;
|
return STREAM_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ import org.eclipse.jetty.http2.frames.FrameType;
|
||||||
*/
|
*/
|
||||||
public class HeaderParser
|
public class HeaderParser
|
||||||
{
|
{
|
||||||
|
private final RateControl rateControl;
|
||||||
private State state = State.LENGTH;
|
private State state = State.LENGTH;
|
||||||
private int cursor;
|
private int cursor;
|
||||||
|
|
||||||
|
@ -38,6 +39,16 @@ public class HeaderParser
|
||||||
private int flags;
|
private int flags;
|
||||||
private int streamId;
|
private int streamId;
|
||||||
|
|
||||||
|
HeaderParser(RateControl rateControl)
|
||||||
|
{
|
||||||
|
this.rateControl = rateControl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RateControl getRateControl()
|
||||||
|
{
|
||||||
|
return rateControl;
|
||||||
|
}
|
||||||
|
|
||||||
protected void reset()
|
protected void reset()
|
||||||
{
|
{
|
||||||
state = State.LENGTH;
|
state = State.LENGTH;
|
||||||
|
|
|
@ -32,7 +32,6 @@ public class HeadersBodyParser extends BodyParser
|
||||||
{
|
{
|
||||||
private final HeaderBlockParser headerBlockParser;
|
private final HeaderBlockParser headerBlockParser;
|
||||||
private final HeaderBlockFragments headerBlockFragments;
|
private final HeaderBlockFragments headerBlockFragments;
|
||||||
private final RateControl rateControl;
|
|
||||||
private State state = State.PREPARE;
|
private State state = State.PREPARE;
|
||||||
private int cursor;
|
private int cursor;
|
||||||
private int length;
|
private int length;
|
||||||
|
@ -41,12 +40,11 @@ public class HeadersBodyParser extends BodyParser
|
||||||
private int parentStreamId;
|
private int parentStreamId;
|
||||||
private int weight;
|
private int weight;
|
||||||
|
|
||||||
public HeadersBodyParser(HeaderParser headerParser, Parser.Listener listener, HeaderBlockParser headerBlockParser, HeaderBlockFragments headerBlockFragments, RateControl rateControl)
|
public HeadersBodyParser(HeaderParser headerParser, Parser.Listener listener, HeaderBlockParser headerBlockParser, HeaderBlockFragments headerBlockFragments)
|
||||||
{
|
{
|
||||||
super(headerParser, listener);
|
super(headerParser, listener);
|
||||||
this.headerBlockParser = headerBlockParser;
|
this.headerBlockParser = headerBlockParser;
|
||||||
this.headerBlockFragments = headerBlockFragments;
|
this.headerBlockFragments = headerBlockFragments;
|
||||||
this.rateControl = rateControl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reset()
|
private void reset()
|
||||||
|
@ -71,7 +69,7 @@ public class HeadersBodyParser extends BodyParser
|
||||||
{
|
{
|
||||||
MetaData metaData = headerBlockParser.parse(BufferUtil.EMPTY_BUFFER, 0);
|
MetaData metaData = headerBlockParser.parse(BufferUtil.EMPTY_BUFFER, 0);
|
||||||
HeadersFrame frame = new HeadersFrame(getStreamId(), metaData, null, isEndStream());
|
HeadersFrame frame = new HeadersFrame(getStreamId(), metaData, null, isEndStream());
|
||||||
if (rateControl != null && !rateControl.onEvent(frame))
|
if (!rateControlOnEvent(frame))
|
||||||
connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_headers_frame_rate");
|
connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_headers_frame_rate");
|
||||||
else
|
else
|
||||||
onHeaders(frame);
|
onHeaders(frame);
|
||||||
|
@ -193,7 +191,7 @@ public class HeadersBodyParser extends BodyParser
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
HeadersFrame frame = new HeadersFrame(getStreamId(), metaData, null, isEndStream());
|
HeadersFrame frame = new HeadersFrame(getStreamId(), metaData, null, isEndStream());
|
||||||
if (rateControl != null && !rateControl.onEvent(frame))
|
if (!rateControlOnEvent(frame))
|
||||||
connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_headers_frame_rate");
|
connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_headers_frame_rate");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,15 +56,19 @@ public class Parser
|
||||||
private UnknownBodyParser unknownBodyParser;
|
private UnknownBodyParser unknownBodyParser;
|
||||||
private int maxFrameLength = Frame.DEFAULT_MAX_LENGTH;
|
private int maxFrameLength = Frame.DEFAULT_MAX_LENGTH;
|
||||||
private int maxSettingsKeys = SettingsFrame.DEFAULT_MAX_KEYS;
|
private int maxSettingsKeys = SettingsFrame.DEFAULT_MAX_KEYS;
|
||||||
private RateControl rateControl;
|
|
||||||
private boolean continuation;
|
private boolean continuation;
|
||||||
private State state = State.HEADER;
|
private State state = State.HEADER;
|
||||||
|
|
||||||
public Parser(ByteBufferPool byteBufferPool, Listener listener, int maxDynamicTableSize, int maxHeaderSize)
|
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.byteBufferPool = byteBufferPool;
|
||||||
this.listener = listener;
|
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.hpackDecoder = new HpackDecoder(maxDynamicTableSize, maxHeaderSize);
|
||||||
this.bodyParsers = new BodyParser[FrameType.values().length];
|
this.bodyParsers = new BodyParser[FrameType.values().length];
|
||||||
}
|
}
|
||||||
|
@ -73,19 +77,19 @@ public class Parser
|
||||||
{
|
{
|
||||||
Listener listener = wrapper.apply(this.listener);
|
Listener listener = wrapper.apply(this.listener);
|
||||||
RateControl rateControl = getRateControl();
|
RateControl rateControl = getRateControl();
|
||||||
unknownBodyParser = new UnknownBodyParser(headerParser, listener, rateControl);
|
unknownBodyParser = new UnknownBodyParser(headerParser, listener);
|
||||||
HeaderBlockParser headerBlockParser = new HeaderBlockParser(headerParser, byteBufferPool, hpackDecoder, unknownBodyParser);
|
HeaderBlockParser headerBlockParser = new HeaderBlockParser(headerParser, byteBufferPool, hpackDecoder, unknownBodyParser);
|
||||||
HeaderBlockFragments headerBlockFragments = new HeaderBlockFragments();
|
HeaderBlockFragments headerBlockFragments = new HeaderBlockFragments();
|
||||||
bodyParsers[FrameType.DATA.getType()] = new DataBodyParser(headerParser, listener, rateControl);
|
bodyParsers[FrameType.DATA.getType()] = new DataBodyParser(headerParser, listener);
|
||||||
bodyParsers[FrameType.HEADERS.getType()] = new HeadersBodyParser(headerParser, listener, headerBlockParser, headerBlockFragments, rateControl);
|
bodyParsers[FrameType.HEADERS.getType()] = new HeadersBodyParser(headerParser, listener, headerBlockParser, headerBlockFragments);
|
||||||
bodyParsers[FrameType.PRIORITY.getType()] = new PriorityBodyParser(headerParser, listener, rateControl);
|
bodyParsers[FrameType.PRIORITY.getType()] = new PriorityBodyParser(headerParser, listener);
|
||||||
bodyParsers[FrameType.RST_STREAM.getType()] = new ResetBodyParser(headerParser, listener);
|
bodyParsers[FrameType.RST_STREAM.getType()] = new ResetBodyParser(headerParser, listener);
|
||||||
bodyParsers[FrameType.SETTINGS.getType()] = new SettingsBodyParser(headerParser, listener, getMaxSettingsKeys(), rateControl);
|
bodyParsers[FrameType.SETTINGS.getType()] = new SettingsBodyParser(headerParser, listener, getMaxSettingsKeys());
|
||||||
bodyParsers[FrameType.PUSH_PROMISE.getType()] = new PushPromiseBodyParser(headerParser, listener, headerBlockParser);
|
bodyParsers[FrameType.PUSH_PROMISE.getType()] = new PushPromiseBodyParser(headerParser, listener, headerBlockParser);
|
||||||
bodyParsers[FrameType.PING.getType()] = new PingBodyParser(headerParser, listener, rateControl);
|
bodyParsers[FrameType.PING.getType()] = new PingBodyParser(headerParser, listener);
|
||||||
bodyParsers[FrameType.GO_AWAY.getType()] = new GoAwayBodyParser(headerParser, listener);
|
bodyParsers[FrameType.GO_AWAY.getType()] = new GoAwayBodyParser(headerParser, listener);
|
||||||
bodyParsers[FrameType.WINDOW_UPDATE.getType()] = new WindowUpdateBodyParser(headerParser, listener);
|
bodyParsers[FrameType.WINDOW_UPDATE.getType()] = new WindowUpdateBodyParser(headerParser, listener);
|
||||||
bodyParsers[FrameType.CONTINUATION.getType()] = new ContinuationBodyParser(headerParser, listener, headerBlockParser, headerBlockFragments, rateControl);
|
bodyParsers[FrameType.CONTINUATION.getType()] = new ContinuationBodyParser(headerParser, listener, headerBlockParser, headerBlockFragments);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reset()
|
private void reset()
|
||||||
|
@ -238,12 +242,7 @@ public class Parser
|
||||||
|
|
||||||
public RateControl getRateControl()
|
public RateControl getRateControl()
|
||||||
{
|
{
|
||||||
return rateControl;
|
return headerParser.getRateControl();
|
||||||
}
|
|
||||||
|
|
||||||
public void setRateControl(RateControl rateControl)
|
|
||||||
{
|
|
||||||
this.rateControl = rateControl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void notifyConnectionFailure(int error, String reason)
|
protected void notifyConnectionFailure(int error, String reason)
|
||||||
|
|
|
@ -26,15 +26,13 @@ import org.eclipse.jetty.http2.frames.PingFrame;
|
||||||
|
|
||||||
public class PingBodyParser extends BodyParser
|
public class PingBodyParser extends BodyParser
|
||||||
{
|
{
|
||||||
private final RateControl rateControl;
|
|
||||||
private State state = State.PREPARE;
|
private State state = State.PREPARE;
|
||||||
private int cursor;
|
private int cursor;
|
||||||
private byte[] payload;
|
private byte[] payload;
|
||||||
|
|
||||||
public PingBodyParser(HeaderParser headerParser, Parser.Listener listener, RateControl rateControl)
|
public PingBodyParser(HeaderParser headerParser, Parser.Listener listener)
|
||||||
{
|
{
|
||||||
super(headerParser, listener);
|
super(headerParser, listener);
|
||||||
this.rateControl = rateControl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reset()
|
private void reset()
|
||||||
|
@ -97,7 +95,7 @@ public class PingBodyParser extends BodyParser
|
||||||
private boolean onPing(ByteBuffer buffer, byte[] payload)
|
private boolean onPing(ByteBuffer buffer, byte[] payload)
|
||||||
{
|
{
|
||||||
PingFrame frame = new PingFrame(payload, hasFlag(Flags.ACK));
|
PingFrame frame = new PingFrame(payload, hasFlag(Flags.ACK));
|
||||||
if (rateControl != null && !rateControl.onEvent(frame))
|
if (!rateControlOnEvent(frame))
|
||||||
return connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_ping_frame_rate");
|
return connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_ping_frame_rate");
|
||||||
reset();
|
reset();
|
||||||
notifyPing(frame);
|
notifyPing(frame);
|
||||||
|
|
|
@ -25,16 +25,14 @@ import org.eclipse.jetty.http2.frames.PriorityFrame;
|
||||||
|
|
||||||
public class PriorityBodyParser extends BodyParser
|
public class PriorityBodyParser extends BodyParser
|
||||||
{
|
{
|
||||||
private final RateControl rateControl;
|
|
||||||
private State state = State.PREPARE;
|
private State state = State.PREPARE;
|
||||||
private int cursor;
|
private int cursor;
|
||||||
private boolean exclusive;
|
private boolean exclusive;
|
||||||
private int parentStreamId;
|
private int parentStreamId;
|
||||||
|
|
||||||
public PriorityBodyParser(HeaderParser headerParser, Parser.Listener listener, RateControl rateControl)
|
public PriorityBodyParser(HeaderParser headerParser, Parser.Listener listener)
|
||||||
{
|
{
|
||||||
super(headerParser, listener);
|
super(headerParser, listener);
|
||||||
this.rateControl = rateControl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reset()
|
private void reset()
|
||||||
|
@ -119,7 +117,7 @@ public class PriorityBodyParser extends BodyParser
|
||||||
private boolean onPriority(ByteBuffer buffer, 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);
|
PriorityFrame frame = new PriorityFrame(getStreamId(), parentStreamId, weight, exclusive);
|
||||||
if (rateControl != null && !rateControl.onEvent(frame))
|
if (!rateControlOnEvent(frame))
|
||||||
return connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_priority_frame_rate");
|
return connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_priority_frame_rate");
|
||||||
reset();
|
reset();
|
||||||
notifyPriority(frame);
|
notifyPriority(frame);
|
||||||
|
|
|
@ -37,9 +37,9 @@ public class ServerParser extends Parser
|
||||||
private State state = State.PREFACE;
|
private State state = State.PREFACE;
|
||||||
private boolean notifyPreface = true;
|
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.listener = listener;
|
||||||
this.prefaceParser = new PrefaceParser(listener);
|
this.prefaceParser = new PrefaceParser(listener);
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,6 @@ public class SettingsBodyParser extends BodyParser
|
||||||
private static final Logger LOG = Log.getLogger(SettingsBodyParser.class);
|
private static final Logger LOG = Log.getLogger(SettingsBodyParser.class);
|
||||||
|
|
||||||
private final int maxKeys;
|
private final int maxKeys;
|
||||||
private final RateControl rateControl;
|
|
||||||
private State state = State.PREPARE;
|
private State state = State.PREPARE;
|
||||||
private int cursor;
|
private int cursor;
|
||||||
private int length;
|
private int length;
|
||||||
|
@ -47,14 +46,13 @@ public class SettingsBodyParser extends BodyParser
|
||||||
|
|
||||||
public SettingsBodyParser(HeaderParser headerParser, Parser.Listener listener)
|
public SettingsBodyParser(HeaderParser headerParser, Parser.Listener listener)
|
||||||
{
|
{
|
||||||
this(headerParser, listener, SettingsFrame.DEFAULT_MAX_KEYS, null);
|
this(headerParser, listener, SettingsFrame.DEFAULT_MAX_KEYS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SettingsBodyParser(HeaderParser headerParser, Parser.Listener listener, int maxKeys, RateControl rateControl)
|
public SettingsBodyParser(HeaderParser headerParser, Parser.Listener listener, int maxKeys)
|
||||||
{
|
{
|
||||||
super(headerParser, listener);
|
super(headerParser, listener);
|
||||||
this.maxKeys = maxKeys;
|
this.maxKeys = maxKeys;
|
||||||
this.rateControl = rateControl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void reset()
|
protected void reset()
|
||||||
|
@ -76,7 +74,7 @@ public class SettingsBodyParser extends BodyParser
|
||||||
protected void emptyBody(ByteBuffer buffer)
|
protected void emptyBody(ByteBuffer buffer)
|
||||||
{
|
{
|
||||||
SettingsFrame frame = new SettingsFrame(Collections.emptyMap(), hasFlag(Flags.ACK));
|
SettingsFrame frame = new SettingsFrame(Collections.emptyMap(), hasFlag(Flags.ACK));
|
||||||
if (rateControl != null && !rateControl.onEvent(frame))
|
if (!rateControlOnEvent(frame))
|
||||||
connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_settings_frame");
|
connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_settings_frame");
|
||||||
else
|
else
|
||||||
onSettings(frame);
|
onSettings(frame);
|
||||||
|
@ -220,7 +218,8 @@ public class SettingsBodyParser extends BodyParser
|
||||||
public static SettingsFrame parseBody(final ByteBuffer buffer)
|
public static SettingsFrame parseBody(final ByteBuffer buffer)
|
||||||
{
|
{
|
||||||
AtomicReference<SettingsFrame> frameRef = new AtomicReference<>();
|
AtomicReference<SettingsFrame> frameRef = new AtomicReference<>();
|
||||||
SettingsBodyParser parser = new SettingsBodyParser(new HeaderParser(), new Parser.Listener.Adapter()
|
// TODO should we do rate control here?
|
||||||
|
SettingsBodyParser parser = new SettingsBodyParser(new HeaderParser(RateControl.NO_RATE_CONTROL), new Parser.Listener.Adapter()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void onSettings(SettingsFrame frame)
|
public void onSettings(SettingsFrame frame)
|
||||||
|
|
|
@ -21,18 +21,15 @@ package org.eclipse.jetty.http2.parser;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
import org.eclipse.jetty.http2.ErrorCode;
|
import org.eclipse.jetty.http2.ErrorCode;
|
||||||
import org.eclipse.jetty.http2.frames.Frame;
|
|
||||||
import org.eclipse.jetty.http2.frames.UnknownFrame;
|
import org.eclipse.jetty.http2.frames.UnknownFrame;
|
||||||
|
|
||||||
public class UnknownBodyParser extends BodyParser
|
public class UnknownBodyParser extends BodyParser
|
||||||
{
|
{
|
||||||
private final RateControl rateControl;
|
|
||||||
private int cursor;
|
private int cursor;
|
||||||
|
|
||||||
public UnknownBodyParser(HeaderParser headerParser, Parser.Listener listener, RateControl rateControl)
|
public UnknownBodyParser(HeaderParser headerParser, Parser.Listener listener)
|
||||||
{
|
{
|
||||||
super(headerParser, listener);
|
super(headerParser, listener);
|
||||||
this.rateControl = rateControl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -41,12 +38,9 @@ public class UnknownBodyParser extends BodyParser
|
||||||
int length = cursor == 0 ? getBodyLength() : cursor;
|
int length = cursor == 0 ? getBodyLength() : cursor;
|
||||||
cursor = consume(buffer, length);
|
cursor = consume(buffer, length);
|
||||||
boolean parsed = cursor == 0;
|
boolean parsed = cursor == 0;
|
||||||
if (parsed && rateControl != null)
|
if (parsed && !rateControlOnEvent(new UnknownFrame(getFrameType())))
|
||||||
{
|
return connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_unknown_frame");
|
||||||
Frame frame = new UnknownFrame(getFrameType());
|
|
||||||
if (!rateControl.onEvent(frame))
|
|
||||||
return connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_unknown_frame");
|
|
||||||
}
|
|
||||||
return parsed;
|
return parsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -135,8 +135,7 @@ public class FrameFloodTest
|
||||||
{
|
{
|
||||||
failed.set(true);
|
failed.set(true);
|
||||||
}
|
}
|
||||||
}, 4096, 8192);
|
}, 4096, 8192, new WindowRateControl(8, Duration.ofSeconds(1)));
|
||||||
parser.setRateControl(new WindowRateControl(8, Duration.ofSeconds(1)));
|
|
||||||
parser.init(UnaryOperator.identity());
|
parser.init(UnaryOperator.identity());
|
||||||
|
|
||||||
if (preamble != null)
|
if (preamble != null)
|
||||||
|
|
|
@ -175,7 +175,7 @@ public class HpackDecoder
|
||||||
name = Huffman.decode(buffer, length);
|
name = Huffman.decode(buffer, length);
|
||||||
else
|
else
|
||||||
name = toASCIIString(buffer, length);
|
name = toASCIIString(buffer, length);
|
||||||
for (int i = 0; i < name.length(); i++)
|
for (int i = name.length(); i-- > 0;)
|
||||||
{
|
{
|
||||||
char c = name.charAt(i);
|
char c = name.charAt(i);
|
||||||
if (c >= 'A' && c <= 'Z')
|
if (c >= 'A' && c <= 'Z')
|
||||||
|
|
|
@ -74,11 +74,13 @@ public class MetaDataBuilder
|
||||||
{
|
{
|
||||||
HttpHeader header = field.getHeader();
|
HttpHeader header = field.getHeader();
|
||||||
String name = field.getName();
|
String name = field.getName();
|
||||||
|
if (name == null || name.length() == 0)
|
||||||
|
throw new HpackException.SessionException("Header size 0");
|
||||||
String value = field.getValue();
|
String value = field.getValue();
|
||||||
int fieldSize = name.length() + (value == null ? 0 : value.length());
|
int fieldSize = name.length() + (value == null ? 0 : value.length());
|
||||||
_size += fieldSize + 32;
|
_size += fieldSize + 32;
|
||||||
if (_size > _maxSize)
|
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)
|
if (field instanceof StaticTableHttpField)
|
||||||
{
|
{
|
||||||
|
|
|
@ -26,6 +26,7 @@ import org.eclipse.jetty.http.HttpHeader;
|
||||||
import org.eclipse.jetty.http.HttpScheme;
|
import org.eclipse.jetty.http.HttpScheme;
|
||||||
import org.eclipse.jetty.http.MetaData;
|
import org.eclipse.jetty.http.MetaData;
|
||||||
import org.eclipse.jetty.http2.hpack.HpackException.CompressionException;
|
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.http2.hpack.HpackException.StreamException;
|
||||||
import org.eclipse.jetty.util.TypeUtil;
|
import org.eclipse.jetty.util.TypeUtil;
|
||||||
import org.hamcrest.Matchers;
|
import org.hamcrest.Matchers;
|
||||||
|
@ -43,6 +44,21 @@ import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
public class HpackDecoderTest
|
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
|
@Test
|
||||||
public void testDecodeD_3() throws Exception
|
public void testDecodeD_3() throws Exception
|
||||||
{
|
{
|
||||||
|
@ -253,7 +269,7 @@ public class HpackDecoderTest
|
||||||
decoder.decode(buffer);
|
decoder.decode(buffer);
|
||||||
fail();
|
fail();
|
||||||
}
|
}
|
||||||
catch (HpackException.SessionException e)
|
catch (SessionException e)
|
||||||
{
|
{
|
||||||
assertThat(e.getMessage(), Matchers.startsWith("Unknown index"));
|
assertThat(e.getMessage(), Matchers.startsWith("Unknown index"));
|
||||||
}
|
}
|
||||||
|
@ -506,4 +522,38 @@ public class HpackDecoderTest
|
||||||
CompressionException ex = assertThrows(CompressionException.class, () -> decoder.decode(buffer));
|
CompressionException ex = assertThrows(CompressionException.class, () -> decoder.decode(buffer));
|
||||||
assertThat(ex.getMessage(), Matchers.containsString("Bad termination"));
|
assertThat(ex.getMessage(), Matchers.containsString("Bad termination"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test()
|
||||||
|
public void testZeroLengthName() throws Exception
|
||||||
|
{
|
||||||
|
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() throws Exception
|
||||||
|
{
|
||||||
|
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"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -501,7 +501,7 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
|
||||||
x.printStackTrace();
|
x.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 4096, 8192);
|
}, 4096, 8192, null);
|
||||||
parser.init(UnaryOperator.identity());
|
parser.init(UnaryOperator.identity());
|
||||||
|
|
||||||
byte[] bytes = new byte[1024];
|
byte[] bytes = new byte[1024];
|
||||||
|
|
|
@ -251,10 +251,9 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne
|
||||||
session.setInitialSessionRecvWindow(getInitialSessionRecvWindow());
|
session.setInitialSessionRecvWindow(getInitialSessionRecvWindow());
|
||||||
session.setWriteThreshold(getHttpConfiguration().getOutputBufferSize());
|
session.setWriteThreshold(getHttpConfiguration().getOutputBufferSize());
|
||||||
|
|
||||||
ServerParser parser = newServerParser(connector, session);
|
ServerParser parser = newServerParser(connector, session, getRateControl());
|
||||||
parser.setMaxFrameLength(getMaxFrameLength());
|
parser.setMaxFrameLength(getMaxFrameLength());
|
||||||
parser.setMaxSettingsKeys(getMaxSettingsKeys());
|
parser.setMaxSettingsKeys(getMaxSettingsKeys());
|
||||||
parser.setRateControl(getRateControl());
|
|
||||||
|
|
||||||
HTTP2Connection connection = new HTTP2ServerConnection(connector.getByteBufferPool(), connector.getExecutor(),
|
HTTP2Connection connection = new HTTP2ServerConnection(connector.getByteBufferPool(), connector.getExecutor(),
|
||||||
endPoint, httpConfiguration, parser, session, getInputBufferSize(), listener);
|
endPoint, httpConfiguration, parser, session, getInputBufferSize(), listener);
|
||||||
|
@ -264,9 +263,9 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne
|
||||||
|
|
||||||
protected abstract ServerSessionListener newSessionListener(Connector connector, EndPoint endPoint);
|
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")
|
@ManagedObject("The container of HTTP/2 sessions")
|
||||||
|
|
Loading…
Reference in New Issue