1 /*
2 * Copyright 2023 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 package io.netty.handler.codec.http;
17
18 import static io.netty.util.internal.ObjectUtil.checkNotNull;
19 import static io.netty.util.internal.ObjectUtil.checkPositive;
20
21 /**
22 * A configuration object for specifying the behaviour of {@link HttpObjectDecoder} and its subclasses.
23 * <p>
24 * The {@link HttpDecoderConfig} objects are mutable to reduce allocation,
25 * but also {@link Cloneable} in case a defensive copy is needed.
26 */
27 public final class HttpDecoderConfig implements Cloneable {
28 private int maxChunkSize = HttpObjectDecoder.DEFAULT_MAX_CHUNK_SIZE;
29 private boolean chunkedSupported = HttpObjectDecoder.DEFAULT_CHUNKED_SUPPORTED;
30 private boolean allowPartialChunks = HttpObjectDecoder.DEFAULT_ALLOW_PARTIAL_CHUNKS;
31 private HttpHeadersFactory headersFactory = DefaultHttpHeadersFactory.headersFactory();
32 private HttpHeadersFactory trailersFactory = DefaultHttpHeadersFactory.trailersFactory();
33 private boolean allowDuplicateContentLengths = HttpObjectDecoder.DEFAULT_ALLOW_DUPLICATE_CONTENT_LENGTHS;
34 private int maxInitialLineLength = HttpObjectDecoder.DEFAULT_MAX_INITIAL_LINE_LENGTH;
35 private int maxHeaderSize = HttpObjectDecoder.DEFAULT_MAX_HEADER_SIZE;
36 private int initialBufferSize = HttpObjectDecoder.DEFAULT_INITIAL_BUFFER_SIZE;
37 private boolean strictLineParsing = HttpObjectDecoder.DEFAULT_STRICT_LINE_PARSING;
38 private boolean useRfc9112TransferEncoding = HttpObjectDecoder.RFC9112_TRANSFER_ENCODING;
39
40 public int getInitialBufferSize() {
41 return initialBufferSize;
42 }
43
44 /**
45 * Set the initial size of the temporary buffer used when parsing the lines of the HTTP headers.
46 *
47 * @param initialBufferSize The buffer size in bytes.
48 * @return This decoder config.
49 */
50 public HttpDecoderConfig setInitialBufferSize(int initialBufferSize) {
51 checkPositive(initialBufferSize, "initialBufferSize");
52 this.initialBufferSize = initialBufferSize;
53 return this;
54 }
55
56 public int getMaxInitialLineLength() {
57 return maxInitialLineLength;
58 }
59
60 /**
61 * Set the maximum length of the first line of the HTTP header.
62 * This limits how much memory Netty will use when parsed the initial HTTP header line.
63 * You would typically set this to the same value as {@link #setMaxHeaderSize(int)}.
64 *
65 * @param maxInitialLineLength The maximum length, in bytes.
66 * @return This decoder config.
67 */
68 public HttpDecoderConfig setMaxInitialLineLength(int maxInitialLineLength) {
69 checkPositive(maxInitialLineLength, "maxInitialLineLength");
70 this.maxInitialLineLength = maxInitialLineLength;
71 return this;
72 }
73
74 public int getMaxHeaderSize() {
75 return maxHeaderSize;
76 }
77
78 /**
79 * Set the maximum line length of header lines.
80 * This limits how much memory Netty will use when parsing HTTP header key-value pairs.
81 * The limit applies to the sum of all the headers, so it applies equally to many short header-lines,
82 * or fewer but longer header lines.
83 * <p>
84 * You would typically set this to the same value as {@link #setMaxInitialLineLength(int)}.
85 *
86 * @param maxHeaderSize The maximum length, in bytes.
87 * @return This decoder config.
88 */
89 public HttpDecoderConfig setMaxHeaderSize(int maxHeaderSize) {
90 checkPositive(maxHeaderSize, "maxHeaderSize");
91 this.maxHeaderSize = maxHeaderSize;
92 return this;
93 }
94
95 public int getMaxChunkSize() {
96 return maxChunkSize;
97 }
98
99 /**
100 * Set the maximum chunk size.
101 * HTTP requests and responses can be quite large, in which case it's better to process the data as a stream of
102 * chunks.
103 * This sets the limit, in bytes, at which Netty will send a chunk down the pipeline.
104 *
105 * @param maxChunkSize The maximum chunk size, in bytes.
106 * @return This decoder config.
107 */
108 public HttpDecoderConfig setMaxChunkSize(int maxChunkSize) {
109 checkPositive(maxChunkSize, "maxChunkSize");
110 this.maxChunkSize = maxChunkSize;
111 return this;
112 }
113
114 public boolean isChunkedSupported() {
115 return chunkedSupported;
116 }
117
118 /**
119 * Set whether {@code Transfer-Encoding: Chunked} should be supported.
120 *
121 * @param chunkedSupported if {@code false}, then a {@code Transfer-Encoding: Chunked} header will produce an error,
122 * instead of a stream of chunks.
123 * @return This decoder config.
124 */
125 public HttpDecoderConfig setChunkedSupported(boolean chunkedSupported) {
126 this.chunkedSupported = chunkedSupported;
127 return this;
128 }
129
130 public boolean isAllowPartialChunks() {
131 return allowPartialChunks;
132 }
133
134 /**
135 * Set whether chunks can be split into multiple messages, if their chunk size exceeds the size of the input buffer.
136 *
137 * @param allowPartialChunks set to {@code false} to only allow sending whole chunks down the pipeline.
138 * @return This decoder config.
139 */
140 public HttpDecoderConfig setAllowPartialChunks(boolean allowPartialChunks) {
141 this.allowPartialChunks = allowPartialChunks;
142 return this;
143 }
144
145 public HttpHeadersFactory getHeadersFactory() {
146 return headersFactory;
147 }
148
149 /**
150 * Set the {@link HttpHeadersFactory} to use when creating new HTTP headers objects.
151 * The default headers factory is {@link DefaultHttpHeadersFactory#headersFactory()}.
152 * <p>
153 * For the purpose of {@link #clone()}, it is assumed that the factory is either immutable, or can otherwise be
154 * shared across different decoders and decoder configs.
155 *
156 * @param headersFactory The header factory to use.
157 * @return This decoder config.
158 */
159 public HttpDecoderConfig setHeadersFactory(HttpHeadersFactory headersFactory) {
160 checkNotNull(headersFactory, "headersFactory");
161 this.headersFactory = headersFactory;
162 return this;
163 }
164
165 public boolean isAllowDuplicateContentLengths() {
166 return allowDuplicateContentLengths;
167 }
168
169 /**
170 * Set whether more than one {@code Content-Length} header is allowed.
171 * You usually want to disallow this (which is the default) as multiple {@code Content-Length} headers can indicate
172 * a request- or response-splitting attack.
173 *
174 * @param allowDuplicateContentLengths set to {@code true} to allow multiple content length headers.
175 * @return This decoder config.
176 */
177 public HttpDecoderConfig setAllowDuplicateContentLengths(boolean allowDuplicateContentLengths) {
178 this.allowDuplicateContentLengths = allowDuplicateContentLengths;
179 return this;
180 }
181
182 /**
183 * Set whether header validation should be enabled or not.
184 * This works by changing the configured {@linkplain #setHeadersFactory(HttpHeadersFactory) header factory}
185 * and {@linkplain #setTrailersFactory(HttpHeadersFactory) trailer factory}.
186 * <p>
187 * You usually want header validation enabled (which is the default) in order to prevent request-/response-splitting
188 * attacks.
189 *
190 * @param validateHeaders set to {@code false} to disable header validation.
191 * @return This decoder config.
192 */
193 public HttpDecoderConfig setValidateHeaders(boolean validateHeaders) {
194 DefaultHttpHeadersFactory noValidation = DefaultHttpHeadersFactory.headersFactory().withValidation(false);
195 headersFactory = validateHeaders ? DefaultHttpHeadersFactory.headersFactory() : noValidation;
196 trailersFactory = validateHeaders ? DefaultHttpHeadersFactory.trailersFactory() : noValidation;
197 return this;
198 }
199
200 public HttpHeadersFactory getTrailersFactory() {
201 return trailersFactory;
202 }
203
204 /**
205 * Set the {@link HttpHeadersFactory} used to create HTTP trailers.
206 * This differs from {@link #setHeadersFactory(HttpHeadersFactory)} in that trailers have different validation
207 * requirements.
208 * The default trailer factory is {@link DefaultHttpHeadersFactory#headersFactory()}.
209 * <p>
210 * For the purpose of {@link #clone()}, it is assumed that the factory is either immutable, or can otherwise be
211 * shared across different decoders and decoder configs.
212 *
213 * @param trailersFactory The headers factory to use for creating trailers.
214 * @return This decoder config.
215 */
216 public HttpDecoderConfig setTrailersFactory(HttpHeadersFactory trailersFactory) {
217 checkNotNull(trailersFactory, "trailersFactory");
218 this.trailersFactory = trailersFactory;
219 return this;
220 }
221
222 public boolean isStrictLineParsing() {
223 return strictLineParsing;
224 }
225
226 /**
227 * The RFC 9112 specification for the HTTP protocol says that the initial start-line, and the following header
228 * field-lines, must be separated by a Carriage Return (CR) and Line Feed (LF) octet pair, but also offers that
229 * implementations "MAY" accept just a Line Feed octet as a separator.
230 * <p>
231 * Parsing leniencies can increase compatibility with a wider range of implementations, but can also cause
232 * security vulnerabilities, when multiple systems disagree on the meaning of leniently parsed messages.
233 * <p>
234 * When <em>strict line parsing</em> is enabled ({@code true}), then Netty will enforce that start- and header
235 * field-lines MUST be separated by a CR LF octet pair, and will produce messagas with failed
236 * {@link io.netty.handler.codec.DecoderResult}s.
237 * <p>
238 * When <em>strict line parsing</em> is disabled ({@code false}), then Netty will accept lone LF octets as line
239 * seperators for the start- and header field-lines.
240 * <p>
241 * See <a href="https://datatracker.ietf.org/doc/html/rfc9112#name-message-format">RFC 9112 Section 2.1</a>.
242 * @param strictLineParsing Whether <em>strict line parsing</em> should be enabled ({@code true}),
243 * or not ({@code false}).
244 * @return This decoder config.
245 */
246 public HttpDecoderConfig setStrictLineParsing(boolean strictLineParsing) {
247 this.strictLineParsing = strictLineParsing;
248 return this;
249 }
250
251 public boolean isUseRfc9112TransferEncoding() {
252 return useRfc9112TransferEncoding;
253 }
254
255 /**
256 * The RFC 9112 specification is more strict than RFC 7230 with regards to having {@code Transfer-Encoding} and
257 * {@code Content-Length} headers in the same HTTP message. Senders are now forbidden from including both headers
258 * in the same message, while servers may reject such requests. When this setting is set to {@code true}, which
259 * is the default, then such messages will be <em>rejected.</em>
260 * <p>
261 * When this setting is set to {@code false}, it restores the RFC 7230 behavior of instead removing any
262 * {@code Content-Length} headers when {@code Transfer-Encoding} headers are present.
263 * @param useRfc9112TransferEncoding Whether to reject messages with both {@code Transfer-Encoding} and
264 * {@code Content-Length} headers.
265 * @return This decoder config.
266 * @see HttpObjectDecoder#handleTransferEncodingChunkedWithContentLength(HttpMessage)
267 */
268 public HttpDecoderConfig setUseRfc9112TransferEncoding(boolean useRfc9112TransferEncoding) {
269 this.useRfc9112TransferEncoding = useRfc9112TransferEncoding;
270 return this;
271 }
272
273 @Override
274 public HttpDecoderConfig clone() {
275 try {
276 return (HttpDecoderConfig) super.clone();
277 } catch (CloneNotSupportedException e) {
278 throw new AssertionError();
279 }
280 }
281 }