1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.handler.codec.http;
17
18 import java.net.InetSocketAddress;
19 import java.net.URI;
20 import java.nio.charset.Charset;
21 import java.nio.charset.IllegalCharsetNameException;
22 import java.nio.charset.UnsupportedCharsetException;
23 import java.util.ArrayList;
24 import java.util.Iterator;
25 import java.util.List;
26
27 import io.netty.util.AsciiString;
28 import io.netty.util.CharsetUtil;
29 import io.netty.util.NetUtil;
30 import io.netty.util.internal.ObjectUtil;
31
32 import static io.netty.util.internal.StringUtil.COMMA;
33 import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
34
35
36
37
38 public final class HttpUtil {
39
40 private static final AsciiString CHARSET_EQUALS = AsciiString.of(HttpHeaderValues.CHARSET + "=");
41 private static final AsciiString SEMICOLON = AsciiString.cached(";");
42 private static final String COMMA_STRING = String.valueOf(COMMA);
43
44 private HttpUtil() { }
45
46
47
48
49
50 public static boolean isOriginForm(URI uri) {
51 return isOriginForm(uri.toString());
52 }
53
54
55
56
57
58 public static boolean isOriginForm(String uri) {
59 return uri.startsWith("/");
60 }
61
62
63
64
65
66 public static boolean isAsteriskForm(URI uri) {
67 return isAsteriskForm(uri.toString());
68 }
69
70
71
72
73
74 public static boolean isAsteriskForm(String uri) {
75 return "*".equals(uri);
76 }
77
78
79
80
81
82
83
84
85 public static boolean isKeepAlive(HttpMessage message) {
86 return !message.headers().containsValue(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE, true) &&
87 (message.protocolVersion().isKeepAliveDefault() ||
88 message.headers().containsValue(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE, true));
89 }
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111 public static void setKeepAlive(HttpMessage message, boolean keepAlive) {
112 setKeepAlive(message.headers(), message.protocolVersion(), keepAlive);
113 }
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134 public static void setKeepAlive(HttpHeaders h, HttpVersion httpVersion, boolean keepAlive) {
135 if (httpVersion.isKeepAliveDefault()) {
136 if (keepAlive) {
137 h.remove(HttpHeaderNames.CONNECTION);
138 } else {
139 h.set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
140 }
141 } else {
142 if (keepAlive) {
143 h.set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
144 } else {
145 h.remove(HttpHeaderNames.CONNECTION);
146 }
147 }
148 }
149
150
151
152
153
154
155
156
157
158
159
160
161
162 public static long getContentLength(HttpMessage message) {
163 String value = message.headers().get(HttpHeaderNames.CONTENT_LENGTH);
164 if (value != null) {
165 return Long.parseLong(value);
166 }
167
168
169
170 long webSocketContentLength = getWebSocketContentLength(message);
171 if (webSocketContentLength >= 0) {
172 return webSocketContentLength;
173 }
174
175
176 throw new NumberFormatException("header not found: " + HttpHeaderNames.CONTENT_LENGTH);
177 }
178
179
180
181
182
183
184
185
186
187
188
189 public static long getContentLength(HttpMessage message, long defaultValue) {
190 String value = message.headers().get(HttpHeaderNames.CONTENT_LENGTH);
191 if (value != null) {
192 return Long.parseLong(value);
193 }
194
195
196
197 long webSocketContentLength = getWebSocketContentLength(message);
198 if (webSocketContentLength >= 0) {
199 return webSocketContentLength;
200 }
201
202
203 return defaultValue;
204 }
205
206
207
208
209
210
211
212
213
214 public static int getContentLength(HttpMessage message, int defaultValue) {
215 return (int) Math.min(Integer.MAX_VALUE, getContentLength(message, (long) defaultValue));
216 }
217
218
219
220
221
222 static int getWebSocketContentLength(HttpMessage message) {
223
224 HttpHeaders h = message.headers();
225 if (message instanceof HttpRequest) {
226 HttpRequest req = (HttpRequest) message;
227 if (HttpMethod.GET.equals(req.method()) &&
228 h.contains(HttpHeaderNames.SEC_WEBSOCKET_KEY1) &&
229 h.contains(HttpHeaderNames.SEC_WEBSOCKET_KEY2)) {
230 return 8;
231 }
232 } else if (message instanceof HttpResponse) {
233 HttpResponse res = (HttpResponse) message;
234 if (res.status().code() == 101 &&
235 h.contains(HttpHeaderNames.SEC_WEBSOCKET_ORIGIN) &&
236 h.contains(HttpHeaderNames.SEC_WEBSOCKET_LOCATION)) {
237 return 16;
238 }
239 }
240
241
242 return -1;
243 }
244
245
246
247
248 public static void setContentLength(HttpMessage message, long length) {
249 message.headers().set(HttpHeaderNames.CONTENT_LENGTH, length);
250 }
251
252 public static boolean isContentLengthSet(HttpMessage m) {
253 return m.headers().contains(HttpHeaderNames.CONTENT_LENGTH);
254 }
255
256
257
258
259
260
261
262
263
264
265 public static boolean is100ContinueExpected(HttpMessage message) {
266 return isExpectHeaderValid(message)
267
268 && message.headers().contains(HttpHeaderNames.EXPECT, HttpHeaderValues.CONTINUE, true);
269 }
270
271
272
273
274
275
276
277
278
279 static boolean isUnsupportedExpectation(HttpMessage message) {
280 if (!isExpectHeaderValid(message)) {
281 return false;
282 }
283
284 final String expectValue = message.headers().get(HttpHeaderNames.EXPECT);
285 return expectValue != null && !HttpHeaderValues.CONTINUE.toString().equalsIgnoreCase(expectValue);
286 }
287
288 private static boolean isExpectHeaderValid(final HttpMessage message) {
289
290
291
292
293
294 return message instanceof HttpRequest &&
295 message.protocolVersion().compareTo(HttpVersion.HTTP_1_1) >= 0;
296 }
297
298
299
300
301
302
303
304
305 public static void set100ContinueExpected(HttpMessage message, boolean expected) {
306 if (expected) {
307 message.headers().set(HttpHeaderNames.EXPECT, HttpHeaderValues.CONTINUE);
308 } else {
309 message.headers().remove(HttpHeaderNames.EXPECT);
310 }
311 }
312
313
314
315
316
317
318
319 public static boolean isTransferEncodingChunked(HttpMessage message) {
320 return message.headers().containsValue(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED, true);
321 }
322
323
324
325
326
327
328
329
330
331 public static void setTransferEncodingChunked(HttpMessage m, boolean chunked) {
332 if (chunked) {
333 m.headers().set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED);
334 m.headers().remove(HttpHeaderNames.CONTENT_LENGTH);
335 } else {
336 List<String> encodings = m.headers().getAll(HttpHeaderNames.TRANSFER_ENCODING);
337 if (encodings.isEmpty()) {
338 return;
339 }
340 List<CharSequence> values = new ArrayList<CharSequence>(encodings);
341 Iterator<CharSequence> valuesIt = values.iterator();
342 while (valuesIt.hasNext()) {
343 CharSequence value = valuesIt.next();
344 if (HttpHeaderValues.CHUNKED.contentEqualsIgnoreCase(value)) {
345 valuesIt.remove();
346 }
347 }
348 if (values.isEmpty()) {
349 m.headers().remove(HttpHeaderNames.TRANSFER_ENCODING);
350 } else {
351 m.headers().set(HttpHeaderNames.TRANSFER_ENCODING, values);
352 }
353 }
354 }
355
356
357
358
359
360
361
362
363 public static Charset getCharset(HttpMessage message) {
364 return getCharset(message, CharsetUtil.ISO_8859_1);
365 }
366
367
368
369
370
371
372
373
374 public static Charset getCharset(CharSequence contentTypeValue) {
375 if (contentTypeValue != null) {
376 return getCharset(contentTypeValue, CharsetUtil.ISO_8859_1);
377 } else {
378 return CharsetUtil.ISO_8859_1;
379 }
380 }
381
382
383
384
385
386
387
388
389
390 public static Charset getCharset(HttpMessage message, Charset defaultCharset) {
391 CharSequence contentTypeValue = message.headers().get(HttpHeaderNames.CONTENT_TYPE);
392 if (contentTypeValue != null) {
393 return getCharset(contentTypeValue, defaultCharset);
394 } else {
395 return defaultCharset;
396 }
397 }
398
399
400
401
402
403
404
405
406
407 public static Charset getCharset(CharSequence contentTypeValue, Charset defaultCharset) {
408 if (contentTypeValue != null) {
409 CharSequence charsetRaw = getCharsetAsSequence(contentTypeValue);
410 if (charsetRaw != null) {
411 if (charsetRaw.length() > 2) {
412 if (charsetRaw.charAt(0) == '"' && charsetRaw.charAt(charsetRaw.length() - 1) == '"') {
413 charsetRaw = charsetRaw.subSequence(1, charsetRaw.length() - 1);
414 }
415 }
416 try {
417 return Charset.forName(charsetRaw.toString());
418 } catch (IllegalCharsetNameException ignored) {
419
420 } catch (UnsupportedCharsetException ignored) {
421
422 }
423 }
424 }
425 return defaultCharset;
426 }
427
428
429
430
431
432
433
434
435
436
437
438
439 @Deprecated
440 public static CharSequence getCharsetAsString(HttpMessage message) {
441 return getCharsetAsSequence(message);
442 }
443
444
445
446
447
448
449
450
451
452
453 public static CharSequence getCharsetAsSequence(HttpMessage message) {
454 CharSequence contentTypeValue = message.headers().get(HttpHeaderNames.CONTENT_TYPE);
455 if (contentTypeValue != null) {
456 return getCharsetAsSequence(contentTypeValue);
457 } else {
458 return null;
459 }
460 }
461
462
463
464
465
466
467
468
469
470
471
472
473 public static CharSequence getCharsetAsSequence(CharSequence contentTypeValue) {
474 ObjectUtil.checkNotNull(contentTypeValue, "contentTypeValue");
475
476 int indexOfCharset = AsciiString.indexOfIgnoreCaseAscii(contentTypeValue, CHARSET_EQUALS, 0);
477 if (indexOfCharset == AsciiString.INDEX_NOT_FOUND) {
478 return null;
479 }
480
481 int indexOfEncoding = indexOfCharset + CHARSET_EQUALS.length();
482 if (indexOfEncoding < contentTypeValue.length()) {
483 CharSequence charsetCandidate = contentTypeValue.subSequence(indexOfEncoding, contentTypeValue.length());
484 int indexOfSemicolon = AsciiString.indexOfIgnoreCaseAscii(charsetCandidate, SEMICOLON, 0);
485 if (indexOfSemicolon == AsciiString.INDEX_NOT_FOUND) {
486 return charsetCandidate;
487 }
488
489 return charsetCandidate.subSequence(0, indexOfSemicolon);
490 }
491
492 return null;
493 }
494
495
496
497
498
499
500
501
502
503
504
505
506 public static CharSequence getMimeType(HttpMessage message) {
507 CharSequence contentTypeValue = message.headers().get(HttpHeaderNames.CONTENT_TYPE);
508 if (contentTypeValue != null) {
509 return getMimeType(contentTypeValue);
510 } else {
511 return null;
512 }
513 }
514
515
516
517
518
519
520
521
522
523
524
525
526
527 public static CharSequence getMimeType(CharSequence contentTypeValue) {
528 ObjectUtil.checkNotNull(contentTypeValue, "contentTypeValue");
529
530 int indexOfSemicolon = AsciiString.indexOfIgnoreCaseAscii(contentTypeValue, SEMICOLON, 0);
531 if (indexOfSemicolon != AsciiString.INDEX_NOT_FOUND) {
532 return contentTypeValue.subSequence(0, indexOfSemicolon);
533 } else {
534 return contentTypeValue.length() > 0 ? contentTypeValue : null;
535 }
536 }
537
538
539
540
541
542
543
544
545 public static String formatHostnameForHttp(InetSocketAddress addr) {
546 String hostString = NetUtil.getHostname(addr);
547 if (NetUtil.isValidIpV6Address(hostString)) {
548 if (!addr.isUnresolved()) {
549 hostString = NetUtil.toAddressString(addr.getAddress());
550 } else if (hostString.charAt(0) == '[' && hostString.charAt(hostString.length() - 1) == ']') {
551
552 return hostString;
553 }
554
555 return '[' + hostString + ']';
556 }
557 return hostString;
558 }
559
560
561
562
563
564
565
566
567
568
569
570 public static long normalizeAndGetContentLength(
571 List<? extends CharSequence> contentLengthFields, boolean isHttp10OrEarlier,
572 boolean allowDuplicateContentLengths) {
573 if (contentLengthFields.isEmpty()) {
574 return -1;
575 }
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590 String firstField = contentLengthFields.get(0).toString();
591 boolean multipleContentLengths =
592 contentLengthFields.size() > 1 || firstField.indexOf(COMMA) >= 0;
593
594 if (multipleContentLengths && !isHttp10OrEarlier) {
595 if (allowDuplicateContentLengths) {
596
597 String firstValue = null;
598 for (CharSequence field : contentLengthFields) {
599 String[] tokens = field.toString().split(COMMA_STRING, -1);
600 for (String token : tokens) {
601 String trimmed = token.trim();
602 if (firstValue == null) {
603 firstValue = trimmed;
604 } else if (!trimmed.equals(firstValue)) {
605 throw new IllegalArgumentException(
606 "Multiple Content-Length values found: " + contentLengthFields);
607 }
608 }
609 }
610
611 firstField = firstValue;
612 } else {
613
614 throw new IllegalArgumentException(
615 "Multiple Content-Length values found: " + contentLengthFields);
616 }
617 }
618
619
620 if (firstField.isEmpty() || !Character.isDigit(firstField.charAt(0))) {
621
622 throw new IllegalArgumentException(
623 "Content-Length value is not a number: " + firstField);
624 }
625 try {
626 final long value = Long.parseLong(firstField);
627 return checkPositiveOrZero(value, "Content-Length value");
628 } catch (NumberFormatException e) {
629
630 throw new IllegalArgumentException(
631 "Content-Length value is not a number: " + firstField, e);
632 }
633 }
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648 static int validateToken(CharSequence token) {
649 if (token instanceof AsciiString) {
650 return validateAsciiStringToken((AsciiString) token);
651 }
652 return validateCharSequenceToken(token);
653 }
654
655
656
657
658
659
660
661 private static int validateAsciiStringToken(AsciiString token) {
662 byte[] array = token.array();
663 for (int i = token.arrayOffset(), len = token.arrayOffset() + token.length(); i < len; i++) {
664 if (!BitSet128.contains(array[i], TOKEN_CHARS_HIGH, TOKEN_CHARS_LOW)) {
665 return i - token.arrayOffset();
666 }
667 }
668 return -1;
669 }
670
671
672
673
674
675
676
677 private static int validateCharSequenceToken(CharSequence token) {
678 for (int i = 0, len = token.length(); i < len; i++) {
679 byte value = (byte) token.charAt(i);
680 if (!BitSet128.contains(value, TOKEN_CHARS_HIGH, TOKEN_CHARS_LOW)) {
681 return i;
682 }
683 }
684 return -1;
685 }
686
687 private static final long TOKEN_CHARS_HIGH;
688 private static final long TOKEN_CHARS_LOW;
689 static {
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716 BitSet128 tokenChars = new BitSet128()
717 .range('0', '9').range('a', 'z').range('A', 'Z')
718 .bits('-', '.', '_', '~')
719 .bits('!', '#', '$', '%', '&', '\'', '*', '+', '^', '`', '|');
720 TOKEN_CHARS_HIGH = tokenChars.high();
721 TOKEN_CHARS_LOW = tokenChars.low();
722 }
723
724 private static final class BitSet128 {
725 private long high;
726 private long low;
727
728 BitSet128 range(char fromInc, char toInc) {
729 for (int bit = fromInc; bit <= toInc; bit++) {
730 if (bit < 64) {
731 low |= 1L << bit;
732 } else {
733 high |= 1L << bit - 64;
734 }
735 }
736 return this;
737 }
738
739 BitSet128 bits(char... bits) {
740 for (char bit : bits) {
741 if (bit < 64) {
742 low |= 1L << bit;
743 } else {
744 high |= 1L << bit - 64;
745 }
746 }
747 return this;
748 }
749
750 long high() {
751 return high;
752 }
753
754 long low() {
755 return low;
756 }
757
758 static boolean contains(byte bit, long high, long low) {
759 if (bit < 0) {
760 return false;
761 }
762 if (bit < 64) {
763 return 0 != (low & 1L << bit);
764 }
765 return 0 != (high & 1L << bit - 64);
766 }
767 }
768 }