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