More values other than chunked defined in Transfer-Encoding header leads to decode failure (#10321)

Motivation:

`containsValue()` will check if there are multiple values defined in the specific header name, we need to use this method instead of `contains()` for the `Transfer-Encoding` header to cover the case that multiple values defined, like: `Transfer-Encoding: gzip, chunked`

Modification:

Change from `contains()` to `containsValue()` in `HttpUtil.isTransferEncodingChunked()` method.

Result:

Fixes #10320
This commit is contained in:
Lin Gao 2020-06-02 20:29:20 +08:00 committed by Norman Maurer
parent 4c7e017199
commit 4b6ceb2ca0
3 changed files with 86 additions and 3 deletions

View File

@ -76,12 +76,23 @@ public abstract class HttpContentDecoder extends MessageToMessageDecoder<HttpObj
cleanup(); cleanup();
final HttpMessage message = (HttpMessage) msg; final HttpMessage message = (HttpMessage) msg;
final HttpHeaders headers = message.headers(); final HttpHeaders headers = message.headers();
// Determine the content encoding.
// Determine the content encoding.
String contentEncoding = headers.get(HttpHeaderNames.CONTENT_ENCODING); String contentEncoding = headers.get(HttpHeaderNames.CONTENT_ENCODING);
if (contentEncoding != null) { if (contentEncoding != null) {
contentEncoding = contentEncoding.trim(); contentEncoding = contentEncoding.trim();
} else { } else {
contentEncoding = IDENTITY; String transferEncoding = headers.get(HttpHeaderNames.TRANSFER_ENCODING);
if (transferEncoding != null) {
int idx = transferEncoding.indexOf(",");
if (idx != -1) {
contentEncoding = transferEncoding.substring(0, idx).trim();
} else {
contentEncoding = transferEncoding.trim();
}
} else {
contentEncoding = IDENTITY;
}
} }
decoder = newContentDecoder(contentEncoding); decoder = newContentDecoder(contentEncoding);

View File

@ -301,7 +301,7 @@ public final class HttpUtil {
* @return True if transfer encoding is chunked, otherwise false * @return True if transfer encoding is chunked, otherwise false
*/ */
public static boolean isTransferEncodingChunked(HttpMessage message) { public static boolean isTransferEncodingChunked(HttpMessage message) {
return message.headers().contains(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED, true); return message.headers().containsValue(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED, true);
} }
/** /**

View File

@ -563,6 +563,78 @@ public class HttpContentDecoderTest {
assertEquals(0, content.refCnt()); assertEquals(0, content.refCnt());
} }
@Test
public void testTransferCodingGZIP() {
String requestStr = "POST / HTTP/1.1\r\n" +
"Content-Length: " + GZ_HELLO_WORLD.length + "\r\n" +
"Transfer-Encoding: gzip\r\n" +
"\r\n";
HttpRequestDecoder decoder = new HttpRequestDecoder();
HttpContentDecoder decompressor = new HttpContentDecompressor();
EmbeddedChannel channel = new EmbeddedChannel(decoder, decompressor);
channel.writeInbound(Unpooled.copiedBuffer(requestStr.getBytes()));
channel.writeInbound(Unpooled.copiedBuffer(GZ_HELLO_WORLD));
HttpRequest request = channel.readInbound();
assertTrue(request.decoderResult().isSuccess());
assertFalse(request.headers().contains(HttpHeaderNames.CONTENT_LENGTH));
HttpContent content = channel.readInbound();
assertTrue(content.decoderResult().isSuccess());
assertEquals(HELLO_WORLD, content.content().toString(CharsetUtil.US_ASCII));
content.release();
LastHttpContent lastHttpContent = channel.readInbound();
assertTrue(lastHttpContent.decoderResult().isSuccess());
lastHttpContent.release();
assertHasInboundMessages(channel, false);
assertHasOutboundMessages(channel, false);
assertFalse(channel.finish());
channel.releaseInbound();
}
@Test
public void testTransferCodingGZIPAndChunked() {
String requestStr = "POST / HTTP/1.1\r\n" +
"Host: example.com\r\n" +
"Content-Type: application/x-www-form-urlencoded\r\n" +
"Trailer: My-Trailer\r\n" +
"Transfer-Encoding: gzip, chunked\r\n" +
"\r\n";
HttpRequestDecoder decoder = new HttpRequestDecoder();
HttpContentDecoder decompressor = new HttpContentDecompressor();
EmbeddedChannel channel = new EmbeddedChannel(decoder, decompressor);
assertTrue(channel.writeInbound(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII)));
String chunkLength = Integer.toHexString(GZ_HELLO_WORLD.length);
assertTrue(channel.writeInbound(Unpooled.copiedBuffer(chunkLength + "\r\n", CharsetUtil.US_ASCII)));
assertTrue(channel.writeInbound(Unpooled.copiedBuffer(GZ_HELLO_WORLD)));
assertTrue(channel.writeInbound(Unpooled.copiedBuffer("\r\n".getBytes(CharsetUtil.US_ASCII))));
assertTrue(channel.writeInbound(Unpooled.copiedBuffer("0\r\n", CharsetUtil.US_ASCII)));
assertTrue(channel.writeInbound(Unpooled.copiedBuffer("My-Trailer: 42\r\n\r\n", CharsetUtil.US_ASCII)));
HttpRequest request = channel.readInbound();
assertTrue(request.decoderResult().isSuccess());
assertTrue(request.headers().containsValue(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED, true));
assertFalse(request.headers().contains(HttpHeaderNames.CONTENT_LENGTH));
HttpContent chunk1 = channel.readInbound();
assertTrue(chunk1.decoderResult().isSuccess());
assertEquals(HELLO_WORLD, chunk1.content().toString(CharsetUtil.US_ASCII));
chunk1.release();
LastHttpContent chunk2 = channel.readInbound();
assertTrue(chunk2.decoderResult().isSuccess());
assertEquals("42", chunk2.trailingHeaders().get("My-Trailer"));
chunk2.release();
assertFalse(channel.finish());
channel.releaseInbound();
}
private static byte[] gzDecompress(byte[] input) { private static byte[] gzDecompress(byte[] input) {
ZlibDecoder decoder = ZlibCodecFactory.newZlibDecoder(ZlibWrapper.GZIP); ZlibDecoder decoder = ZlibCodecFactory.newZlibDecoder(ZlibWrapper.GZIP);
EmbeddedChannel channel = new EmbeddedChannel(decoder); EmbeddedChannel channel = new EmbeddedChannel(decoder);