Fix HttpContentEncoder does not handle multiple Accept-Encoding (#9557)

Motivation:
At the current moment HttpContentEncoder handle only first value of multiple accept-encoding headers.

Modification:

Join multiple accept-encoding headers to one separated by comma.

Result:

Fixes #9553
This commit is contained in:
Andrey Mizurov 2019-09-11 09:46:06 +03:00 committed by Norman Maurer
parent 4a60152dd8
commit f26840478a
4 changed files with 100 additions and 8 deletions

View File

@ -24,11 +24,14 @@ import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.DecoderResult;
import io.netty.handler.codec.MessageToMessageCodec;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.internal.StringUtil;
import java.util.ArrayDeque;
import java.util.List;
import java.util.Queue;
import static io.netty.handler.codec.http.HttpHeaderNames.*;
/**
* Encodes the content of the outbound {@link HttpResponse} and {@link HttpContent}.
* The original content is replaced with the new content encoded by the
@ -73,21 +76,30 @@ public abstract class HttpContentEncoder extends MessageToMessageCodec<HttpReque
}
@Override
protected void decode(ChannelHandlerContext ctx, HttpRequest msg, List<Object> out)
throws Exception {
CharSequence acceptedEncoding = msg.headers().get(HttpHeaderNames.ACCEPT_ENCODING);
if (acceptedEncoding == null) {
acceptedEncoding = HttpContentDecoder.IDENTITY;
protected void decode(ChannelHandlerContext ctx, HttpRequest msg, List<Object> out) throws Exception {
CharSequence acceptEncoding;
List<String> acceptEncodingHeaders = msg.headers().getAll(ACCEPT_ENCODING);
switch (acceptEncodingHeaders.size()) {
case 0:
acceptEncoding = HttpContentDecoder.IDENTITY;
break;
case 1:
acceptEncoding = acceptEncodingHeaders.get(0);
break;
default:
// Multiple message-header fields https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
acceptEncoding = StringUtil.join(",", acceptEncodingHeaders);
break;
}
HttpMethod method = msg.method();
if (HttpMethod.HEAD.equals(method)) {
acceptedEncoding = ZERO_LENGTH_HEAD;
acceptEncoding = ZERO_LENGTH_HEAD;
} else if (HttpMethod.CONNECT.equals(method)) {
acceptedEncoding = ZERO_LENGTH_CONNECT;
acceptEncoding = ZERO_LENGTH_CONNECT;
}
acceptEncodingQueue.add(acceptedEncoding);
acceptEncodingQueue.add(acceptEncoding);
out.add(ReferenceCountUtil.retain(msg));
}

View File

@ -500,6 +500,39 @@ public class HttpContentCompressorTest {
assertTrue(ch.finishAndReleaseAll());
}
@Test
public void testMultipleAcceptEncodingHeaders() {
FullHttpRequest request = newRequest();
request.headers().set(HttpHeaderNames.ACCEPT_ENCODING, "unknown; q=1.0")
.add(HttpHeaderNames.ACCEPT_ENCODING, "gzip; q=0.5")
.add(HttpHeaderNames.ACCEPT_ENCODING, "deflate; q=0");
EmbeddedChannel ch = new EmbeddedChannel(new HttpContentCompressor());
assertTrue(ch.writeInbound(request));
FullHttpResponse res = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
Unpooled.copiedBuffer("Gzip Win", CharsetUtil.US_ASCII));
assertTrue(ch.writeOutbound(res));
assertEncodedResponse(ch);
HttpContent c = ch.readOutbound();
assertThat(ByteBufUtil.hexDump(c.content()), is("1f8b080000000000000072afca2c5008cfcc03000000ffff"));
c.release();
c = ch.readOutbound();
assertThat(ByteBufUtil.hexDump(c.content()), is("03001f2ebf0f08000000"));
c.release();
LastHttpContent last = ch.readOutbound();
assertThat(last.content().readableBytes(), is(0));
last.release();
assertThat(ch.readOutbound(), is(nullValue()));
assertTrue(ch.finishAndReleaseAll());
}
private static FullHttpRequest newRequest() {
FullHttpRequest req = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/");
req.headers().set(HttpHeaderNames.ACCEPT_ENCODING, "gzip");

View File

@ -17,6 +17,7 @@ package io.netty.util.internal;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import static java.util.Objects.requireNonNull;
@ -597,6 +598,36 @@ public final class StringUtil {
return start == 0 && end == length - 1 ? value : value.subSequence(start, end + 1);
}
/**
* Returns a char sequence that contains all {@code elements} joined by a given separator.
*
* @param separator for each element
* @param elements to join together
*
* @return a char sequence joined by a given separator.
*/
public static CharSequence join(CharSequence separator, Iterable<? extends CharSequence> elements) {
ObjectUtil.checkNotNull(separator, "separator");
ObjectUtil.checkNotNull(elements, "elements");
Iterator<? extends CharSequence> iterator = elements.iterator();
if (!iterator.hasNext()) {
return EMPTY_STRING;
}
CharSequence firstElement = iterator.next();
if (!iterator.hasNext()) {
return firstElement;
}
StringBuilder builder = new StringBuilder(firstElement);
do {
builder.append(separator).append(iterator.next());
} while (iterator.hasNext());
return builder;
}
/**
* @return {@code length} if no OWS is found.
*/
@ -622,4 +653,5 @@ public final class StringUtil {
private static boolean isOws(char c) {
return c == SPACE || c == TAB;
}
}

View File

@ -534,4 +534,19 @@ public class StringUtilTest {
assertEquals("", StringUtil.trimOws("\t ").toString());
assertEquals("a b", StringUtil.trimOws("\ta b \t").toString());
}
@Test
public void testJoin() {
assertEquals("",
StringUtil.join(",", Collections.<CharSequence>emptyList()).toString());
assertEquals("a",
StringUtil.join(",", Collections.singletonList("a")).toString());
assertEquals("a,b",
StringUtil.join(",", Arrays.asList("a", "b")).toString());
assertEquals("a,b,c",
StringUtil.join(",", Arrays.asList("a", "b", "c")).toString());
assertEquals("a,b,c,null,d",
StringUtil.join(",", Arrays.asList("a", "b", "c", null, "d")).toString());
}
}