1 /* 2 * Copyright 2014 The gRPC Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package io.grpc.internal; 18 19 import static com.google.common.base.Charsets.US_ASCII; 20 21 import com.google.common.io.BaseEncoding; 22 import io.grpc.InternalMetadata; 23 import io.grpc.Metadata; 24 import java.util.Arrays; 25 import java.util.logging.Logger; 26 27 /** 28 * Utility functions for transport layer framing. 29 * 30 * <p>Within a given transport frame we reserve the first byte to indicate the type of compression 31 * used for the contents of the transport frame. 32 */ 33 public final class TransportFrameUtil { 34 35 private static final Logger logger = Logger.getLogger(TransportFrameUtil.class.getName()); 36 37 private static final byte[] binaryHeaderSuffixBytes = 38 Metadata.BINARY_HEADER_SUFFIX.getBytes(US_ASCII); 39 40 /** 41 * Transform the given headers to a format where only spec-compliant ASCII characters are allowed. 42 * Binary header values are encoded by Base64 in the result. It is safe to modify the returned 43 * array, but not to modify any of the underlying byte arrays. 44 * 45 * @return the interleaved keys and values. 46 */ 47 @SuppressWarnings("BetaApi") // BaseEncoding is stable in Guava 20.0 toHttp2Headers(Metadata headers)48 public static byte[][] toHttp2Headers(Metadata headers) { 49 byte[][] serializedHeaders = InternalMetadata.serialize(headers); 50 // TODO(carl-mastrangelo): eventually remove this once all callers are updated. 51 if (serializedHeaders == null) { 52 return new byte[][]{}; 53 } 54 int k = 0; 55 for (int i = 0; i < serializedHeaders.length; i += 2) { 56 byte[] key = serializedHeaders[i]; 57 byte[] value = serializedHeaders[i + 1]; 58 if (endsWith(key, binaryHeaderSuffixBytes)) { 59 // Binary header. 60 serializedHeaders[k] = key; 61 serializedHeaders[k + 1] = BaseEncoding.base64().encode(value).getBytes(US_ASCII); 62 k += 2; 63 } else { 64 // Non-binary header. 65 // Filter out headers that contain non-spec-compliant ASCII characters. 66 // TODO(zhangkun83): only do such check in development mode since it's expensive 67 if (isSpecCompliantAscii(value)) { 68 serializedHeaders[k] = key; 69 serializedHeaders[k + 1] = value; 70 k += 2; 71 } else { 72 String keyString = new String(key, US_ASCII); 73 logger.warning("Metadata key=" + keyString + ", value=" + Arrays.toString(value) 74 + " contains invalid ASCII characters"); 75 } 76 } 77 } 78 // Fast path, everything worked out fine. 79 if (k == serializedHeaders.length) { 80 return serializedHeaders; 81 } 82 return Arrays.copyOfRange(serializedHeaders, 0, k); 83 } 84 85 /** 86 * Transform HTTP/2-compliant headers to the raw serialized format which can be deserialized by 87 * metadata marshallers. It decodes the Base64-encoded binary headers. This function modifies 88 * the headers in place. By modifying the input array. 89 * 90 * @param http2Headers the interleaved keys and values of HTTP/2-compliant headers 91 * @return the interleaved keys and values in the raw serialized format 92 */ 93 @SuppressWarnings("BetaApi") // BaseEncoding is stable in Guava 20.0 toRawSerializedHeaders(byte[][] http2Headers)94 public static byte[][] toRawSerializedHeaders(byte[][] http2Headers) { 95 for (int i = 0; i < http2Headers.length; i += 2) { 96 byte[] key = http2Headers[i]; 97 byte[] value = http2Headers[i + 1]; 98 http2Headers[i] = key; 99 if (endsWith(key, binaryHeaderSuffixBytes)) { 100 // Binary header 101 http2Headers[i + 1] = BaseEncoding.base64().decode(new String(value, US_ASCII)); 102 } else { 103 // Non-binary header 104 // Nothing to do, the value is already in the right place. 105 } 106 } 107 return http2Headers; 108 } 109 110 /** 111 * Returns {@code true} if {@code subject} ends with {@code suffix}. 112 */ endsWith(byte[] subject, byte[] suffix)113 private static boolean endsWith(byte[] subject, byte[] suffix) { 114 int start = subject.length - suffix.length; 115 if (start < 0) { 116 return false; 117 } 118 for (int i = start; i < subject.length; i++) { 119 if (subject[i] != suffix[i - start]) { 120 return false; 121 } 122 } 123 return true; 124 } 125 126 /** 127 * Returns {@code true} if {@code subject} contains only bytes that are spec-compliant ASCII 128 * characters and space. 129 */ isSpecCompliantAscii(byte[] subject)130 private static boolean isSpecCompliantAscii(byte[] subject) { 131 for (byte b : subject) { 132 if (b < 32 || b > 126) { 133 return false; 134 } 135 } 136 return true; 137 } 138 TransportFrameUtil()139 private TransportFrameUtil() {} 140 } 141