Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion netty/src/main/java/io/grpc/netty/GrpcHttp2HeadersUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -306,13 +306,18 @@ protected static void appendNameAndValue(StringBuilder builder, CharSequence nam

private List<Map.Entry<CharSequence, CharSequence>> namesAndValuesToImmutableList() {
ArrayList<Map.Entry<CharSequence, CharSequence>> list = new ArrayList<>(values.length);
addNamesAndValuesToList(list);
return Collections.unmodifiableList(list);
}

protected final void addNamesAndValuesToList(
List<Map.Entry<CharSequence, CharSequence>> list) {
for (int i = 0; i < namesAndValuesIdx; i += 2) {
String name = new String(namesAndValues[i], US_ASCII);
// If binary headers, the value is base64 encoded.
AsciiString value = values[i / 2];
list.add(new AbstractMap.SimpleImmutableEntry<CharSequence, CharSequence>(name, value));
}
return Collections.unmodifiableList(list);
}

protected final String namesAndValuesToString() {
Expand Down Expand Up @@ -494,6 +499,27 @@ public int size() {
return size;
}

@Override
public Iterator<Map.Entry<CharSequence, CharSequence>> iterator() {
List<Map.Entry<CharSequence, CharSequence>> list = new ArrayList<>(size());
addIfPresent(list, PATH_HEADER, path);
addIfPresent(list, AUTHORITY_HEADER, authority);
addIfPresent(list, METHOD_HEADER, method);
addIfPresent(list, SCHEME_HEADER, scheme);
addIfPresent(list, TE_HEADER, te);
addNamesAndValuesToList(list);
return Collections.unmodifiableList(list).iterator();
}

private static void addIfPresent(
List<Map.Entry<CharSequence, CharSequence>> list, AsciiString name, AsciiString value) {
if (value != null) {
list.add(
new AbstractMap.SimpleImmutableEntry<CharSequence, CharSequence>(
name.toString(), value));
}
}

@Override
public String toString() {
StringBuilder builder = new StringBuilder(getClass().getSimpleName()).append('[');
Expand Down
36 changes: 36 additions & 0 deletions netty/src/test/java/io/grpc/netty/GrpcHttp2HeadersUtilsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,42 @@ public void headerIteartor_nonEmpty() {
assertThat(it.hasNext()).isFalse();
}

@Test
public void headerIterator_withPseudoHeadersAndTe() {
Http2Headers http2Headers = new GrpcHttp2RequestHeaders(2);
http2Headers.add(AsciiString.of(":path"), AsciiString.of("mypath"));
http2Headers.add(AsciiString.of(":authority"), AsciiString.of("myauthority"));
http2Headers.add(AsciiString.of(":method"), AsciiString.of("mymethod"));
http2Headers.add(AsciiString.of(":scheme"), AsciiString.of("myscheme"));
http2Headers.add(AsciiString.of("te"), AsciiString.of("trailers"));
http2Headers.add(AsciiString.of("custom"), AsciiString.of("val"));
Iterator<Map.Entry<CharSequence, CharSequence>> it = http2Headers.iterator();

assertNextEntry(it, ":path", AsciiString.of("mypath"));
assertNextEntry(it, ":authority", AsciiString.of("myauthority"));
assertNextEntry(it, ":method", AsciiString.of("mymethod"));
assertNextEntry(it, ":scheme", AsciiString.of("myscheme"));
assertNextEntry(it, "te", AsciiString.of("trailers"));
assertNextEntry(it, "custom", AsciiString.of("val"));
assertThat(it.hasNext()).isFalse();
}

@Test
public void headerIterator_nullPseudoHeadersSkipped() {
Http2Headers http2Headers = new GrpcHttp2RequestHeaders(2);
http2Headers.add(AsciiString.of(":path"), AsciiString.of("mypath"));
http2Headers.add(AsciiString.of(":authority"), AsciiString.of("myauthority"));
http2Headers.remove(AsciiString.of(":authority"));
http2Headers.add(AsciiString.of(":scheme"), AsciiString.of("myscheme"));
http2Headers.add(AsciiString.of("custom"), AsciiString.of("val"));
Iterator<Map.Entry<CharSequence, CharSequence>> it = http2Headers.iterator();

assertNextEntry(it, ":path", AsciiString.of("mypath"));
assertNextEntry(it, ":scheme", AsciiString.of("myscheme"));
assertNextEntry(it, "custom", AsciiString.of("val"));
assertThat(it.hasNext()).isFalse();
}

private static void assertNextEntry(
Iterator<Map.Entry<CharSequence, CharSequence>> it, CharSequence key, CharSequence value) {
Map.Entry<CharSequence, CharSequence> entry = it.next();
Expand Down
66 changes: 66 additions & 0 deletions netty/src/test/java/io/grpc/netty/NettyServerHandlerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http2.DefaultHttp2FrameWriter;
import io.netty.handler.codec.http2.DefaultHttp2Headers;
import io.netty.handler.codec.http2.Http2CodecUtil;
import io.netty.handler.codec.http2.Http2Error;
Expand Down Expand Up @@ -742,6 +743,71 @@ public void headersWithOnlyHostBecomesAuthority() throws Exception {
Truth.assertThat(metadataCaptor.getValue().get(hostKey)).isNull();
}

@Test
public void trailersWithPseudoHeadersShouldFail() throws Exception {
manualSetUp();
createStream();

Http2Headers trailers = new DefaultHttp2Headers()
.method(HTTP_METHOD)
.set(CONTENT_TYPE_HEADER, CONTENT_TYPE_GRPC)
.set(TE_HEADER, TE_TRAILERS)
.path(new AsciiString("/smuggled/method"));
channelRead(trailersFrame(STREAM_ID, trailers));

verifyWrite()
.writeRstStream(
eq(ctx()),
eq(STREAM_ID),
eq(Http2Error.PROTOCOL_ERROR.code()),
any(ChannelPromise.class));
verify(transportListener, times(1))
.streamCreated(any(ServerStream.class), anyString(), any(Metadata.class));
ArgumentCaptor<Status> captor = ArgumentCaptor.forClass(Status.class);
verify(streamListener).closed(captor.capture());
assertEquals(Code.INTERNAL, captor.getValue().getCode());
verify(streamListener, never()).halfClosed();
}

@Test
public void trailersWithPseudoHeadersAfterRejectedHeadersShouldFail() throws Exception {
manualSetUp();
Http2Headers badHeaders = new DefaultHttp2Headers()
.method(HTTP_METHOD)
.path(new AsciiString("/foo/bar"));
channelRead(headersFrame(STREAM_ID, badHeaders));
verifyWrite()
.writeHeaders(
eq(ctx()),
eq(STREAM_ID),
any(Http2Headers.class),
eq(0),
eq(false),
any(ChannelPromise.class));

Http2Headers trailers = new DefaultHttp2Headers()
.method(HTTP_METHOD)
.set(CONTENT_TYPE_HEADER, CONTENT_TYPE_GRPC)
.set(TE_HEADER, TE_TRAILERS)
.path(new AsciiString("/smuggled/method"));
channelRead(trailersFrame(STREAM_ID, trailers));

verifyWrite()
.writeRstStream(
eq(ctx()),
eq(STREAM_ID),
eq(Http2Error.PROTOCOL_ERROR.code()),
any(ChannelPromise.class));
verify(transportListener, never())
.streamCreated(any(ServerStream.class), anyString(), any(Metadata.class));
}

private ByteBuf trailersFrame(int streamId, Http2Headers headers) {
ChannelHandlerContext ctx = newMockContext();
new DefaultHttp2FrameWriter().writeHeaders(ctx, streamId, headers, 0, true, newPromise());
return captureWrite(ctx);
}

@Test
public void keepAliveManagerOnDataReceived_headersRead() throws Exception {
manualSetUp();
Expand Down
Loading