HTTP/2 to HTTP/1 non-ascii headers reset stream
Motivation: The HTTP/2 specification indicates that when converting from HTTP/2 to HTTP/1.x and non-ascii characters are detected that an error should be thrown. Modifications: - The ASCII validation is already done but the exception that is raised is not properly converted to a RST_STREAM error. Result: - If HTTP/2 to HTTP/1.x translation layer is in use and a non-ascii header is received then a RST_STREAM frame should be sent in response.
This commit is contained in:
parent
d5042baf58
commit
4a86dce2ca
@ -14,6 +14,7 @@
|
||||
*/
|
||||
package io.netty.handler.codec.http2;
|
||||
|
||||
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||
import io.netty.handler.codec.AsciiString;
|
||||
import io.netty.handler.codec.BinaryHeaders;
|
||||
import io.netty.handler.codec.TextHeaders.EntryVisitor;
|
||||
@ -210,8 +211,12 @@ public final class HttpUtil {
|
||||
throws Http2Exception {
|
||||
// HTTP/2 does not define a way to carry the version identifier that is
|
||||
// included in the HTTP/1.1 request line.
|
||||
FullHttpRequest msg = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.valueOf(http2Headers.method()
|
||||
.toString()), http2Headers.path().toString(), validateHttpHeaders);
|
||||
final AsciiString method = checkNotNull(http2Headers.method(),
|
||||
"method header cannot be null in conversion to HTTP/1.x");
|
||||
final AsciiString path = checkNotNull(http2Headers.path(),
|
||||
"path header cannot be null in conversion to HTTP/1.x");
|
||||
FullHttpRequest msg = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.valueOf(method
|
||||
.toString()), path.toString(), validateHttpHeaders);
|
||||
addHttp2ToHttpHeaders(streamId, http2Headers, msg, false);
|
||||
return msg;
|
||||
}
|
||||
@ -235,7 +240,8 @@ public final class HttpUtil {
|
||||
} catch (Http2Exception ex) {
|
||||
throw ex;
|
||||
} catch (Exception ex) {
|
||||
PlatformDependent.throwException(ex);
|
||||
throw new Http2StreamException(streamId, Http2Error.PROTOCOL_ERROR,
|
||||
"HTTP/2 to HTTP/1.x headers conversion error", ex);
|
||||
}
|
||||
|
||||
headers.remove(HttpHeaderNames.TRANSFER_ENCODING);
|
||||
|
@ -14,12 +14,23 @@
|
||||
*/
|
||||
package io.netty.handler.codec.http2;
|
||||
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.getEmbeddedHttp2Exception;
|
||||
import static io.netty.handler.codec.http2.Http2TestUtil.as;
|
||||
import static io.netty.handler.codec.http2.Http2TestUtil.runInChannel;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandlerAdapter;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
@ -28,6 +39,7 @@ import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.handler.codec.AsciiString;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpRequest;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||
import io.netty.handler.codec.http.FullHttpMessage;
|
||||
@ -39,8 +51,16 @@ import io.netty.handler.codec.http.HttpMethod;
|
||||
import io.netty.handler.codec.http.HttpObject;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.handler.codec.http.HttpVersion;
|
||||
import io.netty.handler.codec.http2.Http2TestUtil.FrameAdapter;
|
||||
import io.netty.handler.codec.http2.Http2TestUtil.Http2Runnable;
|
||||
import io.netty.util.CharsetUtil;
|
||||
import io.netty.util.NetUtil;
|
||||
import io.netty.util.concurrent.Future;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@ -48,15 +68,6 @@ import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import static io.netty.handler.codec.http2.Http2TestUtil.*;
|
||||
import static java.util.concurrent.TimeUnit.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* Testing the {@link InboundHttp2ToHttpPriorityAdapter} and base class {@link InboundHttp2ToHttpAdapter} for HTTP/2
|
||||
* frames into {@link HttpObject}s
|
||||
@ -82,6 +93,7 @@ public class InboundHttp2ToHttpAdapterTest {
|
||||
private int maxContentLength;
|
||||
private HttpResponseDelegator serverDelegator;
|
||||
private HttpResponseDelegator clientDelegator;
|
||||
private Http2Exception serverException;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
@ -112,6 +124,18 @@ public class InboundHttp2ToHttpAdapterTest {
|
||||
serverDelegator = new HttpResponseDelegator(serverListener, serverLatch);
|
||||
p.addLast(serverDelegator);
|
||||
serverConnectedChannel = ch;
|
||||
p.addLast(new ChannelHandlerAdapter() {
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
Http2Exception e = getEmbeddedHttp2Exception(cause);
|
||||
if (e != null) {
|
||||
serverException = e;
|
||||
serverLatch.countDown();
|
||||
} else {
|
||||
super.exceptionCaught(ctx, cause);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -167,10 +191,8 @@ public class InboundHttp2ToHttpAdapterTest {
|
||||
httpHeaders.set(HttpUtil.ExtensionHeaderNames.AUTHORITY.text(), "example.org");
|
||||
httpHeaders.setInt(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 3);
|
||||
httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, 0);
|
||||
final Http2Headers http2Headers =
|
||||
new DefaultHttp2Headers().method(as("GET")).scheme(as("https"))
|
||||
.authority(as("example.org"))
|
||||
.path(as("/some/path/resource2"));
|
||||
final Http2Headers http2Headers = new DefaultHttp2Headers().method(as("GET")).scheme(as("https"))
|
||||
.authority(as("example.org")).path(as("/some/path/resource2"));
|
||||
runInChannel(clientChannel, new Http2Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@ -184,10 +206,30 @@ public class InboundHttp2ToHttpAdapterTest {
|
||||
capturedRequests = requestCaptor.getAllValues();
|
||||
assertEquals(request, capturedRequests.get(0));
|
||||
} finally {
|
||||
request.release();
|
||||
request.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clientRequestSingleHeaderNonAsciiShouldThrow() throws Exception {
|
||||
final Http2Headers http2Headers = new DefaultHttp2Headers()
|
||||
.method(as("GET"))
|
||||
.scheme(as("https"))
|
||||
.authority(as("example.org"))
|
||||
.path(as("/some/path/resource2"))
|
||||
.add(new AsciiString("çã".getBytes(CharsetUtil.UTF_8)),
|
||||
new AsciiString("Ãã".getBytes(CharsetUtil.UTF_8)));
|
||||
runInChannel(clientChannel, new Http2Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
frameWriter.writeHeaders(ctxClient(), 3, http2Headers, 0, true, newPromiseClient());
|
||||
ctxClient().flush();
|
||||
}
|
||||
});
|
||||
awaitRequests();
|
||||
assertTrue(serverException instanceof Http2StreamException);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clientRequestOneDataFrame() throws Exception {
|
||||
final String text = "hello world";
|
||||
@ -198,8 +240,8 @@ public class InboundHttp2ToHttpAdapterTest {
|
||||
HttpHeaders httpHeaders = request.headers();
|
||||
httpHeaders.setInt(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 3);
|
||||
httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, text.length());
|
||||
final Http2Headers http2Headers = new DefaultHttp2Headers().method(as("GET"))
|
||||
.path(as("/some/path/resource2"));
|
||||
final Http2Headers http2Headers = new DefaultHttp2Headers().method(as("GET")).path(
|
||||
as("/some/path/resource2"));
|
||||
runInChannel(clientChannel, new Http2Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@ -228,17 +270,17 @@ public class InboundHttp2ToHttpAdapterTest {
|
||||
HttpHeaders httpHeaders = request.headers();
|
||||
httpHeaders.setInt(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 3);
|
||||
httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, text.length());
|
||||
final Http2Headers http2Headers = new DefaultHttp2Headers().method(as("GET"))
|
||||
.path(as("/some/path/resource2"));
|
||||
final Http2Headers http2Headers = new DefaultHttp2Headers().method(as("GET")).path(
|
||||
as("/some/path/resource2"));
|
||||
final int midPoint = text.length() / 2;
|
||||
runInChannel(clientChannel, new Http2Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
frameWriter.writeHeaders(ctxClient(), 3, http2Headers, 0, false, newPromiseClient());
|
||||
frameWriter
|
||||
.writeData(ctxClient(), 3, content.slice(0, midPoint).retain(), 0, false, newPromiseClient());
|
||||
frameWriter.writeData(ctxClient(), 3, content.slice(midPoint, text.length() - midPoint).retain(), 0,
|
||||
true, newPromiseClient());
|
||||
frameWriter.writeData(ctxClient(), 3, content.slice(0, midPoint).retain(), 0, false,
|
||||
newPromiseClient());
|
||||
frameWriter.writeData(ctxClient(), 3, content.slice(midPoint, text.length() - midPoint).retain(),
|
||||
0, true, newPromiseClient());
|
||||
ctxClient().flush();
|
||||
}
|
||||
});
|
||||
@ -262,8 +304,8 @@ public class InboundHttp2ToHttpAdapterTest {
|
||||
HttpHeaders httpHeaders = request.headers();
|
||||
httpHeaders.setInt(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 3);
|
||||
httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, text.length());
|
||||
final Http2Headers http2Headers = new DefaultHttp2Headers().method(as("GET"))
|
||||
.path(as("/some/path/resource2"));
|
||||
final Http2Headers http2Headers = new DefaultHttp2Headers().method(as("GET")).path(
|
||||
as("/some/path/resource2"));
|
||||
runInChannel(clientChannel, new Http2Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@ -300,13 +342,10 @@ public class InboundHttp2ToHttpAdapterTest {
|
||||
trailingHeaders.set("FoO", "goo");
|
||||
trailingHeaders.set("foO2", "goo2");
|
||||
trailingHeaders.add("fOo2", "goo3");
|
||||
final Http2Headers http2Headers =
|
||||
new DefaultHttp2Headers().method(as("GET")).path(
|
||||
as("/some/path/resource2"));
|
||||
final Http2Headers http2Headers2 =
|
||||
new DefaultHttp2Headers().set(as("foo"), as("goo"))
|
||||
.set(as("foo2"), as("goo2"))
|
||||
.add(as("foo2"), as("goo3"));
|
||||
final Http2Headers http2Headers = new DefaultHttp2Headers().method(as("GET")).path(
|
||||
as("/some/path/resource2"));
|
||||
final Http2Headers http2Headers2 = new DefaultHttp2Headers().set(as("foo"), as("goo"))
|
||||
.set(as("foo2"), as("goo2")).add(as("foo2"), as("goo3"));
|
||||
runInChannel(clientChannel, new Http2Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@ -340,13 +379,10 @@ public class InboundHttp2ToHttpAdapterTest {
|
||||
trailingHeaders.set("Foo", "goo");
|
||||
trailingHeaders.set("fOo2", "goo2");
|
||||
trailingHeaders.add("foO2", "goo3");
|
||||
final Http2Headers http2Headers =
|
||||
new DefaultHttp2Headers().method(as("GET")).path(
|
||||
as("/some/path/resource2"));
|
||||
final Http2Headers http2Headers2 =
|
||||
new DefaultHttp2Headers().set(as("foo"), as("goo"))
|
||||
.set(as("foo2"), as("goo2"))
|
||||
.add(as("foo2"), as("goo3"));
|
||||
final Http2Headers http2Headers = new DefaultHttp2Headers().method(as("GET")).path(
|
||||
as("/some/path/resource2"));
|
||||
final Http2Headers http2Headers2 = new DefaultHttp2Headers().set(as("foo"), as("goo"))
|
||||
.set(as("foo2"), as("goo2")).add(as("foo2"), as("goo3"));
|
||||
runInChannel(clientChannel, new Http2Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@ -386,10 +422,10 @@ public class InboundHttp2ToHttpAdapterTest {
|
||||
httpHeaders2.setInt(HttpUtil.ExtensionHeaderNames.STREAM_DEPENDENCY_ID.text(), 3);
|
||||
httpHeaders2.setInt(HttpUtil.ExtensionHeaderNames.STREAM_WEIGHT.text(), 123);
|
||||
httpHeaders2.setInt(HttpHeaderNames.CONTENT_LENGTH, text2.length());
|
||||
final Http2Headers http2Headers = new DefaultHttp2Headers().method(as("PUT"))
|
||||
.path(as("/some/path/resource"));
|
||||
final Http2Headers http2Headers2 = new DefaultHttp2Headers().method(as("PUT"))
|
||||
.path(as("/some/path/resource2"));
|
||||
final Http2Headers http2Headers = new DefaultHttp2Headers().method(as("PUT")).path(
|
||||
as("/some/path/resource"));
|
||||
final Http2Headers http2Headers2 = new DefaultHttp2Headers().method(as("PUT")).path(
|
||||
as("/some/path/resource2"));
|
||||
runInChannel(clientChannel, new Http2Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@ -433,10 +469,10 @@ public class InboundHttp2ToHttpAdapterTest {
|
||||
HttpHeaders httpHeaders2 = request2.headers();
|
||||
httpHeaders2.setInt(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 5);
|
||||
httpHeaders2.setInt(HttpHeaderNames.CONTENT_LENGTH, text2.length());
|
||||
final Http2Headers http2Headers = new DefaultHttp2Headers().method(as("PUT"))
|
||||
.path(as("/some/path/resource"));
|
||||
final Http2Headers http2Headers2 = new DefaultHttp2Headers().method(as("PUT"))
|
||||
.path(as("/some/path/resource2"));
|
||||
final Http2Headers http2Headers = new DefaultHttp2Headers().method(as("PUT")).path(
|
||||
as("/some/path/resource"));
|
||||
final Http2Headers http2Headers2 = new DefaultHttp2Headers().method(as("PUT")).path(
|
||||
as("/some/path/resource2"));
|
||||
HttpHeaders httpHeaders3 = request3.headers();
|
||||
httpHeaders3.setInt(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 5);
|
||||
httpHeaders3.setInt(HttpUtil.ExtensionHeaderNames.STREAM_DEPENDENCY_ID.text(), 3);
|
||||
@ -478,8 +514,8 @@ public class InboundHttp2ToHttpAdapterTest {
|
||||
content, true);
|
||||
final FullHttpMessage response2 = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CREATED,
|
||||
content2, true);
|
||||
final FullHttpMessage request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1,
|
||||
HttpMethod.GET, "/push/test", true);
|
||||
final FullHttpMessage request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/push/test",
|
||||
true);
|
||||
try {
|
||||
HttpHeaders httpHeaders = response.headers();
|
||||
httpHeaders.setInt(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 3);
|
||||
@ -494,8 +530,7 @@ public class InboundHttp2ToHttpAdapterTest {
|
||||
httpHeaders = request.headers();
|
||||
httpHeaders.setInt(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 3);
|
||||
httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, 0);
|
||||
final Http2Headers http2Headers3 = new DefaultHttp2Headers().method(as("GET"))
|
||||
.path(as("/push/test"));
|
||||
final Http2Headers http2Headers3 = new DefaultHttp2Headers().method(as("GET")).path(as("/push/test"));
|
||||
runInChannel(clientChannel, new Http2Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@ -510,9 +545,8 @@ public class InboundHttp2ToHttpAdapterTest {
|
||||
assertEquals(request, capturedRequests.get(0));
|
||||
|
||||
final Http2Headers http2Headers = new DefaultHttp2Headers().status(as("200"));
|
||||
final Http2Headers http2Headers2 =
|
||||
new DefaultHttp2Headers().status(as("201")).scheme(as("https"))
|
||||
.authority(as("example.org"));
|
||||
final Http2Headers http2Headers2 = new DefaultHttp2Headers().status(as("201")).scheme(as("https"))
|
||||
.authority(as("example.org"));
|
||||
runInChannel(serverConnectedChannel, new Http2Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@ -544,11 +578,8 @@ public class InboundHttp2ToHttpAdapterTest {
|
||||
httpHeaders.setInt(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 3);
|
||||
httpHeaders.set(HttpHeaderNames.EXPECT, HttpHeaderValues.CONTINUE);
|
||||
httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, 0);
|
||||
final Http2Headers http2Headers =
|
||||
new DefaultHttp2Headers()
|
||||
.method(as("PUT"))
|
||||
.path(as("/info/test"))
|
||||
.set(as(HttpHeaderNames.EXPECT.toString()), as(HttpHeaderValues.CONTINUE.toString()));
|
||||
final Http2Headers http2Headers = new DefaultHttp2Headers().method(as("PUT")).path(as("/info/test"))
|
||||
.set(as(HttpHeaderNames.EXPECT.toString()), as(HttpHeaderValues.CONTINUE.toString()));
|
||||
final FullHttpMessage response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE);
|
||||
final String text = "a big payload";
|
||||
final ByteBuf payload = Unpooled.copiedBuffer(text.getBytes());
|
||||
|
Loading…
Reference in New Issue
Block a user