diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/HttpUtil.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/HttpUtil.java index 7572fe46eb..0dddd8b881 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/HttpUtil.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/HttpUtil.java @@ -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); diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/InboundHttp2ToHttpAdapterTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/InboundHttp2ToHttpAdapterTest.java index faa037269d..a952b4d4e3 100644 --- a/codec-http2/src/test/java/io/netty/handler/codec/http2/InboundHttp2ToHttpAdapterTest.java +++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/InboundHttp2ToHttpAdapterTest.java @@ -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());