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