diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyHeaders.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyHeaders.java index 4ebc75f975..13a3ae244d 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyHeaders.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyHeaders.java @@ -36,7 +36,13 @@ public class DefaultSpdyHeaders extends DefaultHeaders implements }; public DefaultSpdyHeaders() { - super(CASE_INSENSITIVE_HASHER, HeaderValueConverterAndValidator.INSTANCE, SpydNameValidator); + this(true); + } + + public DefaultSpdyHeaders(boolean validate) { + super(CASE_INSENSITIVE_HASHER, + validate ? HeaderValueConverterAndValidator.INSTANCE : HeaderValueConverter.INSTANCE, + validate ? SpydNameValidator : NameValidator.NOT_NULL); } @Override @@ -269,8 +275,8 @@ public class DefaultSpdyHeaders extends DefaultHeaders implements ignoreCase ? CASE_INSENSITIVE_HASHER : CASE_SENSITIVE_HASHER); } - private static final class HeaderValueConverterAndValidator extends CharSequenceValueConverter { - public static final HeaderValueConverterAndValidator INSTANCE = new HeaderValueConverterAndValidator(); + private static class HeaderValueConverter extends CharSequenceValueConverter { + public static final HeaderValueConverter INSTANCE = new HeaderValueConverter(); @Override public CharSequence convertObject(Object value) { @@ -281,6 +287,16 @@ public class DefaultSpdyHeaders extends DefaultHeaders implements seq = value.toString(); } + return seq; + } + } + + private static final class HeaderValueConverterAndValidator extends HeaderValueConverter { + public static final HeaderValueConverterAndValidator INSTANCE = new HeaderValueConverterAndValidator(); + + @Override + public CharSequence convertObject(Object value) { + final CharSequence seq = super.convertObject(value); SpdyCodecUtil.validateHeaderValue(seq); return seq; } diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyHeadersFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyHeadersFrame.java index a74934899e..f177144393 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyHeadersFrame.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyHeadersFrame.java @@ -27,7 +27,7 @@ public class DefaultSpdyHeadersFrame extends DefaultSpdyStreamFrame private boolean invalid; private boolean truncated; - private final SpdyHeaders headers = new DefaultSpdyHeaders(); + private final SpdyHeaders headers; /** * Creates a new instance. @@ -35,7 +35,18 @@ public class DefaultSpdyHeadersFrame extends DefaultSpdyStreamFrame * @param streamId the Stream-ID of this frame */ public DefaultSpdyHeadersFrame(int streamId) { + this(streamId, true); + } + + /** + * Creates a new instance. + * + * @param streamId the Stream-ID of this frame + * @param validate validate the header names and values when adding them to the {@link SpdyHeaders} + */ + public DefaultSpdyHeadersFrame(int streamId, boolean validate) { super(streamId); + headers = new DefaultSpdyHeaders(validate); } @Override diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdySynReplyFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdySynReplyFrame.java index 0eacb9ee95..7efc905641 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdySynReplyFrame.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdySynReplyFrame.java @@ -32,6 +32,16 @@ public class DefaultSpdySynReplyFrame extends DefaultSpdyHeadersFrame super(streamId); } + /** + * Creates a new instance. + * + * @param streamId the Stream-ID of this frame + * @param validateHeaders validate the header names and values when adding them to the {@link SpdyHeaders} + */ + public DefaultSpdySynReplyFrame(int streamId, boolean validateHeaders) { + super(streamId, validateHeaders); + } + @Override public SpdySynReplyFrame setStreamId(int streamId) { super.setStreamId(streamId); diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdySynStreamFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdySynStreamFrame.java index a4495977e7..f8adc1c5f1 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdySynStreamFrame.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdySynStreamFrame.java @@ -35,7 +35,19 @@ public class DefaultSpdySynStreamFrame extends DefaultSpdyHeadersFrame * @param priority the priority of the stream */ public DefaultSpdySynStreamFrame(int streamId, int associatedStreamId, byte priority) { - super(streamId); + this(streamId, associatedStreamId, priority, true); + } + + /** + * Creates a new instance. + * + * @param streamId the Stream-ID of this frame + * @param associatedStreamId the Associated-To-Stream-ID of this frame + * @param priority the priority of the stream + * @param validateHeaders validate the header names and values when adding them to the {@link SpdyHeaders} + */ + public DefaultSpdySynStreamFrame(int streamId, int associatedStreamId, byte priority, boolean validateHeaders) { + super(streamId, validateHeaders); setAssociatedStreamId(associatedStreamId); setPriority(priority); } diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameCodec.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameCodec.java index 2595d31b7f..057c7507a1 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameCodec.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameCodec.java @@ -47,35 +47,62 @@ public class SpdyFrameCodec extends ByteToMessageDecoder private ChannelHandlerContext ctx; private boolean read; + private final boolean validateHeaders; /** - * Creates a new instance with the specified {@code version} and + * Creates a new instance with the specified {@code version}, + * {@code validateHeaders (true)}, and * the default decoder and encoder options * ({@code maxChunkSize (8192)}, {@code maxHeaderSize (16384)}, * {@code compressionLevel (6)}, {@code windowBits (15)}, * and {@code memLevel (8)}). */ public SpdyFrameCodec(SpdyVersion version) { - this(version, 8192, 16384, 6, 15, 8); + this(version, true); } /** - * Creates a new instance with the specified decoder and encoder options. + * Creates a new instance with the specified {@code version}, + * {@code validateHeaders}, and + * the default decoder and encoder options + * ({@code maxChunkSize (8192)}, {@code maxHeaderSize (16384)}, + * {@code compressionLevel (6)}, {@code windowBits (15)}, + * and {@code memLevel (8)}). + */ + public SpdyFrameCodec(SpdyVersion version, boolean validateHeaders) { + this(version, 8192, 16384, 6, 15, 8, validateHeaders); + } + + /** + * Creates a new instance with the specified {@code version}, {@code validateHeaders (true)}, + * decoder and encoder options. */ public SpdyFrameCodec( SpdyVersion version, int maxChunkSize, int maxHeaderSize, int compressionLevel, int windowBits, int memLevel) { + this(version, maxChunkSize, maxHeaderSize, compressionLevel, windowBits, memLevel, true); + } + + /** + * Creates a new instance with the specified {@code version}, {@code validateHeaders}, + * decoder and encoder options. + */ + public SpdyFrameCodec( + SpdyVersion version, int maxChunkSize, int maxHeaderSize, + int compressionLevel, int windowBits, int memLevel, boolean validateHeaders) { this(version, maxChunkSize, SpdyHeaderBlockDecoder.newInstance(version, maxHeaderSize), - SpdyHeaderBlockEncoder.newInstance(version, compressionLevel, windowBits, memLevel)); + SpdyHeaderBlockEncoder.newInstance(version, compressionLevel, windowBits, memLevel), validateHeaders); } protected SpdyFrameCodec(SpdyVersion version, int maxChunkSize, - SpdyHeaderBlockDecoder spdyHeaderBlockDecoder, SpdyHeaderBlockEncoder spdyHeaderBlockEncoder) { + SpdyHeaderBlockDecoder spdyHeaderBlockDecoder, SpdyHeaderBlockEncoder spdyHeaderBlockEncoder, + boolean validateHeaders) { spdyFrameDecoder = new SpdyFrameDecoder(version, this, maxChunkSize); spdyFrameEncoder = new SpdyFrameEncoder(version); this.spdyHeaderBlockDecoder = spdyHeaderBlockDecoder; this.spdyHeaderBlockEncoder = spdyHeaderBlockEncoder; + this.validateHeaders = validateHeaders; } @Override @@ -274,7 +301,8 @@ public class SpdyFrameCodec extends ByteToMessageDecoder @Override public void readSynStreamFrame( int streamId, int associatedToStreamId, byte priority, boolean last, boolean unidirectional) { - SpdySynStreamFrame spdySynStreamFrame = new DefaultSpdySynStreamFrame(streamId, associatedToStreamId, priority); + SpdySynStreamFrame spdySynStreamFrame = + new DefaultSpdySynStreamFrame(streamId, associatedToStreamId, priority, validateHeaders); spdySynStreamFrame.setLast(last); spdySynStreamFrame.setUnidirectional(unidirectional); spdyHeadersFrame = spdySynStreamFrame; @@ -282,7 +310,7 @@ public class SpdyFrameCodec extends ByteToMessageDecoder @Override public void readSynReplyFrame(int streamId, boolean last) { - SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamId); + SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamId, validateHeaders); spdySynReplyFrame.setLast(last); spdyHeadersFrame = spdySynReplyFrame; } @@ -335,7 +363,7 @@ public class SpdyFrameCodec extends ByteToMessageDecoder @Override public void readHeadersFrame(int streamId, boolean last) { - spdyHeadersFrame = new DefaultSpdyHeadersFrame(streamId); + spdyHeadersFrame = new DefaultSpdyHeadersFrame(streamId, validateHeaders); spdyHeadersFrame.setLast(last); } diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpEncoder.java index a2c47aa1e9..d3c5d58247 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpEncoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpEncoder.java @@ -123,15 +123,32 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder { private int currentStreamId; + private final boolean validateHeaders; + private final boolean headersToLowerCase; + /** * Creates a new instance. * * @param version the protocol version */ public SpdyHttpEncoder(SpdyVersion version) { + this(version, true, true); + } + + /** + * Creates a new instance. + * + * @param version the protocol version + * @param headersToLowerCase convert header names to lowercase. In a controlled environment, + * one can disable the conversion. + * @param validateHeaders validate the header names and values when adding them to the {@link SpdyHeaders} + */ + public SpdyHttpEncoder(SpdyVersion version, boolean headersToLowerCase, boolean validateHeaders) { if (version == null) { throw new NullPointerException("version"); } + this.headersToLowerCase = headersToLowerCase; + this.validateHeaders = validateHeaders; } @Override @@ -172,12 +189,14 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder { out.add(spdyDataFrame); } else { // Create SPDY HEADERS frame out of trailers - SpdyHeadersFrame spdyHeadersFrame = new DefaultSpdyHeadersFrame(currentStreamId); + SpdyHeadersFrame spdyHeadersFrame = new DefaultSpdyHeadersFrame(currentStreamId, validateHeaders); spdyHeadersFrame.setLast(true); Iterator> itr = trailers.iteratorCharSequence(); while (itr.hasNext()) { Map.Entry entry = itr.next(); - spdyHeadersFrame.headers().add(AsciiString.of(entry.getKey()).toLowerCase(), entry.getValue()); + final CharSequence headerName = + headersToLowerCase ? AsciiString.of(entry.getKey()).toLowerCase() : entry.getKey(); + spdyHeadersFrame.headers().add(headerName, entry.getValue()); } // Write DATA frame and append HEADERS frame @@ -217,7 +236,7 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder { httpHeaders.remove(HttpHeaderNames.TRANSFER_ENCODING); SpdySynStreamFrame spdySynStreamFrame = - new DefaultSpdySynStreamFrame(streamId, associatedToStreamId, priority); + new DefaultSpdySynStreamFrame(streamId, associatedToStreamId, priority, validateHeaders); // Unfold the first line of the message into name/value pairs SpdyHeaders frameHeaders = spdySynStreamFrame.headers(); @@ -240,7 +259,9 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder { Iterator> itr = httpHeaders.iteratorCharSequence(); while (itr.hasNext()) { Map.Entry entry = itr.next(); - frameHeaders.add(AsciiString.of(entry.getKey()).toLowerCase(), entry.getValue()); + final CharSequence headerName = + headersToLowerCase ? AsciiString.of(entry.getKey()).toLowerCase() : entry.getKey(); + frameHeaders.add(headerName, entry.getValue()); } currentStreamId = spdySynStreamFrame.streamId(); if (associatedToStreamId == 0) { @@ -268,9 +289,9 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder { SpdyHeadersFrame spdyHeadersFrame; if (SpdyCodecUtil.isServerId(streamId)) { - spdyHeadersFrame = new DefaultSpdyHeadersFrame(streamId); + spdyHeadersFrame = new DefaultSpdyHeadersFrame(streamId, validateHeaders); } else { - spdyHeadersFrame = new DefaultSpdySynReplyFrame(streamId); + spdyHeadersFrame = new DefaultSpdySynReplyFrame(streamId, validateHeaders); } SpdyHeaders frameHeaders = spdyHeadersFrame.headers(); // Unfold the first line of the response into name/value pairs @@ -281,7 +302,9 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder { Iterator> itr = httpHeaders.iteratorCharSequence(); while (itr.hasNext()) { Map.Entry entry = itr.next(); - spdyHeadersFrame.headers().add(AsciiString.of(entry.getKey()).toLowerCase(), entry.getValue()); + final CharSequence headerName = + headersToLowerCase ? AsciiString.of(entry.getKey()).toLowerCase() : entry.getKey(); + spdyHeadersFrame.headers().add(headerName, entry.getValue()); } currentStreamId = streamId;