1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 package io.netty.handler.codec.http2;
33
34 import io.netty.buffer.ByteBuf;
35 import io.netty.handler.codec.http.HttpHeaderValidationUtil;
36 import io.netty.handler.codec.http2.HpackUtil.IndexType;
37 import io.netty.util.AsciiString;
38
39 import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_HEADER_TABLE_SIZE;
40 import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_HEADER_LIST_SIZE;
41 import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_HEADER_TABLE_SIZE;
42 import static io.netty.handler.codec.http2.Http2CodecUtil.MIN_HEADER_LIST_SIZE;
43 import static io.netty.handler.codec.http2.Http2CodecUtil.MIN_HEADER_TABLE_SIZE;
44 import static io.netty.handler.codec.http2.Http2CodecUtil.headerListSizeExceeded;
45 import static io.netty.handler.codec.http2.Http2Error.COMPRESSION_ERROR;
46 import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
47 import static io.netty.handler.codec.http2.Http2Exception.connectionError;
48 import static io.netty.handler.codec.http2.Http2Exception.streamError;
49 import static io.netty.handler.codec.http2.Http2Headers.PseudoHeaderName.getPseudoHeader;
50 import static io.netty.handler.codec.http2.Http2Headers.PseudoHeaderName.hasPseudoHeaderFormat;
51 import static io.netty.util.AsciiString.EMPTY_STRING;
52 import static io.netty.util.internal.ObjectUtil.checkPositive;
53
54 final class HpackDecoder {
55 private static final Http2Exception DECODE_ULE_128_DECOMPRESSION_EXCEPTION =
56 Http2Exception.newStatic(COMPRESSION_ERROR, "HPACK - decompression failure",
57 Http2Exception.ShutdownHint.HARD_SHUTDOWN, HpackDecoder.class,
58 "decodeULE128(..)");
59 private static final Http2Exception DECODE_ULE_128_TO_LONG_DECOMPRESSION_EXCEPTION =
60 Http2Exception.newStatic(COMPRESSION_ERROR, "HPACK - long overflow",
61 Http2Exception.ShutdownHint.HARD_SHUTDOWN, HpackDecoder.class, "decodeULE128(..)");
62 private static final Http2Exception DECODE_ULE_128_TO_INT_DECOMPRESSION_EXCEPTION =
63 Http2Exception.newStatic(COMPRESSION_ERROR, "HPACK - int overflow",
64 Http2Exception.ShutdownHint.HARD_SHUTDOWN, HpackDecoder.class, "decodeULE128ToInt(..)");
65 private static final Http2Exception DECODE_ILLEGAL_INDEX_VALUE =
66 Http2Exception.newStatic(COMPRESSION_ERROR, "HPACK - illegal index value",
67 Http2Exception.ShutdownHint.HARD_SHUTDOWN, HpackDecoder.class, "decode(..)");
68 private static final Http2Exception INDEX_HEADER_ILLEGAL_INDEX_VALUE =
69 Http2Exception.newStatic(COMPRESSION_ERROR, "HPACK - illegal index value",
70 Http2Exception.ShutdownHint.HARD_SHUTDOWN, HpackDecoder.class, "indexHeader(..)");
71 private static final Http2Exception READ_NAME_ILLEGAL_INDEX_VALUE =
72 Http2Exception.newStatic(COMPRESSION_ERROR, "HPACK - illegal index value",
73 Http2Exception.ShutdownHint.HARD_SHUTDOWN, HpackDecoder.class, "readName(..)");
74 private static final Http2Exception INVALID_MAX_DYNAMIC_TABLE_SIZE =
75 Http2Exception.newStatic(COMPRESSION_ERROR, "HPACK - invalid max dynamic table size",
76 Http2Exception.ShutdownHint.HARD_SHUTDOWN, HpackDecoder.class,
77 "setDynamicTableSize(..)");
78 private static final Http2Exception MAX_DYNAMIC_TABLE_SIZE_CHANGE_REQUIRED =
79 Http2Exception.newStatic(COMPRESSION_ERROR, "HPACK - max dynamic table size change required",
80 Http2Exception.ShutdownHint.HARD_SHUTDOWN, HpackDecoder.class, "decode(..)");
81 private static final byte READ_HEADER_REPRESENTATION = 0;
82 private static final byte READ_INDEXED_HEADER = 1;
83 private static final byte READ_INDEXED_HEADER_NAME = 2;
84 private static final byte READ_LITERAL_HEADER_NAME_LENGTH_PREFIX = 3;
85 private static final byte READ_LITERAL_HEADER_NAME_LENGTH = 4;
86 private static final byte READ_LITERAL_HEADER_NAME = 5;
87 private static final byte READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX = 6;
88 private static final byte READ_LITERAL_HEADER_VALUE_LENGTH = 7;
89 private static final byte READ_LITERAL_HEADER_VALUE = 8;
90
91 private final HpackHuffmanDecoder huffmanDecoder = new HpackHuffmanDecoder();
92 private final HpackDynamicTable hpackDynamicTable;
93 private long maxHeaderListSize;
94 private long maxDynamicTableSize;
95 private long encoderMaxDynamicTableSize;
96 private boolean maxDynamicTableSizeChangeRequired;
97
98
99
100
101
102
103
104
105 HpackDecoder(long maxHeaderListSize) {
106 this(maxHeaderListSize, DEFAULT_HEADER_TABLE_SIZE);
107 }
108
109
110
111
112
113 HpackDecoder(long maxHeaderListSize, int maxHeaderTableSize) {
114 this.maxHeaderListSize = checkPositive(maxHeaderListSize, "maxHeaderListSize");
115
116 maxDynamicTableSize = encoderMaxDynamicTableSize = maxHeaderTableSize;
117 maxDynamicTableSizeChangeRequired = false;
118 hpackDynamicTable = new HpackDynamicTable(maxHeaderTableSize);
119 }
120
121
122
123
124
125
126 void decode(int streamId, ByteBuf in, Http2Headers headers, boolean validateHeaders) throws Http2Exception {
127 Http2HeadersSink sink = new Http2HeadersSink(
128 streamId, headers, maxHeaderListSize, validateHeaders);
129
130
131 decodeDynamicTableSizeUpdates(in);
132 decode(in, sink);
133
134
135
136 sink.finish();
137 }
138
139 private void decodeDynamicTableSizeUpdates(ByteBuf in) throws Http2Exception {
140 byte b;
141 while (in.isReadable() && ((b = in.getByte(in.readerIndex())) & 0x20) == 0x20 && ((b & 0xC0) == 0x00)) {
142 in.readByte();
143 int index = b & 0x1F;
144 if (index == 0x1F) {
145 setDynamicTableSize(decodeULE128(in, (long) index));
146 } else {
147 setDynamicTableSize(index);
148 }
149 }
150 }
151
152 private void decode(ByteBuf in, Http2HeadersSink sink) throws Http2Exception {
153 int index = 0;
154 int nameLength = 0;
155 int valueLength = 0;
156 byte state = READ_HEADER_REPRESENTATION;
157 boolean huffmanEncoded = false;
158 AsciiString name = null;
159 IndexType indexType = IndexType.NONE;
160 while (in.isReadable()) {
161 switch (state) {
162 case READ_HEADER_REPRESENTATION:
163 byte b = in.readByte();
164 if (maxDynamicTableSizeChangeRequired && (b & 0xE0) != 0x20) {
165
166 throw MAX_DYNAMIC_TABLE_SIZE_CHANGE_REQUIRED;
167 }
168 if (b < 0) {
169
170 index = b & 0x7F;
171 switch (index) {
172 case 0:
173 throw DECODE_ILLEGAL_INDEX_VALUE;
174 case 0x7F:
175 state = READ_INDEXED_HEADER;
176 break;
177 default:
178 HpackHeaderField indexedHeader = getIndexedHeader(index);
179 sink.appendToHeaderList(
180 (AsciiString) indexedHeader.name,
181 (AsciiString) indexedHeader.value);
182 }
183 } else if ((b & 0x40) == 0x40) {
184
185 indexType = IndexType.INCREMENTAL;
186 index = b & 0x3F;
187 switch (index) {
188 case 0:
189 state = READ_LITERAL_HEADER_NAME_LENGTH_PREFIX;
190 break;
191 case 0x3F:
192 state = READ_INDEXED_HEADER_NAME;
193 break;
194 default:
195
196 name = readName(index);
197 nameLength = name.length();
198 state = READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;
199 }
200 } else if ((b & 0x20) == 0x20) {
201
202
203 throw connectionError(COMPRESSION_ERROR, "Dynamic table size update must happen " +
204 "at the beginning of the header block");
205 } else {
206
207 indexType = (b & 0x10) == 0x10 ? IndexType.NEVER : IndexType.NONE;
208 index = b & 0x0F;
209 switch (index) {
210 case 0:
211 state = READ_LITERAL_HEADER_NAME_LENGTH_PREFIX;
212 break;
213 case 0x0F:
214 state = READ_INDEXED_HEADER_NAME;
215 break;
216 default:
217
218 name = readName(index);
219 nameLength = name.length();
220 state = READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;
221 }
222 }
223 break;
224
225 case READ_INDEXED_HEADER:
226 HpackHeaderField indexedHeader = getIndexedHeader(decodeULE128(in, index));
227 sink.appendToHeaderList(
228 (AsciiString) indexedHeader.name,
229 (AsciiString) indexedHeader.value);
230 state = READ_HEADER_REPRESENTATION;
231 break;
232
233 case READ_INDEXED_HEADER_NAME:
234
235 name = readName(decodeULE128(in, index));
236 nameLength = name.length();
237 state = READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;
238 break;
239
240 case READ_LITERAL_HEADER_NAME_LENGTH_PREFIX:
241 b = in.readByte();
242 huffmanEncoded = (b & 0x80) == 0x80;
243 index = b & 0x7F;
244 if (index == 0x7f) {
245 state = READ_LITERAL_HEADER_NAME_LENGTH;
246 } else {
247 nameLength = index;
248 state = READ_LITERAL_HEADER_NAME;
249 }
250 break;
251
252 case READ_LITERAL_HEADER_NAME_LENGTH:
253
254 nameLength = decodeULE128(in, index);
255
256 state = READ_LITERAL_HEADER_NAME;
257 break;
258
259 case READ_LITERAL_HEADER_NAME:
260
261 if (in.readableBytes() < nameLength) {
262 throw notEnoughDataException(in);
263 }
264
265 name = readStringLiteral(in, nameLength, huffmanEncoded);
266
267 state = READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;
268 break;
269
270 case READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX:
271 b = in.readByte();
272 huffmanEncoded = (b & 0x80) == 0x80;
273 index = b & 0x7F;
274 switch (index) {
275 case 0x7f:
276 state = READ_LITERAL_HEADER_VALUE_LENGTH;
277 break;
278 case 0:
279 insertHeader(sink, name, EMPTY_STRING, indexType);
280 state = READ_HEADER_REPRESENTATION;
281 break;
282 default:
283 valueLength = index;
284 state = READ_LITERAL_HEADER_VALUE;
285 }
286
287 break;
288
289 case READ_LITERAL_HEADER_VALUE_LENGTH:
290
291 valueLength = decodeULE128(in, index);
292
293 state = READ_LITERAL_HEADER_VALUE;
294 break;
295
296 case READ_LITERAL_HEADER_VALUE:
297
298 if (in.readableBytes() < valueLength) {
299 throw notEnoughDataException(in);
300 }
301
302 AsciiString value = readStringLiteral(in, valueLength, huffmanEncoded);
303 insertHeader(sink, name, value, indexType);
304 state = READ_HEADER_REPRESENTATION;
305 break;
306
307 default:
308 throw new Error("should not reach here state: " + state);
309 }
310 }
311
312 if (state != READ_HEADER_REPRESENTATION) {
313 throw connectionError(COMPRESSION_ERROR, "Incomplete header block fragment.");
314 }
315 }
316
317
318
319
320
321 void setMaxHeaderTableSize(long maxHeaderTableSize) throws Http2Exception {
322 if (maxHeaderTableSize < MIN_HEADER_TABLE_SIZE || maxHeaderTableSize > MAX_HEADER_TABLE_SIZE) {
323 throw connectionError(PROTOCOL_ERROR, "Header Table Size must be >= %d and <= %d but was %d",
324 MIN_HEADER_TABLE_SIZE, MAX_HEADER_TABLE_SIZE, maxHeaderTableSize);
325 }
326 maxDynamicTableSize = maxHeaderTableSize;
327 if (maxDynamicTableSize < encoderMaxDynamicTableSize) {
328
329
330 maxDynamicTableSizeChangeRequired = true;
331 hpackDynamicTable.setCapacity(maxDynamicTableSize);
332 }
333 }
334
335 void setMaxHeaderListSize(long maxHeaderListSize) throws Http2Exception {
336 if (maxHeaderListSize < MIN_HEADER_LIST_SIZE || maxHeaderListSize > MAX_HEADER_LIST_SIZE) {
337 throw connectionError(PROTOCOL_ERROR, "Header List Size must be >= %d and <= %d but was %d",
338 MIN_HEADER_TABLE_SIZE, MAX_HEADER_TABLE_SIZE, maxHeaderListSize);
339 }
340 this.maxHeaderListSize = maxHeaderListSize;
341 }
342
343 long getMaxHeaderListSize() {
344 return maxHeaderListSize;
345 }
346
347
348
349
350
351 long getMaxHeaderTableSize() {
352 return hpackDynamicTable.capacity();
353 }
354
355
356
357
358 int length() {
359 return hpackDynamicTable.length();
360 }
361
362
363
364
365 long size() {
366 return hpackDynamicTable.size();
367 }
368
369
370
371
372 HpackHeaderField getHeaderField(int index) {
373 return hpackDynamicTable.getEntry(index + 1);
374 }
375
376 private void setDynamicTableSize(long dynamicTableSize) throws Http2Exception {
377 if (dynamicTableSize > maxDynamicTableSize) {
378 throw INVALID_MAX_DYNAMIC_TABLE_SIZE;
379 }
380 encoderMaxDynamicTableSize = dynamicTableSize;
381 maxDynamicTableSizeChangeRequired = false;
382 hpackDynamicTable.setCapacity(dynamicTableSize);
383 }
384
385 private static HeaderType validateHeader(int streamId, AsciiString name, CharSequence value,
386 HeaderType previousHeaderType) throws Http2Exception {
387 if (hasPseudoHeaderFormat(name)) {
388 if (previousHeaderType == HeaderType.REGULAR_HEADER) {
389 throw streamError(streamId, PROTOCOL_ERROR,
390 "Pseudo-header field '%s' found after regular header.", name);
391 }
392 final Http2Headers.PseudoHeaderName pseudoHeader = getPseudoHeader(name);
393 final HeaderType currentHeaderType = pseudoHeader.isRequestOnly() ?
394 HeaderType.REQUEST_PSEUDO_HEADER : HeaderType.RESPONSE_PSEUDO_HEADER;
395 if (previousHeaderType != null && currentHeaderType != previousHeaderType) {
396 throw streamError(streamId, PROTOCOL_ERROR, "Mix of request and response pseudo-headers.");
397 }
398 return currentHeaderType;
399 }
400 if (HttpHeaderValidationUtil.isConnectionHeader(name, true)) {
401 throw streamError(streamId, PROTOCOL_ERROR, "Illegal connection-specific header '%s' encountered.", name);
402 }
403 if (HttpHeaderValidationUtil.isTeNotTrailers(name, value)) {
404 throw streamError(streamId, PROTOCOL_ERROR,
405 "Illegal value specified for the 'TE' header (only 'trailers' is allowed).");
406 }
407
408 return HeaderType.REGULAR_HEADER;
409 }
410
411 private AsciiString readName(int index) throws Http2Exception {
412 if (index <= HpackStaticTable.length) {
413 HpackHeaderField hpackHeaderField = HpackStaticTable.getEntry(index);
414 return (AsciiString) hpackHeaderField.name;
415 }
416 if (index - HpackStaticTable.length <= hpackDynamicTable.length()) {
417 HpackHeaderField hpackHeaderField = hpackDynamicTable.getEntry(index - HpackStaticTable.length);
418 return (AsciiString) hpackHeaderField.name;
419 }
420 throw READ_NAME_ILLEGAL_INDEX_VALUE;
421 }
422
423 private HpackHeaderField getIndexedHeader(int index) throws Http2Exception {
424 if (index <= HpackStaticTable.length) {
425 return HpackStaticTable.getEntry(index);
426 }
427 if (index - HpackStaticTable.length <= hpackDynamicTable.length()) {
428 return hpackDynamicTable.getEntry(index - HpackStaticTable.length);
429 }
430 throw INDEX_HEADER_ILLEGAL_INDEX_VALUE;
431 }
432
433 private void insertHeader(Http2HeadersSink sink, AsciiString name, AsciiString value, IndexType indexType) {
434 sink.appendToHeaderList(name, value);
435
436 switch (indexType) {
437 case NONE:
438 case NEVER:
439 break;
440
441 case INCREMENTAL:
442 hpackDynamicTable.add(new HpackHeaderField(name, value));
443 break;
444
445 default:
446 throw new Error("should not reach here");
447 }
448 }
449
450 private AsciiString readStringLiteral(ByteBuf in, int length, boolean huffmanEncoded) throws Http2Exception {
451 if (huffmanEncoded) {
452 return huffmanDecoder.decode(in, length);
453 }
454 byte[] buf = new byte[length];
455 in.readBytes(buf);
456 return new AsciiString(buf, false);
457 }
458
459 private static IllegalArgumentException notEnoughDataException(ByteBuf in) {
460 return new IllegalArgumentException("decode only works with an entire header block! " + in);
461 }
462
463
464
465
466
467
468 static int decodeULE128(ByteBuf in, int result) throws Http2Exception {
469 final int readerIndex = in.readerIndex();
470 final long v = decodeULE128(in, (long) result);
471 if (v > Integer.MAX_VALUE) {
472
473
474
475
476
477 in.readerIndex(readerIndex);
478 throw DECODE_ULE_128_TO_INT_DECOMPRESSION_EXCEPTION;
479 }
480 return (int) v;
481 }
482
483
484
485
486
487
488 static long decodeULE128(ByteBuf in, long result) throws Http2Exception {
489 assert result <= 0x7f && result >= 0;
490 final boolean resultStartedAtZero = result == 0;
491 final int writerIndex = in.writerIndex();
492 for (int readerIndex = in.readerIndex(), shift = 0; readerIndex < writerIndex; ++readerIndex, shift += 7) {
493 byte b = in.getByte(readerIndex);
494 if (shift == 56 && ((b & 0x80) != 0 || b == 0x7F && !resultStartedAtZero)) {
495
496
497
498
499
500
501
502 throw DECODE_ULE_128_TO_LONG_DECOMPRESSION_EXCEPTION;
503 }
504
505 if ((b & 0x80) == 0) {
506 in.readerIndex(readerIndex + 1);
507 return result + ((b & 0x7FL) << shift);
508 }
509 result += (b & 0x7FL) << shift;
510 }
511
512 throw DECODE_ULE_128_DECOMPRESSION_EXCEPTION;
513 }
514
515
516
517
518 private enum HeaderType {
519 REGULAR_HEADER,
520 REQUEST_PSEUDO_HEADER,
521 RESPONSE_PSEUDO_HEADER
522 }
523
524 private static final class Http2HeadersSink {
525 private final Http2Headers headers;
526 private final long maxHeaderListSize;
527 private final int streamId;
528 private final boolean validateHeaders;
529 private long headersLength;
530 private boolean exceededMaxLength;
531 private HeaderType previousType;
532 private Http2Exception validationException;
533
534 Http2HeadersSink(int streamId, Http2Headers headers, long maxHeaderListSize, boolean validateHeaders) {
535 this.headers = headers;
536 this.maxHeaderListSize = maxHeaderListSize;
537 this.streamId = streamId;
538 this.validateHeaders = validateHeaders;
539 }
540
541 void finish() throws Http2Exception {
542 if (exceededMaxLength) {
543 headerListSizeExceeded(streamId, maxHeaderListSize, true);
544 } else if (validationException != null) {
545 throw validationException;
546 }
547 }
548
549 void appendToHeaderList(AsciiString name, AsciiString value) {
550 headersLength += HpackHeaderField.sizeOf(name, value);
551 exceededMaxLength |= headersLength > maxHeaderListSize;
552
553 if (exceededMaxLength || validationException != null) {
554
555 return;
556 }
557
558 try {
559 headers.add(name, value);
560 if (validateHeaders) {
561 previousType = validateHeader(streamId, name, value, previousType);
562 }
563 } catch (IllegalArgumentException ex) {
564 validationException = streamError(streamId, PROTOCOL_ERROR, ex,
565 "Validation failed for header '%s': %s", name, ex.getMessage());
566 } catch (Http2Exception ex) {
567 validationException = streamError(streamId, PROTOCOL_ERROR, ex, ex.getMessage());
568 }
569 }
570 }
571 }