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  *   http://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 0x00:
371         case '\t':
372         case '\n':
373         case 0x0b:
374         case '\f':
375         case '\r':
376         case ' ':
377         case ',':
378         case ':':
379         case ';':
380         case '=':
381             throw new IllegalArgumentException(
382                "a header name cannot contain the following prohibited characters: =,;: \\t\\r\\n\\v\\f: " +
383                        value);
384         default:
385             // Check to see if the character is not an ASCII character, or invalid
386             if (value < 0) {
387                 throw new IllegalArgumentException("a header name cannot contain non-ASCII character: " + value);
388             }
389         }
390     }
391
392     private static void validateHeaderNameElement(char value) {
393         switch (value) {
394         case 0x00:
395         case '\t':
396         case '\n':
397         case 0x0b:
398         case '\f':
399         case '\r':
400         case ' ':
401         case ',':
402         case ':':
403         case ';':
404         case '=':
405             throw new IllegalArgumentException(
406                "a header name cannot contain the following prohibited characters: =,;: \\t\\r\\n\\v\\f: " +
407                        value);
408         default:
409             // Check to see if the character is not an ASCII character, or invalid
410             if (value > 127) {
411                 throw new IllegalArgumentException("a header name cannot contain non-ASCII character: " +
412                         value);
413             }
414         }
415     }
416
417     static ValueConverter<CharSequence> valueConverter(boolean validate) {
418         return validate ? HeaderValueConverterAndValidator.INSTANCE : HeaderValueConverter.INSTANCE;
419     }
420
421     @SuppressWarnings("unchecked")
422     static NameValidator<CharSequence> nameValidator(boolean validate) {
423         return validate ? HttpNameValidator : NameValidator.NOT_NULL;
424     }
425
426     private static class HeaderValueConverter extends CharSequenceValueConverter {
427         static final HeaderValueConverter INSTANCE = new HeaderValueConverter();
428
429         @Override
430         public CharSequence convertObject(Object value) {
431             if (value instanceof CharSequence) {
432                 return (CharSequence) value;
433             }
434             if (value instanceof Date) {
435                 return DateFormatter.format((Date) value);
436             }
437             if (value instanceof Calendar) {
438                 return DateFormatter.format(((Calendar) value).getTime());
439             }
440             return value.toString();
441         }
442     }
443
444     private static final class HeaderValueConverterAndValidator extends HeaderValueConverter {
445         static final HeaderValueConverterAndValidator INSTANCE = new HeaderValueConverterAndValidator();
446
447         @Override
448         public CharSequence convertObject(Object value) {
449             CharSequence seq = super.convertObject(value);
450             int state = 0;
451             // Start looping through each of the character
452             for (int index = 0; index < seq.length(); index++) {
453                 state = validateValueChar(seq, state, seq.charAt(index));
454             }
455
456             if (state != 0) {
457                 throw new IllegalArgumentException("a header value must not end with '\\r' or '\\n':" + seq);
458             }
459             return seq;
460         }
461
462         private static int validateValueChar(CharSequence seq, int state, char character) {
463             /*
464              * State:
465              * 0: Previous character was neither CR nor LF
466              * 1: The previous character was CR
467              * 2: The previous character was LF
468              */

469             if ((character & HIGHEST_INVALID_VALUE_CHAR_MASK) == 0) {
470                 // Check the absolutely prohibited characters.
471                 switch (character) {
472                 case 0x0: // NULL
473                     throw new IllegalArgumentException("a header value contains a prohibited character '\0': " + seq);
474                 case 0x0b: // Vertical tab
475                     throw new IllegalArgumentException("a header value contains a prohibited character '\\v': " + seq);
476                 case '\f':
477                     throw new IllegalArgumentException("a header value contains a prohibited character '\\f': " + seq);
478                 }
479             }
480
481             // Check the CRLF (HT | SP) pattern
482             switch (state) {
483                 case 0:
484                     switch (character) {
485                         case '\r':
486                             return 1;
487                         case '\n':
488                             return 2;
489                     }
490                     break;
491                 case 1:
492                     switch (character) {
493                         case '\n':
494                             return 2;
495                         default:
496                             throw new IllegalArgumentException("only '\\n' is allowed after '\\r': " + seq);
497                     }
498                 case 2:
499                     switch (character) {
500                         case '\t':
501                         case ' ':
502                             return 0;
503                         default:
504                             throw new IllegalArgumentException("only ' ' and '\\t' are allowed after '\\n': " + seq);
505                     }
506             }
507             return state;
508         }
509     }
510 }
511