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