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 }