diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/AbstractHttp2ConnectionHandlerBuilder.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/AbstractHttp2ConnectionHandlerBuilder.java index ab54fd8ea2..f8c8fc0724 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/AbstractHttp2ConnectionHandlerBuilder.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/AbstractHttp2ConnectionHandlerBuilder.java @@ -535,8 +535,8 @@ public abstract class AbstractHttp2ConnectionHandlerBuilder connectionSpecificHeaders = Collections.unmodifiableList( - Arrays.asList(CONNECTION, TRANSFER_ENCODING, KEEP_ALIVE, UPGRADE)); - - private Http2HeadersValidator() { - } - - /** - * Validates connection-specific headers according to - * RFC7540, section-8.1.2.2 - */ - static void validateConnectionSpecificHeaders(Http2Headers headers, int streamId) throws Http2Exception { - for (int i = 0; i < connectionSpecificHeaders.size(); i++) { - final AsciiString header = connectionSpecificHeaders.get(i); - if (headers.contains(header)) { - throw streamError(streamId, PROTOCOL_ERROR, - "Connection-specific headers like [%s] must not be used with HTTP/2.", header); - } - } - - final CharSequence teHeader = headers.get(TE); - if (teHeader != null && !AsciiString.contentEqualsIgnoreCase(teHeader, TRAILERS)) { - throw streamError(streamId, PROTOCOL_ERROR, - "TE header must not contain any value other than \"%s\"", TRAILERS); - } - } - - /** - * Validates response pseudo-header fields - */ - static void validateResponsePseudoHeaders(Http2Headers headers, int streamId) throws Http2Exception { - for (Entry entry : headers) { - final CharSequence key = entry.getKey(); - if (!hasPseudoHeaderFormat(key)) { - // We know that pseudo header appears first so we can stop - // looking once we get to the first non pseudo headers. - break; - } - - final PseudoHeaderName pseudoHeader = PseudoHeaderName.getPseudoHeader(key); - if (pseudoHeader.isRequestOnly()) { - throw streamError(streamId, PROTOCOL_ERROR, - "Request pseudo-header [%s] is not allowed in a response.", key); - } - } - } - - /** - * Validates request pseudo-header fields according to - * RFC7540, section-8.1.2.3 - */ - static void validateRequestPseudoHeaders(Http2Headers headers, int streamId) throws Http2Exception { - final CharSequence method = headers.get(METHOD.value()); - if (method == null) { - throw streamError(streamId, PROTOCOL_ERROR, - "Mandatory header [:method] is missing."); - } - - if (HttpMethod.CONNECT.asciiName().contentEqualsIgnoreCase(method)) { - if (headers.contains(SCHEME.value())) { - throw streamError(streamId, PROTOCOL_ERROR, - "Header [:scheme] must be omitted when using CONNECT method."); - } - - if (headers.contains(PATH.value())) { - throw streamError(streamId, PROTOCOL_ERROR, - "Header [:path] must be omitted when using CONNECT method."); - } - - if (headers.getAll(METHOD.value()).size() > 1) { - throw streamError(streamId, PROTOCOL_ERROR, - "Header [:method] should have a unique value."); - } - } else { - final CharSequence path = headers.get(PATH.value()); - if (path != null && path.length() == 0) { - throw streamError(streamId, PROTOCOL_ERROR, "[:path] header cannot be empty."); - } - - int methodHeadersCount = 0; - int pathHeadersCount = 0; - int schemeHeadersCount = 0; - for (Entry entry : headers) { - final CharSequence key = entry.getKey(); - if (!hasPseudoHeaderFormat(key)) { - // We know that pseudo header appears first so we can stop - // looking once we get to the first non pseudo headers. - break; - } - - final PseudoHeaderName pseudoHeader = PseudoHeaderName.getPseudoHeader(key); - if (METHOD.value().contentEquals(key)) { - methodHeadersCount++; - } else if (PATH.value().contentEquals(key)) { - pathHeadersCount++; - } else if (SCHEME.value().contentEquals(key)) { - schemeHeadersCount++; - } else if (!pseudoHeader.isRequestOnly()) { - throw streamError(streamId, PROTOCOL_ERROR, - "Response pseudo-header [%s] is not allowed in a request.", key); - } - } - - validatePseudoHeaderCount(streamId, methodHeadersCount, METHOD); - validatePseudoHeaderCount(streamId, pathHeadersCount, PATH); - validatePseudoHeaderCount(streamId, schemeHeadersCount, SCHEME); - } - } - - private static void validatePseudoHeaderCount(int streamId, int valueCount, PseudoHeaderName headerName) - throws Http2Exception { - if (valueCount == 0) { - throw streamError(streamId, PROTOCOL_ERROR, - "Mandatory header [%s] is missing.", headerName.value()); - } else if (valueCount > 1) { - throw streamError(streamId, PROTOCOL_ERROR, - "Header [%s] should have a unique value.", headerName.value()); - } - } -} diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/HttpConversionUtil.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/HttpConversionUtil.java index b7e3e79faf..69f5454be3 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/HttpConversionUtil.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/HttpConversionUtil.java @@ -391,14 +391,9 @@ public final class HttpConversionUtil { if (in instanceof HttpRequest) { HttpRequest request = (HttpRequest) in; URI requestTargetUri = URI.create(request.uri()); + out.path(toHttp2Path(requestTargetUri)); out.method(request.method().asciiName()); - - // According to the spec https://tools.ietf.org/html/rfc7540#section-8.3 scheme and path - // should be omitted for CONNECT method - if (request.method() != HttpMethod.CONNECT) { - setHttp2Scheme(inHeaders, requestTargetUri, out); - out.path(toHttp2Path(requestTargetUri)); - } + setHttp2Scheme(inHeaders, requestTargetUri, out); if (!isOriginForm(requestTargetUri) && !isAsteriskForm(requestTargetUri)) { // Attempt to take from HOST header before taking from the request-line diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/DataCompressionHttp2Test.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/DataCompressionHttp2Test.java index 8eba3006ad..c9752342da 100644 --- a/codec-http2/src/test/java/io/netty/handler/codec/http2/DataCompressionHttp2Test.java +++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/DataCompressionHttp2Test.java @@ -68,7 +68,6 @@ public class DataCompressionHttp2Test { private static final AsciiString GET = new AsciiString("GET"); private static final AsciiString POST = new AsciiString("POST"); private static final AsciiString PATH = new AsciiString("/some/path"); - private static final AsciiString SCHEME = new AsciiString("http"); @Mock private Http2FrameListener serverListener; @@ -137,7 +136,7 @@ public class DataCompressionHttp2Test { @Test public void justHeadersNoData() throws Exception { bootstrapEnv(0); - final Http2Headers headers = new DefaultHttp2Headers().method(GET).path(PATH).scheme(SCHEME) + final Http2Headers headers = new DefaultHttp2Headers().method(GET).path(PATH) .set(HttpHeaderNames.CONTENT_ENCODING, HttpHeaderValues.GZIP); runInChannel(clientChannel, () -> { @@ -155,7 +154,7 @@ public class DataCompressionHttp2Test { final ByteBuf data = Unpooled.copiedBuffer(text.getBytes()); bootstrapEnv(data.readableBytes()); try { - final Http2Headers headers = new DefaultHttp2Headers().method(POST).path(PATH).scheme(SCHEME) + final Http2Headers headers = new DefaultHttp2Headers().method(POST).path(PATH) .set(HttpHeaderNames.CONTENT_ENCODING, HttpHeaderValues.GZIP); runInChannel(clientChannel, () -> { @@ -176,7 +175,7 @@ public class DataCompressionHttp2Test { final ByteBuf data = Unpooled.copiedBuffer(text.getBytes()); bootstrapEnv(data.readableBytes()); try { - final Http2Headers headers = new DefaultHttp2Headers().method(POST).path(PATH).scheme(SCHEME) + final Http2Headers headers = new DefaultHttp2Headers().method(POST).path(PATH) .set(HttpHeaderNames.CONTENT_ENCODING, HttpHeaderValues.GZIP); runInChannel(clientChannel, () -> { @@ -199,7 +198,7 @@ public class DataCompressionHttp2Test { final ByteBuf data2 = Unpooled.copiedBuffer(text2.getBytes()); bootstrapEnv(data1.readableBytes() + data2.readableBytes()); try { - final Http2Headers headers = new DefaultHttp2Headers().method(POST).path(PATH).scheme(SCHEME) + final Http2Headers headers = new DefaultHttp2Headers().method(POST).path(PATH) .set(HttpHeaderNames.CONTENT_ENCODING, HttpHeaderValues.GZIP); runInChannel(clientChannel, () -> { @@ -224,7 +223,7 @@ public class DataCompressionHttp2Test { bootstrapEnv(BUFFER_SIZE); final ByteBuf data = Unpooled.wrappedBuffer(bytes); try { - final Http2Headers headers = new DefaultHttp2Headers().method(POST).path(PATH).scheme(SCHEME) + final Http2Headers headers = new DefaultHttp2Headers().method(POST).path(PATH) .set(HttpHeaderNames.CONTENT_ENCODING, HttpHeaderValues.DEFLATE); runInChannel(clientChannel, () -> { diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoderTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoderTest.java index 8b9594bab5..2d15029199 100644 --- a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoderTest.java +++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoderTest.java @@ -23,13 +23,13 @@ import io.netty.channel.ChannelPromise; import io.netty.channel.DefaultChannelPromise; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.util.concurrent.ImmediateEventExecutor; -import io.netty.handler.codec.http2.Http2Headers.PseudoHeaderName; import junit.framework.AssertionFailedError; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import java.util.Collections; @@ -39,11 +39,9 @@ import static io.netty.buffer.Unpooled.EMPTY_BUFFER; import static io.netty.buffer.Unpooled.wrappedBuffer; import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_PRIORITY_WEIGHT; import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR; -import static io.netty.handler.codec.http2.Http2Stream.State.HALF_CLOSED_REMOTE; import static io.netty.handler.codec.http2.Http2Stream.State.IDLE; import static io.netty.handler.codec.http2.Http2Stream.State.OPEN; import static io.netty.handler.codec.http2.Http2Stream.State.RESERVED_REMOTE; -import static io.netty.handler.codec.http2.Http2TestUtil.newHttp2HeadersWithRequestPseudoHeaders; import static io.netty.util.CharsetUtil.UTF_8; import static org.hamcrest.Matchers.instanceOf; @@ -185,8 +183,7 @@ public class DefaultHttp2ConnectionDecoderTest { when(ctx.newPromise()).thenReturn(promise); when(ctx.write(any())).thenReturn(future); - decoder = new DefaultHttp2ConnectionDecoder(connection, encoder, reader, - Http2PromisedRequestVerifier.ALWAYS_VERIFY, true, true, true); + decoder = new DefaultHttp2ConnectionDecoder(connection, encoder, reader); decoder.lifecycleManager(lifecycleManager); decoder.frameListener(listener); @@ -501,64 +498,6 @@ public class DefaultHttp2ConnectionDecoderTest { eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(false)); } - @Test(expected = Http2Exception.class) - public void requestPseudoHeadersInResponseThrows() throws Exception { - when(connection.isServer()).thenReturn(false); - when(connection.stream(STREAM_ID)).thenReturn(null); - when(connection.streamMayHaveExisted(STREAM_ID)).thenReturn(false); - when(remote.createStream(eq(STREAM_ID), anyBoolean())).thenReturn(stream); - when(stream.state()).thenReturn(HALF_CLOSED_REMOTE); - final Http2Headers headers = newHttp2HeadersWithRequestPseudoHeaders(); - decode().onHeadersRead(ctx, STREAM_ID, headers, 0, false); - } - - @Test(expected = Http2Exception.class) - public void missingPseudoHeadersInLeadingHeaderThrows() throws Exception { - when(connection.isServer()).thenReturn(true); - when(connection.stream(STREAM_ID)).thenReturn(null); - when(connection.streamMayHaveExisted(STREAM_ID)).thenReturn(false); - when(remote.createStream(eq(STREAM_ID), anyBoolean())).thenReturn(stream); - when(stream.state()).thenReturn(HALF_CLOSED_REMOTE); - final Http2Headers headers = newHttp2HeadersWithRequestPseudoHeaders(); - headers.remove(PseudoHeaderName.METHOD.value()); - decode().onHeadersRead(ctx, STREAM_ID, headers, 0, false); - } - - @Test - public void missingPseudoHeadersInLeadingHeaderShouldNotThrowsIfValidationDisabled() throws Exception { - when(connection.isServer()).thenReturn(true); - when(connection.stream(STREAM_ID)).thenReturn(null); - when(connection.streamMayHaveExisted(STREAM_ID)).thenReturn(false); - when(remote.createStream(eq(STREAM_ID), anyBoolean())).thenReturn(stream); - when(stream.state()).thenReturn(HALF_CLOSED_REMOTE); - final Http2Headers headers = newHttp2HeadersWithRequestPseudoHeaders(); - headers.remove(PseudoHeaderName.METHOD.value()); - - decoder = new DefaultHttp2ConnectionDecoder(connection, encoder, reader, - Http2PromisedRequestVerifier.ALWAYS_VERIFY, true, true, false); - decoder.lifecycleManager(lifecycleManager); - decoder.frameListener(listener); - - // Simulate receiving the initial settings from the remote endpoint. - decode().onSettingsRead(ctx, new Http2Settings()); - // Simulate receiving the SETTINGS ACK for the initial settings. - decode().onSettingsAckRead(ctx); - - decode().onHeadersRead(ctx, STREAM_ID, headers, 0, false); - } - - @Test - public void missingPseudoHeadersInTrailerHeaderDoesNotThrow() throws Exception { - when(connection.isServer()).thenReturn(true); - when(connection.stream(STREAM_ID)).thenReturn(stream); - - decode().onHeadersRead(ctx, STREAM_ID, newHttp2HeadersWithRequestPseudoHeaders(), 0, false); - - final Http2Headers headers = newHttp2HeadersWithRequestPseudoHeaders(); - headers.remove(PseudoHeaderName.METHOD.value()); - decode().onHeadersRead(ctx, STREAM_ID, headers, 0, true); - } - @Test(expected = Http2Exception.class) public void trailersDoNotEndStreamThrows() throws Exception { decode().onHeadersRead(ctx, STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, false); diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/HpackDecoderTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/HpackDecoderTest.java index 7abbbe4598..7b26d90d17 100644 --- a/codec-http2/src/test/java/io/netty/handler/codec/http2/HpackDecoderTest.java +++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/HpackDecoderTest.java @@ -575,6 +575,46 @@ public class HpackDecoderTest { } } + @Test + public void requestPseudoHeaderInResponse() throws Exception { + ByteBuf in = Unpooled.buffer(200); + try { + HpackEncoder hpackEncoder = new HpackEncoder(true); + + Http2Headers toEncode = new DefaultHttp2Headers(); + toEncode.add(":status", "200"); + toEncode.add(":method", "GET"); + hpackEncoder.encodeHeaders(1, in, toEncode, NEVER_SENSITIVE); + + Http2Headers decoded = new DefaultHttp2Headers(); + + expectedException.expect(Http2Exception.StreamException.class); + hpackDecoder.decode(1, in, decoded, true); + } finally { + in.release(); + } + } + + @Test + public void responsePseudoHeaderInRequest() throws Exception { + ByteBuf in = Unpooled.buffer(200); + try { + HpackEncoder hpackEncoder = new HpackEncoder(true); + + Http2Headers toEncode = new DefaultHttp2Headers(); + toEncode.add(":method", "GET"); + toEncode.add(":status", "200"); + hpackEncoder.encodeHeaders(1, in, toEncode, NEVER_SENSITIVE); + + Http2Headers decoded = new DefaultHttp2Headers(); + + expectedException.expect(Http2Exception.StreamException.class); + hpackDecoder.decode(1, in, decoded, true); + } finally { + in.release(); + } + } + @Test public void pseudoHeaderAfterRegularHeader() throws Exception { ByteBuf in = Unpooled.buffer(200); @@ -604,7 +644,7 @@ public class HpackDecoderTest { Http2Headers toEncode = new DefaultHttp2Headers(); toEncode.add(":method", "GET"); - toEncode.add(":unknownpseudoheader", "200"); + toEncode.add(":status", "200"); toEncode.add("foo", "bar"); hpackEncoder.encodeHeaders(1, in1, toEncode, NEVER_SENSITIVE); @@ -624,7 +664,7 @@ public class HpackDecoderTest { assertEquals(3, decoded.size()); assertEquals("GET", decoded.method().toString()); - assertEquals("200", decoded.get(":unknownpseudoheader").toString()); + assertEquals("200", decoded.status().toString()); assertEquals("bar", decoded.get("foo").toString()); } finally { in1.release(); diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2HeadersValidatorTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2HeadersValidatorTest.java deleted file mode 100644 index 23f29c9f2d..0000000000 --- a/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2HeadersValidatorTest.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright 2018 The Netty Project - * - * The Netty Project licenses this file to you under the Apache License, version 2.0 (the - * "License"); you may not use this file except in compliance with the License. You may obtain a - * copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ -package io.netty.handler.codec.http2; - -import io.netty.handler.codec.http2.Http2Exception.StreamException; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; - -import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION; -import static io.netty.handler.codec.http.HttpHeaderNames.TE; -import static io.netty.handler.codec.http2.Http2Headers.PseudoHeaderName.METHOD; -import static io.netty.handler.codec.http2.Http2Headers.PseudoHeaderName.PATH; -import static io.netty.handler.codec.http2.Http2Headers.PseudoHeaderName.SCHEME; -import static io.netty.handler.codec.http2.Http2Headers.PseudoHeaderName.STATUS; -import static io.netty.handler.codec.http2.Http2HeadersValidator.validateConnectionSpecificHeaders; -import static io.netty.handler.codec.http2.Http2HeadersValidator.validateRequestPseudoHeaders; -import static io.netty.handler.codec.http2.Http2TestUtil.newHttp2HeadersWithRequestPseudoHeaders; - -public class Http2HeadersValidatorTest { - - private static final int STREAM_ID = 3; - - @Rule - public final ExpectedException expectedException = ExpectedException.none(); - - @Test - public void validateConnectionSpecificHeadersShouldThrowIfConnectionHeaderPresent() throws Http2Exception { - expectedException.expect(StreamException.class); - expectedException.expectMessage("Connection-specific headers like [connection] must not be used with HTTP"); - - final Http2Headers headers = newHttp2HeadersWithRequestPseudoHeaders(); - headers.add(CONNECTION, "keep-alive"); - validateConnectionSpecificHeaders(headers, STREAM_ID); - } - - @Test - public void validateConnectionSpecificHeadersShouldThrowIfTeHeaderValueIsNotTrailers() throws Http2Exception { - expectedException.expect(StreamException.class); - expectedException.expectMessage("TE header must not contain any value other than \"trailers\""); - - final Http2Headers headers = newHttp2HeadersWithRequestPseudoHeaders(); - headers.add(TE, "trailers, deflate"); - validateConnectionSpecificHeaders(headers, STREAM_ID); - } - - @Test - public void validatePseudoHeadersShouldThrowWhenMethodHeaderIsMissing() throws Http2Exception { - expectedException.expect(StreamException.class); - expectedException.expectMessage("Mandatory header [:method] is missing."); - - final Http2Headers headers = newHttp2HeadersWithRequestPseudoHeaders(); - headers.remove(METHOD.value()); - validateRequestPseudoHeaders(headers, STREAM_ID); - } - - @Test - public void validatePseudoHeadersShouldThrowWhenPathHeaderIsMissing() throws Http2Exception { - expectedException.expect(StreamException.class); - expectedException.expectMessage("Mandatory header [:path] is missing."); - - final Http2Headers headers = newHttp2HeadersWithRequestPseudoHeaders(); - headers.remove(PATH.value()); - validateRequestPseudoHeaders(headers, STREAM_ID); - } - - @Test - public void validatePseudoHeadersShouldThrowWhenPathHeaderIsEmpty() throws Http2Exception { - expectedException.expect(StreamException.class); - expectedException.expectMessage("[:path] header cannot be empty."); - - final Http2Headers headers = newHttp2HeadersWithRequestPseudoHeaders(); - headers.set(PATH.value(), ""); - validateRequestPseudoHeaders(headers, STREAM_ID); - } - - @Test - public void validatePseudoHeadersShouldThrowWhenSchemeHeaderIsMissing() throws Http2Exception { - expectedException.expect(StreamException.class); - expectedException.expectMessage("Mandatory header [:scheme] is missing."); - - final Http2Headers headers = newHttp2HeadersWithRequestPseudoHeaders(); - headers.remove(SCHEME.value()); - validateRequestPseudoHeaders(headers, STREAM_ID); - } - - @Test - public void validatePseudoHeadersShouldThrowIfMethodHeaderIsNotUnique() throws Http2Exception { - expectedException.expect(StreamException.class); - expectedException.expectMessage("Header [:method] should have a unique value."); - - final Http2Headers headers = newHttp2HeadersWithRequestPseudoHeaders(); - headers.add(METHOD.value(), "GET"); - validateRequestPseudoHeaders(headers, STREAM_ID); - } - - @Test - public void validatePseudoHeadersShouldThrowIfPathHeaderIsNotUnique() throws Http2Exception { - expectedException.expect(StreamException.class); - expectedException.expectMessage("Header [:path] should have a unique value."); - - final Http2Headers headers = newHttp2HeadersWithRequestPseudoHeaders(); - headers.add(PATH.value(), "/"); - validateRequestPseudoHeaders(headers, STREAM_ID); - } - - @Test - public void validatePseudoHeadersShouldThrowIfSchemeHeaderIsNotUnique() throws Http2Exception { - expectedException.expect(StreamException.class); - expectedException.expectMessage("Header [:scheme] should have a unique value."); - - final Http2Headers headers = newHttp2HeadersWithRequestPseudoHeaders(); - headers.add(SCHEME.value(), "/"); - validateRequestPseudoHeaders(headers, STREAM_ID); - } - - @Test - public void validatePseudoHeadersShouldThrowIfMethodHeaderIsNotUniqueWhenMethodIsConnect() throws Http2Exception { - expectedException.expect(StreamException.class); - expectedException.expectMessage("Header [:method] should have a unique value."); - - final Http2Headers headers = newHttp2HeadersWithRequestPseudoHeaders(); - headers.remove(SCHEME.value()); - headers.remove(PATH.value()); - headers.set(METHOD.value(), "CONNECT"); - headers.add(METHOD.value(), "CONNECT"); - validateRequestPseudoHeaders(headers, STREAM_ID); - } - - @Test - public void validatePseudoHeadersShouldThrowIfPathHeaderIsPresentWhenMethodIsConnect() throws Http2Exception { - expectedException.expect(StreamException.class); - expectedException.expectMessage("Header [:path] must be omitted when using CONNECT method."); - - final Http2Headers headers = newHttp2HeadersWithRequestPseudoHeaders(); - headers.set(METHOD.value(), "CONNECT"); - headers.remove(SCHEME.value()); - validateRequestPseudoHeaders(headers, STREAM_ID); - } - - @Test - public void validatePseudoHeadersShouldThrowIfSchemeHeaderIsPresentWhenMethodIsConnect() throws Http2Exception { - expectedException.expect(StreamException.class); - expectedException.expectMessage("Header [:scheme] must be omitted when using CONNECT method."); - - final Http2Headers headers = newHttp2HeadersWithRequestPseudoHeaders(); - headers.set(METHOD.value(), "CONNECT"); - headers.remove(PATH.value()); - validateRequestPseudoHeaders(headers, STREAM_ID); - } - - @Test - public void validatePseudoHeadersShouldThrowIfResponseHeaderInRequest() throws Http2Exception { - expectedException.expect(StreamException.class); - expectedException.expectMessage("Response pseudo-header [:status] is not allowed in a request."); - - final Http2Headers headers = newHttp2HeadersWithRequestPseudoHeaders(); - headers.add(STATUS.value(), "200"); - validateRequestPseudoHeaders(headers, STREAM_ID); - } - -} diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2MultiplexCodecBuilderTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2MultiplexCodecBuilderTest.java index c1c23dde35..9e991c9696 100644 --- a/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2MultiplexCodecBuilderTest.java +++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2MultiplexCodecBuilderTest.java @@ -42,7 +42,6 @@ import org.junit.Test; import java.util.concurrent.CountDownLatch; import static io.netty.handler.codec.http2.Http2CodecUtil.isStreamIdValid; -import static io.netty.handler.codec.http2.Http2TestUtil.newHttp2HeadersWithRequestPseudoHeaders; import static java.util.concurrent.TimeUnit.SECONDS; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -158,8 +157,8 @@ public class Http2MultiplexCodecBuilderTest { assertTrue(childChannel2.isActive()); assertFalse(isStreamIdValid(childChannel2.stream().id())); - Http2Headers headers1 = newHttp2HeadersWithRequestPseudoHeaders(); - Http2Headers headers2 = newHttp2HeadersWithRequestPseudoHeaders(); + Http2Headers headers1 = new DefaultHttp2Headers(); + Http2Headers headers2 = new DefaultHttp2Headers(); // Test that streams can be made active (headers sent) in different order than the corresponding channels // have been created. childChannel2.writeAndFlush(new DefaultHttp2HeadersFrame(headers2)); @@ -188,7 +187,7 @@ public class Http2MultiplexCodecBuilderTest { assertTrue(childChannel.isRegistered()); assertTrue(childChannel.isActive()); - Http2Headers headers = newHttp2HeadersWithRequestPseudoHeaders(); + Http2Headers headers = new DefaultHttp2Headers(); childChannel.writeAndFlush(new DefaultHttp2HeadersFrame(headers)); ByteBuf data = Unpooled.buffer(100).writeZero(100); childChannel.writeAndFlush(new DefaultHttp2DataFrame(data, true)); diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2TestUtil.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2TestUtil.java index ffc8939284..d9d1102f38 100644 --- a/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2TestUtil.java +++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2TestUtil.java @@ -94,13 +94,6 @@ public final class Http2TestUtil { return data; } - public static Http2Headers newHttp2HeadersWithRequestPseudoHeaders() { - return new DefaultHttp2Headers(true) - .method("GET") - .path("/") - .scheme("https"); - } - /** * Returns an {@link AsciiString} that wraps a randomly-filled byte array. */ diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/HttpToHttp2ConnectionHandlerTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/HttpToHttp2ConnectionHandlerTest.java index 6c9744009c..2ab0d97603 100644 --- a/codec-http2/src/test/java/io/netty/handler/codec/http2/HttpToHttp2ConnectionHandlerTest.java +++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/HttpToHttp2ConnectionHandlerTest.java @@ -244,8 +244,8 @@ public class HttpToHttp2ConnectionHandlerTest { final HttpHeaders httpHeaders = request.headers(); httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 5); final Http2Headers http2Headers = - new DefaultHttp2Headers().method(new AsciiString("CONNECT")) - .authority(new AsciiString("www.example.com:80")); + new DefaultHttp2Headers().method(new AsciiString("CONNECT")).path(new AsciiString("/")) + .scheme(new AsciiString("http")).authority(new AsciiString("www.example.com:80")); ChannelPromise writePromise = newPromise(); verifyHeadersOnly(http2Headers, writePromise, clientChannel.writeAndFlush(request, writePromise)); 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 d1f8e4e6db..f68ee9ef5e 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 @@ -42,7 +42,6 @@ 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.HttpConversionUtil.ExtensionHeaderNames; import io.netty.util.AsciiString; import io.netty.util.CharsetUtil; import io.netty.util.concurrent.Future; @@ -256,15 +255,12 @@ public class InboundHttp2ToHttpAdapterTest { httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, text.length()); httpHeaders.setShort(HttpConversionUtil.ExtensionHeaderNames.STREAM_WEIGHT.text(), (short) 16); - httpHeaders.set(ExtensionHeaderNames.SCHEME.text(), "http"); - final Http2Headers http2Headers = new DefaultHttp2Headers() - .method(new AsciiString("GET")) - .scheme(new AsciiString("http")) - .path(new AsciiString("/some/path/resource2")); + final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("GET")).path( + new AsciiString("/some/path/resource2")); runInChannel(clientChannel, () -> { clientHandler.encoder().writeHeaders(ctxClient(), 3, http2Headers, 0, false, newPromiseClient()); clientHandler.encoder().writeData(ctxClient(), 3, content.retainedDuplicate(), 0, true, - newPromiseClient()); + newPromiseClient()); clientChannel.flush(); }); awaitRequests(); @@ -289,11 +285,8 @@ public class InboundHttp2ToHttpAdapterTest { httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, text.length()); httpHeaders.setShort(HttpConversionUtil.ExtensionHeaderNames.STREAM_WEIGHT.text(), (short) 16); - httpHeaders.set(ExtensionHeaderNames.SCHEME.text(), "http"); - final Http2Headers http2Headers = new DefaultHttp2Headers() - .method(new AsciiString("GET")) - .scheme(new AsciiString("http")) - .path(new AsciiString("/some/path/resource2")); + final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("GET")).path( + new AsciiString("/some/path/resource2")); final int midPoint = text.length() / 2; runInChannel(clientChannel, () -> { clientHandler.encoder().writeHeaders(ctxClient(), 3, http2Headers, 0, false, newPromiseClient()); @@ -326,11 +319,8 @@ public class InboundHttp2ToHttpAdapterTest { httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, text.length()); httpHeaders.setShort(HttpConversionUtil.ExtensionHeaderNames.STREAM_WEIGHT.text(), (short) 16); - httpHeaders.set(ExtensionHeaderNames.SCHEME.text(), "http"); - final Http2Headers http2Headers = new DefaultHttp2Headers() - .method(new AsciiString("GET")) - .scheme("http") - .path(new AsciiString("/some/path/resource2")); + final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("GET")).path( + new AsciiString("/some/path/resource2")); runInChannel(clientChannel, () -> { clientHandler.encoder().writeHeaders(ctxClient(), 3, http2Headers, 0, false, newPromiseClient()); clientHandler.encoder().writeData(ctxClient(), 3, content.retain(), 0, false, newPromiseClient()); @@ -360,15 +350,12 @@ public class InboundHttp2ToHttpAdapterTest { httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, text.length()); httpHeaders.setShort(HttpConversionUtil.ExtensionHeaderNames.STREAM_WEIGHT.text(), (short) 16); - httpHeaders.set(ExtensionHeaderNames.SCHEME.text(), "http"); HttpHeaders trailingHeaders = request.trailingHeaders(); trailingHeaders.set(of("Foo"), of("goo")); trailingHeaders.set(of("fOo2"), of("goo2")); trailingHeaders.add(of("foO2"), of("goo3")); - final Http2Headers http2Headers = new DefaultHttp2Headers() - .method(new AsciiString("GET")) - .scheme(new AsciiString("http")) - .path(new AsciiString("/some/path/resource2")); + final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("GET")).path( + new AsciiString("/some/path/resource2")); final Http2Headers http2Headers2 = new DefaultHttp2Headers() .set(new AsciiString("foo"), new AsciiString("goo")) .set(new AsciiString("foo2"), new AsciiString("goo2")) @@ -406,31 +393,24 @@ public class InboundHttp2ToHttpAdapterTest { httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, text.length()); httpHeaders.setShort(HttpConversionUtil.ExtensionHeaderNames.STREAM_WEIGHT.text(), (short) 16); - httpHeaders.set(ExtensionHeaderNames.SCHEME.text(), "http"); HttpHeaders httpHeaders2 = request2.headers(); httpHeaders2.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 5); httpHeaders2.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_DEPENDENCY_ID.text(), 3); httpHeaders2.setShort(HttpConversionUtil.ExtensionHeaderNames.STREAM_WEIGHT.text(), (short) 123); httpHeaders2.setInt(HttpHeaderNames.CONTENT_LENGTH, text2.length()); - - httpHeaders2.set(ExtensionHeaderNames.SCHEME.text(), "http"); - final Http2Headers http2Headers = new DefaultHttp2Headers() - .method(new AsciiString("PUT")) - .scheme(new AsciiString("http")) - .path(new AsciiString("/some/path/resource")); - final Http2Headers http2Headers2 = new DefaultHttp2Headers() - .method(new AsciiString("PUT")) - .scheme(new AsciiString("http")) - .path(new AsciiString("/some/path/resource2")); + final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("PUT")).path( + new AsciiString("/some/path/resource")); + final Http2Headers http2Headers2 = new DefaultHttp2Headers().method(new AsciiString("PUT")).path( + new AsciiString("/some/path/resource2")); runInChannel(clientChannel, () -> { clientHandler.encoder().writeHeaders(ctxClient(), 3, http2Headers, 0, false, newPromiseClient()); clientHandler.encoder().writeHeaders(ctxClient(), 5, http2Headers2, 3, (short) 123, true, 0, false, newPromiseClient()); clientChannel.flush(); // Headers are queued in the flow controller and so flush them. clientHandler.encoder().writeData(ctxClient(), 3, content.retainedDuplicate(), 0, true, - newPromiseClient()); + newPromiseClient()); clientHandler.encoder().writeData(ctxClient(), 5, content2.retainedDuplicate(), 0, true, - newPromiseClient()); + newPromiseClient()); clientChannel.flush(); }); awaitRequests(); @@ -474,10 +454,7 @@ public class InboundHttp2ToHttpAdapterTest { httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, 0); httpHeaders.setShort(HttpConversionUtil.ExtensionHeaderNames.STREAM_WEIGHT.text(), (short) 16); - httpHeaders.set(ExtensionHeaderNames.SCHEME.text(), "http"); - final Http2Headers http2Headers3 = new DefaultHttp2Headers() - .method(new AsciiString("GET")) - .scheme("http") + final Http2Headers http2Headers3 = new DefaultHttp2Headers().method(new AsciiString("GET")) .path(new AsciiString("/push/test")); runInChannel(clientChannel, () -> { clientHandler.encoder().writeHeaders(ctxClient(), 3, http2Headers3, 0, true, newPromiseClient()); @@ -529,11 +506,9 @@ public class InboundHttp2ToHttpAdapterTest { httpHeaders.set(HttpHeaderNames.EXPECT, HttpHeaderValues.CONTINUE); httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, 0); httpHeaders.setShort(HttpConversionUtil.ExtensionHeaderNames.STREAM_WEIGHT.text(), (short) 16); - httpHeaders.set(ExtensionHeaderNames.SCHEME.text(), "http"); final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("PUT")) .path(new AsciiString("/info/test")) - .scheme(new AsciiString("http")) .set(new AsciiString(HttpHeaderNames.EXPECT.toString()), new AsciiString(HttpHeaderValues.CONTINUE.toString())); final FullHttpMessage response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE); diff --git a/testsuite-http2/pom.xml b/testsuite-http2/pom.xml index 006ec2ffac..87aa65e054 100644 --- a/testsuite-http2/pom.xml +++ b/testsuite-http2/pom.xml @@ -94,6 +94,15 @@ 5.1 - half closed (remote): Sends a HEADERS frame 5.1 - closed: Sends a HEADERS frame 5.1.1 - Sends stream identifier that is numerically smaller than previous + 8.1.2.2 - Sends a HEADERS frame that contains the connection-specific header field + 8.1.2.2 - Sends a HEADERS frame that contains the TE header field with any value other than "trailers" + 8.1.2.3 - Sends a HEADERS frame with empty ":path" pseudo-header field + 8.1.2.3 - Sends a HEADERS frame that omits ":method" pseudo-header field + 8.1.2.3 - Sends a HEADERS frame that omits ":scheme" pseudo-header field + 8.1.2.3 - Sends a HEADERS frame that omits ":path" pseudo-header field + 8.1.2.3 - Sends a HEADERS frame with duplicated ":method" pseudo-header field + 8.1.2.3 - Sends a HEADERS frame with duplicated ":method" pseudo-header field + 8.1.2.3 - Sends a HEADERS frame with duplicated ":scheme" pseudo-header field 8.1.2.6 - Sends a HEADERS frame with the "content-length" header field which does not equal the DATA frame payload length 8.1.2.6 - Sends a HEADERS frame with the "content-length" header field which does not equal the sum of the multiple DATA frames payload length