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 import io.netty.util.internal.UnstableApi;
32
33 import static io.netty.util.internal.StringUtil.COMMA;
34 import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
35
36
37
38
39 public final class HttpUtil {
40
41 private static final AsciiString CHARSET_EQUALS = AsciiString.of(HttpHeaderValues.CHARSET + "=");
42 private static final AsciiString SEMICOLON = AsciiString.cached(";");
43 private static final String COMMA_STRING = String.valueOf(COMMA);
44
45 private HttpUtil() { }
46
47
48
49
50
51 public static boolean isOriginForm(URI uri) {
52 return uri.getScheme() == null && uri.getSchemeSpecificPart() == null &&
53 uri.getHost() == null && uri.getAuthority() == null;
54 }
55
56
57
58
59
60 public static boolean isAsteriskForm(URI uri) {
61 return "*".equals(uri.getPath()) &&
62 uri.getScheme() == null && uri.getSchemeSpecificPart() == null &&
63 uri.getHost() == null && uri.getAuthority() == null && uri.getQuery() == null &&
64 uri.getFragment() == null;
65 }
66
67
68
69
70
71
72
73
74 public static boolean isKeepAlive(HttpMessage message) {
75 return !message.headers().containsValue(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE, true) &&
76 (message.protocolVersion().isKeepAliveDefault() ||
77 message.headers().containsValue(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE, true));
78 }
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100 public static void setKeepAlive(HttpMessage message, boolean keepAlive) {
101 setKeepAlive(message.headers(), message.protocolVersion(), keepAlive);
102 }
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123 public static void setKeepAlive(HttpHeaders h, HttpVersion httpVersion, boolean keepAlive) {
124 if (httpVersion.isKeepAliveDefault()) {
125 if (keepAlive) {
126 h.remove(HttpHeaderNames.CONNECTION);
127 } else {
128 h.set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
129 }
130 } else {
131 if (keepAlive) {
132 h.set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
133 } else {
134 h.remove(HttpHeaderNames.CONNECTION);
135 }
136 }
137 }
138
139
140
141
142
143
144
145
146
147
148
149
150
151 public static long getContentLength(HttpMessage message) {
152 String value = message.headers().get(HttpHeaderNames.CONTENT_LENGTH);
153 if (value != null) {
154 return Long.parseLong(value);
155 }
156
157
158
159 long webSocketContentLength = getWebSocketContentLength(message);
160 if (webSocketContentLength >= 0) {
161 return webSocketContentLength;
162 }
163
164
165 throw new NumberFormatException("header not found: " + HttpHeaderNames.CONTENT_LENGTH);
166 }
167
168
169
170
171
172
173
174
175
176
177
178 public static long getContentLength(HttpMessage message, long defaultValue) {
179 String value = message.headers().get(HttpHeaderNames.CONTENT_LENGTH);
180 if (value != null) {
181 return Long.parseLong(value);
182 }
183
184
185
186 long webSocketContentLength = getWebSocketContentLength(message);
187 if (webSocketContentLength >= 0) {
188 return webSocketContentLength;
189 }
190
191
192 return defaultValue;
193 }
194
195
196
197
198
199
200
201
202
203 public static int getContentLength(HttpMessage message, int defaultValue) {
204 return (int) Math.min(Integer.MAX_VALUE, getContentLength(message, (long) defaultValue));
205 }
206
207
208
209
210
211 private static int getWebSocketContentLength(HttpMessage message) {
212
213 HttpHeaders h = message.headers();
214 if (message instanceof HttpRequest) {
215 HttpRequest req = (HttpRequest) message;
216 if (HttpMethod.GET.equals(req.method()) &&
217 h.contains(HttpHeaderNames.SEC_WEBSOCKET_KEY1) &&
218 h.contains(HttpHeaderNames.SEC_WEBSOCKET_KEY2)) {
219 return 8;
220 }
221 } else if (message instanceof HttpResponse) {
222 HttpResponse res = (HttpResponse) message;
223 if (res.status().code() == 101 &&
224 h.contains(HttpHeaderNames.SEC_WEBSOCKET_ORIGIN) &&
225 h.contains(HttpHeaderNames.SEC_WEBSOCKET_LOCATION)) {
226 return 16;
227 }
228 }
229
230
231 return -1;
232 }
233
234
235
236
237 public static void setContentLength(HttpMessage message, long length) {
238 message.headers().set(HttpHeaderNames.CONTENT_LENGTH, length);
239 }
240
241 public static boolean isContentLengthSet(HttpMessage m) {
242 return m.headers().contains(HttpHeaderNames.CONTENT_LENGTH);
243 }
244
245
246
247
248
249
250
251
252
253
254 public static boolean is100ContinueExpected(HttpMessage message) {
255 return isExpectHeaderValid(message)
256
257 && message.headers().contains(HttpHeaderNames.EXPECT, HttpHeaderValues.CONTINUE, true);
258 }
259
260
261
262
263
264
265
266
267
268 static boolean isUnsupportedExpectation(HttpMessage message) {
269 if (!isExpectHeaderValid(message)) {
270 return false;
271 }
272
273 final String expectValue = message.headers().get(HttpHeaderNames.EXPECT);
274 return expectValue != null && !HttpHeaderValues.CONTINUE.toString().equalsIgnoreCase(expectValue);
275 }
276
277 private static boolean isExpectHeaderValid(final HttpMessage message) {
278
279
280
281
282
283 return message instanceof HttpRequest &&
284 message.protocolVersion().compareTo(HttpVersion.HTTP_1_1) >= 0;
285 }
286
287
288
289
290
291
292
293
294 public static void set100ContinueExpected(HttpMessage message, boolean expected) {
295 if (expected) {
296 message.headers().set(HttpHeaderNames.EXPECT, HttpHeaderValues.CONTINUE);
297 } else {
298 message.headers().remove(HttpHeaderNames.EXPECT);
299 }
300 }
301
302
303
304
305
306
307
308 public static boolean isTransferEncodingChunked(HttpMessage message) {
309 return message.headers().containsValue(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED, true);
310 }
311
312
313
314
315
316
317
318
319
320 public static void setTransferEncodingChunked(HttpMessage m, boolean chunked) {
321 if (chunked) {
322 m.headers().set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED);
323 m.headers().remove(HttpHeaderNames.CONTENT_LENGTH);
324 } else {
325 List<String> encodings = m.headers().getAll(HttpHeaderNames.TRANSFER_ENCODING);
326 if (encodings.isEmpty()) {
327 return;
328 }
329 List<CharSequence> values = new ArrayList<CharSequence>(encodings);
330 Iterator<CharSequence> valuesIt = values.iterator();
331 while (valuesIt.hasNext()) {
332 CharSequence value = valuesIt.next();
333 if (HttpHeaderValues.CHUNKED.contentEqualsIgnoreCase(value)) {
334 valuesIt.remove();
335 }
336 }
337 if (values.isEmpty()) {
338 m.headers().remove(HttpHeaderNames.TRANSFER_ENCODING);
339 } else {
340 m.headers().set(HttpHeaderNames.TRANSFER_ENCODING, values);
341 }
342 }
343 }
344
345
346
347
348
349
350
351
352 public static Charset getCharset(HttpMessage message) {
353 return getCharset(message, CharsetUtil.ISO_8859_1);
354 }
355
356
357
358
359
360
361
362
363 public static Charset getCharset(CharSequence contentTypeValue) {
364 if (contentTypeValue != null) {
365 return getCharset(contentTypeValue, CharsetUtil.ISO_8859_1);
366 } else {
367 return CharsetUtil.ISO_8859_1;
368 }
369 }
370
371
372
373
374
375
376
377
378
379 public static Charset getCharset(HttpMessage message, Charset defaultCharset) {
380 CharSequence contentTypeValue = message.headers().get(HttpHeaderNames.CONTENT_TYPE);
381 if (contentTypeValue != null) {
382 return getCharset(contentTypeValue, defaultCharset);
383 } else {
384 return defaultCharset;
385 }
386 }
387
388
389
390
391
392
393
394
395
396 public static Charset getCharset(CharSequence contentTypeValue, Charset defaultCharset) {
397 if (contentTypeValue != null) {
398 CharSequence charsetRaw = getCharsetAsSequence(contentTypeValue);
399 if (charsetRaw != null) {
400 if (charsetRaw.length() > 2) {
401 if (charsetRaw.charAt(0) == '"' && charsetRaw.charAt(charsetRaw.length() - 1) == '"') {
402 charsetRaw = charsetRaw.subSequence(1, charsetRaw.length() - 1);
403 }
404 }
405 try {
406 return Charset.forName(charsetRaw.toString());
407 } catch (IllegalCharsetNameException ignored) {
408
409 } catch (UnsupportedCharsetException ignored) {
410
411 }
412 }
413 }
414 return defaultCharset;
415 }
416
417
418
419
420
421
422
423
424
425
426
427
428 @Deprecated
429 public static CharSequence getCharsetAsString(HttpMessage message) {
430 return getCharsetAsSequence(message);
431 }
432
433
434
435
436
437
438
439
440
441
442 public static CharSequence getCharsetAsSequence(HttpMessage message) {
443 CharSequence contentTypeValue = message.headers().get(HttpHeaderNames.CONTENT_TYPE);
444 if (contentTypeValue != null) {
445 return getCharsetAsSequence(contentTypeValue);
446 } else {
447 return null;
448 }
449 }
450
451
452
453
454
455
456
457
458
459
460
461
462 public static CharSequence getCharsetAsSequence(CharSequence contentTypeValue) {
463 ObjectUtil.checkNotNull(contentTypeValue, "contentTypeValue");
464
465 int indexOfCharset = AsciiString.indexOfIgnoreCaseAscii(contentTypeValue, CHARSET_EQUALS, 0);
466 if (indexOfCharset == AsciiString.INDEX_NOT_FOUND) {
467 return null;
468 }
469
470 int indexOfEncoding = indexOfCharset + CHARSET_EQUALS.length();
471 if (indexOfEncoding < contentTypeValue.length()) {
472 CharSequence charsetCandidate = contentTypeValue.subSequence(indexOfEncoding, contentTypeValue.length());
473 int indexOfSemicolon = AsciiString.indexOfIgnoreCaseAscii(charsetCandidate, SEMICOLON, 0);
474 if (indexOfSemicolon == AsciiString.INDEX_NOT_FOUND) {
475 return charsetCandidate;
476 }
477
478 return charsetCandidate.subSequence(0, indexOfSemicolon);
479 }
480
481 return null;
482 }
483
484
485
486
487
488
489
490
491
492
493
494
495 public static CharSequence getMimeType(HttpMessage message) {
496 CharSequence contentTypeValue = message.headers().get(HttpHeaderNames.CONTENT_TYPE);
497 if (contentTypeValue != null) {
498 return getMimeType(contentTypeValue);
499 } else {
500 return null;
501 }
502 }
503
504
505
506
507
508
509
510
511
512
513
514
515
516 public static CharSequence getMimeType(CharSequence contentTypeValue) {
517 ObjectUtil.checkNotNull(contentTypeValue, "contentTypeValue");
518
519 int indexOfSemicolon = AsciiString.indexOfIgnoreCaseAscii(contentTypeValue, SEMICOLON, 0);
520 if (indexOfSemicolon != AsciiString.INDEX_NOT_FOUND) {
521 return contentTypeValue.subSequence(0, indexOfSemicolon);
522 } else {
523 return contentTypeValue.length() > 0 ? contentTypeValue : null;
524 }
525 }
526
527
528
529
530
531
532
533
534 public static String formatHostnameForHttp(InetSocketAddress addr) {
535 String hostString = NetUtil.getHostname(addr);
536 if (NetUtil.isValidIpV6Address(hostString)) {
537 if (!addr.isUnresolved()) {
538 hostString = NetUtil.toAddressString(addr.getAddress());
539 }
540 return '[' + hostString + ']';
541 }
542 return hostString;
543 }
544
545
546
547
548
549
550
551
552
553
554
555 @UnstableApi
556 public static long normalizeAndGetContentLength(
557 List<? extends CharSequence> contentLengthFields, boolean isHttp10OrEarlier,
558 boolean allowDuplicateContentLengths) {
559 if (contentLengthFields.isEmpty()) {
560 return -1;
561 }
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576 String firstField = contentLengthFields.get(0).toString();
577 boolean multipleContentLengths =
578 contentLengthFields.size() > 1 || firstField.indexOf(COMMA) >= 0;
579
580 if (multipleContentLengths && !isHttp10OrEarlier) {
581 if (allowDuplicateContentLengths) {
582
583 String firstValue = null;
584 for (CharSequence field : contentLengthFields) {
585 String[] tokens = field.toString().split(COMMA_STRING, -1);
586 for (String token : tokens) {
587 String trimmed = token.trim();
588 if (firstValue == null) {
589 firstValue = trimmed;
590 } else if (!trimmed.equals(firstValue)) {
591 throw new IllegalArgumentException(
592 "Multiple Content-Length values found: " + contentLengthFields);
593 }
594 }
595 }
596
597 firstField = firstValue;
598 } else {
599
600 throw new IllegalArgumentException(
601 "Multiple Content-Length values found: " + contentLengthFields);
602 }
603 }
604
605
606 if (firstField.isEmpty() || !Character.isDigit(firstField.charAt(0))) {
607
608 throw new IllegalArgumentException(
609 "Content-Length value is not a number: " + firstField);
610 }
611 try {
612 final long value = Long.parseLong(firstField);
613 return checkPositiveOrZero(value, "Content-Length value");
614 } catch (NumberFormatException e) {
615
616 throw new IllegalArgumentException(
617 "Content-Length value is not a number: " + firstField, e);
618 }
619 }
620 }