View Javadoc
1   /*
2    * Copyright 2012 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.netty5.handler.codec.http;
17  
18  import io.netty5.handler.codec.CharSequenceValueConverter;
19  import io.netty5.handler.codec.DateFormatter;
20  import io.netty5.handler.codec.DefaultHeaders;
21  import io.netty5.handler.codec.DefaultHeaders.NameValidator;
22  import io.netty5.handler.codec.DefaultHeadersImpl;
23  import io.netty5.handler.codec.HeadersUtils;
24  import io.netty5.handler.codec.ValueConverter;
25  import io.netty5.util.AsciiString;
26  import io.netty5.util.ByteProcessor;
27  
28  import java.util.ArrayList;
29  import java.util.Calendar;
30  import java.util.Collections;
31  import java.util.Date;
32  import java.util.Iterator;
33  import java.util.List;
34  import java.util.Map;
35  import java.util.Map.Entry;
36  import java.util.Set;
37  
38  import static io.netty5.util.AsciiString.CASE_INSENSITIVE_HASHER;
39  import static io.netty5.util.AsciiString.CASE_SENSITIVE_HASHER;
40  
41  /**
42   * Default implementation of {@link HttpHeaders}.
43   */
44  public class DefaultHttpHeaders extends HttpHeaders {
45      private static final int HIGHEST_INVALID_VALUE_CHAR_MASK = ~15;
46      private static final ByteProcessor HEADER_NAME_VALIDATOR = value -> {
47          validateHeaderNameElement(value);
48          return true;
49      };
50      static final NameValidator<CharSequence> HttpNameValidator = name -> {
51          if (name == null || name.length() == 0) {
52              throw new IllegalArgumentException("empty headers are not allowed [" + name + ']');
53          }
54          if (name instanceof AsciiString) {
55              ((AsciiString) name).forEachByte(HEADER_NAME_VALIDATOR);
56          } else {
57              // Go through each character in the name
58              for (int index = 0; index < name.length(); ++index) {
59                  validateHeaderNameElement(name.charAt(index));
60              }
61          }
62      };
63  
64      private final DefaultHeaders<CharSequence, CharSequence, ?> headers;
65  
66      public DefaultHttpHeaders() {
67          this(true);
68      }
69  
70      /**
71       * <b>Warning!</b> Setting {@code validate} to {@code false} will mean that Netty won't
72       * validate & protect against user-supplied header values that are malicious.
73       * This can leave your server implementation vulnerable to
74       * <a href="https://cwe.mitre.org/data/definitions/113.html">
75       *     CWE-113: Improper Neutralization of CRLF Sequences in HTTP Headers ('HTTP Response Splitting')
76       * </a>.
77       * When disabling this validation, it is the responsibility of the caller to ensure that the values supplied
78       * do not contain a non-url-escaped carriage return (CR) and/or line feed (LF) characters.
79       *
80       * @param validate Should Netty validate Header values to ensure they aren't malicious.
81       */
82      public DefaultHttpHeaders(boolean validate) {
83          this(validate, nameValidator(validate));
84      }
85  
86      protected DefaultHttpHeaders(boolean validate, NameValidator<CharSequence> nameValidator) {
87          this(new DefaultHeadersImpl<>(CASE_INSENSITIVE_HASHER,
88                  valueConverter(validate),
89                  nameValidator));
90      }
91  
92      protected DefaultHttpHeaders(DefaultHeaders<CharSequence, CharSequence, ?> headers) {
93          this.headers = headers;
94      }
95  
96      @Override
97      public HttpHeaders add(HttpHeaders headers) {
98          if (headers instanceof DefaultHttpHeaders) {
99              this.headers.add(((DefaultHttpHeaders) headers).headers);
100             return this;
101         } else {
102             return super.add(headers);
103         }
104     }
105 
106     @Override
107     public HttpHeaders set(HttpHeaders headers) {
108         if (headers instanceof DefaultHttpHeaders) {
109             this.headers.set(((DefaultHttpHeaders) headers).headers);
110             return this;
111         } else {
112             return super.set(headers);
113         }
114     }
115 
116     @Override
117     public HttpHeaders add(String name, Object value) {
118         headers.addObject(name, value);
119         return this;
120     }
121 
122     @Override
123     public HttpHeaders add(CharSequence name, Object value) {
124         headers.addObject(name, value);
125         return this;
126     }
127 
128     @Override
129     public HttpHeaders add(String name, Iterable<?> values) {
130         headers.addObject(name, values);
131         return this;
132     }
133 
134     @Override
135     public HttpHeaders add(CharSequence name, Iterable<?> values) {
136         headers.addObject(name, values);
137         return this;
138     }
139 
140     @Override
141     public HttpHeaders addInt(CharSequence name, int value) {
142         headers.addInt(name, value);
143         return this;
144     }
145 
146     @Override
147     public HttpHeaders addShort(CharSequence name, short value) {
148         headers.addShort(name, value);
149         return this;
150     }
151 
152     @Override
153     public HttpHeaders remove(String name) {
154         headers.remove(name);
155         return this;
156     }
157 
158     @Override
159     public HttpHeaders remove(CharSequence name) {
160         headers.remove(name);
161         return this;
162     }
163 
164     @Override
165     public HttpHeaders set(String name, Object value) {
166         headers.setObject(name, value);
167         return this;
168     }
169 
170     @Override
171     public HttpHeaders set(CharSequence name, Object value) {
172         headers.setObject(name, value);
173         return this;
174     }
175 
176     @Override
177     public HttpHeaders set(String name, Iterable<?> values) {
178         headers.setObject(name, values);
179         return this;
180     }
181 
182     @Override
183     public HttpHeaders set(CharSequence name, Iterable<?> values) {
184         headers.setObject(name, values);
185         return this;
186     }
187 
188     @Override
189     public HttpHeaders setInt(CharSequence name, int value) {
190         headers.setInt(name, value);
191         return this;
192     }
193 
194     @Override
195     public HttpHeaders setShort(CharSequence name, short value) {
196         headers.setShort(name, value);
197         return this;
198     }
199 
200     @Override
201     public HttpHeaders clear() {
202         headers.clear();
203         return this;
204     }
205 
206     @Override
207     public String get(String name) {
208         return get((CharSequence) name);
209     }
210 
211     @Override
212     public String get(CharSequence name) {
213         return HeadersUtils.getAsString(headers, name);
214     }
215 
216     @Override
217     public Integer getInt(CharSequence name) {
218         return headers.getInt(name);
219     }
220 
221     @Override
222     public int getInt(CharSequence name, int defaultValue) {
223         return headers.getInt(name, defaultValue);
224     }
225 
226     @Override
227     public Short getShort(CharSequence name) {
228         return headers.getShort(name);
229     }
230 
231     @Override
232     public short getShort(CharSequence name, short defaultValue) {
233         return headers.getShort(name, defaultValue);
234     }
235 
236     @Override
237     public Long getTimeMillis(CharSequence name) {
238         return headers.getTimeMillis(name);
239     }
240 
241     @Override
242     public long getTimeMillis(CharSequence name, long defaultValue) {
243         return headers.getTimeMillis(name, defaultValue);
244     }
245 
246     @Override
247     public List<String> getAll(String name) {
248         return getAll((CharSequence) name);
249     }
250 
251     @Override
252     public List<String> getAll(CharSequence name) {
253         return HeadersUtils.getAllAsString(headers, name);
254     }
255 
256     @Override
257     public List<Entry<String, String>> entries() {
258         if (isEmpty()) {
259             return Collections.emptyList();
260         }
261         List<Entry<String, String>> entriesConverted = new ArrayList<>(
262                 headers.size());
263         for (Entry<String, String> entry : this) {
264             entriesConverted.add(entry);
265         }
266         return entriesConverted;
267     }
268 
269     @Deprecated
270     @Override
271     public Iterator<Map.Entry<String, String>> iterator() {
272         return HeadersUtils.iteratorAsString(headers);
273     }
274 
275     @Override
276     public Iterator<Entry<CharSequence, CharSequence>> iteratorCharSequence() {
277         return headers.iterator();
278     }
279 
280     @Override
281     public Iterator<String> valueStringIterator(CharSequence name) {
282         final Iterator<CharSequence> itr = valueCharSequenceIterator(name);
283         return new Iterator<>() {
284             @Override
285             public boolean hasNext() {
286                 return itr.hasNext();
287             }
288 
289             @Override
290             public String next() {
291                 return itr.next().toString();
292             }
293 
294             @Override
295             public void remove() {
296                 itr.remove();
297             }
298         };
299     }
300 
301     @Override
302     public Iterator<CharSequence> valueCharSequenceIterator(CharSequence name) {
303         return headers.valueIterator(name);
304     }
305 
306     @Override
307     public boolean contains(String name) {
308         return contains((CharSequence) name);
309     }
310 
311     @Override
312     public boolean contains(CharSequence name) {
313         return headers.contains(name);
314     }
315 
316     @Override
317     public boolean isEmpty() {
318         return headers.isEmpty();
319     }
320 
321     @Override
322     public int size() {
323         return headers.size();
324     }
325 
326     @Override
327     public boolean contains(String name, String value, boolean ignoreCase) {
328         return contains((CharSequence) name, (CharSequence) value, ignoreCase);
329     }
330 
331     @Override
332     public boolean contains(CharSequence name, CharSequence value, boolean ignoreCase) {
333         return headers.contains(name, value, ignoreCase ? CASE_INSENSITIVE_HASHER : CASE_SENSITIVE_HASHER);
334     }
335 
336     @Override
337     public Set<String> names() {
338         return HeadersUtils.namesAsString(headers);
339     }
340 
341     @Override
342     public boolean equals(Object o) {
343         return o instanceof DefaultHttpHeaders
344                 && headers.equals(((DefaultHttpHeaders) o).headers, CASE_SENSITIVE_HASHER);
345     }
346 
347     @Override
348     public int hashCode() {
349         return headers.hashCode(CASE_SENSITIVE_HASHER);
350     }
351 
352     @Override
353     public HttpHeaders copy() {
354         return new DefaultHttpHeaders(headers.copy());
355     }
356 
357     private static void validateHeaderNameElement(byte value) {
358         switch (value) {
359         case 0x1c:
360         case 0x1d:
361         case 0x1e:
362         case 0x1f:
363         case 0x00:
364         case '\t':
365         case '\n':
366         case 0x0b:
367         case '\f':
368         case '\r':
369         case ' ':
370         case ',':
371         case ':':
372         case ';':
373         case '=':
374             throw new IllegalArgumentException(
375                "a header name cannot contain the following prohibited characters: =,;: \\t\\r\\n\\v\\f: " +
376                        value);
377         default:
378             // Check to see if the character is not an ASCII character, or invalid
379             if (value < 0) {
380                 throw new IllegalArgumentException("a header name cannot contain non-ASCII character: " + value);
381             }
382         }
383     }
384 
385     private static void validateHeaderNameElement(char value) {
386         switch (value) {
387         case 0x1c:
388         case 0x1d:
389         case 0x1e:
390         case 0x1f:
391         case 0x00:
392         case '\t':
393         case '\n':
394         case 0x0b:
395         case '\f':
396         case '\r':
397         case ' ':
398         case ',':
399         case ':':
400         case ';':
401         case '=':
402             throw new IllegalArgumentException(
403                "a header name cannot contain the following prohibited characters: =,;: \\t\\r\\n\\v\\f: " +
404                        value);
405         default:
406             // Check to see if the character is not an ASCII character, or invalid
407             if (value > 127) {
408                 throw new IllegalArgumentException("a header name cannot contain non-ASCII character: " +
409                         value);
410             }
411         }
412     }
413 
414     static ValueConverter<CharSequence> valueConverter(boolean validate) {
415         return validate ? HeaderValueConverterAndValidator.INSTANCE : HeaderValueConverter.INSTANCE;
416     }
417 
418     @SuppressWarnings("unchecked")
419     static NameValidator<CharSequence> nameValidator(boolean validate) {
420         return validate ? HttpNameValidator : NameValidator.NOT_NULL;
421     }
422 
423     private static class HeaderValueConverter extends CharSequenceValueConverter {
424         static final HeaderValueConverter INSTANCE = new HeaderValueConverter();
425 
426         @Override
427         public CharSequence convertObject(Object value) {
428             if (value instanceof CharSequence) {
429                 return (CharSequence) value;
430             }
431             if (value instanceof Date) {
432                 return DateFormatter.format((Date) value);
433             }
434             if (value instanceof Calendar) {
435                 return DateFormatter.format(((Calendar) value).getTime());
436             }
437             return value.toString();
438         }
439     }
440 
441     private static final class HeaderValueConverterAndValidator extends HeaderValueConverter {
442         static final HeaderValueConverterAndValidator INSTANCE = new HeaderValueConverterAndValidator();
443 
444         @Override
445         public CharSequence convertObject(Object value) {
446             CharSequence seq = super.convertObject(value);
447             int state = 0;
448             // Start looping through each of the character
449             for (int index = 0; index < seq.length(); index++) {
450                 state = validateValueChar(seq, state, seq.charAt(index));
451             }
452 
453             if (state != 0) {
454                 throw new IllegalArgumentException("a header value must not end with '\\r' or '\\n':" + seq);
455             }
456             return seq;
457         }
458 
459         private static int validateValueChar(CharSequence seq, int state, char character) {
460             /*
461              * State:
462              * 0: Previous character was neither CR nor LF
463              * 1: The previous character was CR
464              * 2: The previous character was LF
465              */
466             if ((character & HIGHEST_INVALID_VALUE_CHAR_MASK) == 0) {
467                 // Check the absolutely prohibited characters.
468                 switch (character) {
469                 case 0x0: // NULL
470                     throw new IllegalArgumentException("a header value contains a prohibited character '\0': " + seq);
471                 case 0x0b: // Vertical tab
472                     throw new IllegalArgumentException("a header value contains a prohibited character '\\v': " + seq);
473                 case '\f':
474                     throw new IllegalArgumentException("a header value contains a prohibited character '\\f': " + seq);
475                 default:
476                     break;
477                 }
478             }
479 
480             // Check the CRLF (HT | SP) pattern
481             switch (state) {
482                 case 0:
483                     switch (character) {
484                         case '\r':
485                             return 1;
486                         case '\n':
487                             return 2;
488                         default:
489                             break;
490                     }
491                     break;
492                 case 1:
493                     if (character == '\n') {
494                         return 2;
495                     }
496                     throw new IllegalArgumentException("only '\\n' is allowed after '\\r': " + seq);
497                 case 2:
498                     switch (character) {
499                         case '\t':
500                         case ' ':
501                             return 0;
502                         default:
503                             throw new IllegalArgumentException("only ' ' and '\\t' are allowed after '\\n': " + seq);
504                     }
505                 default:
506                     break;
507             }
508             return state;
509         }
510     }
511 }