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.ArrayList; 25 import java.util.Arrays; 26 import java.util.List; 27 import java.util.logging.Logger; 28 import javax.annotation.CheckReturnValue; 29 30 /** 31 * Utility functions for transport layer framing. 32 * 33 * <p>Within a given transport frame we reserve the first byte to indicate the type of compression 34 * used for the contents of the transport frame. 35 */ 36 public final class TransportFrameUtil { 37 38 private static final Logger logger = Logger.getLogger(TransportFrameUtil.class.getName()); 39 40 private static final byte[] binaryHeaderSuffixBytes = 41 Metadata.BINARY_HEADER_SUFFIX.getBytes(US_ASCII); 42 43 /** 44 * Transform the given headers to a format where only spec-compliant ASCII characters are allowed. 45 * Binary header values are encoded by Base64 in the result. It is safe to modify the returned 46 * array, but not to modify any of the underlying byte arrays. 47 * 48 * @return the interleaved keys and values. 49 */ toHttp2Headers(Metadata headers)50 public static byte[][] toHttp2Headers(Metadata headers) { 51 byte[][] serializedHeaders = InternalMetadata.serialize(headers); 52 // TODO(carl-mastrangelo): eventually remove this once all callers are updated. 53 if (serializedHeaders == null) { 54 return new byte[][]{}; 55 } 56 int k = 0; 57 for (int i = 0; i < serializedHeaders.length; i += 2) { 58 byte[] key = serializedHeaders[i]; 59 byte[] value = serializedHeaders[i + 1]; 60 if (endsWith(key, binaryHeaderSuffixBytes)) { 61 // Binary header. 62 serializedHeaders[k] = key; 63 serializedHeaders[k + 1] 64 = InternalMetadata.BASE64_ENCODING_OMIT_PADDING.encode(value).getBytes(US_ASCII); 65 k += 2; 66 } else { 67 // Non-binary header. 68 // Filter out headers that contain non-spec-compliant ASCII characters. 69 // TODO(zhangkun83): only do such check in development mode since it's expensive 70 if (isSpecCompliantAscii(value)) { 71 serializedHeaders[k] = key; 72 serializedHeaders[k + 1] = value; 73 k += 2; 74 } else { 75 String keyString = new String(key, US_ASCII); 76 logger.warning("Metadata key=" + keyString + ", value=" + Arrays.toString(value) 77 + " contains invalid ASCII characters"); 78 } 79 } 80 } 81 // Fast path, everything worked out fine. 82 if (k == serializedHeaders.length) { 83 return serializedHeaders; 84 } 85 return Arrays.copyOfRange(serializedHeaders, 0, k); 86 } 87 88 /** 89 * Transform HTTP/2-compliant headers to the raw serialized format which can be deserialized by 90 * metadata marshallers. It decodes the Base64-encoded binary headers. 91 * 92 * <p>Warning: This function may partially modify the headers in place by modifying the input 93 * array (but not modifying any single byte), so the input reference {@code http2Headers} can not 94 * be used again. 95 * 96 * @param http2Headers the interleaved keys and values of HTTP/2-compliant headers 97 * @return the interleaved keys and values in the raw serialized format 98 */ 99 @CheckReturnValue toRawSerializedHeaders(byte[][] http2Headers)100 public static byte[][] toRawSerializedHeaders(byte[][] http2Headers) { 101 for (int i = 0; i < http2Headers.length; i += 2) { 102 byte[] key = http2Headers[i]; 103 byte[] value = http2Headers[i + 1]; 104 if (endsWith(key, binaryHeaderSuffixBytes)) { 105 // Binary header 106 for (int idx = 0; idx < value.length; idx++) { 107 if (value[idx] == (byte) ',') { 108 return serializeHeadersWithCommasInBin(http2Headers, i); 109 } 110 } 111 byte[] decodedVal = BaseEncoding.base64().decode(new String(value, US_ASCII)); 112 http2Headers[i + 1] = decodedVal; 113 } else { 114 // Non-binary header 115 // Nothing to do, the value is already in the right place. 116 } 117 } 118 return http2Headers; 119 } 120 serializeHeadersWithCommasInBin(byte[][] http2Headers, int resumeFrom)121 private static byte[][] serializeHeadersWithCommasInBin(byte[][] http2Headers, int resumeFrom) { 122 List<byte[]> headerList = new ArrayList<>(http2Headers.length + 10); 123 for (int i = 0; i < resumeFrom; i++) { 124 headerList.add(http2Headers[i]); 125 } 126 for (int i = resumeFrom; i < http2Headers.length; i += 2) { 127 byte[] key = http2Headers[i]; 128 byte[] value = http2Headers[i + 1]; 129 if (!endsWith(key, binaryHeaderSuffixBytes)) { 130 headerList.add(key); 131 headerList.add(value); 132 continue; 133 } 134 // Binary header 135 int prevIdx = 0; 136 for (int idx = 0; idx <= value.length; idx++) { 137 if (idx != value.length && value[idx] != (byte) ',') { 138 continue; 139 } 140 byte[] decodedVal = 141 BaseEncoding.base64().decode(new String(value, prevIdx, idx - prevIdx, US_ASCII)); 142 prevIdx = idx + 1; 143 headerList.add(key); 144 headerList.add(decodedVal); 145 } 146 } 147 return headerList.toArray(new byte[0][]); 148 } 149 150 /** 151 * Returns {@code true} if {@code subject} ends with {@code suffix}. 152 */ endsWith(byte[] subject, byte[] suffix)153 private static boolean endsWith(byte[] subject, byte[] suffix) { 154 int start = subject.length - suffix.length; 155 if (start < 0) { 156 return false; 157 } 158 for (int i = start; i < subject.length; i++) { 159 if (subject[i] != suffix[i - start]) { 160 return false; 161 } 162 } 163 return true; 164 } 165 166 /** 167 * Returns {@code true} if {@code subject} contains only bytes that are spec-compliant ASCII 168 * characters and space. 169 */ isSpecCompliantAscii(byte[] subject)170 private static boolean isSpecCompliantAscii(byte[] subject) { 171 for (byte b : subject) { 172 if (b < 32 || b > 126) { 173 return false; 174 } 175 } 176 return true; 177 } 178 TransportFrameUtil()179 private TransportFrameUtil() {} 180 } 181