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     private static void validateHeaderNameElement(byte value) {
352         switch (value) {
353         case 0x00:
354         case '\t':
355         case '\n':
356         case 0x0b:
357         case '\f':
358         case '\r':
359         case ' ':
360         case ',':
361         case ':':
362         case ';':
363         case '=':
364             throw new IllegalArgumentException(
365                "a header name cannot contain the following prohibited characters: =,;: \\t\\r\\n\\v\\f: " +
366                        value);
367         default:
368             // Check to see if the character is not an ASCII character, or invalid
369             if (value < 0) {
370                 throw new IllegalArgumentException("a header name cannot contain non-ASCII character: " +
371                         value);
372             }
373         }
374     }
375 
376     private static void validateHeaderNameElement(char value) {
377         switch (value) {
378         case 0x00:
379         case '\t':
380         case '\n':
381         case 0x0b:
382         case '\f':
383         case '\r':
384         case ' ':
385         case ',':
386         case ':':
387         case ';':
388         case '=':
389             throw new IllegalArgumentException(
390                "a header name cannot contain the following prohibited characters: =,;: \\t\\r\\n\\v\\f: " +
391                        value);
392         default:
393             // Check to see if the character is not an ASCII character, or invalid
394             if (value > 127) {
395                 throw new IllegalArgumentException("a header name cannot contain non-ASCII character: " +
396                         value);
397             }
398         }
399     }
400 
401     static ValueConverter<CharSequence> valueConverter(boolean validate) {
402         return validate ? HeaderValueConverterAndValidator.INSTANCE : HeaderValueConverter.INSTANCE;
403     }
404 
405     @SuppressWarnings("unchecked")
406     static NameValidator<CharSequence> nameValidator(boolean validate) {
407         return validate ? HttpNameValidator : NameValidator.NOT_NULL;
408     }
409 
410     private static class HeaderValueConverter extends CharSequenceValueConverter {
411         static final HeaderValueConverter INSTANCE = new HeaderValueConverter();
412 
413         @Override
414         public CharSequence convertObject(Object value) {
415             if (value instanceof CharSequence) {
416                 return (CharSequence) value;
417             }
418             if (value instanceof Date) {
419                 return DateFormatter.format((Date) value);
420             }
421             if (value instanceof Calendar) {
422                 return DateFormatter.format(((Calendar) value).getTime());
423             }
424             return value.toString();
425         }
426     }
427 
428     private static final class HeaderValueConverterAndValidator extends HeaderValueConverter {
429         static final HeaderValueConverterAndValidator INSTANCE = new HeaderValueConverterAndValidator();
430 
431         @Override
432         public CharSequence convertObject(Object value) {
433             CharSequence seq = super.convertObject(value);
434             int state = 0;
435             // Start looping through each of the character
436             for (int index = 0; index < seq.length(); index++) {
437                 state = validateValueChar(seq, state, seq.charAt(index));
438             }
439 
440             if (state != 0) {
441                 throw new IllegalArgumentException("a header value must not end with '\\r' or '\\n':" + seq);
442             }
443             return seq;
444         }
445 
446         private static int validateValueChar(CharSequence seq, int state, char character) {
447             /*
448              * State:
449              * 0: Previous character was neither CR nor LF
450              * 1: The previous character was CR
451              * 2: The previous character was LF
452              */
453             if ((character & HIGHEST_INVALID_VALUE_CHAR_MASK) == 0) {
454                 // Check the absolutely prohibited characters.
455                 switch (character) {
456                 case 0x0: // NULL
457                     throw new IllegalArgumentException("a header value contains a prohibited character '\0': " + seq);
458                 case 0x0b: // Vertical tab
459                     throw new IllegalArgumentException("a header value contains a prohibited character '\\v': " + seq);
460                 case '\f':
461                     throw new IllegalArgumentException("a header value contains a prohibited character '\\f': " + seq);
462                 }
463             }
464 
465             // Check the CRLF (HT | SP) pattern
466             switch (state) {
467                 case 0:
468                     switch (character) {
469                         case '\r':
470                             return 1;
471                         case '\n':
472                             return 2;
473                     }
474                     break;
475                 case 1:
476                     switch (character) {
477                         case '\n':
478                             return 2;
479                         default:
480                             throw new IllegalArgumentException("only '\\n' is allowed after '\\r': " + seq);
481                     }
482                 case 2:
483                     switch (character) {
484                         case '\t':
485                         case ' ':
486                             return 0;
487                         default:
488                             throw new IllegalArgumentException("only ' ' and '\\t' are allowed after '\\n': " + seq);
489                     }
490             }
491             return state;
492         }
493     }
494 }