View Javadoc
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 }