View Javadoc
1   /*
2    * Copyright 2015 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://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, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  
17  /*
18   * Copyright 2014 Twitter, Inc.
19   *
20   * Licensed under the Apache License, Version 2.0 (the "License");
21   * you may not use this file except in compliance with the License.
22   * You may obtain a copy of the License at
23   *
24   *     https://www.apache.org/licenses/LICENSE-2.0
25   *
26   * Unless required by applicable law or agreed to in writing, software
27   * distributed under the License is distributed on an "AS IS" BASIS,
28   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
29   * See the License for the specific language governing permissions and
30   * limitations under the License.
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       * Create a new instance.
100      * @param maxHeaderListSize This is the only setting that can be configured before notifying the peer.
101      *  This is because <a href="https://tools.ietf.org/html/rfc7540#section-6.5.1">SETTINGS_MAX_HEADER_LIST_SIZE</a>
102      *  allows a lower than advertised limit from being enforced, and the default limit is unlimited
103      *  (which is dangerous).
104      */
105     HpackDecoder(long maxHeaderListSize) {
106         this(maxHeaderListSize, DEFAULT_HEADER_TABLE_SIZE);
107     }
108 
109     /**
110      * Exposed Used for testing only! Default values used in the initial settings frame are overridden intentionally
111      * for testing but violate the RFC if used outside the scope of testing.
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      * Decode the header block into header fields.
123      * <p>
124      * This method assumes the entire header block is contained in {@code in}.
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         // Check for dynamic table size updates, which must occur at the beginning:
130         // https://www.rfc-editor.org/rfc/rfc7541.html#section-4.2
131         decodeDynamicTableSizeUpdates(in);
132         decode(in, sink);
133 
134         // Now that we've read all of our headers we can perform the validation steps. We must
135         // delay throwing until this point to prevent dynamic table corruption.
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                         // HpackEncoder MUST signal maximum dynamic table size change
166                         throw MAX_DYNAMIC_TABLE_SIZE_CHANGE_REQUIRED;
167                     }
168                     if (b < 0) {
169                         // Indexed Header Field
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                         // Literal Header Field with Incremental Indexing
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                                 // Index was stored as the prefix
196                                 name = readName(index);
197                                 nameLength = name.length();
198                                 state = READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;
199                         }
200                     } else if ((b & 0x20) == 0x20) {
201                         // Dynamic Table Size Update
202                         // See https://www.rfc-editor.org/rfc/rfc7541.html#section-4.2
203                         throw connectionError(COMPRESSION_ERROR, "Dynamic table size update must happen " +
204                             "at the beginning of the header block");
205                     } else {
206                         // Literal Header Field without Indexing / never Indexed
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                                 // Index was stored as the prefix
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                     // Header Name matches an entry in the Header Table
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                     // Header Name is a Literal String
254                     nameLength = decodeULE128(in, index);
255 
256                     state = READ_LITERAL_HEADER_NAME;
257                     break;
258 
259                 case READ_LITERAL_HEADER_NAME:
260                     // Wait until entire name is readable
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                     // Header Value is a Literal String
291                     valueLength = decodeULE128(in, index);
292 
293                     state = READ_LITERAL_HEADER_VALUE;
294                     break;
295 
296                 case READ_LITERAL_HEADER_VALUE:
297                     // Wait until entire value is readable
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      * Set the maximum table size. If this is below the maximum size of the dynamic table used by
319      * the encoder, the beginning of the next header block MUST signal this change.
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             // decoder requires less space than encoder
329             // encoder MUST signal this change
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      * Return the maximum table size. This is the maximum size allowed by both the encoder and the
349      * decoder.
350      */
351     long getMaxHeaderTableSize() {
352         return hpackDynamicTable.capacity();
353     }
354 
355     /**
356      * Return the number of header fields in the dynamic table. Exposed for testing.
357      */
358     int length() {
359         return hpackDynamicTable.length();
360     }
361 
362     /**
363      * Return the size of the dynamic table. Exposed for testing.
364      */
365     long size() {
366         return hpackDynamicTable.size();
367     }
368 
369     /**
370      * Return the header field at the given index. Exposed for testing.
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      * Unsigned Little Endian Base 128 Variable-Length Integer Encoding
465      * <p>
466      * Visible for testing only!
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             // the maximum value that can be represented by a signed 32 bit number is:
473             // [0x1,0x7f] + 0x7f + (0x7f << 7) + (0x7f << 14) + (0x7f << 21) + (0x6 << 28)
474             // OR
475             // 0x0 + 0x7f + (0x7f << 7) + (0x7f << 14) + (0x7f << 21) + (0x7 << 28)
476             // we should reset the readerIndex if we overflowed the int type.
477             in.readerIndex(readerIndex);
478             throw DECODE_ULE_128_TO_INT_DECOMPRESSION_EXCEPTION;
479         }
480         return (int) v;
481     }
482 
483     /**
484      * Unsigned Little Endian Base 128 Variable-Length Integer Encoding
485      * <p>
486      * Visible for testing only!
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                 // the maximum value that can be represented by a signed 64 bit number is:
496                 // [0x01L, 0x7fL] + 0x7fL + (0x7fL << 7) + (0x7fL << 14) + (0x7fL << 21) + (0x7fL << 28) + (0x7fL << 35)
497                 // + (0x7fL << 42) + (0x7fL << 49) + (0x7eL << 56)
498                 // OR
499                 // 0x0L + 0x7fL + (0x7fL << 7) + (0x7fL << 14) + (0x7fL << 21) + (0x7fL << 28) + (0x7fL << 35) +
500                 // (0x7fL << 42) + (0x7fL << 49) + (0x7fL << 56)
501                 // this means any more shifts will result in overflow so we should break out and throw an error.
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      * HTTP/2 header types.
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                 // We don't store the header since we've already failed validation requirements.
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 }