core: discard outbound content-length header (#8522)

Since netty version v4.1.67, content-lenght header validation will be enforced. So once grpc upgrades netty to that version or above, RPCs with invalid content-length header will fail.

Some libraries such as HTTP to gRPC adapters blindly copy all HTTP headers to gRPC metadata, but the content-length header is one of those that shouldn't be forwarded because gRPC uses different encoding. This mistake has already been in existence for a long time.

Discard outbound content-length headers in gRPC, so that users who encounter invalid content-length issue when upgrading grpc-java version on server/client side would be able to workaround by upgrading grpc-java on client/server side as well without fixing the HTTP adapter.
This commit is contained in:
ZHANG Dapeng 2021-09-13 17:15:45 -07:00 committed by GitHub
parent 876f56e2ea
commit 3b237339c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 27 additions and 0 deletions

View File

@ -24,6 +24,7 @@ import static io.grpc.Contexts.statusFromCancelled;
import static io.grpc.Status.DEADLINE_EXCEEDED;
import static io.grpc.internal.GrpcUtil.CONTENT_ACCEPT_ENCODING_KEY;
import static io.grpc.internal.GrpcUtil.CONTENT_ENCODING_KEY;
import static io.grpc.internal.GrpcUtil.CONTENT_LENGTH_KEY;
import static io.grpc.internal.GrpcUtil.MESSAGE_ACCEPT_ENCODING_KEY;
import static io.grpc.internal.GrpcUtil.MESSAGE_ENCODING_KEY;
import static java.lang.Math.max;
@ -163,6 +164,7 @@ final class ClientCallImpl<ReqT, RespT> extends ClientCall<ReqT, RespT> {
DecompressorRegistry decompressorRegistry,
Compressor compressor,
boolean fullStreamDecompression) {
headers.discardAll(CONTENT_LENGTH_KEY);
headers.discardAll(MESSAGE_ENCODING_KEY);
if (compressor != Codec.Identity.NONE) {
headers.put(MESSAGE_ENCODING_KEY, compressor.getMessageEncoding());

View File

@ -109,6 +109,9 @@ public final class GrpcUtil {
public static final Metadata.Key<byte[]> CONTENT_ACCEPT_ENCODING_KEY =
InternalMetadata.keyOf(GrpcUtil.CONTENT_ACCEPT_ENCODING, new AcceptEncodingMarshaller());
static final Metadata.Key<String> CONTENT_LENGTH_KEY =
Metadata.Key.of("content-length", Metadata.ASCII_STRING_MARSHALLER);
private static final class AcceptEncodingMarshaller implements TrustedAsciiMarshaller<byte[]> {
@Override
public byte[] toAsciiString(byte[] value) {

View File

@ -20,6 +20,7 @@ import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static io.grpc.internal.GrpcUtil.ACCEPT_ENCODING_SPLITTER;
import static io.grpc.internal.GrpcUtil.CONTENT_LENGTH_KEY;
import static io.grpc.internal.GrpcUtil.MESSAGE_ACCEPT_ENCODING_KEY;
import static io.grpc.internal.GrpcUtil.MESSAGE_ENCODING_KEY;
@ -107,6 +108,7 @@ final class ServerCallImpl<ReqT, RespT> extends ServerCall<ReqT, RespT> {
checkState(!sendHeadersCalled, "sendHeaders has already been called");
checkState(!closeCalled, "call is closed");
headers.discardAll(CONTENT_LENGTH_KEY);
headers.discardAll(MESSAGE_ENCODING_KEY);
if (compressor == null) {
compressor = Codec.Identity.NONE;

View File

@ -469,6 +469,15 @@ public class ClientCallImplTest {
assertNull(m.get(GrpcUtil.MESSAGE_ENCODING_KEY));
}
@Test
public void prepareHeaders_ignoreContentLength() {
Metadata m = new Metadata();
m.put(GrpcUtil.CONTENT_LENGTH_KEY, "123");
ClientCallImpl.prepareHeaders(m, decompressorRegistry, Codec.Identity.NONE, false);
assertNull(m.get(GrpcUtil.CONTENT_LENGTH_KEY));
}
@Test
public void prepareHeaders_acceptedMessageEncodingsAdded() {
Metadata m = new Metadata();

View File

@ -17,6 +17,7 @@
package io.grpc.internal;
import static com.google.common.base.Charsets.UTF_8;
import static io.grpc.internal.GrpcUtil.CONTENT_LENGTH_KEY;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
@ -151,6 +152,16 @@ public class ServerCallImplTest {
verify(stream).writeHeaders(headers);
}
@Test
public void sendHeader_contentLengthDiscarded() {
Metadata headers = new Metadata();
headers.put(CONTENT_LENGTH_KEY, "123");
call.sendHeaders(headers);
verify(stream).writeHeaders(headers);
assertNull(headers.get(CONTENT_LENGTH_KEY));
}
@Test
public void sendHeader_failsOnSecondCall() {
call.sendHeaders(new Metadata());