HTTP to HTTP/2 translation errors (round 2)

Motivation:
Commit 0d8ce23c83 failed to fix the Host header processing. Host is not a URI but is instead defined in https://tools.ietf.org/html/rfc3986#section-3.2.2 as host        = IP-literal / IPv4address / reg-name

Modifications:
- Host should not be treated as a URI.
- We should be more explicit about required fields, and unexpected input by throwing exceptions.

Result:
Translation from HTTP/1.x to HTTP/2 is more correct.
This commit is contained in:
Scott Mitchell 2015-08-24 16:13:17 -07:00
parent 6046adef2b
commit 99d6a97b4a
2 changed files with 92 additions and 162 deletions

View File

@ -39,7 +39,6 @@ import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import java.util.regex.Pattern;
import static io.netty.handler.codec.http.HttpScheme.HTTP; import static io.netty.handler.codec.http.HttpScheme.HTTP;
import static io.netty.handler.codec.http.HttpScheme.HTTPS; import static io.netty.handler.codec.http.HttpScheme.HTTPS;
@ -289,24 +288,15 @@ public final class HttpUtil {
URI requestTargetUri = URI.create(request.uri()); URI requestTargetUri = URI.create(request.uri());
out.path(toHttp2Path(requestTargetUri)); out.path(toHttp2Path(requestTargetUri));
out.method(request.method().asciiName()); out.method(request.method().asciiName());
setHttp2Scheme(inHeaders, requestTargetUri, out);
if (!isOriginForm(requestTargetUri) && !isAsteriskForm(requestTargetUri)) {
// Attempt to take from HOST header before taking from the request-line // Attempt to take from HOST header before taking from the request-line
String host = inHeaders.getAsString(HttpHeaderNames.HOST); String host = inHeaders.getAsString(HttpHeaderNames.HOST);
boolean shouldSetAuthroity = !isOriginForm(requestTargetUri) && !isAsteriskForm(requestTargetUri); if (host == null || host.isEmpty()) {
if (host == null) { setHttp2Authority(inHeaders, requestTargetUri.getAuthority(), out);
if (shouldSetAuthroity) {
setHttp2Authority(inHeaders, requestTargetUri, out);
}
setHttp2Scheme(inHeaders, requestTargetUri, true, out);
} else { } else {
URI hostUri = URI.create(host); setHttp2Authority(inHeaders, host, out);
if (shouldSetAuthroity) {
setHttp2Authority(inHeaders, hostUri, out);
}
if (!setHttp2Scheme(inHeaders, hostUri, false, out)) {
/** :scheme must be present as defined by
<a href="https://tools.ietf.org/html/rfc7540#section-8.1.2.3">rfc7540, 8.1.2.3</a>. */
setHttp2Scheme(inHeaders, requestTargetUri, true, out);
} }
} }
} else if (in instanceof HttpResponse) { } else if (in instanceof HttpResponse) {
@ -364,15 +354,16 @@ public final class HttpUtil {
return path.isEmpty() ? EMPTY_REQUEST_PATH : new AsciiString(path); return path.isEmpty() ? EMPTY_REQUEST_PATH : new AsciiString(path);
} }
private static void setHttp2Authority(HttpHeaders in, URI uri, Http2Headers out) { private static void setHttp2Authority(HttpHeaders in, String autority, Http2Headers out) {
// The authority MUST NOT include the deprecated "userinfo" subcomponent // The authority MUST NOT include the deprecated "userinfo" subcomponent
String value = uri.getAuthority(); if (autority != null) {
if (value != null) { int endOfUserInfo = autority.indexOf('@');
int endOfUserInfo = value.indexOf('@');
if (endOfUserInfo < 0) { if (endOfUserInfo < 0) {
out.authority(new AsciiString(value)); out.authority(new AsciiString(autority));
} else if (endOfUserInfo + 1 < value.length()) { } else if (endOfUserInfo + 1 < autority.length()) {
out.authority(new AsciiString(value.substring(endOfUserInfo + 1))); out.authority(new AsciiString(autority.substring(endOfUserInfo + 1)));
} else {
throw new IllegalArgumentException("autority: " + autority);
} }
} else { } else {
// Consume the Authority extension header if present // Consume the Authority extension header if present
@ -384,23 +375,28 @@ public final class HttpUtil {
} }
} }
private static boolean setHttp2Scheme(HttpHeaders in, URI uri, boolean mustSet, Http2Headers out) { private static void setHttp2Scheme(HttpHeaders in, URI uri, Http2Headers out) {
String value = uri.getScheme(); String value = uri.getScheme();
if (value != null) { if (value != null) {
out.scheme(new AsciiString(value)); out.scheme(new AsciiString(value));
return true; return;
} }
// Consume the Scheme extension header if present // Consume the Scheme extension header if present
CharSequence cValue = in.get(ExtensionHeaderNames.SCHEME.text()); CharSequence cValue = in.get(ExtensionHeaderNames.SCHEME.text());
if (cValue != null) { if (cValue != null) {
out.scheme(AsciiString.of(cValue)); out.scheme(AsciiString.of(cValue));
return true; return;
} }
if (uri.getPort() >= 0 || mustSet) {
out.scheme(uri.getPort() == HTTPS.port() ? HTTPS.name() : HTTP.name()); if (uri.getPort() == HTTPS.port()) {
return true; out.scheme(HTTPS.name());
} else if (uri.getPort() == HTTP.port()) {
out.scheme(HTTP.name());
} else {
throw new IllegalArgumentException(":scheme must be specified. " +
"see https://tools.ietf.org/html/rfc7540#section-8.1.2.3");
} }
return false;
} }
/** /**

View File

@ -118,13 +118,14 @@ public class HttpToHttp2ConnectionHandlerTest {
} }
@Test @Test
public void testJustHeadersRequest() throws Exception { public void testHeadersOnlyRequest() throws Exception {
bootstrapEnv(2, 1, 0); bootstrapEnv(2, 1, 0);
final FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, GET, "/example"); final FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, GET,
"http://my-user_name@www.example.org:5555/example");
final HttpHeaders httpHeaders = request.headers(); final HttpHeaders httpHeaders = request.headers();
httpHeaders.setInt(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 5); httpHeaders.setInt(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 5);
httpHeaders.set(HttpHeaderNames.HOST, httpHeaders.set(HttpHeaderNames.HOST,
"http://my-user_name@www.example.org:5555/example"); "my-user_name@www.example.org:5555");
httpHeaders.set(HttpUtil.ExtensionHeaderNames.AUTHORITY.text(), "www.example.org:5555"); httpHeaders.set(HttpUtil.ExtensionHeaderNames.AUTHORITY.text(), "www.example.org:5555");
httpHeaders.set(HttpUtil.ExtensionHeaderNames.SCHEME.text(), "http"); httpHeaders.set(HttpUtil.ExtensionHeaderNames.SCHEME.text(), "http");
httpHeaders.add("foo", "goo"); httpHeaders.add("foo", "goo");
@ -136,18 +137,9 @@ public class HttpToHttp2ConnectionHandlerTest {
.add(new AsciiString("foo"), new AsciiString("goo")) .add(new AsciiString("foo"), new AsciiString("goo"))
.add(new AsciiString("foo"), new AsciiString("goo2")) .add(new AsciiString("foo"), new AsciiString("goo2"))
.add(new AsciiString("foo2"), new AsciiString("goo2")); .add(new AsciiString("foo2"), new AsciiString("goo2"));
ChannelPromise writePromise = newPromise();
ChannelFuture writeFuture = clientChannel.writeAndFlush(request, writePromise);
assertTrue(writePromise.awaitUninterruptibly(WAIT_TIME_SECONDS, SECONDS)); ChannelPromise writePromise = newPromise();
assertTrue(writePromise.isSuccess()); verifyHeadersOnly(http2Headers, writePromise, clientChannel.writeAndFlush(request, writePromise));
assertTrue(writeFuture.awaitUninterruptibly(WAIT_TIME_SECONDS, SECONDS));
assertTrue(writeFuture.isSuccess());
awaitRequests();
verify(serverListener).onHeadersRead(any(ChannelHandlerContext.class), eq(5),
eq(http2Headers), eq(0), anyShort(), anyBoolean(), eq(0), eq(true));
verify(serverListener, never()).onDataRead(any(ChannelHandlerContext.class), anyInt(),
any(ByteBuf.class), anyInt(), anyBoolean());
} }
@Test @Test
@ -156,22 +148,14 @@ public class HttpToHttp2ConnectionHandlerTest {
final FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, GET, "/where?q=now&f=then#section1"); final FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, GET, "/where?q=now&f=then#section1");
final HttpHeaders httpHeaders = request.headers(); final HttpHeaders httpHeaders = request.headers();
httpHeaders.setInt(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 5); httpHeaders.setInt(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 5);
httpHeaders.set(HttpUtil.ExtensionHeaderNames.SCHEME.text(), "http");
final Http2Headers http2Headers = final Http2Headers http2Headers =
new DefaultHttp2Headers().method(new AsciiString("GET")) new DefaultHttp2Headers().method(new AsciiString("GET"))
.path(new AsciiString("/where?q=now&f=then#section1")) .path(new AsciiString("/where?q=now&f=then#section1"))
.scheme(new AsciiString("http")); .scheme(new AsciiString("http"));
ChannelPromise writePromise = newPromise();
ChannelFuture writeFuture = clientChannel.writeAndFlush(request, writePromise);
assertTrue(writePromise.awaitUninterruptibly(WAIT_TIME_SECONDS, SECONDS)); ChannelPromise writePromise = newPromise();
assertTrue(writePromise.isSuccess()); verifyHeadersOnly(http2Headers, writePromise, clientChannel.writeAndFlush(request, writePromise));
assertTrue(writeFuture.awaitUninterruptibly(WAIT_TIME_SECONDS, SECONDS));
assertTrue(writeFuture.isSuccess());
awaitRequests();
verify(serverListener).onHeadersRead(any(ChannelHandlerContext.class), eq(5),
eq(http2Headers), eq(0), anyShort(), anyBoolean(), eq(0), eq(true));
verify(serverListener, never()).onDataRead(any(ChannelHandlerContext.class), anyInt(),
any(ByteBuf.class), anyInt(), anyBoolean());
} }
@Test @Test
@ -181,26 +165,17 @@ public class HttpToHttp2ConnectionHandlerTest {
final HttpHeaders httpHeaders = request.headers(); final HttpHeaders httpHeaders = request.headers();
httpHeaders.setInt(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 5); httpHeaders.setInt(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 5);
httpHeaders.set(HttpHeaderNames.HOST, httpHeaders.set(HttpHeaderNames.HOST,
"https://foouser@www.example.org:5555/ignored_host"); "foouser@www.example.org:5555");
httpHeaders.set(HttpUtil.ExtensionHeaderNames.PATH.text(), "ignored_path"); httpHeaders.set(HttpUtil.ExtensionHeaderNames.PATH.text(), "ignored_path");
httpHeaders.set(HttpUtil.ExtensionHeaderNames.AUTHORITY.text(), "ignored_authority"); httpHeaders.set(HttpUtil.ExtensionHeaderNames.AUTHORITY.text(), "ignored_authority");
httpHeaders.set(HttpUtil.ExtensionHeaderNames.SCHEME.text(), "ignored_scheme"); httpHeaders.set(HttpUtil.ExtensionHeaderNames.SCHEME.text(), "https");
final Http2Headers http2Headers = final Http2Headers http2Headers =
new DefaultHttp2Headers().method(new AsciiString("GET")) new DefaultHttp2Headers().method(new AsciiString("GET"))
.path(new AsciiString("/pub/WWW/TheProject.html")) .path(new AsciiString("/pub/WWW/TheProject.html"))
.authority(new AsciiString("www.example.org:5555")).scheme(new AsciiString("https")); .authority(new AsciiString("www.example.org:5555")).scheme(new AsciiString("https"));
ChannelPromise writePromise = newPromise();
ChannelFuture writeFuture = clientChannel.writeAndFlush(request, writePromise);
assertTrue(writePromise.awaitUninterruptibly(WAIT_TIME_SECONDS, SECONDS)); ChannelPromise writePromise = newPromise();
assertTrue(writePromise.isSuccess()); verifyHeadersOnly(http2Headers, writePromise, clientChannel.writeAndFlush(request, writePromise));
assertTrue(writeFuture.awaitUninterruptibly(WAIT_TIME_SECONDS, SECONDS));
assertTrue(writeFuture.isSuccess());
awaitRequests();
verify(serverListener).onHeadersRead(any(ChannelHandlerContext.class), eq(5),
eq(http2Headers), eq(0), anyShort(), anyBoolean(), eq(0), eq(true));
verify(serverListener, never()).onDataRead(any(ChannelHandlerContext.class), anyInt(),
any(ByteBuf.class), anyInt(), anyBoolean());
} }
@Test @Test
@ -216,18 +191,9 @@ public class HttpToHttp2ConnectionHandlerTest {
new DefaultHttp2Headers().method(new AsciiString("GET")) new DefaultHttp2Headers().method(new AsciiString("GET"))
.path(new AsciiString("/pub/WWW/TheProject.html")) .path(new AsciiString("/pub/WWW/TheProject.html"))
.authority(new AsciiString("www.example.org:5555")).scheme(new AsciiString("https")); .authority(new AsciiString("www.example.org:5555")).scheme(new AsciiString("https"));
ChannelPromise writePromise = newPromise();
ChannelFuture writeFuture = clientChannel.writeAndFlush(request, writePromise);
assertTrue(writePromise.awaitUninterruptibly(WAIT_TIME_SECONDS, SECONDS)); ChannelPromise writePromise = newPromise();
assertTrue(writePromise.isSuccess()); verifyHeadersOnly(http2Headers, writePromise, clientChannel.writeAndFlush(request, writePromise));
assertTrue(writeFuture.awaitUninterruptibly(WAIT_TIME_SECONDS, SECONDS));
assertTrue(writeFuture.isSuccess());
awaitRequests();
verify(serverListener).onHeadersRead(any(ChannelHandlerContext.class), eq(5),
eq(http2Headers), eq(0), anyShort(), anyBoolean(), eq(0), eq(true));
verify(serverListener, never()).onDataRead(any(ChannelHandlerContext.class), anyInt(),
any(ByteBuf.class), anyInt(), anyBoolean());
} }
@Test @Test
@ -241,18 +207,9 @@ public class HttpToHttp2ConnectionHandlerTest {
new DefaultHttp2Headers().method(new AsciiString("GET")) new DefaultHttp2Headers().method(new AsciiString("GET"))
.path(new AsciiString("/pub/WWW/TheProject.html")) .path(new AsciiString("/pub/WWW/TheProject.html"))
.authority(new AsciiString("www.example.org:5555")).scheme(new AsciiString("http")); .authority(new AsciiString("www.example.org:5555")).scheme(new AsciiString("http"));
ChannelPromise writePromise = newPromise();
ChannelFuture writeFuture = clientChannel.writeAndFlush(request, writePromise);
assertTrue(writePromise.awaitUninterruptibly(WAIT_TIME_SECONDS, SECONDS)); ChannelPromise writePromise = newPromise();
assertTrue(writePromise.isSuccess()); verifyHeadersOnly(http2Headers, writePromise, clientChannel.writeAndFlush(request, writePromise));
assertTrue(writeFuture.awaitUninterruptibly(WAIT_TIME_SECONDS, SECONDS));
assertTrue(writeFuture.isSuccess());
awaitRequests();
verify(serverListener).onHeadersRead(any(ChannelHandlerContext.class), eq(5),
eq(http2Headers), eq(0), anyShort(), anyBoolean(), eq(0), eq(true));
verify(serverListener, never()).onDataRead(any(ChannelHandlerContext.class), anyInt(),
any(ByteBuf.class), anyInt(), anyBoolean());
} }
@Test @Test
@ -264,18 +221,9 @@ public class HttpToHttp2ConnectionHandlerTest {
final Http2Headers http2Headers = final Http2Headers http2Headers =
new DefaultHttp2Headers().method(new AsciiString("CONNECT")).path(new AsciiString("/")) new DefaultHttp2Headers().method(new AsciiString("CONNECT")).path(new AsciiString("/"))
.scheme(new AsciiString("http")).authority(new AsciiString("www.example.com:80")); .scheme(new AsciiString("http")).authority(new AsciiString("www.example.com:80"));
ChannelPromise writePromise = newPromise();
ChannelFuture writeFuture = clientChannel.writeAndFlush(request, writePromise);
assertTrue(writePromise.awaitUninterruptibly(WAIT_TIME_SECONDS, SECONDS)); ChannelPromise writePromise = newPromise();
assertTrue(writePromise.isSuccess()); verifyHeadersOnly(http2Headers, writePromise, clientChannel.writeAndFlush(request, writePromise));
assertTrue(writeFuture.awaitUninterruptibly(WAIT_TIME_SECONDS, SECONDS));
assertTrue(writeFuture.isSuccess());
awaitRequests();
verify(serverListener).onHeadersRead(any(ChannelHandlerContext.class), eq(5),
eq(http2Headers), eq(0), anyShort(), anyBoolean(), eq(0), eq(true));
verify(serverListener, never()).onDataRead(any(ChannelHandlerContext.class), anyInt(),
any(ByteBuf.class), anyInt(), anyBoolean());
} }
@Test @Test
@ -284,22 +232,14 @@ public class HttpToHttp2ConnectionHandlerTest {
final FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, OPTIONS, "*"); final FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, OPTIONS, "*");
final HttpHeaders httpHeaders = request.headers(); final HttpHeaders httpHeaders = request.headers();
httpHeaders.setInt(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 5); httpHeaders.setInt(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 5);
httpHeaders.set(HttpHeaderNames.HOST, "http://www.example.com:80"); httpHeaders.set(HttpHeaderNames.HOST, "www.example.com:80");
httpHeaders.set(HttpUtil.ExtensionHeaderNames.SCHEME.text(), "http");
final Http2Headers http2Headers = final Http2Headers http2Headers =
new DefaultHttp2Headers().method(new AsciiString("OPTIONS")).path(new AsciiString("*")) new DefaultHttp2Headers().method(new AsciiString("OPTIONS")).path(new AsciiString("*"))
.scheme(new AsciiString("http")).authority(new AsciiString("www.example.com:80")); .scheme(new AsciiString("http")).authority(new AsciiString("www.example.com:80"));
ChannelPromise writePromise = newPromise();
ChannelFuture writeFuture = clientChannel.writeAndFlush(request, writePromise);
assertTrue(writePromise.awaitUninterruptibly(WAIT_TIME_SECONDS, SECONDS)); ChannelPromise writePromise = newPromise();
assertTrue(writePromise.isSuccess()); verifyHeadersOnly(http2Headers, writePromise, clientChannel.writeAndFlush(request, writePromise));
assertTrue(writeFuture.awaitUninterruptibly(WAIT_TIME_SECONDS, SECONDS));
assertTrue(writeFuture.isSuccess());
awaitRequests();
verify(serverListener).onHeadersRead(any(ChannelHandlerContext.class), eq(5),
eq(http2Headers), eq(0), anyShort(), anyBoolean(), eq(0), eq(true));
verify(serverListener, never()).onDataRead(any(ChannelHandlerContext.class), anyInt(),
any(ByteBuf.class), anyInt(), anyBoolean());
} }
@Test @Test
@ -310,77 +250,55 @@ public class HttpToHttp2ConnectionHandlerTest {
final FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, GET, "/"); final FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, GET, "/");
final HttpHeaders httpHeaders = request.headers(); final HttpHeaders httpHeaders = request.headers();
httpHeaders.setInt(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 5); httpHeaders.setInt(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 5);
httpHeaders.set(HttpHeaderNames.HOST, "http://[::1]:80"); httpHeaders.set(HttpHeaderNames.HOST, "[::1]:80");
httpHeaders.set(HttpUtil.ExtensionHeaderNames.SCHEME.text(), "http");
final Http2Headers http2Headers = final Http2Headers http2Headers =
new DefaultHttp2Headers().method(new AsciiString("GET")).path(new AsciiString("/")) new DefaultHttp2Headers().method(new AsciiString("GET")).path(new AsciiString("/"))
.scheme(new AsciiString("http")).authority(new AsciiString("[::1]:80")); .scheme(new AsciiString("http")).authority(new AsciiString("[::1]:80"));
ChannelPromise writePromise = newPromise();
ChannelFuture writeFuture = clientChannel.writeAndFlush(request, writePromise);
assertTrue(writePromise.awaitUninterruptibly(WAIT_TIME_SECONDS, SECONDS)); ChannelPromise writePromise = newPromise();
assertTrue(writePromise.isSuccess()); verifyHeadersOnly(http2Headers, writePromise, clientChannel.writeAndFlush(request, writePromise));
assertTrue(writeFuture.awaitUninterruptibly(WAIT_TIME_SECONDS, SECONDS));
assertTrue(writeFuture.isSuccess());
awaitRequests();
verify(serverListener).onHeadersRead(any(ChannelHandlerContext.class), eq(5),
eq(http2Headers), eq(0), anyShort(), anyBoolean(), eq(0), eq(true));
verify(serverListener, never()).onDataRead(any(ChannelHandlerContext.class), anyInt(),
any(ByteBuf.class), anyInt(), anyBoolean());
} }
@Test @Test
public void testHostNoSchemeFormRequestTargetHandled() throws Exception { public void testHostFormRequestTargetHandled() throws Exception {
bootstrapEnv(2, 1, 0); bootstrapEnv(2, 1, 0);
final FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, GET, "/"); final FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, GET, "/");
final HttpHeaders httpHeaders = request.headers(); final HttpHeaders httpHeaders = request.headers();
httpHeaders.setInt(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 5); httpHeaders.setInt(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 5);
// This is an "irregular" host in that the scheme is "localhost"
httpHeaders.set(HttpHeaderNames.HOST, "localhost:80"); httpHeaders.set(HttpHeaderNames.HOST, "localhost:80");
httpHeaders.set(HttpUtil.ExtensionHeaderNames.SCHEME.text(), "http");
final Http2Headers http2Headers = final Http2Headers http2Headers =
new DefaultHttp2Headers().method(new AsciiString("GET")).path(new AsciiString("/")) new DefaultHttp2Headers().method(new AsciiString("GET")).path(new AsciiString("/"))
.scheme(new AsciiString("localhost")); .scheme(new AsciiString("http")).authority(new AsciiString("localhost:80"));
ChannelPromise writePromise = newPromise();
ChannelFuture writeFuture = clientChannel.writeAndFlush(request, writePromise);
assertTrue(writePromise.awaitUninterruptibly(WAIT_TIME_SECONDS, SECONDS)); ChannelPromise writePromise = newPromise();
assertTrue(writePromise.isSuccess()); verifyHeadersOnly(http2Headers, writePromise, clientChannel.writeAndFlush(request, writePromise));
assertTrue(writeFuture.awaitUninterruptibly(WAIT_TIME_SECONDS, SECONDS));
assertTrue(writeFuture.isSuccess());
awaitRequests();
verify(serverListener).onHeadersRead(any(ChannelHandlerContext.class), eq(5),
eq(http2Headers), eq(0), anyShort(), anyBoolean(), eq(0), eq(true));
verify(serverListener, never()).onDataRead(any(ChannelHandlerContext.class), anyInt(),
any(ByteBuf.class), anyInt(), anyBoolean());
} }
@Test @Test
public void testBadHostIPv4FormRequestTargetHandled() throws Exception { public void testHostIPv4FormRequestTargetHandled() throws Exception {
// Invalid according to
// https://tools.ietf.org/html/rfc7230#section-2.7.1 -> https://tools.ietf.org/html/rfc3986#section-3
bootstrapEnv(2, 1, 0); bootstrapEnv(2, 1, 0);
final FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, GET, "/"); final FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, GET, "/");
final HttpHeaders httpHeaders = request.headers(); final HttpHeaders httpHeaders = request.headers();
httpHeaders.setInt(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 5); httpHeaders.setInt(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 5);
httpHeaders.set(HttpHeaderNames.HOST, "1.2.3.4:80"); httpHeaders.set(HttpHeaderNames.HOST, "1.2.3.4:80");
ChannelPromise writePromise = newPromise(); httpHeaders.set(HttpUtil.ExtensionHeaderNames.SCHEME.text(), "http");
ChannelFuture writeFuture = clientChannel.writeAndFlush(request, writePromise); final Http2Headers http2Headers =
new DefaultHttp2Headers().method(new AsciiString("GET")).path(new AsciiString("/"))
.scheme(new AsciiString("http")).authority(new AsciiString("1.2.3.4:80"));
assertTrue(writePromise.awaitUninterruptibly(WAIT_TIME_SECONDS, SECONDS)); ChannelPromise writePromise = newPromise();
assertTrue(writePromise.isDone()); verifyHeadersOnly(http2Headers, writePromise, clientChannel.writeAndFlush(request, writePromise));
assertFalse(writePromise.isSuccess());
assertTrue(writeFuture.isDone());
assertFalse(writeFuture.isSuccess());
} }
@Test @Test
public void testBadHostIPv6FormRequestTargetHandled() throws Exception { public void testNoSchemeRequestTargetHandled() throws Exception {
// Invalid according to
// https://tools.ietf.org/html/rfc7230#section-2.7.1 -> https://tools.ietf.org/html/rfc3986#section-3
bootstrapEnv(2, 1, 0); bootstrapEnv(2, 1, 0);
final FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, GET, "/"); final FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, GET, "/");
final HttpHeaders httpHeaders = request.headers(); final HttpHeaders httpHeaders = request.headers();
httpHeaders.setInt(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 5); httpHeaders.setInt(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 5);
httpHeaders.set(HttpHeaderNames.HOST, "[::1]:80"); httpHeaders.set(HttpHeaderNames.HOST, "localhost");
ChannelPromise writePromise = newPromise(); ChannelPromise writePromise = newPromise();
ChannelFuture writeFuture = clientChannel.writeAndFlush(request, writePromise); ChannelFuture writeFuture = clientChannel.writeAndFlush(request, writePromise);
@ -404,16 +322,17 @@ public class HttpToHttp2ConnectionHandlerTest {
}).when(serverListener).onDataRead(any(ChannelHandlerContext.class), eq(3), }).when(serverListener).onDataRead(any(ChannelHandlerContext.class), eq(3),
any(ByteBuf.class), eq(0), eq(true)); any(ByteBuf.class), eq(0), eq(true));
bootstrapEnv(3, 1, 0); bootstrapEnv(3, 1, 0);
final FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, POST, "/example", final FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, POST,
"http://your_user-name123@www.example.org:5555/example",
Unpooled.copiedBuffer(text, UTF_8)); Unpooled.copiedBuffer(text, UTF_8));
final HttpHeaders httpHeaders = request.headers(); final HttpHeaders httpHeaders = request.headers();
httpHeaders.set(HttpHeaderNames.HOST, "http://your_user-name123@www.example.org:5555/example"); httpHeaders.set(HttpHeaderNames.HOST, "www.example-origin.org:5555");
httpHeaders.add("foo", "goo"); httpHeaders.add("foo", "goo");
httpHeaders.add("foo", "goo2"); httpHeaders.add("foo", "goo2");
httpHeaders.add("foo2", "goo2"); httpHeaders.add("foo2", "goo2");
final Http2Headers http2Headers = final Http2Headers http2Headers =
new DefaultHttp2Headers().method(new AsciiString("POST")).path(new AsciiString("/example")) new DefaultHttp2Headers().method(new AsciiString("POST")).path(new AsciiString("/example"))
.authority(new AsciiString("www.example.org:5555")).scheme(new AsciiString("http")) .authority(new AsciiString("www.example-origin.org:5555")).scheme(new AsciiString("http"))
.add(new AsciiString("foo"), new AsciiString("goo")) .add(new AsciiString("foo"), new AsciiString("goo"))
.add(new AsciiString("foo"), new AsciiString("goo2")) .add(new AsciiString("foo"), new AsciiString("goo2"))
.add(new AsciiString("foo2"), new AsciiString("goo2")); .add(new AsciiString("foo2"), new AsciiString("goo2"));
@ -446,10 +365,11 @@ public class HttpToHttp2ConnectionHandlerTest {
}).when(serverListener).onDataRead(any(ChannelHandlerContext.class), eq(3), }).when(serverListener).onDataRead(any(ChannelHandlerContext.class), eq(3),
any(ByteBuf.class), eq(0), eq(false)); any(ByteBuf.class), eq(0), eq(false));
bootstrapEnv(4, 1, 1); bootstrapEnv(4, 1, 1);
final FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, POST, "/example", final FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, POST,
"http://your_user-name123@www.example.org:5555/example",
Unpooled.copiedBuffer(text, UTF_8)); Unpooled.copiedBuffer(text, UTF_8));
final HttpHeaders httpHeaders = request.headers(); final HttpHeaders httpHeaders = request.headers();
httpHeaders.set(HttpHeaderNames.HOST, "http://your_user-name123@www.example.org:5555/example"); httpHeaders.set(HttpHeaderNames.HOST, "www.example.org:5555");
httpHeaders.add("foo", "goo"); httpHeaders.add("foo", "goo");
httpHeaders.add("foo", "goo2"); httpHeaders.add("foo", "goo2");
httpHeaders.add("foo2", "goo2"); httpHeaders.add("foo2", "goo2");
@ -497,9 +417,10 @@ public class HttpToHttp2ConnectionHandlerTest {
}).when(serverListener).onDataRead(any(ChannelHandlerContext.class), eq(3), }).when(serverListener).onDataRead(any(ChannelHandlerContext.class), eq(3),
any(ByteBuf.class), eq(0), eq(false)); any(ByteBuf.class), eq(0), eq(false));
bootstrapEnv(4, 1, 1); bootstrapEnv(4, 1, 1);
final HttpRequest request = new DefaultHttpRequest(HTTP_1_1, POST, "/example"); final HttpRequest request = new DefaultHttpRequest(HTTP_1_1, POST,
"http://your_user-name123@www.example.org:5555/example");
final HttpHeaders httpHeaders = request.headers(); final HttpHeaders httpHeaders = request.headers();
httpHeaders.set(HttpHeaderNames.HOST, "http://your_user-name123@www.example.org:5555/example"); httpHeaders.set(HttpHeaderNames.HOST, "www.example.org:5555");
httpHeaders.add(HttpHeaderNames.TRANSFER_ENCODING, "chunked"); httpHeaders.add(HttpHeaderNames.TRANSFER_ENCODING, "chunked");
httpHeaders.add("foo", "goo"); httpHeaders.add("foo", "goo");
httpHeaders.add("foo", "goo2"); httpHeaders.add("foo", "goo2");
@ -593,6 +514,19 @@ public class HttpToHttp2ConnectionHandlerTest {
clientChannel = ccf.channel(); clientChannel = ccf.channel();
} }
private void verifyHeadersOnly(Http2Headers expected, ChannelPromise writePromise, ChannelFuture writeFuture)
throws Exception {
assertTrue(writePromise.awaitUninterruptibly(WAIT_TIME_SECONDS, SECONDS));
assertTrue(writePromise.isSuccess());
assertTrue(writeFuture.awaitUninterruptibly(WAIT_TIME_SECONDS, SECONDS));
assertTrue(writeFuture.isSuccess());
awaitRequests();
verify(serverListener).onHeadersRead(any(ChannelHandlerContext.class), eq(5),
eq(expected), eq(0), anyShort(), anyBoolean(), eq(0), eq(true));
verify(serverListener, never()).onDataRead(any(ChannelHandlerContext.class), anyInt(),
any(ByteBuf.class), anyInt(), anyBoolean());
}
private void awaitRequests() throws Exception { private void awaitRequests() throws Exception {
assertTrue(requestLatch.await(WAIT_TIME_SECONDS, SECONDS)); assertTrue(requestLatch.await(WAIT_TIME_SECONDS, SECONDS));
if (trailersLatch != null) { if (trailersLatch != null) {