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