1
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
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
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
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
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
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
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
469 if ((character & HIGHEST_INVALID_VALUE_CHAR_MASK) == 0) {
470
471 switch (character) {
472 case 0x0:
473 throw new IllegalArgumentException("a header value contains a prohibited character '\0': " + seq);
474 case 0x0b:
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
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