SPDY: handle too large header blocks
This commit is contained in:
parent
6aa4d147d8
commit
55360d2bc8
@ -233,6 +233,12 @@ public class HttpResponseStatus implements Comparable<HttpResponseStatus> {
|
|||||||
*/
|
*/
|
||||||
public static final HttpResponseStatus UPGRADE_REQUIRED = new HttpResponseStatus(426, "Upgrade Required");
|
public static final HttpResponseStatus UPGRADE_REQUIRED = new HttpResponseStatus(426, "Upgrade Required");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 431 Request Header Fields Too Large (RFC6585)
|
||||||
|
*/
|
||||||
|
public static final HttpResponseStatus REQUEST_HEADER_FIELDS_TOO_LARGE =
|
||||||
|
new HttpResponseStatus(431, "Request Header Fields Too Large");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 500 Internal Server Error
|
* 500 Internal Server Error
|
||||||
*/
|
*/
|
||||||
|
@ -28,6 +28,7 @@ public class DefaultSpdyHeadersFrame extends DefaultSpdyStreamFrame
|
|||||||
implements SpdyHeadersFrame {
|
implements SpdyHeadersFrame {
|
||||||
|
|
||||||
private boolean invalid;
|
private boolean invalid;
|
||||||
|
private boolean truncated;
|
||||||
private final SpdyHeaders headers = new SpdyHeaders();
|
private final SpdyHeaders headers = new SpdyHeaders();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -47,6 +48,14 @@ public class DefaultSpdyHeadersFrame extends DefaultSpdyStreamFrame
|
|||||||
invalid = true;
|
invalid = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isTruncated() {
|
||||||
|
return truncated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTruncated() {
|
||||||
|
truncated = true;
|
||||||
|
}
|
||||||
|
|
||||||
public void addHeader(final String name, final Object value) {
|
public void addHeader(final String name, final Object value) {
|
||||||
headers.addHeader(name, value);
|
headers.addHeader(name, value);
|
||||||
}
|
}
|
||||||
|
@ -243,7 +243,8 @@ public class SpdyFrameDecoder extends FrameDecoder {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spdyHeadersFrame != null && spdyHeadersFrame.isInvalid()) {
|
if (spdyHeadersFrame != null &&
|
||||||
|
(spdyHeadersFrame.isInvalid() || spdyHeadersFrame.isTruncated())) {
|
||||||
Object frame = spdyHeadersFrame;
|
Object frame = spdyHeadersFrame;
|
||||||
spdyHeadersFrame = null;
|
spdyHeadersFrame = null;
|
||||||
if (length == 0) {
|
if (length == 0) {
|
||||||
|
@ -94,8 +94,8 @@ public class SpdyHeaderBlockRawDecoder extends SpdyHeaderBlockDecoder {
|
|||||||
}
|
}
|
||||||
headerSize += nameLength;
|
headerSize += nameLength;
|
||||||
if (headerSize > maxHeaderSize) {
|
if (headerSize > maxHeaderSize) {
|
||||||
throw new TooLongFrameException(
|
frame.setTruncated();
|
||||||
"Header block exceeds " + maxHeaderSize);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to read name
|
// Try to read name
|
||||||
@ -143,8 +143,8 @@ public class SpdyHeaderBlockRawDecoder extends SpdyHeaderBlockDecoder {
|
|||||||
|
|
||||||
headerSize += valueLength;
|
headerSize += valueLength;
|
||||||
if (headerSize > maxHeaderSize) {
|
if (headerSize > maxHeaderSize) {
|
||||||
throw new TooLongFrameException(
|
frame.setTruncated();
|
||||||
"Header block exceeds " + maxHeaderSize);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to read value
|
// Try to read value
|
||||||
|
@ -35,6 +35,17 @@ public interface SpdyHeadersFrame extends SpdyStreamFrame {
|
|||||||
*/
|
*/
|
||||||
void setInvalid();
|
void setInvalid();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if this header block has been truncated due to
|
||||||
|
* length restrictions.
|
||||||
|
*/
|
||||||
|
boolean isTruncated();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark this header block as truncated.
|
||||||
|
*/
|
||||||
|
void setTruncated();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the header value with the specified header name. If there is
|
* Returns the header value with the specified header name. If there is
|
||||||
* more than one header value for the specified header name, the first
|
* more than one header value for the specified header name, the first
|
||||||
|
@ -126,6 +126,14 @@ public class SpdyHttpDecoder extends OneToOneDecoder {
|
|||||||
Channels.write(ctx, Channels.future(channel), spdyRstStreamFrame);
|
Channels.write(ctx, Channels.future(channel), spdyRstStreamFrame);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If a client receives a response with a truncated header block,
|
||||||
|
// reply with a RST_STREAM with error code INTERNAL_ERROR.
|
||||||
|
if (spdySynStreamFrame.isTruncated()) {
|
||||||
|
SpdyRstStreamFrame spdyRstStreamFrame =
|
||||||
|
new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.INTERNAL_ERROR);
|
||||||
|
Channels.write(ctx, Channels.future(channel), spdyRstStreamFrame);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
HttpResponse httpResponse = createHttpResponse(spdyVersion, spdySynStreamFrame);
|
HttpResponse httpResponse = createHttpResponse(spdyVersion, spdySynStreamFrame);
|
||||||
|
|
||||||
@ -149,6 +157,19 @@ public class SpdyHttpDecoder extends OneToOneDecoder {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// SYN_STREAM frames initiated by the client are HTTP requests
|
// SYN_STREAM frames initiated by the client are HTTP requests
|
||||||
|
|
||||||
|
// If a client sends a request with a truncated header block, the server must
|
||||||
|
// reply with a HTTP 431 REQUEST HEADER FIELDS TOO LARGE reply.
|
||||||
|
if (spdySynStreamFrame.isTruncated()) {
|
||||||
|
SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamId);
|
||||||
|
spdySynReplyFrame.setLast(true);
|
||||||
|
SpdyHeaders.setStatus(spdyVersion,
|
||||||
|
spdySynReplyFrame,
|
||||||
|
HttpResponseStatus.REQUEST_HEADER_FIELDS_TOO_LARGE);
|
||||||
|
SpdyHeaders.setVersion(spdyVersion, spdySynReplyFrame, HttpVersion.HTTP_1_0);
|
||||||
|
Channels.write(ctx, Channels.future(channel), spdySynReplyFrame);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
HttpRequest httpRequest = createHttpRequest(spdyVersion, spdySynStreamFrame);
|
HttpRequest httpRequest = createHttpRequest(spdyVersion, spdySynStreamFrame);
|
||||||
|
|
||||||
@ -178,6 +199,14 @@ public class SpdyHttpDecoder extends OneToOneDecoder {
|
|||||||
SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
|
SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
|
||||||
int streamId = spdySynReplyFrame.getStreamId();
|
int streamId = spdySynReplyFrame.getStreamId();
|
||||||
|
|
||||||
|
// If a client receives a SYN_REPLY with a truncated header block,
|
||||||
|
// reply with a RST_STREAM frame with error code INTERNAL_ERROR.
|
||||||
|
if (spdySynReplyFrame.isTruncated()) {
|
||||||
|
SpdyRstStreamFrame spdyRstStreamFrame =
|
||||||
|
new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.INTERNAL_ERROR);
|
||||||
|
Channels.write(ctx, Channels.future(channel), spdyRstStreamFrame);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
HttpResponse httpResponse = createHttpResponse(spdyVersion, spdySynReplyFrame);
|
HttpResponse httpResponse = createHttpResponse(spdyVersion, spdySynReplyFrame);
|
||||||
|
|
||||||
@ -210,8 +239,11 @@ public class SpdyHttpDecoder extends OneToOneDecoder {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Map.Entry<String, String> e: spdyHeadersFrame.getHeaders()) {
|
// Ignore trailers in a truncated HEADERS frame.
|
||||||
httpMessage.addHeader(e.getKey(), e.getValue());
|
if (!spdyHeadersFrame.isTruncated()) {
|
||||||
|
for (Map.Entry<String, String> e : spdyHeadersFrame.getHeaders()) {
|
||||||
|
httpMessage.addHeader(e.getKey(), e.getValue());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spdyHeadersFrame.isLast()) {
|
if (spdyHeadersFrame.isLast()) {
|
||||||
|
@ -0,0 +1,120 @@
|
|||||||
|
package org.jboss.netty.handler.codec.spdy;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
import org.jboss.netty.bootstrap.ClientBootstrap;
|
||||||
|
import org.jboss.netty.bootstrap.ServerBootstrap;
|
||||||
|
import org.jboss.netty.channel.*;
|
||||||
|
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
|
||||||
|
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
|
||||||
|
import org.jboss.netty.util.TestUtil;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.jboss.netty.handler.codec.spdy.SpdyCodecUtil.*;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
public class SpdyFrameDecoderTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTooLargeHeaderNameOnSynStreamRequest() throws Exception {
|
||||||
|
for (int version = SPDY_MIN_VERSION; version <= SPDY_MAX_VERSION; version++) {
|
||||||
|
List<Integer> headerSizes = Arrays.asList(90, 900);
|
||||||
|
for (int maxHeaderSize : headerSizes) { // 90 catches the header name, 900 the value
|
||||||
|
SpdyHeadersFrame frame = new DefaultSpdySynStreamFrame(1, 0, (byte) 0);
|
||||||
|
addHeader(frame, 100, 1000);
|
||||||
|
CaptureHandler captureHandler = new CaptureHandler();
|
||||||
|
ServerBootstrap sb = new ServerBootstrap(
|
||||||
|
newServerSocketChannelFactory(Executors.newCachedThreadPool()));
|
||||||
|
ClientBootstrap cb = new ClientBootstrap(
|
||||||
|
newClientSocketChannelFactory(Executors.newCachedThreadPool()));
|
||||||
|
|
||||||
|
sb.getPipeline().addLast("decoder", new SpdyFrameDecoder(version, 10000, maxHeaderSize));
|
||||||
|
sb.getPipeline().addLast("sessionHandler", new SpdySessionHandler(version, true));
|
||||||
|
sb.getPipeline().addLast("handler", captureHandler);
|
||||||
|
|
||||||
|
cb.getPipeline().addLast("encoder", new SpdyFrameEncoder(version));
|
||||||
|
|
||||||
|
Channel sc = sb.bind(new InetSocketAddress(0));
|
||||||
|
int port = ((InetSocketAddress) sc.getLocalAddress()).getPort();
|
||||||
|
|
||||||
|
ChannelFuture ccf = cb.connect(new InetSocketAddress(TestUtil.getLocalHost(), port));
|
||||||
|
assertTrue(ccf.awaitUninterruptibly().isSuccess());
|
||||||
|
Channel cc = ccf.getChannel();
|
||||||
|
|
||||||
|
sendAndWaitForFrame(cc, frame, captureHandler);
|
||||||
|
|
||||||
|
assertNotNull("version " + version + ", not null message",
|
||||||
|
captureHandler.message);
|
||||||
|
String message = "version " + version + ", should be SpdyHeadersFrame, was " +
|
||||||
|
captureHandler.message.getClass();
|
||||||
|
assertTrue(
|
||||||
|
message,
|
||||||
|
captureHandler.message instanceof SpdyHeadersFrame);
|
||||||
|
SpdyHeadersFrame writtenFrame = (SpdyHeadersFrame) captureHandler.message;
|
||||||
|
|
||||||
|
assertTrue("should be truncated", writtenFrame.isTruncated());
|
||||||
|
assertFalse("should not be invalid", writtenFrame.isInvalid());
|
||||||
|
|
||||||
|
sc.close().awaitUninterruptibly();
|
||||||
|
cb.shutdown();
|
||||||
|
sb.shutdown();
|
||||||
|
cb.releaseExternalResources();
|
||||||
|
sb.releaseExternalResources();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendAndWaitForFrame(Channel cc, SpdyFrame frame, CaptureHandler handler) {
|
||||||
|
cc.write(frame);
|
||||||
|
long theFuture = System.currentTimeMillis() + 3000;
|
||||||
|
while (handler.message == null && System.currentTimeMillis() < theFuture) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(1);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// Ignore.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addHeader(SpdyHeadersFrame frame, int headerNameSize, int headerValueSize) {
|
||||||
|
frame.addHeader("k", "v");
|
||||||
|
StringBuilder headerName = new StringBuilder();
|
||||||
|
for (int i = 0; i < headerNameSize; i++) {
|
||||||
|
headerName.append('h');
|
||||||
|
}
|
||||||
|
StringBuilder headerValue = new StringBuilder();
|
||||||
|
for (int i = 0; i < headerValueSize; i++) {
|
||||||
|
headerValue.append('a');
|
||||||
|
}
|
||||||
|
frame.addHeader(headerName.toString(), headerValue.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ChannelFactory newClientSocketChannelFactory(Executor executor) {
|
||||||
|
return new NioClientSocketChannelFactory(executor, executor);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ChannelFactory newServerSocketChannelFactory(Executor executor) {
|
||||||
|
return new NioServerSocketChannelFactory(executor, executor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class CaptureHandler extends SimpleChannelUpstreamHandler {
|
||||||
|
public volatile Object message;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
|
||||||
|
this.message = e.getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exceptionCaught(
|
||||||
|
ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
|
||||||
|
System.out.println(e.getCause());
|
||||||
|
e.getCause().printStackTrace();
|
||||||
|
message = e.getCause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user