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.netty5.handler.codec.http2;
33
34 import io.netty5.buffer.api.Buffer;
35 import io.netty5.handler.codec.http2.HpackUtil.IndexType;
36 import io.netty5.util.AsciiString;
37
38 import static io.netty5.handler.codec.http2.Http2CodecUtil.DEFAULT_HEADER_TABLE_SIZE;
39 import static io.netty5.handler.codec.http2.Http2CodecUtil.MAX_HEADER_LIST_SIZE;
40 import static io.netty5.handler.codec.http2.Http2CodecUtil.MAX_HEADER_TABLE_SIZE;
41 import static io.netty5.handler.codec.http2.Http2CodecUtil.MIN_HEADER_LIST_SIZE;
42 import static io.netty5.handler.codec.http2.Http2CodecUtil.MIN_HEADER_TABLE_SIZE;
43 import static io.netty5.handler.codec.http2.Http2CodecUtil.headerListSizeExceeded;
44 import static io.netty5.handler.codec.http2.Http2Error.COMPRESSION_ERROR;
45 import static io.netty5.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
46 import static io.netty5.handler.codec.http2.Http2Exception.connectionError;
47 import static io.netty5.handler.codec.http2.Http2Exception.streamError;
48 import static io.netty5.handler.codec.http2.Http2Headers.PseudoHeaderName.getPseudoHeader;
49 import static io.netty5.handler.codec.http2.Http2Headers.PseudoHeaderName.hasPseudoHeaderFormat;
50 import static io.netty5.util.AsciiString.EMPTY_STRING;
51 import static io.netty5.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, Buffer 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(Buffer 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.readableBytes() > 0) {
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 public void setMaxHeaderListSize(long maxHeaderListSize) throws Http2Exception {
324 if (maxHeaderListSize < MIN_HEADER_LIST_SIZE || maxHeaderListSize > MAX_HEADER_LIST_SIZE) {
325 throw connectionError(PROTOCOL_ERROR, "Header List Size must be >= %d and <= %d but was %d",
326 MIN_HEADER_TABLE_SIZE, MAX_HEADER_TABLE_SIZE, maxHeaderListSize);
327 }
328 this.maxHeaderListSize = maxHeaderListSize;
329 }
330
331 public long getMaxHeaderListSize() {
332 return maxHeaderListSize;
333 }
334
335
336
337
338
339 public long getMaxHeaderTableSize() {
340 return hpackDynamicTable.capacity();
341 }
342
343
344
345
346 int length() {
347 return hpackDynamicTable.length();
348 }
349
350
351
352
353 long size() {
354 return hpackDynamicTable.size();
355 }
356
357
358
359
360 HpackHeaderField getHeaderField(int index) {
361 return hpackDynamicTable.getEntry(index + 1);
362 }
363
364 private void setDynamicTableSize(long dynamicTableSize) throws Http2Exception {
365 if (dynamicTableSize > maxDynamicTableSize) {
366 throw INVALID_MAX_DYNAMIC_TABLE_SIZE;
367 }
368 encoderMaxDynamicTableSize = dynamicTableSize;
369 maxDynamicTableSizeChangeRequired = false;
370 hpackDynamicTable.setCapacity(dynamicTableSize);
371 }
372
373 private static HeaderType validate(int streamId, CharSequence name,
374 HeaderType previousHeaderType, Http2Headers headers) throws Http2Exception {
375 if (hasPseudoHeaderFormat(name)) {
376 if (previousHeaderType == HeaderType.REGULAR_HEADER) {
377 throw streamError(streamId, PROTOCOL_ERROR,
378 "Pseudo-header field '%s' found after regular header.", name);
379 }
380
381 final Http2Headers.PseudoHeaderName pseudoHeader = getPseudoHeader(name);
382 if (pseudoHeader == null) {
383 throw streamError(streamId, PROTOCOL_ERROR, "Invalid HTTP/2 pseudo-header '%s' encountered.", name);
384 }
385
386 final HeaderType currentHeaderType = pseudoHeader.isRequestOnly() ?
387 HeaderType.REQUEST_PSEUDO_HEADER : HeaderType.RESPONSE_PSEUDO_HEADER;
388 if (previousHeaderType != null && currentHeaderType != previousHeaderType) {
389 throw streamError(streamId, PROTOCOL_ERROR, "Mix of request and response pseudo-headers.");
390 }
391
392 if (headers.contains(name)) {
393 throw streamError(streamId, PROTOCOL_ERROR, "Duplicate HTTP/2 pseudo-header '%s' encountered.", name);
394 }
395
396 return currentHeaderType;
397 }
398
399 return HeaderType.REGULAR_HEADER;
400 }
401
402 private CharSequence readName(int index) throws Http2Exception {
403 if (index <= HpackStaticTable.length) {
404 HpackHeaderField hpackHeaderField = HpackStaticTable.getEntry(index);
405 return hpackHeaderField.name;
406 }
407 if (index - HpackStaticTable.length <= hpackDynamicTable.length()) {
408 HpackHeaderField hpackHeaderField = hpackDynamicTable.getEntry(index - HpackStaticTable.length);
409 return hpackHeaderField.name;
410 }
411 throw READ_NAME_ILLEGAL_INDEX_VALUE;
412 }
413
414 private HpackHeaderField getIndexedHeader(int index) throws Http2Exception {
415 if (index <= HpackStaticTable.length) {
416 return HpackStaticTable.getEntry(index);
417 }
418 if (index - HpackStaticTable.length <= hpackDynamicTable.length()) {
419 return hpackDynamicTable.getEntry(index - HpackStaticTable.length);
420 }
421 throw INDEX_HEADER_ILLEGAL_INDEX_VALUE;
422 }
423
424 private void insertHeader(Sink sink, CharSequence name, CharSequence value, IndexType indexType) {
425 sink.appendToHeaderList(name, value);
426
427 switch (indexType) {
428 case NONE:
429 case NEVER:
430 break;
431
432 case INCREMENTAL:
433 hpackDynamicTable.add(new HpackHeaderField(name, value));
434 break;
435
436 default:
437 throw new Error("should not reach here");
438 }
439 }
440
441 private CharSequence readStringLiteral(Buffer in, int length, boolean huffmanEncoded) throws Http2Exception {
442 if (huffmanEncoded) {
443 return huffmanDecoder.decode(in, length);
444 }
445 byte[] buf = new byte[length];
446 in.readBytes(buf, 0, length);
447 return new AsciiString(buf, false);
448 }
449
450 private static IllegalArgumentException notEnoughDataException(Buffer in) {
451 return new IllegalArgumentException("decode only works with an entire header block! " + in);
452 }
453
454
455
456
457
458
459 static int decodeULE128(Buffer in, int result) throws Http2Exception {
460 final int readerIndex = in.readerOffset();
461 final long v = decodeULE128(in, (long) result);
462 if (v > Integer.MAX_VALUE) {
463
464
465
466
467
468 in.readerOffset(readerIndex);
469 throw DECODE_ULE_128_TO_INT_DECOMPRESSION_EXCEPTION;
470 }
471 return (int) v;
472 }
473
474
475
476
477
478
479 static long decodeULE128(Buffer in, long result) throws Http2Exception {
480 assert result <= 0x7f && result >= 0;
481 final boolean resultStartedAtZero = result == 0;
482 final int writerOffset = in.writerOffset();
483 for (int readerIndex = in.readerOffset(), shift = 0; readerIndex < writerOffset; ++readerIndex, shift += 7) {
484 byte b = in.getByte(readerIndex);
485 if (shift == 56 && ((b & 0x80) != 0 || b == 0x7F && !resultStartedAtZero)) {
486
487
488
489
490
491
492
493 throw DECODE_ULE_128_TO_LONG_DECOMPRESSION_EXCEPTION;
494 }
495
496 if ((b & 0x80) == 0) {
497 in.readerOffset(readerIndex + 1);
498 return result + ((b & 0x7FL) << shift);
499 }
500 result += (b & 0x7FL) << shift;
501 }
502
503 throw DECODE_ULE_128_DECOMPRESSION_EXCEPTION;
504 }
505
506
507
508
509 private enum HeaderType {
510 REGULAR_HEADER,
511 REQUEST_PSEUDO_HEADER,
512 RESPONSE_PSEUDO_HEADER
513 }
514
515 private interface Sink {
516 void appendToHeaderList(CharSequence name, CharSequence value);
517 void finish() throws Http2Exception;
518 }
519
520 private static final class Http2HeadersSink implements Sink {
521 private final Http2Headers headers;
522 private final long maxHeaderListSize;
523 private final int streamId;
524 private final boolean validate;
525 private long headersLength;
526 private boolean exceededMaxLength;
527 private HeaderType previousType;
528 private Http2Exception validationException;
529
530 Http2HeadersSink(int streamId, Http2Headers headers, long maxHeaderListSize, boolean validate) {
531 this.headers = headers;
532 this.maxHeaderListSize = maxHeaderListSize;
533 this.streamId = streamId;
534 this.validate = validate;
535 }
536
537 @Override
538 public void finish() throws Http2Exception {
539 if (exceededMaxLength) {
540 headerListSizeExceeded(streamId, maxHeaderListSize, true);
541 } else if (validationException != null) {
542 throw validationException;
543 }
544 }
545
546 @Override
547 public void appendToHeaderList(CharSequence name, CharSequence value) {
548 headersLength += HpackHeaderField.sizeOf(name, value);
549 exceededMaxLength |= headersLength > maxHeaderListSize;
550
551 if (exceededMaxLength || validationException != null) {
552
553 return;
554 }
555
556 if (validate) {
557 try {
558 previousType = validate(streamId, name, previousType, headers);
559 } catch (Http2Exception ex) {
560 validationException = ex;
561 return;
562 }
563 }
564 headers.add(name, value);
565 }
566 }
567 }