Projects
Mega:23.09
netty
_service:tar_scm:CVE-2021-21295-pre3.patch
Sign Up
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File _service:tar_scm:CVE-2021-21295-pre3.patch of Package netty
From 9557c88da2aaa49b3c3fd7525462dc0694681c19 Mon Sep 17 00:00:00 2001 From: Bennett Lynch <bennett.lynch@gmail.com> Date: Mon, 6 Jul 2020 01:25:13 -0700 Subject: [PATCH] Add option to HttpObjectDecoder to allow duplicate Content-Lengths (#10349) Motivation: Since https://github.com/netty/netty/pull/9865 (Netty 4.1.44) the default behavior of the HttpObjectDecoder has been to reject any HTTP message that is found to have multiple Content-Length headers when decoding. This behavior is well-justified as per the risks outlined in https://github.com/netty/netty/issues/9861, however, we can see from the cited RFC section that there are multiple possible options offered for responding to this scenario: > If a message is received that has multiple Content-Length header > fields with field-values consisting of the same decimal value, or a > single Content-Length header field with a field value containing a > list of identical decimal values (e.g., "Content-Length: 42, 42"), > indicating that duplicate Content-Length header fields have been > generated or combined by an upstream message processor, then the > recipient MUST either reject the message as invalid or replace the > duplicated field-values with a single valid Content-Length field > containing that decimal value prior to determining the message body > length or forwarding the message. https://tools.ietf.org/html/rfc7230#section-3.3.2 Netty opted for the first option (rejecting as invalid), which seems like the safest, but the second option (replacing duplicate values with a single value) is also valid behavior. Modifications: * Introduce "allowDuplicateContentLengths" parameter to HttpObjectDecoder (defaulting to false). * When set to true, will allow multiple Content-Length headers only if they are all the same value. The duplicated field-values will be replaced with a single valid Content-Length field. * Add new parameterized test class for testing different variations of multiple Content-Length headers. Result: This is a backwards-compatible change with no functional change to the existing behavior. Note that the existing logic would result in NumberFormatExceptions for header values like "Content-Length: 42, 42". The new logic correctly reports these as IllegalArgumentException with the proper error message. Additionally note that this behavior is only applied to HTTP/1.1, but I suspect that we may want to expand that to include HTTP/1.0 as well... That behavior is not modified here to minimize the scope of this change. --- .../handler/codec/http/HttpClientCodec.java | 35 ++++- .../handler/codec/http/HttpObjectDecoder.java | 75 ++++++++- .../codec/http/HttpRequestDecoder.java | 7 + .../codec/http/HttpResponseDecoder.java | 7 + .../handler/codec/http/HttpServerCodec.java | 16 ++ .../MultipleContentLengthHeadersTest.java | 144 ++++++++++++++++++ 6 files changed, 268 insertions(+), 16 deletions(-) create mode 100644 codec-http/src/test/java/io/netty/handler/codec/http/MultipleContentLengthHeadersTest.java diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpClientCodec.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpClientCodec.java index a832bfdff3..9a99fff97f 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpClientCodec.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpClientCodec.java @@ -28,9 +28,11 @@ import java.util.List; import java.util.Queue; import java.util.concurrent.atomic.AtomicLong; +import static io.netty.handler.codec.http.HttpObjectDecoder.DEFAULT_ALLOW_DUPLICATE_CONTENT_LENGTHS; import static io.netty.handler.codec.http.HttpObjectDecoder.DEFAULT_MAX_CHUNK_SIZE; import static io.netty.handler.codec.http.HttpObjectDecoder.DEFAULT_MAX_HEADER_SIZE; import static io.netty.handler.codec.http.HttpObjectDecoder.DEFAULT_MAX_INITIAL_LINE_LENGTH; +import static io.netty.handler.codec.http.HttpObjectDecoder.DEFAULT_VALIDATE_HEADERS; /** * A combination of {@link HttpRequestEncoder} and {@link HttpResponseDecoder} @@ -48,6 +50,8 @@ import static io.netty.handler.codec.http.HttpObjectDecoder.DEFAULT_MAX_INITIAL_ */ public final class HttpClientCodec extends CombinedChannelDuplexHandler<HttpResponseDecoder, HttpRequestEncoder> implements HttpClientUpgradeHandler.SourceCodec { + public static final boolean DEFAULT_FAIL_ON_MISSING_RESPONSE = false; + public static final boolean DEFAULT_PARSE_HTTP_AFTER_CONNECT_REQUEST = false; /** A queue that is used for correlating a request and a response. */ private final Queue<HttpMethod> queue = new ArrayDeque<HttpMethod>(); @@ -65,14 +69,15 @@ public final class HttpClientCodec extends CombinedChannelDuplexHandler<HttpResp * {@code maxChunkSize (8192)}). */ public HttpClientCodec() { - this(DEFAULT_MAX_INITIAL_LINE_LENGTH, DEFAULT_MAX_HEADER_SIZE, DEFAULT_MAX_CHUNK_SIZE, false); + this(DEFAULT_MAX_INITIAL_LINE_LENGTH, DEFAULT_MAX_HEADER_SIZE, DEFAULT_MAX_CHUNK_SIZE, + DEFAULT_FAIL_ON_MISSING_RESPONSE); } /** * Creates a new instance with the specified decoder options. */ public HttpClientCodec(int maxInitialLineLength, int maxHeaderSize, int maxChunkSize) { - this(maxInitialLineLength, maxHeaderSize, maxChunkSize, false); + this(maxInitialLineLength, maxHeaderSize, maxChunkSize, DEFAULT_FAIL_ON_MISSING_RESPONSE); } /** @@ -80,7 +85,7 @@ public final class HttpClientCodec extends CombinedChannelDuplexHandler<HttpResp */ public HttpClientCodec( int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean failOnMissingResponse) { - this(maxInitialLineLength, maxHeaderSize, maxChunkSize, failOnMissingResponse, true); + this(maxInitialLineLength, maxHeaderSize, maxChunkSize, failOnMissingResponse, DEFAULT_VALIDATE_HEADERS); } /** @@ -89,7 +94,8 @@ public final class HttpClientCodec extends CombinedChannelDuplexHandler<HttpResp public HttpClientCodec( int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean failOnMissingResponse, boolean validateHeaders) { - this(maxInitialLineLength, maxHeaderSize, maxChunkSize, failOnMissingResponse, validateHeaders, false); + this(maxInitialLineLength, maxHeaderSize, maxChunkSize, failOnMissingResponse, validateHeaders, + DEFAULT_PARSE_HTTP_AFTER_CONNECT_REQUEST); } /** @@ -110,7 +116,7 @@ public final class HttpClientCodec extends CombinedChannelDuplexHandler<HttpResp int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean failOnMissingResponse, boolean validateHeaders, int initialBufferSize) { this(maxInitialLineLength, maxHeaderSize, maxChunkSize, failOnMissingResponse, validateHeaders, - initialBufferSize, false); + initialBufferSize, DEFAULT_PARSE_HTTP_AFTER_CONNECT_REQUEST); } /** @@ -119,7 +125,19 @@ public final class HttpClientCodec extends CombinedChannelDuplexHandler<HttpResp public HttpClientCodec( int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean failOnMissingResponse, boolean validateHeaders, int initialBufferSize, boolean parseHttpAfterConnectRequest) { - init(new Decoder(maxInitialLineLength, maxHeaderSize, maxChunkSize, validateHeaders, initialBufferSize), + this(maxInitialLineLength, maxHeaderSize, maxChunkSize, failOnMissingResponse, validateHeaders, + initialBufferSize, parseHttpAfterConnectRequest, DEFAULT_ALLOW_DUPLICATE_CONTENT_LENGTHS); + } + + /** + * Creates a new instance with the specified decoder options. + */ + public HttpClientCodec( + int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean failOnMissingResponse, + boolean validateHeaders, int initialBufferSize, boolean parseHttpAfterConnectRequest, + boolean allowDuplicateContentLengths) { + init(new Decoder(maxInitialLineLength, maxHeaderSize, maxChunkSize, validateHeaders, initialBufferSize, + allowDuplicateContentLengths), new Encoder()); this.parseHttpAfterConnectRequest = parseHttpAfterConnectRequest; this.failOnMissingResponse = failOnMissingResponse; @@ -186,8 +204,9 @@ public final class HttpClientCodec extends CombinedChannelDuplexHandler<HttpResp } Decoder(int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean validateHeaders, - int initialBufferSize) { - super(maxInitialLineLength, maxHeaderSize, maxChunkSize, validateHeaders, initialBufferSize); + int initialBufferSize, boolean allowDuplicateContentLengths) { + super(maxInitialLineLength, maxHeaderSize, maxChunkSize, validateHeaders, initialBufferSize, + allowDuplicateContentLengths); } @Override diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java index ed7caa7801..b52e36ac92 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java @@ -16,6 +16,7 @@ package io.netty.handler.codec.http; import static io.netty.util.internal.ObjectUtil.checkPositive; +import static io.netty.util.internal.StringUtil.COMMA; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; @@ -29,6 +30,7 @@ import io.netty.util.ByteProcessor; import io.netty.util.internal.AppendableCharSequence; import java.util.List; +import java.util.regex.Pattern; /** * Decodes {@link ByteBuf}s into {@link HttpMessage}s and @@ -37,10 +39,11 @@ import java.util.List; * <h3>Parameters that prevents excessive memory consumption</h3> * <table border="1"> * <tr> - * <th>Name</th><th>Meaning</th> + * <th>Name</th><th>Default value</th><th>Meaning</th> * </tr> * <tr> * <td>{@code maxInitialLineLength}</td> + * <td>{@value #DEFAULT_MAX_INITIAL_LINE_LENGTH}</td> * <td>The maximum length of the initial line * (e.g. {@code "GET / HTTP/1.0"} or {@code "HTTP/1.0 200 OK"}) * If the length of the initial line exceeds this value, a @@ -48,11 +51,13 @@ import java.util.List; * </tr> * <tr> * <td>{@code maxHeaderSize}</td> + * <td>{@value #DEFAULT_MAX_HEADER_SIZE}</td> * <td>The maximum length of all headers. If the sum of the length of each * header exceeds this value, a {@link TooLongFrameException} will be raised.</td> * </tr> * <tr> * <td>{@code maxChunkSize}</td> + * <td>{@value #DEFAULT_MAX_CHUNK_SIZE}</td> * <td>The maximum length of the content or each chunk. If the content length * (or the length of each chunk) exceeds this value, the content or chunk * will be split into multiple {@link HttpContent}s whose length is @@ -60,6 +65,21 @@ import java.util.List; * </tr> * </table> * + * <h3>Parameters that control parsing behavior</h3> + * <table border="1"> + * <tr> + * <th>Name</th><th>Default value</th><th>Meaning</th> + * </tr> + * <tr> + * <td>{@code allowDuplicateContentLengths}</td> + * <td>{@value #DEFAULT_ALLOW_DUPLICATE_CONTENT_LENGTHS}</td> + * <td>When set to {@code false}, will reject any messages that contain multiple Content-Length header fields. + * When set to {@code true}, will allow multiple Content-Length headers only if they are all the same decimal value. + * The duplicated field-values will be replaced with a single valid Content-Length field. + * See <a href="https://tools.ietf.org/html/rfc7230#section-3.3.2">RFC 7230, Section 3.3.2</a>.</td> + * </tr> + * </table> + * * <h3>Chunked Content</h3> * * If the content of an HTTP message is greater than {@code maxChunkSize} or @@ -108,12 +128,15 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder { public static final int DEFAULT_MAX_CHUNK_SIZE = 8192; public static final boolean DEFAULT_VALIDATE_HEADERS = true; public static final int DEFAULT_INITIAL_BUFFER_SIZE = 128; + public static final boolean DEFAULT_ALLOW_DUPLICATE_CONTENT_LENGTHS = false; private static final String EMPTY_VALUE = ""; + private static final Pattern COMMA_PATTERN = Pattern.compile(","); private final int maxChunkSize; private final boolean chunkedSupported; protected final boolean validateHeaders; + private final boolean allowDuplicateContentLengths; private final HeaderParser headerParser; private final LineParser lineParser; @@ -176,9 +199,20 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder { DEFAULT_INITIAL_BUFFER_SIZE); } + /** + * Creates a new instance with the specified parameters. + */ protected HttpObjectDecoder( int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean chunkedSupported, boolean validateHeaders, int initialBufferSize) { + this(maxInitialLineLength, maxHeaderSize, maxChunkSize, chunkedSupported, validateHeaders, initialBufferSize, + DEFAULT_ALLOW_DUPLICATE_CONTENT_LENGTHS); + } + + protected HttpObjectDecoder( + int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, + boolean chunkedSupported, boolean validateHeaders, int initialBufferSize, + boolean allowDuplicateContentLengths) { checkPositive(maxInitialLineLength, "maxInitialLineLength"); checkPositive(maxHeaderSize, "maxHeaderSize"); checkPositive(maxChunkSize, "maxChunkSize"); @@ -189,6 +223,7 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder { this.maxChunkSize = maxChunkSize; this.chunkedSupported = chunkedSupported; this.validateHeaders = validateHeaders; + this.allowDuplicateContentLengths = allowDuplicateContentLengths; } @Override @@ -602,10 +637,9 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder { name = null; value = null; - List<String> values = headers.getAll(HttpHeaderNames.CONTENT_LENGTH); - int contentLengthValuesCount = values.size(); + List<String> contentLengthFields = headers.getAll(HttpHeaderNames.CONTENT_LENGTH); - if (contentLengthValuesCount > 0) { + if (!contentLengthFields.isEmpty()) { // Guard against multiple Content-Length headers as stated in // https://tools.ietf.org/html/rfc7230#section-3.3.2: // @@ -619,17 +653,42 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder { // duplicated field-values with a single valid Content-Length field // containing that decimal value prior to determining the message body // length or forwarding the message. - if (contentLengthValuesCount > 1 && message.protocolVersion() == HttpVersion.HTTP_1_1) { - throw new IllegalArgumentException("Multiple Content-Length headers found"); + boolean multipleContentLengths = + contentLengthFields.size() > 1 || contentLengthFields.get(0).indexOf(COMMA) >= 0; + if (multipleContentLengths && message.protocolVersion() == HttpVersion.HTTP_1_1) { + if (allowDuplicateContentLengths) { + // Find and enforce that all Content-Length values are the same + String firstValue = null; + for (String field : contentLengthFields) { + String[] tokens = COMMA_PATTERN.split(field, -1); + for (String token : tokens) { + String trimmed = token.trim(); + if (firstValue == null) { + firstValue = trimmed; + } else if (!trimmed.equals(firstValue)) { + throw new IllegalArgumentException( + "Multiple Content-Length values found: " + contentLengthFields); + } + } + } + // Replace the duplicated field-values with a single valid Content-Length field + headers.set(HttpHeaderNames.CONTENT_LENGTH, firstValue); + contentLength = Long.parseLong(firstValue); + } else { + // Reject the message as invalid + throw new IllegalArgumentException( + "Multiple Content-Length values found: " + contentLengthFields); + } + } else { + contentLength = Long.parseLong(contentLengthFields.get(0)); } - contentLength = Long.parseLong(values.get(0)); } if (isContentAlwaysEmpty(message)) { HttpUtil.setTransferEncodingChunked(message, false); return State.SKIP_CONTROL_CHARS; } else if (HttpUtil.isTransferEncodingChunked(message)) { - if (contentLengthValuesCount > 0 && message.protocolVersion() == HttpVersion.HTTP_1_1) { + if (!contentLengthFields.isEmpty() && message.protocolVersion() == HttpVersion.HTTP_1_1) { handleTransferEncodingChunkedWithContentLength(message); } return State.READ_CHUNK_SIZE; diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpRequestDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpRequestDecoder.java index 70c1db5540..ba2d79ecb4 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpRequestDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpRequestDecoder.java @@ -82,6 +82,13 @@ public class HttpRequestDecoder extends HttpObjectDecoder { initialBufferSize); } + public HttpRequestDecoder( + int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean validateHeaders, + int initialBufferSize, boolean allowDuplicateContentLengths) { + super(maxInitialLineLength, maxHeaderSize, maxChunkSize, DEFAULT_CHUNKED_SUPPORTED, validateHeaders, + initialBufferSize, allowDuplicateContentLengths); + } + @Override protected HttpMessage createMessage(String[] initialLine) throws Exception { return new DefaultHttpRequest( diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpResponseDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpResponseDecoder.java index 39d4d6a5ad..62f6dd3554 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpResponseDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpResponseDecoder.java @@ -113,6 +113,13 @@ public class HttpResponseDecoder extends HttpObjectDecoder { initialBufferSize); } + public HttpResponseDecoder( + int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean validateHeaders, + int initialBufferSize, boolean allowDuplicateContentLengths) { + super(maxInitialLineLength, maxHeaderSize, maxChunkSize, DEFAULT_CHUNKED_SUPPORTED, validateHeaders, + initialBufferSize, allowDuplicateContentLengths); + } + @Override protected HttpMessage createMessage(String[] initialLine) { return new DefaultHttpResponse( diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpServerCodec.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpServerCodec.java index 8ae6295cf7..b2b905e083 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpServerCodec.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpServerCodec.java @@ -75,6 +75,16 @@ public final class HttpServerCodec extends CombinedChannelDuplexHandler<HttpRequ new HttpServerResponseEncoder()); } + /** + * Creates a new instance with the specified decoder options. + */ + public HttpServerCodec(int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean validateHeaders, + int initialBufferSize, boolean allowDuplicateContentLengths) { + init(new HttpServerRequestDecoder(maxInitialLineLength, maxHeaderSize, maxChunkSize, validateHeaders, + initialBufferSize, allowDuplicateContentLengths), + new HttpServerResponseEncoder()); + } + /** * Upgrades to another protocol from HTTP. Removes the {@link HttpRequestDecoder} and * {@link HttpResponseEncoder} from the pipeline. @@ -99,6 +109,12 @@ public final class HttpServerCodec extends CombinedChannelDuplexHandler<HttpRequ super(maxInitialLineLength, maxHeaderSize, maxChunkSize, validateHeaders, initialBufferSize); } + HttpServerRequestDecoder(int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, + boolean validateHeaders, int initialBufferSize, boolean allowDuplicateContentLengths) { + super(maxInitialLineLength, maxHeaderSize, maxChunkSize, validateHeaders, initialBufferSize, + allowDuplicateContentLengths); + } + @Override protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception { int oldSize = out.size(); diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/MultipleContentLengthHeadersTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/MultipleContentLengthHeadersTest.java new file mode 100644 index 0000000000..29c7d84b71 --- /dev/null +++ b/codec-http/src/test/java/io/netty/handler/codec/http/MultipleContentLengthHeadersTest.java @@ -0,0 +1,144 @@ +/* + * Copyright 2020 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.http; + +import io.netty.buffer.Unpooled; +import io.netty.channel.embedded.EmbeddedChannel; +import io.netty.util.CharsetUtil; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import static io.netty.handler.codec.http.HttpObjectDecoder.DEFAULT_INITIAL_BUFFER_SIZE; +import static io.netty.handler.codec.http.HttpObjectDecoder.DEFAULT_MAX_CHUNK_SIZE; +import static io.netty.handler.codec.http.HttpObjectDecoder.DEFAULT_MAX_HEADER_SIZE; +import static io.netty.handler.codec.http.HttpObjectDecoder.DEFAULT_MAX_INITIAL_LINE_LENGTH; +import static io.netty.handler.codec.http.HttpObjectDecoder.DEFAULT_VALIDATE_HEADERS; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.core.IsInstanceOf.instanceOf; + +@RunWith(Parameterized.class) +public class MultipleContentLengthHeadersTest { + + private final boolean allowDuplicateContentLengths; + private final boolean sameValue; + private final boolean singleField; + + private EmbeddedChannel channel; + + @Parameters + public static Collection<Object[]> parameters() { + return Arrays.asList(new Object[][] { + { false, false, false }, + { false, false, true }, + { false, true, false }, + { false, true, true }, + { true, false, false }, + { true, false, true }, + { true, true, false }, + { true, true, true } + }); + } + + public MultipleContentLengthHeadersTest( + boolean allowDuplicateContentLengths, boolean sameValue, boolean singleField) { + this.allowDuplicateContentLengths = allowDuplicateContentLengths; + this.sameValue = sameValue; + this.singleField = singleField; + } + + @Before + public void setUp() { + HttpRequestDecoder decoder = new HttpRequestDecoder( + DEFAULT_MAX_INITIAL_LINE_LENGTH, + DEFAULT_MAX_HEADER_SIZE, + DEFAULT_MAX_CHUNK_SIZE, + DEFAULT_VALIDATE_HEADERS, + DEFAULT_INITIAL_BUFFER_SIZE, + allowDuplicateContentLengths); + channel = new EmbeddedChannel(decoder); + } + + @Test + public void testMultipleContentLengthHeadersBehavior() { + String requestStr = setupRequestString(); + assertThat(channel.writeInbound(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII)), is(true)); + HttpRequest request = channel.readInbound(); + + if (allowDuplicateContentLengths) { + if (sameValue) { + assertValid(request); + List<String> contentLengths = request.headers().getAll(HttpHeaderNames.CONTENT_LENGTH); + assertThat(contentLengths, contains("1")); + LastHttpContent body = channel.readInbound(); + assertThat(body.content().readableBytes(), is(1)); + assertThat(body.content().readCharSequence(1, CharsetUtil.US_ASCII).toString(), is("a")); + } else { + assertInvalid(request); + } + } else { + assertInvalid(request); + } + assertThat(channel.finish(), is(false)); + } + + private String setupRequestString() { + String firstValue = "1"; + String secondValue = sameValue ? firstValue : "2"; + String contentLength; + if (singleField) { + contentLength = "Content-Length: " + firstValue + ", " + secondValue + "\r\n\r\n"; + } else { + contentLength = "Content-Length: " + firstValue + "\r\n" + + "Content-Length: " + secondValue + "\r\n\r\n"; + } + return "PUT /some/path HTTP/1.1\r\n" + + contentLength + + "ab"; + } + + @Test + public void testDanglingComma() { + String requestStr = "GET /some/path HTTP/1.1\r\n" + + "Content-Length: 1,\r\n" + + "Connection: close\n\n" + + "ab"; + assertThat(channel.writeInbound(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII)), is(true)); + HttpRequest request = channel.readInbound(); + assertInvalid(request); + assertThat(channel.finish(), is(false)); + } + + private static void assertValid(HttpRequest request) { + assertThat(request.decoderResult().isFailure(), is(false)); + } + + private static void assertInvalid(HttpRequest request) { + assertThat(request.decoderResult().isFailure(), is(true)); + assertThat(request.decoderResult().cause(), instanceOf(IllegalArgumentException.class)); + assertThat(request.decoderResult().cause().getMessage(), + containsString("Multiple Content-Length values found")); + } +} -- 2.23.0
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.
浙ICP备2022010568号-2