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.AsciiString;
19  import io.netty.handler.codec.DefaultTextHeaders;
20  import io.netty.handler.codec.TextHeaders;
21  
22  import java.util.Calendar;
23  import java.util.Date;
24  
25  public class DefaultHttpHeaders extends DefaultTextHeaders implements HttpHeaders {
26  
27      private static final int HIGHEST_INVALID_NAME_CHAR_MASK = ~63;
28      private static final int HIGHEST_INVALID_VALUE_CHAR_MASK = ~15;
29  
30      /**
31       * A look-up table used for checking if a character in a header name is prohibited.
32       */
33      private static final byte[] LOOKUP_TABLE = new byte[~HIGHEST_INVALID_NAME_CHAR_MASK + 1];
34  
35      static {
36          LOOKUP_TABLE['\t'] = -1;
37          LOOKUP_TABLE['\n'] = -1;
38          LOOKUP_TABLE[0x0b] = -1;
39          LOOKUP_TABLE['\f'] = -1;
40          LOOKUP_TABLE[' '] = -1;
41          LOOKUP_TABLE[','] = -1;
42          LOOKUP_TABLE[':'] = -1;
43          LOOKUP_TABLE[';'] = -1;
44          LOOKUP_TABLE['='] = -1;
45      }
46  
47      private static final class HttpHeadersValidationConverter extends DefaultTextValueTypeConverter {
48          private final boolean validate;
49  
50          HttpHeadersValidationConverter(boolean validate) {
51              this.validate = validate;
52          }
53  
54          @Override
55          public CharSequence convertObject(Object value) {
56              if (value == null) {
57                  throw new NullPointerException("value");
58              }
59  
60              CharSequence seq;
61              if (value instanceof CharSequence) {
62                  seq = (CharSequence) value;
63              } else if (value instanceof Number) {
64                  seq = value.toString();
65              } else if (value instanceof Date) {
66                  seq = HttpHeaderDateFormat.get().format((Date) value);
67              } else if (value instanceof Calendar) {
68                  seq = HttpHeaderDateFormat.get().format(((Calendar) value).getTime());
69              } else {
70                  seq = value.toString();
71              }
72  
73              if (validate) {
74                  if (value instanceof AsciiString) {
75                      validateValue((AsciiString) seq);
76                  } else {
77                      validateValue(seq);
78                  }
79              }
80  
81              return seq;
82          }
83  
84          private static void validateValue(AsciiString seq) {
85              int state = 0;
86              // Start looping through each of the character
87              final int start = seq.arrayOffset();
88              final int end = start + seq.length();
89              final byte[] array = seq.array();
90              for (int index = start; index < end; index++) {
91                  state = validateValueChar(seq, state, (char) (array[index] & 0xFF));
92              }
93  
94              if (state != 0) {
95                  throw new IllegalArgumentException("a header value must not end with '\\r' or '\\n':" + seq);
96              }
97          }
98  
99          private static void validateValue(CharSequence seq) {
100             int state = 0;
101             // Start looping through each of the character
102             for (int index = 0; index < seq.length(); index++) {
103                 state = validateValueChar(seq, state, seq.charAt(index));
104             }
105 
106             if (state != 0) {
107                 throw new IllegalArgumentException("a header value must not end with '\\r' or '\\n':" + seq);
108             }
109         }
110 
111         private static int validateValueChar(CharSequence seq, int state, char character) {
112             /*
113              * State:
114              * 0: Previous character was neither CR nor LF
115              * 1: The previous character was CR
116              * 2: The previous character was LF
117              */
118             if ((character & HIGHEST_INVALID_VALUE_CHAR_MASK) == 0) {
119                 // Check the absolutely prohibited characters.
120                 switch (character) {
121                 case 0x0: // NULL
122                     throw new IllegalArgumentException("a header value contains a prohibited character '\0': " + seq);
123                 case 0x0b: // Vertical tab
124                     throw new IllegalArgumentException("a header value contains a prohibited character '\\v': " + seq);
125                 case '\f':
126                     throw new IllegalArgumentException("a header value contains a prohibited character '\\f': " + seq);
127                 }
128             }
129 
130             // Check the CRLF (HT | SP) pattern
131             switch (state) {
132             case 0:
133                 switch (character) {
134                 case '\r':
135                     state = 1;
136                     break;
137                 case '\n':
138                     state = 2;
139                     break;
140                 }
141                 break;
142             case 1:
143                 switch (character) {
144                 case '\n':
145                     state = 2;
146                     break;
147                 default:
148                     throw new IllegalArgumentException("only '\\n' is allowed after '\\r': " + seq);
149                 }
150                 break;
151             case 2:
152                 switch (character) {
153                 case '\t':
154                 case ' ':
155                     state = 0;
156                     break;
157                 default:
158                     throw new IllegalArgumentException("only ' ' and '\\t' are allowed after '\\n': " + seq);
159                 }
160             }
161             return state;
162         }
163     }
164 
165     static class HttpHeadersNameConverter implements NameConverter<CharSequence> {
166         protected final boolean validate;
167 
168         HttpHeadersNameConverter(boolean validate) {
169             this.validate = validate;
170         }
171 
172         @Override
173         public CharSequence convertName(CharSequence name) {
174             if (validate) {
175                 if (name instanceof AsciiString) {
176                     validateName((AsciiString) name);
177                 } else {
178                     validateName(name);
179                 }
180             }
181 
182             return name;
183         }
184 
185         private static void validateName(AsciiString name) {
186             // Go through each characters in the name
187             final int start = name.arrayOffset();
188             final int end = start + name.length();
189             final byte[] array = name.array();
190             for (int index = start; index < end; index ++) {
191                 byte b = array[index];
192 
193                 // Check to see if the character is not an ASCII character
194                 if (b < 0) {
195                     throw new IllegalArgumentException("a header name cannot contain non-ASCII characters: " + name);
196                 }
197 
198                 // Check for prohibited characters.
199                 validateNameChar(name, b);
200             }
201         }
202 
203         private static void validateName(CharSequence name) {
204             // Go through each characters in the name
205             for (int index = 0; index < name.length(); index++) {
206                 char character = name.charAt(index);
207 
208                 // Check to see if the character is not an ASCII character
209                 if (character > 127) {
210                     throw new IllegalArgumentException("a header name cannot contain non-ASCII characters: " + name);
211                 }
212 
213                 // Check for prohibited characters.
214                 validateNameChar(name, character);
215             }
216         }
217 
218         private static void validateNameChar(CharSequence name, int character) {
219             if ((character & HIGHEST_INVALID_NAME_CHAR_MASK) == 0 && LOOKUP_TABLE[character] != 0) {
220                 throw new IllegalArgumentException(
221                         "a header name cannot contain the following prohibited characters: =,;: \\t\\r\\n\\v\\f: " +
222                         name);
223             }
224         }
225     }
226 
227     private static final HttpHeadersValidationConverter
228         VALIDATE_OBJECT_CONVERTER = new HttpHeadersValidationConverter(true);
229     private static final HttpHeadersValidationConverter
230         NO_VALIDATE_OBJECT_CONVERTER = new HttpHeadersValidationConverter(false);
231     private static final HttpHeadersNameConverter VALIDATE_NAME_CONVERTER = new HttpHeadersNameConverter(true);
232     private static final HttpHeadersNameConverter NO_VALIDATE_NAME_CONVERTER = new HttpHeadersNameConverter(false);
233 
234     public DefaultHttpHeaders() {
235         this(true);
236     }
237 
238     public DefaultHttpHeaders(boolean validate) {
239         this(true, validate? VALIDATE_NAME_CONVERTER : NO_VALIDATE_NAME_CONVERTER, false);
240     }
241 
242     protected DefaultHttpHeaders(boolean validate, boolean singleHeaderFields) {
243         this(true, validate? VALIDATE_NAME_CONVERTER : NO_VALIDATE_NAME_CONVERTER, singleHeaderFields);
244     }
245 
246     protected DefaultHttpHeaders(boolean validate, NameConverter<CharSequence> nameConverter,
247                                  boolean singleHeaderFields) {
248         super(true, validate ? VALIDATE_OBJECT_CONVERTER : NO_VALIDATE_OBJECT_CONVERTER, nameConverter,
249                 singleHeaderFields);
250     }
251 
252     @Override
253     public HttpHeaders add(CharSequence name, CharSequence value) {
254         super.add(name, value);
255         return this;
256     }
257 
258     @Override
259     public HttpHeaders add(CharSequence name, Iterable<? extends CharSequence> values) {
260         super.add(name, values);
261         return this;
262     }
263 
264     @Override
265     public HttpHeaders add(CharSequence name, CharSequence... values) {
266         super.add(name, values);
267         return this;
268     }
269 
270     @Override
271     public HttpHeaders addObject(CharSequence name, Object value) {
272         super.addObject(name, value);
273         return this;
274     }
275 
276     @Override
277     public HttpHeaders addObject(CharSequence name, Iterable<?> values) {
278         super.addObject(name, values);
279         return this;
280     }
281 
282     @Override
283     public HttpHeaders addObject(CharSequence name, Object... values) {
284         super.addObject(name, values);
285         return this;
286     }
287 
288     @Override
289     public HttpHeaders addBoolean(CharSequence name, boolean value) {
290         super.addBoolean(name, value);
291         return this;
292     }
293 
294     @Override
295     public HttpHeaders addChar(CharSequence name, char value) {
296         super.addChar(name, value);
297         return this;
298     }
299 
300     @Override
301     public HttpHeaders addByte(CharSequence name, byte value) {
302         super.addByte(name, value);
303         return this;
304     }
305 
306     @Override
307     public HttpHeaders addShort(CharSequence name, short value) {
308         super.addShort(name, value);
309         return this;
310     }
311 
312     @Override
313     public HttpHeaders addInt(CharSequence name, int value) {
314         super.addInt(name, value);
315         return this;
316     }
317 
318     @Override
319     public HttpHeaders addLong(CharSequence name, long value) {
320         super.addLong(name, value);
321         return this;
322     }
323 
324     @Override
325     public HttpHeaders addFloat(CharSequence name, float value) {
326         super.addFloat(name, value);
327         return this;
328     }
329 
330     @Override
331     public HttpHeaders addDouble(CharSequence name, double value) {
332         super.addDouble(name, value);
333         return this;
334     }
335 
336     @Override
337     public HttpHeaders addTimeMillis(CharSequence name, long value) {
338         super.addTimeMillis(name, value);
339         return this;
340     }
341 
342     @Override
343     public HttpHeaders add(TextHeaders headers) {
344         super.add(headers);
345         return this;
346     }
347 
348     @Override
349     public HttpHeaders set(CharSequence name, CharSequence value) {
350         super.set(name, value);
351         return this;
352     }
353 
354     @Override
355     public HttpHeaders set(CharSequence name, Iterable<? extends CharSequence> values) {
356         super.set(name, values);
357         return this;
358     }
359 
360     @Override
361     public HttpHeaders set(CharSequence name, CharSequence... values) {
362         super.set(name, values);
363         return this;
364     }
365 
366     @Override
367     public HttpHeaders setObject(CharSequence name, Object value) {
368         super.setObject(name, value);
369         return this;
370     }
371 
372     @Override
373     public HttpHeaders setObject(CharSequence name, Iterable<?> values) {
374         super.setObject(name, values);
375         return this;
376     }
377 
378     @Override
379     public HttpHeaders setObject(CharSequence name, Object... values) {
380         super.setObject(name, values);
381         return this;
382     }
383 
384     @Override
385     public HttpHeaders setBoolean(CharSequence name, boolean value) {
386         super.setBoolean(name, value);
387         return this;
388     }
389 
390     @Override
391     public HttpHeaders setChar(CharSequence name, char value) {
392         super.setChar(name, value);
393         return this;
394     }
395 
396     @Override
397     public HttpHeaders setByte(CharSequence name, byte value) {
398         super.setByte(name, value);
399         return this;
400     }
401 
402     @Override
403     public HttpHeaders setShort(CharSequence name, short value) {
404         super.setShort(name, value);
405         return this;
406     }
407 
408     @Override
409     public HttpHeaders setInt(CharSequence name, int value) {
410         super.setInt(name, value);
411         return this;
412     }
413 
414     @Override
415     public HttpHeaders setLong(CharSequence name, long value) {
416         super.setLong(name, value);
417         return this;
418     }
419 
420     @Override
421     public HttpHeaders setFloat(CharSequence name, float value) {
422         super.setFloat(name, value);
423         return this;
424     }
425 
426     @Override
427     public HttpHeaders setDouble(CharSequence name, double value) {
428         super.setDouble(name, value);
429         return this;
430     }
431 
432     @Override
433     public HttpHeaders setTimeMillis(CharSequence name, long value) {
434         super.setTimeMillis(name, value);
435         return this;
436     }
437 
438     @Override
439     public HttpHeaders set(TextHeaders headers) {
440         super.set(headers);
441         return this;
442     }
443 
444     @Override
445     public HttpHeaders setAll(TextHeaders headers) {
446         super.setAll(headers);
447         return this;
448     }
449 
450     @Override
451     public HttpHeaders clear() {
452         super.clear();
453         return this;
454     }
455 }