View Javadoc
1   /*
2    * Copyright 2014 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
5    * "License"); you may not use this file except in compliance with the License. You may obtain a
6    * 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 distributed under the License
11   * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12   * or implied. See the License for the specific language governing permissions and limitations under
13   * the License.
14   */
15  
16  package io.netty.handler.codec.http2;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.util.internal.ObjectUtil;
20  import io.netty.util.internal.UnstableApi;
21  
22  import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_HEADER_LIST_SIZE;
23  import static io.netty.handler.codec.http2.Http2Error.COMPRESSION_ERROR;
24  import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR;
25  import static io.netty.handler.codec.http2.Http2Exception.connectionError;
26  
27  @UnstableApi
28  public class DefaultHttp2HeadersDecoder implements Http2HeadersDecoder, Http2HeadersDecoder.Configuration {
29      private static final float HEADERS_COUNT_WEIGHT_NEW = 1 / 5f;
30      private static final float HEADERS_COUNT_WEIGHT_HISTORICAL = 1 - HEADERS_COUNT_WEIGHT_NEW;
31  
32      private final HpackDecoder hpackDecoder;
33      private final boolean validateHeaders;
34      private final boolean validateHeaderValues;
35      private long maxHeaderListSizeGoAway;
36  
37      /**
38       * Used to calculate an exponential moving average of header sizes to get an estimate of how large the data
39       * structure for storing headers should be.
40       */
41      private float headerArraySizeAccumulator = 8;
42  
43      public DefaultHttp2HeadersDecoder() {
44          this(true);
45      }
46  
47      /**
48       * Create a new instance.
49       * @param validateHeaders {@code true} to validate headers are valid according to the RFC.
50       */
51      public DefaultHttp2HeadersDecoder(boolean validateHeaders) {
52          this(validateHeaders, DEFAULT_HEADER_LIST_SIZE);
53      }
54  
55      /**
56       * Create a new instance.
57       *
58       * @param validateHeaders {@code true} to validate headers are valid according to the RFC.
59       * This validates everything except header values.
60       * @param validateHeaderValues {@code true} to validate that header <em>values</em> are valid according to the RFC.
61       * Since this is potentially expensive, it can be enabled separately from {@code validateHeaders}.
62       */
63      public DefaultHttp2HeadersDecoder(boolean validateHeaders, boolean validateHeaderValues) {
64          this(validateHeaders, validateHeaderValues, DEFAULT_HEADER_LIST_SIZE);
65      }
66  
67      /**
68       * Create a new instance.
69       * @param validateHeaders {@code true} to validate headers are valid according to the RFC.
70       * @param maxHeaderListSize This is the only setting that can be configured before notifying the peer.
71       *  This is because <a href="https://tools.ietf.org/html/rfc7540#section-6.5.1">SETTINGS_MAX_HEADER_LIST_SIZE</a>
72       *  allows a lower than advertised limit from being enforced, and the default limit is unlimited
73       *  (which is dangerous).
74       */
75      public DefaultHttp2HeadersDecoder(boolean validateHeaders, long maxHeaderListSize) {
76          this(validateHeaders, false, new HpackDecoder(maxHeaderListSize));
77      }
78  
79      /**
80       * Create a new instance.
81       * @param validateHeaders {@code true} to validate headers are valid according to the RFC.
82       * This validates everything except header values.
83       * @param validateHeaderValues {@code true} to validate that header <em>values</em> are valid according to the RFC.
84       * Since this is potentially expensive, it can be enabled separately from {@code validateHeaders}.
85       * @param maxHeaderListSize This is the only setting that can be configured before notifying the peer.
86       *  This is because <a href="https://tools.ietf.org/html/rfc7540#section-6.5.1">SETTINGS_MAX_HEADER_LIST_SIZE</a>
87       *  allows a lower than advertised limit from being enforced, and the default limit is unlimited
88       *  (which is dangerous).
89       */
90      public DefaultHttp2HeadersDecoder(boolean validateHeaders, boolean validateHeaderValues, long maxHeaderListSize) {
91          this(validateHeaders, validateHeaderValues, new HpackDecoder(maxHeaderListSize));
92      }
93  
94      /**
95       * Create a new instance.
96       * @param validateHeaders {@code true} to validate headers are valid according to the RFC.
97       * This validates everything except header values.
98       * @param maxHeaderListSize This is the only setting that can be configured before notifying the peer.
99       *  This is because <a href="https://tools.ietf.org/html/rfc7540#section-6.5.1">SETTINGS_MAX_HEADER_LIST_SIZE</a>
100      *  allows a lower than advertised limit from being enforced, and the default limit is unlimited
101      *  (which is dangerous).
102      * @param initialHuffmanDecodeCapacity Does nothing, do not use.
103      */
104     public DefaultHttp2HeadersDecoder(boolean validateHeaders, long maxHeaderListSize,
105                                       @Deprecated int initialHuffmanDecodeCapacity) {
106         this(validateHeaders, false, new HpackDecoder(maxHeaderListSize));
107     }
108 
109     /**
110      * Exposed 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     DefaultHttp2HeadersDecoder(boolean validateHeaders, boolean validateHeaderValues, HpackDecoder hpackDecoder) {
114         this.hpackDecoder = ObjectUtil.checkNotNull(hpackDecoder, "hpackDecoder");
115         this.validateHeaders = validateHeaders;
116         this.validateHeaderValues = validateHeaderValues;
117         maxHeaderListSizeGoAway =
118                 Http2CodecUtil.calculateMaxHeaderListSizeGoAway(hpackDecoder.getMaxHeaderListSize());
119     }
120 
121     @Override
122     public void maxHeaderTableSize(long max) throws Http2Exception {
123         hpackDecoder.setMaxHeaderTableSize(max);
124     }
125 
126     @Override
127     public long maxHeaderTableSize() {
128         return hpackDecoder.getMaxHeaderTableSize();
129     }
130 
131     @Override
132     public void maxHeaderListSize(long max, long goAwayMax) throws Http2Exception {
133         if (goAwayMax < max || goAwayMax < 0) {
134             throw connectionError(INTERNAL_ERROR, "Header List Size GO_AWAY %d must be non-negative and >= %d",
135                     goAwayMax, max);
136         }
137         hpackDecoder.setMaxHeaderListSize(max);
138         maxHeaderListSizeGoAway = goAwayMax;
139     }
140 
141     @Override
142     public long maxHeaderListSize() {
143         return hpackDecoder.getMaxHeaderListSize();
144     }
145 
146     @Override
147     public long maxHeaderListSizeGoAway() {
148         return maxHeaderListSizeGoAway;
149     }
150 
151     @Override
152     public Configuration configuration() {
153         return this;
154     }
155 
156     @Override
157     public Http2Headers decodeHeaders(int streamId, ByteBuf headerBlock) throws Http2Exception {
158         try {
159             final Http2Headers headers = newHeaders();
160             hpackDecoder.decode(streamId, headerBlock, headers, validateHeaders);
161             headerArraySizeAccumulator = HEADERS_COUNT_WEIGHT_NEW * headers.size() +
162                                          HEADERS_COUNT_WEIGHT_HISTORICAL * headerArraySizeAccumulator;
163             return headers;
164         } catch (Http2Exception e) {
165             throw e;
166         } catch (Throwable e) {
167             // Default handler for any other types of errors that may have occurred. For example,
168             // the Header builder throws IllegalArgumentException if the key or value was invalid
169             // for any reason (e.g. the key was an invalid pseudo-header).
170             throw connectionError(COMPRESSION_ERROR, e, "Error decoding headers: %s", e.getMessage());
171         }
172     }
173 
174     /**
175      * A weighted moving average estimating how many headers are expected during the decode process.
176      * @return an estimate of how many headers are expected during the decode process.
177      */
178     protected final int numberOfHeadersGuess() {
179         return (int) headerArraySizeAccumulator;
180     }
181 
182     /**
183      * Determines if the headers should be validated as a result of the decode operation.
184      * <p>
185      * <strong>Note:</strong> This does not include validation of header <em>values</em>, since that is potentially
186      * expensive to do. Value validation is instead {@linkplain #validateHeaderValues() enabled separately}.
187      *
188      * @return {@code true} if the headers should be validated as a result of the decode operation.
189      */
190     protected final boolean validateHeaders() {
191         return validateHeaders;
192     }
193 
194     /**
195      * Determines if the header values should be validated as a result of the decode operation.
196      * <p>
197      * <strong>Note:</strong> This <em>only</em> validates the values of headers. All other header validations are
198      * instead {@linkplain #validateHeaders() enabled separately}.
199      *
200      * @return {@code true} if the header values should be validated as a result of the decode operation.
201      */
202     protected boolean validateHeaderValues() { // Not 'final' due to backwards compatibility.
203         return validateHeaderValues;
204     }
205 
206     /**
207      * Create a new {@link Http2Headers} object which will store the results of the decode operation.
208      * @return a new {@link Http2Headers} object which will store the results of the decode operation.
209      */
210     protected Http2Headers newHeaders() {
211         return new DefaultHttp2Headers(validateHeaders, validateHeaderValues, (int) headerArraySizeAccumulator);
212     }
213 }