SPDY: handle too large header blocks

This commit is contained in:
Jeff Hodges 2013-06-18 15:07:36 -07:00 committed by Trustin Lee
parent 6aa4d147d8
commit 55360d2bc8
7 changed files with 186 additions and 7 deletions

View File

@ -233,6 +233,12 @@ public class HttpResponseStatus implements Comparable<HttpResponseStatus> {
*/
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
*/

View File

@ -28,6 +28,7 @@ public class DefaultSpdyHeadersFrame extends DefaultSpdyStreamFrame
implements SpdyHeadersFrame {
private boolean invalid;
private boolean truncated;
private final SpdyHeaders headers = new SpdyHeaders();
/**
@ -47,6 +48,14 @@ public class DefaultSpdyHeadersFrame extends DefaultSpdyStreamFrame
invalid = true;
}
public boolean isTruncated() {
return truncated;
}
public void setTruncated() {
truncated = true;
}
public void addHeader(final String name, final Object value) {
headers.addHeader(name, value);
}

View File

@ -243,7 +243,8 @@ public class SpdyFrameDecoder extends FrameDecoder {
return null;
}
if (spdyHeadersFrame != null && spdyHeadersFrame.isInvalid()) {
if (spdyHeadersFrame != null &&
(spdyHeadersFrame.isInvalid() || spdyHeadersFrame.isTruncated())) {
Object frame = spdyHeadersFrame;
spdyHeadersFrame = null;
if (length == 0) {

View File

@ -94,8 +94,8 @@ public class SpdyHeaderBlockRawDecoder extends SpdyHeaderBlockDecoder {
}
headerSize += nameLength;
if (headerSize > maxHeaderSize) {
throw new TooLongFrameException(
"Header block exceeds " + maxHeaderSize);
frame.setTruncated();
return;
}
// Try to read name
@ -143,8 +143,8 @@ public class SpdyHeaderBlockRawDecoder extends SpdyHeaderBlockDecoder {
headerSize += valueLength;
if (headerSize > maxHeaderSize) {
throw new TooLongFrameException(
"Header block exceeds " + maxHeaderSize);
frame.setTruncated();
return;
}
// Try to read value

View File

@ -35,6 +35,17 @@ public interface SpdyHeadersFrame extends SpdyStreamFrame {
*/
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
* more than one header value for the specified header name, the first

View File

@ -126,6 +126,14 @@ public class SpdyHttpDecoder extends OneToOneDecoder {
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 {
HttpResponse httpResponse = createHttpResponse(spdyVersion, spdySynStreamFrame);
@ -149,6 +157,19 @@ public class SpdyHttpDecoder extends OneToOneDecoder {
}
} else {
// 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 {
HttpRequest httpRequest = createHttpRequest(spdyVersion, spdySynStreamFrame);
@ -178,6 +199,14 @@ public class SpdyHttpDecoder extends OneToOneDecoder {
SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
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 {
HttpResponse httpResponse = createHttpResponse(spdyVersion, spdySynReplyFrame);
@ -210,8 +239,11 @@ public class SpdyHttpDecoder extends OneToOneDecoder {
return null;
}
for (Map.Entry<String, String> e: spdyHeadersFrame.getHeaders()) {
httpMessage.addHeader(e.getKey(), e.getValue());
// Ignore trailers in a truncated HEADERS frame.
if (!spdyHeadersFrame.isTruncated()) {
for (Map.Entry<String, String> e : spdyHeadersFrame.getHeaders()) {
httpMessage.addHeader(e.getKey(), e.getValue());
}
}
if (spdyHeadersFrame.isLast()) {

View File

@ -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();
}
}
}