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;
import io.grpc.Metadata.AsciiMarshaller;
import io.grpc.Metadata.Key;
import java.nio.charset.Charset;
@ -44,7 +45,14 @@ public final class InternalMetadata {
@Internal
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

View File

@ -599,11 +599,15 @@ public final class Metadata {
* <b>not</b> end with {@link #BINARY_HEADER_SUFFIX}
*/
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) {
return new TrustedAsciiKey<T>(name, marshaller);
static <T> Key<T> of(String name, boolean pseudo, AsciiMarshaller<T> 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;
@ -626,13 +630,12 @@ public final class Metadata {
return valid;
}
private static String validateName(String n) {
private static String validateName(String n, boolean pseudo) {
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++) {
char tChar = n.charAt(i);
// TODO(notcarl): remove this hack once pseudo headers are properly handled
if (tChar == ':' && i == 0) {
if (pseudo && tChar == ':' && i == 0) {
continue;
}
@ -642,9 +645,9 @@ public final class Metadata {
return n;
}
private Key(String name) {
private Key(String name, boolean pseudo) {
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);
}
@ -722,7 +725,7 @@ public final class Metadata {
/** Keys have a name and a binary marshaller used for serialization. */
private BinaryKey(String name, BinaryMarshaller<T> marshaller) {
super(name);
super(name, false /* not pseudo */);
checkArgument(
name.endsWith(BINARY_HEADER_SUFFIX),
"Binary header is named %s. It must end with %s",
@ -747,8 +750,8 @@ public final class Metadata {
private final AsciiMarshaller<T> marshaller;
/** Keys have a name and an ASCII marshaller used for serialization. */
private AsciiKey(String name, AsciiMarshaller<T> marshaller) {
super(name);
private AsciiKey(String name, boolean pseudo, AsciiMarshaller<T> marshaller) {
super(name, pseudo);
Preconditions.checkArgument(
!name.endsWith(BINARY_HEADER_SUFFIX),
"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;
/** Keys have a name and an ASCII marshaller used for serialization. */
private TrustedAsciiKey(String name, TrustedAsciiMarshaller<T> marshaller) {
super(name);
private TrustedAsciiKey(String name, boolean pseudo, TrustedAsciiMarshaller<T> marshaller) {
super(name, pseudo);
Preconditions.checkArgument(
!name.endsWith(BINARY_HEADER_SUFFIX),
"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.
*/
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
@ -377,7 +377,7 @@ public final class Status {
* Key to bind status message to trailing metadata.
*/
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}.

View File

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