1 /* 2 * Copyright (C) 2013 Square, Inc. 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 package com.squareup.okhttp.internal.spdy; 17 18 import java.io.IOException; 19 import java.util.ArrayList; 20 import java.util.Arrays; 21 import java.util.Collections; 22 import java.util.LinkedHashMap; 23 import java.util.List; 24 import java.util.Map; 25 import okio.Buffer; 26 import okio.BufferedSource; 27 import okio.ByteString; 28 import okio.Okio; 29 import okio.Source; 30 31 /** 32 * Read and write HPACK v10. 33 * 34 * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12 35 * 36 * This implementation uses an array for the dynamic table and a list for 37 * indexed entries. Dynamic entries are added to the array, starting in the 38 * last position moving forward. When the array fills, it is doubled. 39 */ 40 final class Hpack { 41 private static final int PREFIX_4_BITS = 0x0f; 42 private static final int PREFIX_5_BITS = 0x1f; 43 private static final int PREFIX_6_BITS = 0x3f; 44 private static final int PREFIX_7_BITS = 0x7f; 45 46 private static final Header[] STATIC_HEADER_TABLE = new Header[] { 47 new Header(Header.TARGET_AUTHORITY, ""), 48 new Header(Header.TARGET_METHOD, "GET"), 49 new Header(Header.TARGET_METHOD, "POST"), 50 new Header(Header.TARGET_PATH, "/"), 51 new Header(Header.TARGET_PATH, "/index.html"), 52 new Header(Header.TARGET_SCHEME, "http"), 53 new Header(Header.TARGET_SCHEME, "https"), 54 new Header(Header.RESPONSE_STATUS, "200"), 55 new Header(Header.RESPONSE_STATUS, "204"), 56 new Header(Header.RESPONSE_STATUS, "206"), 57 new Header(Header.RESPONSE_STATUS, "304"), 58 new Header(Header.RESPONSE_STATUS, "400"), 59 new Header(Header.RESPONSE_STATUS, "404"), 60 new Header(Header.RESPONSE_STATUS, "500"), 61 new Header("accept-charset", ""), 62 new Header("accept-encoding", "gzip, deflate"), 63 new Header("accept-language", ""), 64 new Header("accept-ranges", ""), 65 new Header("accept", ""), 66 new Header("access-control-allow-origin", ""), 67 new Header("age", ""), 68 new Header("allow", ""), 69 new Header("authorization", ""), 70 new Header("cache-control", ""), 71 new Header("content-disposition", ""), 72 new Header("content-encoding", ""), 73 new Header("content-language", ""), 74 new Header("content-length", ""), 75 new Header("content-location", ""), 76 new Header("content-range", ""), 77 new Header("content-type", ""), 78 new Header("cookie", ""), 79 new Header("date", ""), 80 new Header("etag", ""), 81 new Header("expect", ""), 82 new Header("expires", ""), 83 new Header("from", ""), 84 new Header("host", ""), 85 new Header("if-match", ""), 86 new Header("if-modified-since", ""), 87 new Header("if-none-match", ""), 88 new Header("if-range", ""), 89 new Header("if-unmodified-since", ""), 90 new Header("last-modified", ""), 91 new Header("link", ""), 92 new Header("location", ""), 93 new Header("max-forwards", ""), 94 new Header("proxy-authenticate", ""), 95 new Header("proxy-authorization", ""), 96 new Header("range", ""), 97 new Header("referer", ""), 98 new Header("refresh", ""), 99 new Header("retry-after", ""), 100 new Header("server", ""), 101 new Header("set-cookie", ""), 102 new Header("strict-transport-security", ""), 103 new Header("transfer-encoding", ""), 104 new Header("user-agent", ""), 105 new Header("vary", ""), 106 new Header("via", ""), 107 new Header("www-authenticate", "") 108 }; 109 Hpack()110 private Hpack() { 111 } 112 113 // http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#section-3.1 114 static final class Reader { 115 116 private final List<Header> headerList = new ArrayList<>(); 117 private final BufferedSource source; 118 119 private int headerTableSizeSetting; 120 private int maxDynamicTableByteCount; 121 // Visible for testing. 122 Header[] dynamicTable = new Header[8]; 123 // Array is populated back to front, so new entries always have lowest index. 124 int nextHeaderIndex = dynamicTable.length - 1; 125 int headerCount = 0; 126 int dynamicTableByteCount = 0; 127 Reader(int headerTableSizeSetting, Source source)128 Reader(int headerTableSizeSetting, Source source) { 129 this.headerTableSizeSetting = headerTableSizeSetting; 130 this.maxDynamicTableByteCount = headerTableSizeSetting; 131 this.source = Okio.buffer(source); 132 } 133 maxDynamicTableByteCount()134 int maxDynamicTableByteCount() { 135 return maxDynamicTableByteCount; 136 } 137 138 /** 139 * Called by the reader when the peer sent {@link Settings#HEADER_TABLE_SIZE}. 140 * While this establishes the maximum dynamic table size, the 141 * {@link #maxDynamicTableByteCount} set during processing may limit the 142 * table size to a smaller amount. 143 * <p> Evicts entries or clears the table as needed. 144 */ headerTableSizeSetting(int headerTableSizeSetting)145 void headerTableSizeSetting(int headerTableSizeSetting) { 146 this.headerTableSizeSetting = headerTableSizeSetting; 147 this.maxDynamicTableByteCount = headerTableSizeSetting; 148 adjustDynamicTableByteCount(); 149 } 150 adjustDynamicTableByteCount()151 private void adjustDynamicTableByteCount() { 152 if (maxDynamicTableByteCount < dynamicTableByteCount) { 153 if (maxDynamicTableByteCount == 0) { 154 clearDynamicTable(); 155 } else { 156 evictToRecoverBytes(dynamicTableByteCount - maxDynamicTableByteCount); 157 } 158 } 159 } 160 clearDynamicTable()161 private void clearDynamicTable() { 162 headerList.clear(); 163 Arrays.fill(dynamicTable, null); 164 nextHeaderIndex = dynamicTable.length - 1; 165 headerCount = 0; 166 dynamicTableByteCount = 0; 167 } 168 169 /** Returns the count of entries evicted. */ evictToRecoverBytes(int bytesToRecover)170 private int evictToRecoverBytes(int bytesToRecover) { 171 int entriesToEvict = 0; 172 if (bytesToRecover > 0) { 173 // determine how many headers need to be evicted. 174 for (int j = dynamicTable.length - 1; j >= nextHeaderIndex && bytesToRecover > 0; j--) { 175 bytesToRecover -= dynamicTable[j].hpackSize; 176 dynamicTableByteCount -= dynamicTable[j].hpackSize; 177 headerCount--; 178 entriesToEvict++; 179 } 180 System.arraycopy(dynamicTable, nextHeaderIndex + 1, dynamicTable, 181 nextHeaderIndex + 1 + entriesToEvict, headerCount); 182 nextHeaderIndex += entriesToEvict; 183 } 184 return entriesToEvict; 185 } 186 187 /** 188 * Read {@code byteCount} bytes of headers from the source stream. This 189 * implementation does not propagate the never indexed flag of a header. 190 */ readHeaders()191 void readHeaders() throws IOException { 192 while (!source.exhausted()) { 193 int b = source.readByte() & 0xff; 194 if (b == 0x80) { // 10000000 195 throw new IOException("index == 0"); 196 } else if ((b & 0x80) == 0x80) { // 1NNNNNNN 197 int index = readInt(b, PREFIX_7_BITS); 198 readIndexedHeader(index - 1); 199 } else if (b == 0x40) { // 01000000 200 readLiteralHeaderWithIncrementalIndexingNewName(); 201 } else if ((b & 0x40) == 0x40) { // 01NNNNNN 202 int index = readInt(b, PREFIX_6_BITS); 203 readLiteralHeaderWithIncrementalIndexingIndexedName(index - 1); 204 } else if ((b & 0x20) == 0x20) { // 001NNNNN 205 maxDynamicTableByteCount = readInt(b, PREFIX_5_BITS); 206 if (maxDynamicTableByteCount < 0 207 || maxDynamicTableByteCount > headerTableSizeSetting) { 208 throw new IOException("Invalid dynamic table size update " + maxDynamicTableByteCount); 209 } 210 adjustDynamicTableByteCount(); 211 } else if (b == 0x10 || b == 0) { // 000?0000 - Ignore never indexed bit. 212 readLiteralHeaderWithoutIndexingNewName(); 213 } else { // 000?NNNN - Ignore never indexed bit. 214 int index = readInt(b, PREFIX_4_BITS); 215 readLiteralHeaderWithoutIndexingIndexedName(index - 1); 216 } 217 } 218 } 219 getAndResetHeaderList()220 public List<Header> getAndResetHeaderList() { 221 List<Header> result = new ArrayList<>(headerList); 222 headerList.clear(); 223 return result; 224 } 225 readIndexedHeader(int index)226 private void readIndexedHeader(int index) throws IOException { 227 if (isStaticHeader(index)) { 228 Header staticEntry = STATIC_HEADER_TABLE[index]; 229 headerList.add(staticEntry); 230 } else { 231 int dynamicTableIndex = dynamicTableIndex(index - STATIC_HEADER_TABLE.length); 232 if (dynamicTableIndex < 0 || dynamicTableIndex > dynamicTable.length - 1) { 233 throw new IOException("Header index too large " + (index + 1)); 234 } 235 headerList.add(dynamicTable[dynamicTableIndex]); 236 } 237 } 238 239 // referencedHeaders is relative to nextHeaderIndex + 1. dynamicTableIndex(int index)240 private int dynamicTableIndex(int index) { 241 return nextHeaderIndex + 1 + index; 242 } 243 readLiteralHeaderWithoutIndexingIndexedName(int index)244 private void readLiteralHeaderWithoutIndexingIndexedName(int index) throws IOException { 245 ByteString name = getName(index); 246 ByteString value = readByteString(); 247 headerList.add(new Header(name, value)); 248 } 249 readLiteralHeaderWithoutIndexingNewName()250 private void readLiteralHeaderWithoutIndexingNewName() throws IOException { 251 ByteString name = checkLowercase(readByteString()); 252 ByteString value = readByteString(); 253 headerList.add(new Header(name, value)); 254 } 255 readLiteralHeaderWithIncrementalIndexingIndexedName(int nameIndex)256 private void readLiteralHeaderWithIncrementalIndexingIndexedName(int nameIndex) 257 throws IOException { 258 ByteString name = getName(nameIndex); 259 ByteString value = readByteString(); 260 insertIntoDynamicTable(-1, new Header(name, value)); 261 } 262 readLiteralHeaderWithIncrementalIndexingNewName()263 private void readLiteralHeaderWithIncrementalIndexingNewName() throws IOException { 264 ByteString name = checkLowercase(readByteString()); 265 ByteString value = readByteString(); 266 insertIntoDynamicTable(-1, new Header(name, value)); 267 } 268 getName(int index)269 private ByteString getName(int index) { 270 if (isStaticHeader(index)) { 271 return STATIC_HEADER_TABLE[index].name; 272 } else { 273 return dynamicTable[dynamicTableIndex(index - STATIC_HEADER_TABLE.length)].name; 274 } 275 } 276 isStaticHeader(int index)277 private boolean isStaticHeader(int index) { 278 return index >= 0 && index <= STATIC_HEADER_TABLE.length - 1; 279 } 280 281 /** index == -1 when new. */ insertIntoDynamicTable(int index, Header entry)282 private void insertIntoDynamicTable(int index, Header entry) { 283 headerList.add(entry); 284 285 int delta = entry.hpackSize; 286 if (index != -1) { // Index -1 == new header. 287 delta -= dynamicTable[dynamicTableIndex(index)].hpackSize; 288 } 289 290 // if the new or replacement header is too big, drop all entries. 291 if (delta > maxDynamicTableByteCount) { 292 clearDynamicTable(); 293 return; 294 } 295 296 // Evict headers to the required length. 297 int bytesToRecover = (dynamicTableByteCount + delta) - maxDynamicTableByteCount; 298 int entriesEvicted = evictToRecoverBytes(bytesToRecover); 299 300 if (index == -1) { // Adding a value to the dynamic table. 301 if (headerCount + 1 > dynamicTable.length) { // Need to grow the dynamic table. 302 Header[] doubled = new Header[dynamicTable.length * 2]; 303 System.arraycopy(dynamicTable, 0, doubled, dynamicTable.length, dynamicTable.length); 304 nextHeaderIndex = dynamicTable.length - 1; 305 dynamicTable = doubled; 306 } 307 index = nextHeaderIndex--; 308 dynamicTable[index] = entry; 309 headerCount++; 310 } else { // Replace value at same position. 311 index += dynamicTableIndex(index) + entriesEvicted; 312 dynamicTable[index] = entry; 313 } 314 dynamicTableByteCount += delta; 315 } 316 readByte()317 private int readByte() throws IOException { 318 return source.readByte() & 0xff; 319 } 320 readInt(int firstByte, int prefixMask)321 int readInt(int firstByte, int prefixMask) throws IOException { 322 int prefix = firstByte & prefixMask; 323 if (prefix < prefixMask) { 324 return prefix; // This was a single byte value. 325 } 326 327 // This is a multibyte value. Read 7 bits at a time. 328 int result = prefixMask; 329 int shift = 0; 330 while (true) { 331 int b = readByte(); 332 if ((b & 0x80) != 0) { // Equivalent to (b >= 128) since b is in [0..255]. 333 result += (b & 0x7f) << shift; 334 shift += 7; 335 } else { 336 result += b << shift; // Last byte. 337 break; 338 } 339 } 340 return result; 341 } 342 343 /** Reads a potentially Huffman encoded byte string. */ readByteString()344 ByteString readByteString() throws IOException { 345 int firstByte = readByte(); 346 boolean huffmanDecode = (firstByte & 0x80) == 0x80; // 1NNNNNNN 347 int length = readInt(firstByte, PREFIX_7_BITS); 348 349 if (huffmanDecode) { 350 return ByteString.of(Huffman.get().decode(source.readByteArray(length))); 351 } else { 352 return source.readByteString(length); 353 } 354 } 355 } 356 357 private static final Map<ByteString, Integer> NAME_TO_FIRST_INDEX = nameToFirstIndex(); 358 nameToFirstIndex()359 private static Map<ByteString, Integer> nameToFirstIndex() { 360 Map<ByteString, Integer> result = new LinkedHashMap<>(STATIC_HEADER_TABLE.length); 361 for (int i = 0; i < STATIC_HEADER_TABLE.length; i++) { 362 if (!result.containsKey(STATIC_HEADER_TABLE[i].name)) { 363 result.put(STATIC_HEADER_TABLE[i].name, i); 364 } 365 } 366 return Collections.unmodifiableMap(result); 367 } 368 369 static final class Writer { 370 private final Buffer out; 371 Writer(Buffer out)372 Writer(Buffer out) { 373 this.out = out; 374 } 375 376 /** This does not use "never indexed" semantics for sensitive headers. */ 377 // http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#section-6.2.3 writeHeaders(List<Header> headerBlock)378 void writeHeaders(List<Header> headerBlock) throws IOException { 379 // TODO: implement index tracking 380 for (int i = 0, size = headerBlock.size(); i < size; i++) { 381 ByteString name = headerBlock.get(i).name.toAsciiLowercase(); 382 Integer staticIndex = NAME_TO_FIRST_INDEX.get(name); 383 if (staticIndex != null) { 384 // Literal Header Field without Indexing - Indexed Name. 385 writeInt(staticIndex + 1, PREFIX_4_BITS, 0); 386 writeByteString(headerBlock.get(i).value); 387 } else { 388 out.writeByte(0x00); // Literal Header without Indexing - New Name. 389 writeByteString(name); 390 writeByteString(headerBlock.get(i).value); 391 } 392 } 393 } 394 395 // http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#section-4.1.1 writeInt(int value, int prefixMask, int bits)396 void writeInt(int value, int prefixMask, int bits) throws IOException { 397 // Write the raw value for a single byte value. 398 if (value < prefixMask) { 399 out.writeByte(bits | value); 400 return; 401 } 402 403 // Write the mask to start a multibyte value. 404 out.writeByte(bits | prefixMask); 405 value -= prefixMask; 406 407 // Write 7 bits at a time 'til we're done. 408 while (value >= 0x80) { 409 int b = value & 0x7f; 410 out.writeByte(b | 0x80); 411 value >>>= 7; 412 } 413 out.writeByte(value); 414 } 415 writeByteString(ByteString data)416 void writeByteString(ByteString data) throws IOException { 417 writeInt(data.size(), PREFIX_7_BITS, 0); 418 out.write(data); 419 } 420 } 421 422 /** 423 * An HTTP/2 response cannot contain uppercase header characters and must 424 * be treated as malformed. 425 */ checkLowercase(ByteString name)426 private static ByteString checkLowercase(ByteString name) throws IOException { 427 for (int i = 0, length = name.size(); i < length; i++) { 428 byte c = name.getByte(i); 429 if (c >= 'A' && c <= 'Z') { 430 throw new IOException("PROTOCOL_ERROR response malformed: mixed case name: " + name.utf8()); 431 } 432 } 433 return name; 434 } 435 } 436