Merge pull request #3996 from eclipse/jetty-9.4.x-3978-http2_vulnerabilities
Fixes #3978 - HTTP/2 vulnerabilities.
This commit is contained in:
commit
22f0cdde29
|
@ -27,7 +27,6 @@ import java.util.Map;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.http.HttpServlet;
|
import javax.servlet.http.HttpServlet;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
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.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;
|
||||||
|
@ -152,7 +152,7 @@ public class HTTP2Test extends AbstractTest
|
||||||
start(new HttpServlet()
|
start(new HttpServlet()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException
|
||||||
{
|
{
|
||||||
resp.getOutputStream().write(content);
|
resp.getOutputStream().write(content);
|
||||||
}
|
}
|
||||||
|
@ -200,7 +200,7 @@ public class HTTP2Test extends AbstractTest
|
||||||
start(new EmptyHttpServlet()
|
start(new EmptyHttpServlet()
|
||||||
{
|
{
|
||||||
@Override
|
@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());
|
IO.copy(request.getInputStream(), response.getOutputStream());
|
||||||
}
|
}
|
||||||
|
@ -244,7 +244,7 @@ public class HTTP2Test extends AbstractTest
|
||||||
start(new HttpServlet()
|
start(new HttpServlet()
|
||||||
{
|
{
|
||||||
@Override
|
@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);
|
int download = request.getIntHeader(downloadBytes);
|
||||||
byte[] content = new byte[download];
|
byte[] content = new byte[download];
|
||||||
|
@ -287,7 +287,7 @@ public class HTTP2Test extends AbstractTest
|
||||||
start(new HttpServlet()
|
start(new HttpServlet()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
protected void service(HttpServletRequest request, HttpServletResponse response)
|
||||||
{
|
{
|
||||||
response.setStatus(status);
|
response.setStatus(status);
|
||||||
}
|
}
|
||||||
|
@ -322,7 +322,7 @@ public class HTTP2Test extends AbstractTest
|
||||||
start(new HttpServlet()
|
start(new HttpServlet()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
protected void service(HttpServletRequest request, HttpServletResponse response)
|
||||||
{
|
{
|
||||||
assertEquals(host, request.getServerName());
|
assertEquals(host, request.getServerName());
|
||||||
assertEquals(port, request.getServerPort());
|
assertEquals(port, request.getServerPort());
|
||||||
|
@ -747,7 +747,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 +757,7 @@ public class HTTP2Test extends AbstractTest
|
||||||
super.onGoAway(frame);
|
super.onGoAway(frame);
|
||||||
goAwayLatch.countDown();
|
goAwayLatch.countDown();
|
||||||
}
|
}
|
||||||
});
|
}, rateControl);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
prepareServer(connectionFactory);
|
prepareServer(connectionFactory);
|
||||||
|
|
|
@ -57,6 +57,7 @@ import org.eclipse.jetty.util.AtomicBiInteger;
|
||||||
import org.eclipse.jetty.util.Atomics;
|
import org.eclipse.jetty.util.Atomics;
|
||||||
import org.eclipse.jetty.util.Callback;
|
import org.eclipse.jetty.util.Callback;
|
||||||
import org.eclipse.jetty.util.CountingCallback;
|
import org.eclipse.jetty.util.CountingCallback;
|
||||||
|
import org.eclipse.jetty.util.MathUtils;
|
||||||
import org.eclipse.jetty.util.Promise;
|
import org.eclipse.jetty.util.Promise;
|
||||||
import org.eclipse.jetty.util.Retainable;
|
import org.eclipse.jetty.util.Retainable;
|
||||||
import org.eclipse.jetty.util.annotation.ManagedAttribute;
|
import org.eclipse.jetty.util.annotation.ManagedAttribute;
|
||||||
|
@ -460,47 +461,33 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
||||||
int windowDelta = frame.getWindowDelta();
|
int windowDelta = frame.getWindowDelta();
|
||||||
if (streamId > 0)
|
if (streamId > 0)
|
||||||
{
|
{
|
||||||
if (windowDelta == 0)
|
IStream stream = getStream(streamId);
|
||||||
|
if (stream != null)
|
||||||
{
|
{
|
||||||
reset(new ResetFrame(streamId, ErrorCode.PROTOCOL_ERROR.code), Callback.NOOP);
|
int streamSendWindow = stream.updateSendWindow(0);
|
||||||
}
|
if (MathUtils.sumOverflows(streamSendWindow, windowDelta))
|
||||||
else
|
|
||||||
{
|
|
||||||
IStream stream = getStream(streamId);
|
|
||||||
if (stream != null)
|
|
||||||
{
|
{
|
||||||
int streamSendWindow = stream.updateSendWindow(0);
|
reset(new ResetFrame(streamId, ErrorCode.FLOW_CONTROL_ERROR.code), Callback.NOOP);
|
||||||
if (sumOverflows(streamSendWindow, windowDelta))
|
|
||||||
{
|
|
||||||
reset(new ResetFrame(streamId, ErrorCode.FLOW_CONTROL_ERROR.code), Callback.NOOP);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
stream.process(frame, Callback.NOOP);
|
|
||||||
onWindowUpdate(stream, frame);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!isRemoteStreamClosed(streamId))
|
stream.process(frame, Callback.NOOP);
|
||||||
onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "unexpected_window_update_frame");
|
onWindowUpdate(stream, frame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!isRemoteStreamClosed(streamId))
|
||||||
|
onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "unexpected_window_update_frame");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (windowDelta == 0)
|
int sessionSendWindow = updateSendWindow(0);
|
||||||
{
|
if (MathUtils.sumOverflows(sessionSendWindow, windowDelta))
|
||||||
onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "invalid_window_update_frame");
|
onConnectionFailure(ErrorCode.FLOW_CONTROL_ERROR.code, "invalid_flow_control_window");
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
onWindowUpdate(null, frame);
|
||||||
int sessionSendWindow = updateSendWindow(0);
|
|
||||||
if (sumOverflows(sessionSendWindow, windowDelta))
|
|
||||||
onConnectionFailure(ErrorCode.FLOW_CONTROL_ERROR.code, "invalid_flow_control_window");
|
|
||||||
else
|
|
||||||
onWindowUpdate(null, frame);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -515,19 +502,6 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
||||||
callback.succeeded();
|
callback.succeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean sumOverflows(int a, int b)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Math.addExact(a, b);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
catch (ArithmeticException x)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onConnectionFailure(int error, String reason)
|
public void onConnectionFailure(int error, String reason)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -96,6 +96,11 @@ public abstract class BodyParser
|
||||||
return headerParser.getLength();
|
return headerParser.getLength();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected int getFrameType()
|
||||||
|
{
|
||||||
|
return headerParser.getFrameType();
|
||||||
|
}
|
||||||
|
|
||||||
protected void notifyData(DataFrame frame)
|
protected void notifyData(DataFrame frame)
|
||||||
{
|
{
|
||||||
try
|
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);
|
notifyStreamFailure(streamId, error, reason);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notifyStreamFailure(int streamId, int error, String reason)
|
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);
|
LOG.info("Failure while notifying listener " + listener, x);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected boolean rateControlOnEvent(Object o)
|
||||||
|
{
|
||||||
|
return headerParser.getRateControl().onEvent(o);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import java.nio.ByteBuffer;
|
||||||
import org.eclipse.jetty.http.MetaData;
|
import org.eclipse.jetty.http.MetaData;
|
||||||
import org.eclipse.jetty.http2.ErrorCode;
|
import org.eclipse.jetty.http2.ErrorCode;
|
||||||
import org.eclipse.jetty.http2.Flags;
|
import org.eclipse.jetty.http2.Flags;
|
||||||
|
import org.eclipse.jetty.http2.frames.ContinuationFrame;
|
||||||
import org.eclipse.jetty.http2.frames.HeadersFrame;
|
import org.eclipse.jetty.http2.frames.HeadersFrame;
|
||||||
|
|
||||||
public class ContinuationBodyParser extends BodyParser
|
public class ContinuationBodyParser extends BodyParser
|
||||||
|
@ -43,7 +44,15 @@ public class ContinuationBodyParser extends BodyParser
|
||||||
protected void emptyBody(ByteBuffer buffer)
|
protected void emptyBody(ByteBuffer buffer)
|
||||||
{
|
{
|
||||||
if (hasFlag(Flags.END_HEADERS))
|
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
|
@Override
|
||||||
|
@ -81,7 +90,7 @@ public class ContinuationBodyParser extends BodyParser
|
||||||
headerBlockFragments.storeFragment(buffer, length, last);
|
headerBlockFragments.storeFragment(buffer, length, last);
|
||||||
reset();
|
reset();
|
||||||
if (last)
|
if (last)
|
||||||
return onHeaders();
|
return onHeaders(buffer);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,15 +103,20 @@ public class ContinuationBodyParser extends BodyParser
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean onHeaders()
|
private boolean onHeaders(ByteBuffer buffer)
|
||||||
{
|
{
|
||||||
ByteBuffer headerBlock = headerBlockFragments.complete();
|
ByteBuffer headerBlock = headerBlockFragments.complete();
|
||||||
MetaData metaData = headerBlockParser.parse(headerBlock, headerBlock.remaining());
|
MetaData metaData = headerBlockParser.parse(headerBlock, headerBlock.remaining());
|
||||||
|
if (metaData == null)
|
||||||
|
return true;
|
||||||
if (metaData == HeaderBlockParser.SESSION_FAILURE)
|
if (metaData == HeaderBlockParser.SESSION_FAILURE)
|
||||||
return false;
|
return false;
|
||||||
if (metaData == null || metaData == HeaderBlockParser.STREAM_FAILURE)
|
|
||||||
return true;
|
|
||||||
HeadersFrame frame = new HeadersFrame(getStreamId(), metaData, headerBlockFragments.getPriorityFrame(), headerBlockFragments.isEndStream());
|
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);
|
notifyHeaders(frame);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,9 +48,17 @@ public class DataBodyParser extends BodyParser
|
||||||
protected void emptyBody(ByteBuffer buffer)
|
protected void emptyBody(ByteBuffer buffer)
|
||||||
{
|
{
|
||||||
if (isPadding())
|
if (isPadding())
|
||||||
|
{
|
||||||
connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_data_frame");
|
connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_data_frame");
|
||||||
|
}
|
||||||
else
|
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
|
@Override
|
||||||
|
@ -134,7 +142,11 @@ public class DataBodyParser extends BodyParser
|
||||||
|
|
||||||
private void onData(ByteBuffer buffer, boolean fragment, int padding)
|
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);
|
notifyData(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,14 +30,24 @@ 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;
|
||||||
|
|
||||||
private int length;
|
private int length;
|
||||||
private int type;
|
private int type;
|
||||||
private int flags;
|
private int flags;
|
||||||
private int streamId;
|
private int streamId;
|
||||||
|
|
||||||
|
public HeaderParser(RateControl rateControl)
|
||||||
|
{
|
||||||
|
this.rateControl = rateControl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RateControl getRateControl()
|
||||||
|
{
|
||||||
|
return rateControl;
|
||||||
|
}
|
||||||
|
|
||||||
protected void reset()
|
protected void reset()
|
||||||
{
|
{
|
||||||
state = State.LENGTH;
|
state = State.LENGTH;
|
||||||
|
|
|
@ -61,17 +61,23 @@ public class HeadersBodyParser extends BodyParser
|
||||||
@Override
|
@Override
|
||||||
protected void emptyBody(ByteBuffer buffer)
|
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);
|
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
|
else
|
||||||
{
|
{
|
||||||
headerBlockFragments.setStreamId(getStreamId());
|
headerBlockFragments.setStreamId(getStreamId());
|
||||||
headerBlockFragments.setEndStream(isEndStream());
|
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;
|
state = State.PADDING;
|
||||||
loop = paddingLength == 0;
|
loop = paddingLength == 0;
|
||||||
if (metaData != HeaderBlockParser.STREAM_FAILURE)
|
if (metaData != HeaderBlockParser.STREAM_FAILURE)
|
||||||
|
{
|
||||||
onHeaders(parentStreamId, weight, exclusive, metaData);
|
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
|
else
|
||||||
|
@ -230,6 +244,11 @@ public class HeadersBodyParser extends BodyParser
|
||||||
if (hasFlag(Flags.PRIORITY))
|
if (hasFlag(Flags.PRIORITY))
|
||||||
priorityFrame = new PriorityFrame(getStreamId(), parentStreamId, weight, exclusive);
|
priorityFrame = new PriorityFrame(getStreamId(), parentStreamId, weight, exclusive);
|
||||||
HeadersFrame frame = new HeadersFrame(getStreamId(), metaData, priorityFrame, isEndStream());
|
HeadersFrame frame = new HeadersFrame(getStreamId(), metaData, priorityFrame, isEndStream());
|
||||||
|
onHeaders(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onHeaders(HeadersFrame frame)
|
||||||
|
{
|
||||||
notifyHeaders(frame);
|
notifyHeaders(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,18 +54,22 @@ public class Parser
|
||||||
private final HpackDecoder hpackDecoder;
|
private final HpackDecoder hpackDecoder;
|
||||||
private final BodyParser[] bodyParsers;
|
private final BodyParser[] bodyParsers;
|
||||||
private UnknownBodyParser unknownBodyParser;
|
private UnknownBodyParser unknownBodyParser;
|
||||||
private int maxFrameLength;
|
private int maxFrameLength = Frame.DEFAULT_MAX_LENGTH;
|
||||||
private int maxSettingsKeys = SettingsFrame.DEFAULT_MAX_KEYS;
|
private int maxSettingsKeys = SettingsFrame.DEFAULT_MAX_KEYS;
|
||||||
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.maxFrameLength = Frame.DEFAULT_MAX_LENGTH;
|
|
||||||
this.bodyParsers = new BodyParser[FrameType.values().length];
|
this.bodyParsers = new BodyParser[FrameType.values().length];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -66,7 +66,7 @@ public class PingBodyParser extends BodyParser
|
||||||
if (buffer.remaining() >= 8)
|
if (buffer.remaining() >= 8)
|
||||||
{
|
{
|
||||||
buffer.get(payload);
|
buffer.get(payload);
|
||||||
return onPing(payload);
|
return onPing(buffer, payload);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -80,7 +80,7 @@ public class PingBodyParser extends BodyParser
|
||||||
payload[8 - cursor] = buffer.get();
|
payload[8 - cursor] = buffer.get();
|
||||||
--cursor;
|
--cursor;
|
||||||
if (cursor == 0)
|
if (cursor == 0)
|
||||||
return onPing(payload);
|
return onPing(buffer, payload);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
@ -92,9 +92,11 @@ public class PingBodyParser extends BodyParser
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean onPing(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 (!rateControlOnEvent(frame))
|
||||||
|
return connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_ping_frame_rate");
|
||||||
reset();
|
reset();
|
||||||
notifyPing(frame);
|
notifyPing(frame);
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -103,7 +103,7 @@ public class PriorityBodyParser extends BodyParser
|
||||||
if (getStreamId() == parentStreamId)
|
if (getStreamId() == parentStreamId)
|
||||||
return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_priority_frame");
|
return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_priority_frame");
|
||||||
int weight = (buffer.get() & 0xFF) + 1;
|
int weight = (buffer.get() & 0xFF) + 1;
|
||||||
return onPriority(parentStreamId, weight, exclusive);
|
return onPriority(buffer, parentStreamId, weight, exclusive);
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
|
@ -114,9 +114,11 @@ public class PriorityBodyParser extends BodyParser
|
||||||
return false;
|
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);
|
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();
|
reset();
|
||||||
notifyPriority(frame);
|
notifyPriority(frame);
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Applications should call this method when they want to signal an
|
||||||
|
* event that is subject to rate control.</p>
|
||||||
|
* <p>Implementations should return true if the event does not exceed
|
||||||
|
* the desired rate, or false to signal that the event exceeded the
|
||||||
|
* desired rate.</p>
|
||||||
|
*
|
||||||
|
* @param event the event subject to rate control.
|
||||||
|
* @return true IFF the rate is within limits
|
||||||
|
*/
|
||||||
|
public boolean onEvent(Object event);
|
||||||
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
package org.eclipse.jetty.http2.parser;
|
package org.eclipse.jetty.http2.parser;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
@ -72,7 +73,12 @@ public class SettingsBodyParser extends BodyParser
|
||||||
@Override
|
@Override
|
||||||
protected void emptyBody(ByteBuffer buffer)
|
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
|
@Override
|
||||||
|
@ -200,6 +206,11 @@ public class SettingsBodyParser extends BodyParser
|
||||||
return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_settings_max_frame_size");
|
return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_settings_max_frame_size");
|
||||||
|
|
||||||
SettingsFrame frame = new SettingsFrame(settings, hasFlag(Flags.ACK));
|
SettingsFrame frame = new SettingsFrame(settings, hasFlag(Flags.ACK));
|
||||||
|
return onSettings(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean onSettings(SettingsFrame frame)
|
||||||
|
{
|
||||||
reset();
|
reset();
|
||||||
notifySettings(frame);
|
notifySettings(frame);
|
||||||
return true;
|
return true;
|
||||||
|
@ -207,40 +218,25 @@ public class SettingsBodyParser extends BodyParser
|
||||||
|
|
||||||
public static SettingsFrame parseBody(final ByteBuffer buffer)
|
public static SettingsFrame parseBody(final ByteBuffer buffer)
|
||||||
{
|
{
|
||||||
final int bodyLength = buffer.remaining();
|
AtomicReference<SettingsFrame> frameRef = new AtomicReference<>();
|
||||||
final AtomicReference<SettingsFrame> frameRef = new AtomicReference<>();
|
SettingsBodyParser parser = new SettingsBodyParser(new HeaderParser(RateControl.NO_RATE_CONTROL), new Parser.Listener.Adapter()
|
||||||
SettingsBodyParser parser = new SettingsBodyParser(null, null)
|
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
protected int getStreamId()
|
public void onSettings(SettingsFrame frame)
|
||||||
{
|
{
|
||||||
return 0;
|
frameRef.set(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int getBodyLength()
|
public void onConnectionFailure(int error, String reason)
|
||||||
{
|
|
||||||
return bodyLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean onSettings(ByteBuffer buffer, Map<Integer, Integer> settings)
|
|
||||||
{
|
|
||||||
frameRef.set(new SettingsFrame(settings, false));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean connectionFailure(ByteBuffer buffer, int error, String reason)
|
|
||||||
{
|
{
|
||||||
frameRef.set(null);
|
frameRef.set(null);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
if (bodyLength == 0)
|
if (buffer.hasRemaining())
|
||||||
parser.emptyBody(buffer);
|
|
||||||
else
|
|
||||||
parser.parse(buffer);
|
parser.parse(buffer);
|
||||||
|
else
|
||||||
|
parser.emptyBody(buffer);
|
||||||
return frameRef.get();
|
return frameRef.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,9 @@ 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.frames.UnknownFrame;
|
||||||
|
|
||||||
public class UnknownBodyParser extends BodyParser
|
public class UnknownBodyParser extends BodyParser
|
||||||
{
|
{
|
||||||
private int cursor;
|
private int cursor;
|
||||||
|
@ -34,7 +37,11 @@ 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);
|
||||||
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)
|
private int consume(ByteBuffer buffer, int length)
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>An implementation of {@link RateControl} that limits the number of
|
||||||
|
* events within a time period.</p>
|
||||||
|
* <p>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}.</p>
|
||||||
|
*/
|
||||||
|
public class WindowRateControl implements RateControl
|
||||||
|
{
|
||||||
|
private final Queue<Long> 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -61,7 +61,7 @@ public class WindowUpdateBodyParser extends BodyParser
|
||||||
if (buffer.remaining() >= 4)
|
if (buffer.remaining() >= 4)
|
||||||
{
|
{
|
||||||
windowDelta = buffer.getInt() & 0x7F_FF_FF_FF;
|
windowDelta = buffer.getInt() & 0x7F_FF_FF_FF;
|
||||||
return onWindowUpdate(windowDelta);
|
return onWindowUpdate(buffer, windowDelta);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -78,7 +78,7 @@ public class WindowUpdateBodyParser extends BodyParser
|
||||||
if (cursor == 0)
|
if (cursor == 0)
|
||||||
{
|
{
|
||||||
windowDelta &= 0x7F_FF_FF_FF;
|
windowDelta &= 0x7F_FF_FF_FF;
|
||||||
return onWindowUpdate(windowDelta);
|
return onWindowUpdate(buffer, windowDelta);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -91,9 +91,17 @@ public class WindowUpdateBodyParser extends BodyParser
|
||||||
return false;
|
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();
|
reset();
|
||||||
notifyWindowUpdate(frame);
|
notifyWindowUpdate(frame);
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,6 +22,7 @@ import java.nio.ByteBuffer;
|
||||||
|
|
||||||
import org.eclipse.jetty.http.HttpField;
|
import org.eclipse.jetty.http.HttpField;
|
||||||
import org.eclipse.jetty.http.HttpHeader;
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
|
import org.eclipse.jetty.http.HttpTokens;
|
||||||
import org.eclipse.jetty.http.MetaData;
|
import org.eclipse.jetty.http.MetaData;
|
||||||
import org.eclipse.jetty.http2.hpack.HpackContext.Entry;
|
import org.eclipse.jetty.http2.hpack.HpackContext.Entry;
|
||||||
import org.eclipse.jetty.util.TypeUtil;
|
import org.eclipse.jetty.util.TypeUtil;
|
||||||
|
@ -175,14 +176,35 @@ 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++)
|
check:
|
||||||
|
for (int i = name.length(); i-- > 0;)
|
||||||
{
|
{
|
||||||
char c = name.charAt(i);
|
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;
|
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);
|
header = HttpHeader.CACHE.get(name);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -113,7 +115,7 @@ public class MetaDataBuilder
|
||||||
{
|
{
|
||||||
case C_STATUS:
|
case C_STATUS:
|
||||||
if (checkPseudoHeader(header, _status))
|
if (checkPseudoHeader(header, _status))
|
||||||
_status = Integer.valueOf(field.getIntValue());
|
_status = field.getIntValue();
|
||||||
_response = true;
|
_response = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
|
@ -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,14 +269,14 @@ 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"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 8.1.2.1. Pseudo-Header Fields */
|
/* 8.1.2.1. Pseudo-Header Fields */
|
||||||
@Test()
|
@Test
|
||||||
public void test8_1_2_1_PsuedoHeaderFields() throws Exception
|
public void test8_1_2_1_PsuedoHeaderFields() throws Exception
|
||||||
{
|
{
|
||||||
// 1:Sends a HEADERS frame that contains a unknown pseudo-header field
|
// 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
|
public void test8_1_2_2_ConnectionSpecificHeaderFields() throws Exception
|
||||||
{
|
{
|
||||||
MetaDataBuilder mdb;
|
MetaDataBuilder mdb;
|
||||||
|
@ -349,7 +365,7 @@ public class HpackDecoderTest
|
||||||
assertNotNull(mdb.build());
|
assertNotNull(mdb.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test()
|
@Test
|
||||||
public void test8_1_2_3_RequestPseudoHeaderFields() throws Exception
|
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_SCHEME, "http"));
|
||||||
mdb.emit(new HttpField(HttpHeader.C_AUTHORITY, "localhost:8080"));
|
mdb.emit(new HttpField(HttpHeader.C_AUTHORITY, "localhost:8080"));
|
||||||
mdb.emit(new HttpField(HttpHeader.C_PATH, ""));
|
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"));
|
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_SCHEME, "http"));
|
||||||
mdb.emit(new HttpField(HttpHeader.C_AUTHORITY, "localhost:8080"));
|
mdb.emit(new HttpField(HttpHeader.C_AUTHORITY, "localhost:8080"));
|
||||||
mdb.emit(new HttpField(HttpHeader.C_PATH, "/"));
|
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"));
|
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_METHOD, "GET"));
|
||||||
mdb.emit(new HttpField(HttpHeader.C_AUTHORITY, "localhost:8080"));
|
mdb.emit(new HttpField(HttpHeader.C_AUTHORITY, "localhost:8080"));
|
||||||
mdb.emit(new HttpField(HttpHeader.C_PATH, "/"));
|
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"));
|
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_METHOD, "GET"));
|
||||||
mdb.emit(new HttpField(HttpHeader.C_SCHEME, "http"));
|
mdb.emit(new HttpField(HttpHeader.C_SCHEME, "http"));
|
||||||
mdb.emit(new HttpField(HttpHeader.C_AUTHORITY, "localhost:8080"));
|
mdb.emit(new HttpField(HttpHeader.C_AUTHORITY, "localhost:8080"));
|
||||||
StreamException ex = assertThrows(StreamException.class, () -> mdb.build());
|
StreamException ex = assertThrows(StreamException.class, mdb::build);
|
||||||
assertThat(ex.getMessage(), Matchers.containsString("No Path"));
|
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_SCHEME, "http"));
|
||||||
mdb.emit(new HttpField(HttpHeader.C_AUTHORITY, "localhost:8080"));
|
mdb.emit(new HttpField(HttpHeader.C_AUTHORITY, "localhost:8080"));
|
||||||
mdb.emit(new HttpField(HttpHeader.C_PATH, "/"));
|
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"));
|
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_AUTHORITY, "localhost:8080"));
|
||||||
mdb.emit(new HttpField(HttpHeader.C_PATH, "/"));
|
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"));
|
assertThat(ex.getMessage(), Matchers.containsString("Duplicate"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test()
|
@Test
|
||||||
public void testHuffmanEncodedStandard() throws Exception
|
public void testHuffmanEncodedStandard() throws Exception
|
||||||
{
|
{
|
||||||
HpackDecoder decoder = new HpackDecoder(4096, 8192);
|
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 */
|
/* 5.2.1: Sends a Huffman-encoded string literal representation with padding longer than 7 bits */
|
||||||
@Test()
|
@Test
|
||||||
public void testHuffmanEncodedExtraPadding() throws Exception
|
public void testHuffmanEncodedExtraPadding()
|
||||||
{
|
{
|
||||||
HpackDecoder decoder = new HpackDecoder(4096, 8192);
|
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 */
|
/* 5.2.2: Sends a Huffman-encoded string literal representation padded by zero */
|
||||||
@Test()
|
@Test
|
||||||
public void testHuffmanEncodedZeroPadding() throws Exception
|
public void testHuffmanEncodedZeroPadding()
|
||||||
{
|
{
|
||||||
HpackDecoder decoder = new HpackDecoder(4096, 8192);
|
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 */
|
/* 5.2.3: Sends a Huffman-encoded string literal representation containing the EOS symbol */
|
||||||
@Test()
|
@Test
|
||||||
public void testHuffmanEncodedWithEOS() throws Exception
|
public void testHuffmanEncodedWithEOS()
|
||||||
{
|
{
|
||||||
HpackDecoder decoder = new HpackDecoder(4096, 8192);
|
HpackDecoder decoder = new HpackDecoder(4096, 8192);
|
||||||
|
|
||||||
|
@ -483,8 +499,8 @@ public class HpackDecoderTest
|
||||||
assertThat(ex.getMessage(), Matchers.containsString("EOS in content"));
|
assertThat(ex.getMessage(), Matchers.containsString("EOS in content"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test()
|
@Test
|
||||||
public void testHuffmanEncodedOneIncompleteOctet() throws Exception
|
public void testHuffmanEncodedOneIncompleteOctet()
|
||||||
{
|
{
|
||||||
HpackDecoder decoder = new HpackDecoder(4096, 8192);
|
HpackDecoder decoder = new HpackDecoder(4096, 8192);
|
||||||
|
|
||||||
|
@ -495,8 +511,8 @@ public class HpackDecoderTest
|
||||||
assertThat(ex.getMessage(), Matchers.containsString("Bad termination"));
|
assertThat(ex.getMessage(), Matchers.containsString("Bad termination"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test()
|
@Test
|
||||||
public void testHuffmanEncodedTwoIncompleteOctet() throws Exception
|
public void testHuffmanEncodedTwoIncompleteOctet()
|
||||||
{
|
{
|
||||||
HpackDecoder decoder = new HpackDecoder(4096, 8192);
|
HpackDecoder decoder = new HpackDecoder(4096, 8192);
|
||||||
|
|
||||||
|
@ -506,4 +522,49 @@ 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()
|
||||||
|
{
|
||||||
|
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"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.http2.client.http;
|
package org.eclipse.jetty.http2.client.http;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.net.ServerSocket;
|
import java.net.ServerSocket;
|
||||||
|
@ -38,7 +37,6 @@ import java.util.concurrent.TimeoutException;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.function.UnaryOperator;
|
import java.util.function.UnaryOperator;
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
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.ResetFrame;
|
||||||
import org.eclipse.jetty.http2.frames.SettingsFrame;
|
import org.eclipse.jetty.http2.frames.SettingsFrame;
|
||||||
import org.eclipse.jetty.http2.generator.Generator;
|
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.ServerParser;
|
||||||
import org.eclipse.jetty.http2.server.RawHTTP2ServerConnectionFactory;
|
import org.eclipse.jetty.http2.server.RawHTTP2ServerConnectionFactory;
|
||||||
import org.eclipse.jetty.io.ByteBufferPool;
|
import org.eclipse.jetty.io.ByteBufferPool;
|
||||||
|
@ -187,7 +186,7 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
|
||||||
start(new AbstractHandler()
|
start(new AbstractHandler()
|
||||||
{
|
{
|
||||||
@Override
|
@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);
|
baseRequest.setHandled(true);
|
||||||
HttpVersion version = HttpVersion.fromString(request.getProtocol());
|
HttpVersion version = HttpVersion.fromString(request.getProtocol());
|
||||||
|
@ -313,7 +312,7 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
|
||||||
start(new AbstractHandler()
|
start(new AbstractHandler()
|
||||||
{
|
{
|
||||||
@Override
|
@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);
|
baseRequest.setHandled(true);
|
||||||
assertEquals(path, request.getRequestURI());
|
assertEquals(path, request.getRequestURI());
|
||||||
|
@ -337,7 +336,7 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
|
||||||
start(new AbstractHandler()
|
start(new AbstractHandler()
|
||||||
{
|
{
|
||||||
@Override
|
@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);
|
baseRequest.setHandled(true);
|
||||||
assertEquals(path, request.getRequestURI());
|
assertEquals(path, request.getRequestURI());
|
||||||
|
@ -501,7 +500,7 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
|
||||||
x.printStackTrace();
|
x.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 4096, 8192);
|
}, 4096, 8192, RateControl.NO_RATE_CONTROL);
|
||||||
parser.init(UnaryOperator.identity());
|
parser.init(UnaryOperator.identity());
|
||||||
|
|
||||||
byte[] bytes = new byte[1024];
|
byte[] bytes = new byte[1024];
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
package org.eclipse.jetty.http2.server;
|
package org.eclipse.jetty.http2.server;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.time.Duration;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
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.Frame;
|
||||||
import org.eclipse.jetty.http2.frames.SettingsFrame;
|
import org.eclipse.jetty.http2.frames.SettingsFrame;
|
||||||
import org.eclipse.jetty.http2.generator.Generator;
|
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.ServerParser;
|
||||||
|
import org.eclipse.jetty.http2.parser.WindowRateControl;
|
||||||
import org.eclipse.jetty.io.Connection;
|
import org.eclipse.jetty.io.Connection;
|
||||||
import org.eclipse.jetty.io.EndPoint;
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
import org.eclipse.jetty.server.AbstractConnectionFactory;
|
import org.eclipse.jetty.server.AbstractConnectionFactory;
|
||||||
|
@ -58,6 +61,7 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne
|
||||||
private int maxHeaderBlockFragment = 0;
|
private int maxHeaderBlockFragment = 0;
|
||||||
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 = new WindowRateControl(20, Duration.ofSeconds(1));
|
||||||
private FlowControlStrategy.Factory flowControlStrategyFactory = () -> new BufferingFlowControlStrategy(0.5F);
|
private FlowControlStrategy.Factory flowControlStrategyFactory = () -> new BufferingFlowControlStrategy(0.5F);
|
||||||
private long streamIdleTimeout;
|
private long streamIdleTimeout;
|
||||||
|
|
||||||
|
@ -178,6 +182,16 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne
|
||||||
this.maxSettingsKeys = maxSettingsKeys;
|
this.maxSettingsKeys = maxSettingsKeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RateControl getRateControl()
|
||||||
|
{
|
||||||
|
return rateControl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRateControl(RateControl rateControl)
|
||||||
|
{
|
||||||
|
this.rateControl = rateControl;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return -1
|
* @return -1
|
||||||
* @deprecated feature removed, no replacement
|
* @deprecated feature removed, no replacement
|
||||||
|
@ -237,7 +251,7 @@ 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());
|
||||||
|
|
||||||
|
@ -249,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")
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue