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