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