core: hide access to pseudo headers

Previously anyone could create metadata keys with a leading ":", due to not having a way to prevent it. This change effectively only allows internal code to make use of this.
This commit is contained in:
Carl Mastrangelo 2017-07-17 11:00:53 -07:00 committed by GitHub
parent 5713ebccba
commit e60a179772
5 changed files with 57 additions and 33 deletions

View File

@ -16,6 +16,7 @@
package io.grpc; package io.grpc;
import io.grpc.Metadata.AsciiMarshaller;
import io.grpc.Metadata.Key; import io.grpc.Metadata.Key;
import java.nio.charset.Charset; import java.nio.charset.Charset;
@ -44,7 +45,14 @@ public final class InternalMetadata {
@Internal @Internal
public static <T> Key<T> keyOf(String name, TrustedAsciiMarshaller<T> marshaller) { public static <T> Key<T> keyOf(String name, TrustedAsciiMarshaller<T> marshaller) {
return Metadata.Key.of(name, marshaller); boolean isPseudo = name != null && !name.isEmpty() && name.charAt(0) == ':';
return Metadata.Key.of(name, isPseudo, marshaller);
}
@Internal
public static <T> Key<T> keyOf(String name, AsciiMarshaller<T> marshaller) {
boolean isPseudo = name != null && !name.isEmpty() && name.charAt(0) == ':';
return Metadata.Key.of(name, isPseudo, marshaller);
} }
@Internal @Internal

View File

@ -599,11 +599,15 @@ public final class Metadata {
* <b>not</b> end with {@link #BINARY_HEADER_SUFFIX} * <b>not</b> end with {@link #BINARY_HEADER_SUFFIX}
*/ */
public static <T> Key<T> of(String name, AsciiMarshaller<T> marshaller) { public static <T> Key<T> of(String name, AsciiMarshaller<T> marshaller) {
return new AsciiKey<T>(name, marshaller); return of(name, false, marshaller);
} }
static <T> Key<T> of(String name, TrustedAsciiMarshaller<T> marshaller) { static <T> Key<T> of(String name, boolean pseudo, AsciiMarshaller<T> marshaller) {
return new TrustedAsciiKey<T>(name, marshaller); return new AsciiKey<T>(name, pseudo, marshaller);
}
static <T> Key<T> of(String name, boolean pseudo, TrustedAsciiMarshaller<T> marshaller) {
return new TrustedAsciiKey<T>(name, pseudo, marshaller);
} }
private final String originalName; private final String originalName;
@ -626,13 +630,12 @@ public final class Metadata {
return valid; return valid;
} }
private static String validateName(String n) { private static String validateName(String n, boolean pseudo) {
checkNotNull(n, "name"); checkNotNull(n, "name");
checkArgument(n.length() != 0, "token must have at least 1 tchar"); checkArgument(!n.isEmpty(), "token must have at least 1 tchar");
for (int i = 0; i < n.length(); i++) { for (int i = 0; i < n.length(); i++) {
char tChar = n.charAt(i); char tChar = n.charAt(i);
// TODO(notcarl): remove this hack once pseudo headers are properly handled if (pseudo && tChar == ':' && i == 0) {
if (tChar == ':' && i == 0) {
continue; continue;
} }
@ -642,9 +645,9 @@ public final class Metadata {
return n; return n;
} }
private Key(String name) { private Key(String name, boolean pseudo) {
this.originalName = checkNotNull(name, "name"); this.originalName = checkNotNull(name, "name");
this.name = validateName(this.originalName.toLowerCase(Locale.ROOT)); this.name = validateName(this.originalName.toLowerCase(Locale.ROOT), pseudo);
this.nameBytes = this.name.getBytes(US_ASCII); this.nameBytes = this.name.getBytes(US_ASCII);
} }
@ -722,7 +725,7 @@ public final class Metadata {
/** Keys have a name and a binary marshaller used for serialization. */ /** Keys have a name and a binary marshaller used for serialization. */
private BinaryKey(String name, BinaryMarshaller<T> marshaller) { private BinaryKey(String name, BinaryMarshaller<T> marshaller) {
super(name); super(name, false /* not pseudo */);
checkArgument( checkArgument(
name.endsWith(BINARY_HEADER_SUFFIX), name.endsWith(BINARY_HEADER_SUFFIX),
"Binary header is named %s. It must end with %s", "Binary header is named %s. It must end with %s",
@ -747,8 +750,8 @@ public final class Metadata {
private final AsciiMarshaller<T> marshaller; private final AsciiMarshaller<T> marshaller;
/** Keys have a name and an ASCII marshaller used for serialization. */ /** Keys have a name and an ASCII marshaller used for serialization. */
private AsciiKey(String name, AsciiMarshaller<T> marshaller) { private AsciiKey(String name, boolean pseudo, AsciiMarshaller<T> marshaller) {
super(name); super(name, pseudo);
Preconditions.checkArgument( Preconditions.checkArgument(
!name.endsWith(BINARY_HEADER_SUFFIX), !name.endsWith(BINARY_HEADER_SUFFIX),
"ASCII header is named %s. Only binary headers may end with %s", "ASCII header is named %s. Only binary headers may end with %s",
@ -772,8 +775,8 @@ public final class Metadata {
private final TrustedAsciiMarshaller<T> marshaller; private final TrustedAsciiMarshaller<T> marshaller;
/** Keys have a name and an ASCII marshaller used for serialization. */ /** Keys have a name and an ASCII marshaller used for serialization. */
private TrustedAsciiKey(String name, TrustedAsciiMarshaller<T> marshaller) { private TrustedAsciiKey(String name, boolean pseudo, TrustedAsciiMarshaller<T> marshaller) {
super(name); super(name, pseudo);
Preconditions.checkArgument( Preconditions.checkArgument(
!name.endsWith(BINARY_HEADER_SUFFIX), !name.endsWith(BINARY_HEADER_SUFFIX),
"ASCII header is named %s. Only binary headers may end with %s", "ASCII header is named %s. Only binary headers may end with %s",

View File

@ -346,7 +346,7 @@ public final class Status {
* Key to bind status code to trailing metadata. * Key to bind status code to trailing metadata.
*/ */
static final Metadata.Key<Status> CODE_KEY static final Metadata.Key<Status> CODE_KEY
= Metadata.Key.of("grpc-status", new StatusCodeMarshaller()); = Metadata.Key.of("grpc-status", false /* not pseudo */, new StatusCodeMarshaller());
/** /**
* Marshals status messages for ({@link #MESSAGE_KEY}. gRPC does not use binary coding of * Marshals status messages for ({@link #MESSAGE_KEY}. gRPC does not use binary coding of
@ -377,7 +377,7 @@ public final class Status {
* Key to bind status message to trailing metadata. * Key to bind status message to trailing metadata.
*/ */
static final Metadata.Key<String> MESSAGE_KEY = static final Metadata.Key<String> MESSAGE_KEY =
Metadata.Key.of("grpc-message", STATUS_MESSAGE_MARSHALLER); Metadata.Key.of("grpc-message", false /* not pseudo */, STATUS_MESSAGE_MARSHALLER);
/** /**
* Extract an error {@link Status} from the causal chain of a {@link Throwable}. * Extract an error {@link Status} from the causal chain of a {@link Throwable}.

View File

@ -63,6 +63,14 @@ public class MetadataTest {
private static final byte[] LANCE_BYTES = LANCE.getBytes(US_ASCII); private static final byte[] LANCE_BYTES = LANCE.getBytes(US_ASCII);
private static final Metadata.Key<Fish> KEY = Metadata.Key.of("test-bin", FISH_MARSHALLER); private static final Metadata.Key<Fish> KEY = Metadata.Key.of("test-bin", FISH_MARSHALLER);
@Test
public void noPseudoHeaders() {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Invalid character");
Metadata.Key.of(":test-bin", FISH_MARSHALLER);
}
@Test @Test
public void testMutations() { public void testMutations() {
Fish lance = new Fish(LANCE); Fish lance = new Fish(LANCE);

View File

@ -25,6 +25,7 @@ import static org.mockito.Matchers.same;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import io.grpc.InternalMetadata;
import io.grpc.Metadata; import io.grpc.Metadata;
import io.grpc.Status; import io.grpc.Status;
import io.grpc.Status.Code; import io.grpc.Status.Code;
@ -40,6 +41,10 @@ import org.mockito.MockitoAnnotations;
/** Unit tests for {@link Http2ClientStreamTransportState}. */ /** Unit tests for {@link Http2ClientStreamTransportState}. */
@RunWith(JUnit4.class) @RunWith(JUnit4.class)
public class Http2ClientStreamTransportStateTest { public class Http2ClientStreamTransportStateTest {
private final Metadata.Key<String> testStatusMashaller =
InternalMetadata.keyOf(":status", Metadata.ASCII_STRING_MARSHALLER);
@Mock private ClientStreamListener mockListener; @Mock private ClientStreamListener mockListener;
@Captor private ArgumentCaptor<Status> statusCaptor; @Captor private ArgumentCaptor<Status> statusCaptor;
@ -53,7 +58,7 @@ public class Http2ClientStreamTransportStateTest {
BaseTransportState state = new BaseTransportState(); BaseTransportState state = new BaseTransportState();
state.setListener(mockListener); state.setListener(mockListener);
Metadata headers = new Metadata(); Metadata headers = new Metadata();
headers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "200"); headers.put(testStatusMashaller, "200");
headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER),
"application/grpc"); "application/grpc");
state.transportHeadersReceived(headers); state.transportHeadersReceived(headers);
@ -67,7 +72,7 @@ public class Http2ClientStreamTransportStateTest {
BaseTransportState state = new BaseTransportState(); BaseTransportState state = new BaseTransportState();
state.setListener(mockListener); state.setListener(mockListener);
Metadata headers = new Metadata(); Metadata headers = new Metadata();
headers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "500"); headers.put(testStatusMashaller, "500");
headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER),
"application/grpc"); "application/grpc");
state.transportHeadersReceived(headers); state.transportHeadersReceived(headers);
@ -96,7 +101,7 @@ public class Http2ClientStreamTransportStateTest {
BaseTransportState state = new BaseTransportState(); BaseTransportState state = new BaseTransportState();
state.setListener(mockListener); state.setListener(mockListener);
Metadata headers = new Metadata(); Metadata headers = new Metadata();
headers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "200"); headers.put(testStatusMashaller, "200");
headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "text/html"); headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "text/html");
state.transportHeadersReceived(headers); state.transportHeadersReceived(headers);
state.transportDataReceived(ReadableBuffers.empty(), true); state.transportDataReceived(ReadableBuffers.empty(), true);
@ -112,7 +117,7 @@ public class Http2ClientStreamTransportStateTest {
BaseTransportState state = new BaseTransportState(); BaseTransportState state = new BaseTransportState();
state.setListener(mockListener); state.setListener(mockListener);
Metadata headers = new Metadata(); Metadata headers = new Metadata();
headers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "401"); headers.put(testStatusMashaller, "401");
headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "text/html"); headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "text/html");
state.transportHeadersReceived(headers); state.transportHeadersReceived(headers);
state.transportDataReceived(ReadableBuffers.empty(), true); state.transportDataReceived(ReadableBuffers.empty(), true);
@ -130,14 +135,14 @@ public class Http2ClientStreamTransportStateTest {
state.setListener(mockListener); state.setListener(mockListener);
Metadata infoHeaders = new Metadata(); Metadata infoHeaders = new Metadata();
infoHeaders.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "100"); infoHeaders.put(testStatusMashaller, "100");
state.transportHeadersReceived(infoHeaders); state.transportHeadersReceived(infoHeaders);
Metadata infoHeaders2 = new Metadata(); Metadata infoHeaders2 = new Metadata();
infoHeaders2.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "101"); infoHeaders2.put(testStatusMashaller, "101");
state.transportHeadersReceived(infoHeaders2); state.transportHeadersReceived(infoHeaders2);
Metadata headers = new Metadata(); Metadata headers = new Metadata();
headers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "200"); headers.put(testStatusMashaller, "200");
headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER),
"application/grpc"); "application/grpc");
state.transportHeadersReceived(headers); state.transportHeadersReceived(headers);
@ -151,7 +156,7 @@ public class Http2ClientStreamTransportStateTest {
BaseTransportState state = new BaseTransportState(); BaseTransportState state = new BaseTransportState();
state.setListener(mockListener); state.setListener(mockListener);
Metadata headers = new Metadata(); Metadata headers = new Metadata();
headers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "200"); headers.put(testStatusMashaller, "200");
headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER),
"application/grpc"); "application/grpc");
state.transportHeadersReceived(headers); state.transportHeadersReceived(headers);
@ -170,7 +175,7 @@ public class Http2ClientStreamTransportStateTest {
BaseTransportState state = new BaseTransportState(); BaseTransportState state = new BaseTransportState();
state.setListener(mockListener); state.setListener(mockListener);
Metadata headers = new Metadata(); Metadata headers = new Metadata();
headers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "200"); headers.put(testStatusMashaller, "200");
headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "text/html"); headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "text/html");
state.transportHeadersReceived(headers); state.transportHeadersReceived(headers);
Metadata headersAgain = new Metadata(); Metadata headersAgain = new Metadata();
@ -201,7 +206,7 @@ public class Http2ClientStreamTransportStateTest {
BaseTransportState state = new BaseTransportState(); BaseTransportState state = new BaseTransportState();
state.setListener(mockListener); state.setListener(mockListener);
Metadata headers = new Metadata(); Metadata headers = new Metadata();
headers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "200"); headers.put(testStatusMashaller, "200");
headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "text/html"); headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "text/html");
state.transportHeadersReceived(headers); state.transportHeadersReceived(headers);
String testString = "This is a test"; String testString = "This is a test";
@ -216,7 +221,7 @@ public class Http2ClientStreamTransportStateTest {
BaseTransportState state = new BaseTransportState(); BaseTransportState state = new BaseTransportState();
state.setListener(mockListener); state.setListener(mockListener);
Metadata trailers = new Metadata(); Metadata trailers = new Metadata();
trailers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "200"); trailers.put(testStatusMashaller, "200");
trailers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), trailers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER),
"application/grpc"); "application/grpc");
trailers.put(Metadata.Key.of("grpc-status", Metadata.ASCII_STRING_MARSHALLER), "0"); trailers.put(Metadata.Key.of("grpc-status", Metadata.ASCII_STRING_MARSHALLER), "0");
@ -231,7 +236,7 @@ public class Http2ClientStreamTransportStateTest {
BaseTransportState state = new BaseTransportState(); BaseTransportState state = new BaseTransportState();
state.setListener(mockListener); state.setListener(mockListener);
Metadata headers = new Metadata(); Metadata headers = new Metadata();
headers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "200"); headers.put(testStatusMashaller, "200");
headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER),
"application/grpc"); "application/grpc");
state.transportHeadersReceived(headers); state.transportHeadersReceived(headers);
@ -248,7 +253,7 @@ public class Http2ClientStreamTransportStateTest {
BaseTransportState state = new BaseTransportState(); BaseTransportState state = new BaseTransportState();
state.setListener(mockListener); state.setListener(mockListener);
Metadata trailers = new Metadata(); Metadata trailers = new Metadata();
trailers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "200"); trailers.put(testStatusMashaller, "200");
trailers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), trailers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER),
"application/grpc"); "application/grpc");
trailers.put(Metadata.Key.of("grpc-status", Metadata.ASCII_STRING_MARSHALLER), "1"); trailers.put(Metadata.Key.of("grpc-status", Metadata.ASCII_STRING_MARSHALLER), "1");
@ -263,7 +268,7 @@ public class Http2ClientStreamTransportStateTest {
BaseTransportState state = new BaseTransportState(); BaseTransportState state = new BaseTransportState();
state.setListener(mockListener); state.setListener(mockListener);
Metadata trailers = new Metadata(); Metadata trailers = new Metadata();
trailers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "401"); trailers.put(testStatusMashaller, "401");
trailers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), trailers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER),
"application/grpc"); "application/grpc");
state.transportTrailersReceived(trailers); state.transportTrailersReceived(trailers);
@ -308,12 +313,12 @@ public class Http2ClientStreamTransportStateTest {
BaseTransportState state = new BaseTransportState(); BaseTransportState state = new BaseTransportState();
state.setListener(mockListener); state.setListener(mockListener);
Metadata headers = new Metadata(); Metadata headers = new Metadata();
headers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "200"); headers.put(testStatusMashaller, "200");
headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER),
"application/grpc"); "application/grpc");
state.transportHeadersReceived(headers); state.transportHeadersReceived(headers);
Metadata trailers = new Metadata(); Metadata trailers = new Metadata();
trailers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "401"); trailers.put(testStatusMashaller, "401");
state.transportTrailersReceived(trailers); state.transportTrailersReceived(trailers);
verify(mockListener).headersRead(headers); verify(mockListener).headersRead(headers);