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 io.netty.handler.codec.DefaultHeaders.NameValidator;
19 import io.netty.handler.codec.DefaultHeaders.ValueValidator;
20
21 import static io.netty.util.internal.ObjectUtil.checkNotNull;
22
23 /**
24 * A builder of {@link HttpHeadersFactory} instances, that itself implements {@link HttpHeadersFactory}.
25 * The builder is immutable, and every {@code with-} method produce a new, modified instance.
26 * <p>
27 * The default builder you most likely want to start with is {@link DefaultHttpHeadersFactory#headersFactory()}.
28 */
29 public final class DefaultHttpHeadersFactory implements HttpHeadersFactory {
30 private static final NameValidator<CharSequence> DEFAULT_NAME_VALIDATOR = new NameValidator<CharSequence>() {
31 @Override
32 public void validateName(CharSequence name) {
33 if (name == null || name.length() == 0) {
34 throw new IllegalArgumentException("empty headers are not allowed [" + name + ']');
35 }
36 int index = HttpHeaderValidationUtil.validateToken(name);
37 if (index != -1) {
38 throw new IllegalArgumentException("a header name can only contain \"token\" characters, " +
39 "but found invalid character 0x" + Integer.toHexString(name.charAt(index)) +
40 " at index " + index + " of header '" + name + "'.");
41 }
42 }
43 };
44 private static final ValueValidator<CharSequence> DEFAULT_VALUE_VALIDATOR = new ValueValidator<CharSequence>() {
45 @Override
46 public void validate(CharSequence value) {
47 int index = HttpHeaderValidationUtil.validateValidHeaderValue(value);
48 if (index != -1) {
49 throw new IllegalArgumentException("a header value contains prohibited character 0x" +
50 Integer.toHexString(value.charAt(index)) + " at index " + index + '.');
51 }
52 }
53 };
54 private static final NameValidator<CharSequence> DEFAULT_TRAILER_NAME_VALIDATOR =
55 new NameValidator<CharSequence>() {
56 @Override
57 public void validateName(CharSequence name) {
58 DEFAULT_NAME_VALIDATOR.validateName(name);
59 if (HttpHeaderNames.CONTENT_LENGTH.contentEqualsIgnoreCase(name)
60 || HttpHeaderNames.TRANSFER_ENCODING.contentEqualsIgnoreCase(name)
61 || HttpHeaderNames.TRAILER.contentEqualsIgnoreCase(name)) {
62 throw new IllegalArgumentException("prohibited trailing header: " + name);
63 }
64 }
65 };
66
67 @SuppressWarnings("unchecked")
68 private static final NameValidator<CharSequence> NO_NAME_VALIDATOR = NameValidator.NOT_NULL;
69 @SuppressWarnings("unchecked")
70 private static final ValueValidator<CharSequence> NO_VALUE_VALIDATOR =
71 (ValueValidator<CharSequence>) ValueValidator.NO_VALIDATION;
72
73 private static final DefaultHttpHeadersFactory DEFAULT =
74 new DefaultHttpHeadersFactory(DEFAULT_NAME_VALIDATOR, DEFAULT_VALUE_VALIDATOR, false);
75 private static final DefaultHttpHeadersFactory DEFAULT_TRAILER =
76 new DefaultHttpHeadersFactory(DEFAULT_TRAILER_NAME_VALIDATOR, DEFAULT_VALUE_VALIDATOR, false);
77 private static final DefaultHttpHeadersFactory DEFAULT_COMBINING =
78 new DefaultHttpHeadersFactory(DEFAULT.nameValidator, DEFAULT.valueValidator, true);
79 private static final DefaultHttpHeadersFactory DEFAULT_NO_VALIDATION =
80 new DefaultHttpHeadersFactory(NO_NAME_VALIDATOR, NO_VALUE_VALIDATOR, false);
81
82 private final NameValidator<CharSequence> nameValidator;
83 private final ValueValidator<CharSequence> valueValidator;
84 private final boolean combiningHeaders;
85
86 /**
87 * Create a header builder with the given settings.
88 *
89 * @param nameValidator The name validator to use, not null.
90 * @param valueValidator The value validator to use, not null.
91 * @param combiningHeaders {@code true} if multi-valued headers should be combined into single lines.
92 */
93 private DefaultHttpHeadersFactory(
94 NameValidator<CharSequence> nameValidator,
95 ValueValidator<CharSequence> valueValidator,
96 boolean combiningHeaders) {
97 this.nameValidator = checkNotNull(nameValidator, "nameValidator");
98 this.valueValidator = checkNotNull(valueValidator, "valueValidator");
99 this.combiningHeaders = combiningHeaders;
100 }
101
102 /**
103 * Get the default implementation of {@link HttpHeadersFactory} for creating headers.
104 * <p>
105 * This {@link DefaultHttpHeadersFactory} creates {@link HttpHeaders} instances that has the
106 * recommended header validation enabled.
107 */
108 public static DefaultHttpHeadersFactory headersFactory() {
109 return DEFAULT;
110 }
111
112 /**
113 * Get the default implementation of {@link HttpHeadersFactory} for creating trailers.
114 * <p>
115 * This {@link DefaultHttpHeadersFactory} creates {@link HttpHeaders} instances that has the
116 * validation enabled that is recommended for trailers.
117 */
118 public static DefaultHttpHeadersFactory trailersFactory() {
119 return DEFAULT_TRAILER;
120 }
121
122 @Override
123 public HttpHeaders newHeaders() {
124 if (isCombiningHeaders()) {
125 return new CombinedHttpHeaders(getNameValidator(), getValueValidator());
126 }
127 return new DefaultHttpHeaders(getNameValidator(), getValueValidator());
128 }
129
130 @Override
131 public HttpHeaders newEmptyHeaders() {
132 if (isCombiningHeaders()) {
133 return new CombinedHttpHeaders(getNameValidator(), getValueValidator(), 2);
134 }
135 return new DefaultHttpHeaders(getNameValidator(), getValueValidator(), 2);
136 }
137
138 /**
139 * Create a new builder that has HTTP header name validation enabled or disabled.
140 * <p>
141 * <b>Warning!</b> Setting {@code validation} to {@code false} will mean that Netty won't
142 * validate & protect against user-supplied headers that are malicious.
143 * This can leave your server implementation vulnerable to
144 * <a href="https://cwe.mitre.org/data/definitions/113.html">
145 * CWE-113: Improper Neutralization of CRLF Sequences in HTTP Headers ('HTTP Response Splitting')
146 * </a>.
147 * When disabling this validation, it is the responsibility of the caller to ensure that the values supplied
148 * do not contain a non-url-escaped carriage return (CR) and/or line feed (LF) characters.
149 *
150 * @param validation If validation should be enabled or disabled.
151 * @return The new builder.
152 */
153 public DefaultHttpHeadersFactory withNameValidation(boolean validation) {
154 return withNameValidator(validation ? DEFAULT_NAME_VALIDATOR : NO_NAME_VALIDATOR);
155 }
156
157 /**
158 * Create a new builder that with the given {@link NameValidator}.
159 * <p>
160 * <b>Warning!</b> If the given validator does not check that the header names are standards compliant, Netty won't
161 * validate & protect against user-supplied headers that are malicious.
162 * This can leave your server implementation vulnerable to
163 * <a href="https://cwe.mitre.org/data/definitions/113.html">
164 * CWE-113: Improper Neutralization of CRLF Sequences in HTTP Headers ('HTTP Response Splitting')
165 * </a>.
166 * When disabling this validation, it is the responsibility of the caller to ensure that the values supplied
167 * do not contain a non-url-escaped carriage return (CR) and/or line feed (LF) characters.
168 *
169 * @param validator The HTTP header name validator to use.
170 * @return The new builder.
171 */
172 public DefaultHttpHeadersFactory withNameValidator(NameValidator<CharSequence> validator) {
173 if (nameValidator == checkNotNull(validator, "validator")) {
174 return this;
175 }
176 if (validator == DEFAULT_NAME_VALIDATOR && valueValidator == DEFAULT_VALUE_VALIDATOR) {
177 return combiningHeaders ? DEFAULT_COMBINING : DEFAULT;
178 }
179 return new DefaultHttpHeadersFactory(validator, valueValidator, combiningHeaders);
180 }
181
182 /**
183 * Create a new builder that has HTTP header value validation enabled or disabled.
184 * <p>
185 * <b>Warning!</b> Setting {@code validation} to {@code false} will mean that Netty won't
186 * validate & protect against user-supplied headers that are malicious.
187 * This can leave your server implementation vulnerable to
188 * <a href="https://cwe.mitre.org/data/definitions/113.html">
189 * CWE-113: Improper Neutralization of CRLF Sequences in HTTP Headers ('HTTP Response Splitting')
190 * </a>.
191 * When disabling this validation, it is the responsibility of the caller to ensure that the values supplied
192 * do not contain a non-url-escaped carriage return (CR) and/or line feed (LF) characters.
193 *
194 * @param validation If validation should be enabled or disabled.
195 * @return The new builder.
196 */
197 public DefaultHttpHeadersFactory withValueValidation(boolean validation) {
198 return withValueValidator(validation ? DEFAULT_VALUE_VALIDATOR : NO_VALUE_VALIDATOR);
199 }
200
201 /**
202 * Create a new builder that with the given {@link ValueValidator}.
203 * <p>
204 * <b>Warning!</b> If the given validator does not check that the header values are standards compliant, Netty won't
205 * validate & protect against user-supplied headers that are malicious.
206 * This can leave your server implementation vulnerable to
207 * <a href="https://cwe.mitre.org/data/definitions/113.html">
208 * CWE-113: Improper Neutralization of CRLF Sequences in HTTP Headers ('HTTP Response Splitting')
209 * </a>.
210 * When disabling this validation, it is the responsibility of the caller to ensure that the values supplied
211 * do not contain a non-url-escaped carriage return (CR) and/or line feed (LF) characters.
212 *
213 * @param validator The HTTP header name validator to use.
214 * @return The new builder.
215 */
216 public DefaultHttpHeadersFactory withValueValidator(ValueValidator<CharSequence> validator) {
217 if (valueValidator == checkNotNull(validator, "validator")) {
218 return this;
219 }
220 if (nameValidator == DEFAULT_NAME_VALIDATOR && validator == DEFAULT_VALUE_VALIDATOR) {
221 return combiningHeaders ? DEFAULT_COMBINING : DEFAULT;
222 }
223 return new DefaultHttpHeadersFactory(nameValidator, validator, combiningHeaders);
224 }
225
226 /**
227 * Create a new builder that has HTTP header validation enabled or disabled.
228 * <p>
229 * <b>Warning!</b> Setting {@code validation} to {@code false} will mean that Netty won't
230 * validate & protect against user-supplied headers that are malicious.
231 * This can leave your server implementation vulnerable to
232 * <a href="https://cwe.mitre.org/data/definitions/113.html">
233 * CWE-113: Improper Neutralization of CRLF Sequences in HTTP Headers ('HTTP Response Splitting')
234 * </a>.
235 * When disabling this validation, it is the responsibility of the caller to ensure that the values supplied
236 * do not contain a non-url-escaped carriage return (CR) and/or line feed (LF) characters.
237 *
238 * @param validation If validation should be enabled or disabled.
239 * @return The new builder.
240 */
241 public DefaultHttpHeadersFactory withValidation(boolean validation) {
242 if (this == DEFAULT && !validation) {
243 return DEFAULT_NO_VALIDATION;
244 }
245 if (this == DEFAULT_NO_VALIDATION && validation) {
246 return DEFAULT;
247 }
248 return withNameValidation(validation).withValueValidation(validation);
249 }
250
251 /**
252 * Create a new builder that will build {@link HttpHeaders} objects that either combine
253 * multi-valued headers, or not.
254 *
255 * @param combiningHeaders {@code true} if multi-valued headers should be combined, otherwise {@code false}.
256 * @return The new builder.
257 */
258 public DefaultHttpHeadersFactory withCombiningHeaders(boolean combiningHeaders) {
259 if (this.combiningHeaders == combiningHeaders) {
260 return this;
261 }
262 return new DefaultHttpHeadersFactory(nameValidator, valueValidator, combiningHeaders);
263 }
264
265 /**
266 * Get the currently configured {@link NameValidator}.
267 * <p>
268 * This method will be used by the {@link #newHeaders()} method.
269 *
270 * @return The configured name validator.
271 */
272 public NameValidator<CharSequence> getNameValidator() {
273 return nameValidator;
274 }
275
276 /**
277 * Get the currently configured {@link ValueValidator}.
278 * <p>
279 * This method will be used by the {@link #newHeaders()} method.
280 *
281 * @return The configured value validator.
282 */
283 public ValueValidator<CharSequence> getValueValidator() {
284 return valueValidator;
285 }
286
287 /**
288 * Check whether header combining is enabled or not.
289 *
290 * @return {@code true} if header value combining is enabled, otherwise {@code false}.
291 */
292 public boolean isCombiningHeaders() {
293 return combiningHeaders;
294 }
295
296 /**
297 * Check whether header name validation is enabled.
298 *
299 * @return {@code true} if header name validation is enabled, otherwise {@code false}.
300 */
301 public boolean isValidatingHeaderNames() {
302 return nameValidator != NO_NAME_VALIDATOR;
303 }
304
305 /**
306 * Check whether header value validation is enabled.
307 *
308 * @return {@code true} if header value validation is enabled, otherwise {@code false}.
309 */
310 public boolean isValidatingHeaderValues() {
311 return valueValidator != NO_VALUE_VALIDATOR;
312 }
313 }