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 | UnsupportedCharsetException ignored) {
419
420 }
421 }
422 }
423 return defaultCharset;
424 }
425
426
427
428
429
430
431
432
433
434
435
436
437 @Deprecated
438 public static CharSequence getCharsetAsString(HttpMessage message) {
439 return getCharsetAsSequence(message);
440 }
441
442
443
444
445
446
447
448
449
450
451 public static CharSequence getCharsetAsSequence(HttpMessage message) {
452 CharSequence contentTypeValue = message.headers().get(HttpHeaderNames.CONTENT_TYPE);
453 if (contentTypeValue != null) {
454 return getCharsetAsSequence(contentTypeValue);
455 } else {
456 return null;
457 }
458 }
459
460
461
462
463
464
465
466
467
468
469
470
471 public static CharSequence getCharsetAsSequence(CharSequence contentTypeValue) {
472 ObjectUtil.checkNotNull(contentTypeValue, "contentTypeValue");
473
474 int indexOfCharset = AsciiString.indexOfIgnoreCaseAscii(contentTypeValue, CHARSET_EQUALS, 0);
475 if (indexOfCharset == AsciiString.INDEX_NOT_FOUND) {
476 return null;
477 }
478
479 int indexOfEncoding = indexOfCharset + CHARSET_EQUALS.length();
480 if (indexOfEncoding < contentTypeValue.length()) {
481 CharSequence charsetCandidate = contentTypeValue.subSequence(indexOfEncoding, contentTypeValue.length());
482 int indexOfSemicolon = AsciiString.indexOfIgnoreCaseAscii(charsetCandidate, SEMICOLON, 0);
483 if (indexOfSemicolon == AsciiString.INDEX_NOT_FOUND) {
484 return charsetCandidate;
485 }
486
487 return charsetCandidate.subSequence(0, indexOfSemicolon);
488 }
489
490 return null;
491 }
492
493
494
495
496
497
498
499
500
501
502
503
504 public static CharSequence getMimeType(HttpMessage message) {
505 CharSequence contentTypeValue = message.headers().get(HttpHeaderNames.CONTENT_TYPE);
506 if (contentTypeValue != null) {
507 return getMimeType(contentTypeValue);
508 } else {
509 return null;
510 }
511 }
512
513
514
515
516
517
518
519
520
521
522
523
524
525 public static CharSequence getMimeType(CharSequence contentTypeValue) {
526 ObjectUtil.checkNotNull(contentTypeValue, "contentTypeValue");
527
528 int indexOfSemicolon = AsciiString.indexOfIgnoreCaseAscii(contentTypeValue, SEMICOLON, 0);
529 if (indexOfSemicolon != AsciiString.INDEX_NOT_FOUND) {
530 return contentTypeValue.subSequence(0, indexOfSemicolon);
531 } else {
532 return contentTypeValue.length() > 0 ? contentTypeValue : null;
533 }
534 }
535
536
537
538
539
540
541
542
543 public static String formatHostnameForHttp(InetSocketAddress addr) {
544 String hostString = NetUtil.getHostname(addr);
545 if (NetUtil.isValidIpV6Address(hostString)) {
546 if (!addr.isUnresolved()) {
547 hostString = NetUtil.toAddressString(addr.getAddress());
548 } else if (hostString.charAt(0) == '[' && hostString.charAt(hostString.length() - 1) == ']') {
549
550 return hostString;
551 }
552
553 return '[' + hostString + ']';
554 }
555 return hostString;
556 }
557
558
559
560
561
562
563
564
565
566
567
568 public static long normalizeAndGetContentLength(
569 List<? extends CharSequence> contentLengthFields, boolean isHttp10OrEarlier,
570 boolean allowDuplicateContentLengths) {
571 if (contentLengthFields.isEmpty()) {
572 return -1;
573 }
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588 String firstField = contentLengthFields.get(0).toString();
589 boolean multipleContentLengths =
590 contentLengthFields.size() > 1 || firstField.indexOf(COMMA) >= 0;
591
592 if (multipleContentLengths && !isHttp10OrEarlier) {
593 if (allowDuplicateContentLengths) {
594
595 String firstValue = null;
596 for (CharSequence field : contentLengthFields) {
597 String[] tokens = field.toString().split(COMMA_STRING, -1);
598 for (String token : tokens) {
599 String trimmed = token.trim();
600 if (firstValue == null) {
601 firstValue = trimmed;
602 } else if (!trimmed.equals(firstValue)) {
603 throw new IllegalArgumentException(
604 "Multiple Content-Length values found: " + contentLengthFields);
605 }
606 }
607 }
608
609 firstField = firstValue;
610 } else {
611
612 throw new IllegalArgumentException(
613 "Multiple Content-Length values found: " + contentLengthFields);
614 }
615 }
616
617
618 if (firstField.isEmpty() || !Character.isDigit(firstField.charAt(0))) {
619
620 throw new IllegalArgumentException(
621 "Content-Length value is not a number: " + firstField);
622 }
623 try {
624 final long value = Long.parseLong(firstField);
625 return checkPositiveOrZero(value, "Content-Length value");
626 } catch (NumberFormatException e) {
627
628 throw new IllegalArgumentException(
629 "Content-Length value is not a number: " + firstField, e);
630 }
631 }
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646 static int validateToken(CharSequence token) {
647 if (token instanceof AsciiString) {
648 return validateAsciiStringToken((AsciiString) token);
649 }
650 return validateCharSequenceToken(token);
651 }
652
653
654
655
656
657
658
659 private static int validateAsciiStringToken(AsciiString token) {
660 byte[] array = token.array();
661 for (int i = token.arrayOffset(), len = token.arrayOffset() + token.length(); i < len; i++) {
662 if (!BitSet128.contains(array[i], TOKEN_CHARS_HIGH, TOKEN_CHARS_LOW)) {
663 return i - token.arrayOffset();
664 }
665 }
666 return -1;
667 }
668
669
670
671
672
673
674
675 private static int validateCharSequenceToken(CharSequence token) {
676 for (int i = 0, len = token.length(); i < len; i++) {
677 byte value = (byte) token.charAt(i);
678 if (!BitSet128.contains(value, TOKEN_CHARS_HIGH, TOKEN_CHARS_LOW)) {
679 return i;
680 }
681 }
682 return -1;
683 }
684
685 private static final long TOKEN_CHARS_HIGH;
686 private static final long TOKEN_CHARS_LOW;
687 static {
688
689
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 BitSet128 tokenChars = new BitSet128()
715 .range('0', '9').range('a', 'z').range('A', 'Z')
716 .bits('-', '.', '_', '~')
717 .bits('!', '#', '$', '%', '&', '\'', '*', '+', '^', '`', '|');
718 TOKEN_CHARS_HIGH = tokenChars.high();
719 TOKEN_CHARS_LOW = tokenChars.low();
720 }
721
722 private static final class BitSet128 {
723 private long high;
724 private long low;
725
726 BitSet128 range(char fromInc, char toInc) {
727 for (int bit = fromInc; bit <= toInc; bit++) {
728 if (bit < 64) {
729 low |= 1L << bit;
730 } else {
731 high |= 1L << bit - 64;
732 }
733 }
734 return this;
735 }
736
737 BitSet128 bits(char... bits) {
738 for (char bit : bits) {
739 if (bit < 64) {
740 low |= 1L << bit;
741 } else {
742 high |= 1L << bit - 64;
743 }
744 }
745 return this;
746 }
747
748 long high() {
749 return high;
750 }
751
752 long low() {
753 return low;
754 }
755
756 static boolean contains(byte bit, long high, long low) {
757 if (bit < 0) {
758 return false;
759 }
760 if (bit < 64) {
761 return 0 != (low & 1L << bit);
762 }
763 return 0 != (high & 1L << bit - 64);
764 }
765 }
766 }