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");
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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()) {
|
||||
|
@ -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