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    *   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      public DefaultHttpHeaders(boolean validate) {
82          this(validate, nameValidator(validate));
83      }
84  
85      protected DefaultHttpHeaders(boolean validate, NameValidator<CharSequence> nameValidator) {
86          this(new DefaultHeadersImpl<CharSequence, CharSequence>(CASE_INSENSITIVE_HASHER,
87                                                                  valueConverter(validate),
88                                                                  nameValidator));
89      }
90  
91      protected DefaultHttpHeaders(DefaultHeaders<CharSequence, CharSequence, ?> headers) {
92          this.headers = headers;
93      }
94  
95      @Override
96      public HttpHeaders add(HttpHeaders headers) {
97          if (headers instanceof DefaultHttpHeaders) {
98              this.headers.add(((DefaultHttpHeaders) headers).headers);
99              return this;
100         } else {
101             return super.add(headers);
102         }
103     }
104 
105     @Override
106     public HttpHeaders set(HttpHeaders headers) {
107         if (headers instanceof DefaultHttpHeaders) {
108             this.headers.set(((DefaultHttpHeaders) headers).headers);
109             return this;
110         } else {
111             return super.set(headers);
112         }
113     }
114 
115     @Override
116     public HttpHeaders add(String name, Object value) {
117         headers.addObject(name, value);
118         return this;
119     }
120 
121     @Override
122     public HttpHeaders add(CharSequence name, Object value) {
123         headers.addObject(name, value);
124         return this;
125     }
126 
127     @Override
128     public HttpHeaders add(String name, Iterable<?> values) {
129         headers.addObject(name, values);
130         return this;
131     }
132 
133     @Override
134     public HttpHeaders add(CharSequence name, Iterable<?> values) {
135         headers.addObject(name, values);
136         return this;
137     }
138 
139     @Override
140     public HttpHeaders addInt(CharSequence name, int value) {
141         headers.addInt(name, value);
142         return this;
143     }
144 
145     @Override
146     public HttpHeaders addShort(CharSequence name, short value) {
147         headers.addShort(name, value);
148         return this;
149     }
150 
151     @Override
152     public HttpHeaders remove(String name) {
153         headers.remove(name);
154         return this;
155     }
156 
157     @Override
158     public HttpHeaders remove(CharSequence name) {
159         headers.remove(name);
160         return this;
161     }
162 
163     @Override
164     public HttpHeaders set(String name, Object value) {
165         headers.setObject(name, value);
166         return this;
167     }
168 
169     @Override
170     public HttpHeaders set(CharSequence name, Object value) {
171         headers.setObject(name, value);
172         return this;
173     }
174 
175     @Override
176     public HttpHeaders set(String name, Iterable<?> values) {
177         headers.setObject(name, values);
178         return this;
179     }
180 
181     @Override
182     public HttpHeaders set(CharSequence name, Iterable<?> values) {
183         headers.setObject(name, values);
184         return this;
185     }
186 
187     @Override
188     public HttpHeaders setInt(CharSequence name, int value) {
189         headers.setInt(name, value);
190         return this;
191     }
192 
193     @Override
194     public HttpHeaders setShort(CharSequence name, short value) {
195         headers.setShort(name, value);
196         return this;
197     }
198 
199     @Override
200     public HttpHeaders clear() {
201         headers.clear();
202         return this;
203     }
204 
205     @Override
206     public String get(String name) {
207         return get((CharSequence) name);
208     }
209 
210     @Override
211     public String get(CharSequence name) {
212         return HeadersUtils.getAsString(headers, name);
213     }
214 
215     @Override
216     public Integer getInt(CharSequence name) {
217         return headers.getInt(name);
218     }
219 
220     @Override
221     public int getInt(CharSequence name, int defaultValue) {
222         return headers.getInt(name, defaultValue);
223     }
224 
225     @Override
226     public Short getShort(CharSequence name) {
227         return headers.getShort(name);
228     }
229 
230     @Override
231     public short getShort(CharSequence name, short defaultValue) {
232         return headers.getShort(name, defaultValue);
233     }
234 
235     @Override
236     public Long getTimeMillis(CharSequence name) {
237         return headers.getTimeMillis(name);
238     }
239 
240     @Override
241     public long getTimeMillis(CharSequence name, long defaultValue) {
242         return headers.getTimeMillis(name, defaultValue);
243     }
244 
245     @Override
246     public List<String> getAll(String name) {
247         return getAll((CharSequence) name);
248     }
249 
250     @Override
251     public List<String> getAll(CharSequence name) {
252         return HeadersUtils.getAllAsString(headers, name);
253     }
254 
255     @Override
256     public List<Entry<String, String>> entries() {
257         if (isEmpty()) {
258             return Collections.emptyList();
259         }
260         List<Entry<String, String>> entriesConverted = new ArrayList<Entry<String, String>>(
261                 headers.size());
262         for (Entry<String, String> entry : this) {
263             entriesConverted.add(entry);
264         }
265         return entriesConverted;
266     }
267 
268     @Deprecated
269     @Override
270     public Iterator<Map.Entry<String, String>> iterator() {
271         return HeadersUtils.iteratorAsString(headers);
272     }
273 
274     @Override
275     public Iterator<Entry<CharSequence, CharSequence>> iteratorCharSequence() {
276         return headers.iterator();
277     }
278 
279     @Override
280     public Iterator<String> valueStringIterator(CharSequence name) {
281         final Iterator<CharSequence> itr = valueCharSequenceIterator(name);
282         return new Iterator<String>() {
283             @Override
284             public boolean hasNext() {
285                 return itr.hasNext();
286             }
287 
288             @Override
289             public String next() {
290                 return itr.next().toString();
291             }
292 
293             @Override
294             public void remove() {
295                 itr.remove();
296             }
297         };
298     }
299 
300     @Override
301     public Iterator<CharSequence> valueCharSequenceIterator(CharSequence name) {
302         return headers.valueIterator(name);
303     }
304 
305     @Override
306     public boolean contains(String name) {
307         return contains((CharSequence) name);
308     }
309 
310     @Override
311     public boolean contains(CharSequence name) {
312         return headers.contains(name);
313     }
314 
315     @Override
316     public boolean isEmpty() {
317         return headers.isEmpty();
318     }
319 
320     @Override
321     public int size() {
322         return headers.size();
323     }
324 
325     @Override
326     public boolean contains(String name, String value, boolean ignoreCase) {
327         return contains((CharSequence) name, (CharSequence) value, ignoreCase);
328     }
329 
330     @Override
331     public boolean contains(CharSequence name, CharSequence value, boolean ignoreCase) {
332         return headers.contains(name, value, ignoreCase ? CASE_INSENSITIVE_HASHER : CASE_SENSITIVE_HASHER);
333     }
334 
335     @Override
336     public Set<String> names() {
337         return HeadersUtils.namesAsString(headers);
338     }
339 
340     @Override
341     public boolean equals(Object o) {
342         return o instanceof DefaultHttpHeaders
343                 && headers.equals(((DefaultHttpHeaders) o).headers, CASE_SENSITIVE_HASHER);
344     }
345 
346     @Override
347     public int hashCode() {
348         return headers.hashCode(CASE_SENSITIVE_HASHER);
349     }
350 
351     @Override
352     public HttpHeaders copy() {
353         return new DefaultHttpHeaders(headers.copy());
354     }
355 
356     private static void validateHeaderNameElement(byte value) {
357         switch (value) {
358         case 0x00:
359         case '\t':
360         case '\n':
361         case 0x0b:
362         case '\f':
363         case '\r':
364         case ' ':
365         case ',':
366         case ':':
367         case ';':
368         case '=':
369             throw new IllegalArgumentException(
370                "a header name cannot contain the following prohibited characters: =,;: \\t\\r\\n\\v\\f: " +
371                        value);
372         default:
373             // Check to see if the character is not an ASCII character, or invalid
374             if (value < 0) {
375                 throw new IllegalArgumentException("a header name cannot contain non-ASCII character: " +
376                         value);
377             }
378         }
379     }
380 
381     private static void validateHeaderNameElement(char value) {
382         switch (value) {
383         case 0x00:
384         case '\t':
385         case '\n':
386         case 0x0b:
387         case '\f':
388         case '\r':
389         case ' ':
390         case ',':
391         case ':':
392         case ';':
393         case '=':
394             throw new IllegalArgumentException(
395                "a header name cannot contain the following prohibited characters: =,;: \\t\\r\\n\\v\\f: " +
396                        value);
397         default:
398             // Check to see if the character is not an ASCII character, or invalid
399             if (value > 127) {
400                 throw new IllegalArgumentException("a header name cannot contain non-ASCII character: " +
401                         value);
402             }
403         }
404     }
405 
406     static ValueConverter<CharSequence> valueConverter(boolean validate) {
407         return validate ? HeaderValueConverterAndValidator.INSTANCE : HeaderValueConverter.INSTANCE;
408     }
409 
410     @SuppressWarnings("unchecked")
411     static NameValidator<CharSequence> nameValidator(boolean validate) {
412         return validate ? HttpNameValidator : NameValidator.NOT_NULL;
413     }
414 
415     private static class HeaderValueConverter extends CharSequenceValueConverter {
416         static final HeaderValueConverter INSTANCE = new HeaderValueConverter();
417 
418         @Override
419         public CharSequence convertObject(Object value) {
420             if (value instanceof CharSequence) {
421                 return (CharSequence) value;
422             }
423             if (value instanceof Date) {
424                 return DateFormatter.format((Date) value);
425             }
426             if (value instanceof Calendar) {
427                 return DateFormatter.format(((Calendar) value).getTime());
428             }
429             return value.toString();
430         }
431     }
432 
433     private static final class HeaderValueConverterAndValidator extends HeaderValueConverter {
434         static final HeaderValueConverterAndValidator INSTANCE = new HeaderValueConverterAndValidator();
435 
436         @Override
437         public CharSequence convertObject(Object value) {
438             CharSequence seq = super.convertObject(value);
439             int state = 0;
440             // Start looping through each of the character
441             for (int index = 0; index < seq.length(); index++) {
442                 state = validateValueChar(seq, state, seq.charAt(index));
443             }
444 
445             if (state != 0) {
446                 throw new IllegalArgumentException("a header value must not end with '\\r' or '\\n':" + seq);
447             }
448             return seq;
449         }
450 
451         private static int validateValueChar(CharSequence seq, int state, char character) {
452             /*
453              * State:
454              * 0: Previous character was neither CR nor LF
455              * 1: The previous character was CR
456              * 2: The previous character was LF
457              */
458             if ((character & HIGHEST_INVALID_VALUE_CHAR_MASK) == 0) {
459                 // Check the absolutely prohibited characters.
460                 switch (character) {
461                 case 0x0: // NULL
462                     throw new IllegalArgumentException("a header value contains a prohibited character '\0': " + seq);
463                 case 0x0b: // Vertical tab
464                     throw new IllegalArgumentException("a header value contains a prohibited character '\\v': " + seq);
465                 case '\f':
466                     throw new IllegalArgumentException("a header value contains a prohibited character '\\f': " + seq);
467                 }
468             }
469 
470             // Check the CRLF (HT | SP) pattern
471             switch (state) {
472                 case 0:
473                     switch (character) {
474                         case '\r':
475                             return 1;
476                         case '\n':
477                             return 2;
478                     }
479                     break;
480                 case 1:
481                     switch (character) {
482                         case '\n':
483                             return 2;
484                         default:
485                             throw new IllegalArgumentException("only '\\n' is allowed after '\\r': " + seq);
486                     }
487                 case 2:
488                     switch (character) {
489                         case '\t':
490                         case ' ':
491                             return 0;
492                         default:
493                             throw new IllegalArgumentException("only ' ' and '\\t' are allowed after '\\n': " + seq);
494                     }
495             }
496             return state;
497         }
498     }
499 }