From 7049d8debb74c05a542233f5b28b1655b07da306 Mon Sep 17 00:00:00 2001 From: Brendt Lucas Date: Fri, 4 Sep 2015 22:17:29 +0100 Subject: [PATCH] Add validateHeaders and headersToLowerCase options for SPDY Motivation: Related to issue #4185. HTTP has the option to disable header validation for optimisation purposes. Introduce the same option for SPDY headers. Also, optimise SpdyHttpEncoder by allowing the user to specify whether or not the encoder needs to convert header names to lowercase. Modifications: Added flags for validation and conversion. Result: SpdyHeader validation and conversion can be disabled. --- .../codec/spdy/DefaultSpdyHeaders.java | 22 ++++++++-- .../codec/spdy/DefaultSpdyHeadersFrame.java | 13 +++++- .../codec/spdy/DefaultSpdySynReplyFrame.java | 10 +++++ .../codec/spdy/DefaultSpdySynStreamFrame.java | 14 +++++- .../handler/codec/spdy/SpdyFrameCodec.java | 44 +++++++++++++++---- .../handler/codec/spdy/SpdyHttpEncoder.java | 37 +++++++++++++--- 6 files changed, 120 insertions(+), 20 deletions(-) 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;