1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package io.netty.handler.codec.http2;
16
17 import io.netty.buffer.ByteBuf;
18 import io.netty.buffer.ByteBufAllocator;
19 import io.netty.handler.codec.UnsupportedValueConverter;
20 import io.netty.handler.codec.http.DefaultFullHttpRequest;
21 import io.netty.handler.codec.http.DefaultFullHttpResponse;
22 import io.netty.handler.codec.http.DefaultHttpRequest;
23 import io.netty.handler.codec.http.DefaultHttpResponse;
24 import io.netty.handler.codec.http.FullHttpMessage;
25 import io.netty.handler.codec.http.FullHttpRequest;
26 import io.netty.handler.codec.http.FullHttpResponse;
27 import io.netty.handler.codec.http.HttpHeaderNames;
28 import io.netty.handler.codec.http.HttpHeaders;
29 import io.netty.handler.codec.http.HttpMessage;
30 import io.netty.handler.codec.http.HttpMethod;
31 import io.netty.handler.codec.http.HttpRequest;
32 import io.netty.handler.codec.http.HttpResponse;
33 import io.netty.handler.codec.http.HttpResponseStatus;
34 import io.netty.handler.codec.http.HttpUtil;
35 import io.netty.handler.codec.http.HttpVersion;
36 import io.netty.util.AsciiString;
37 import io.netty.util.internal.InternalThreadLocalMap;
38 import io.netty.util.internal.StringUtil;
39
40 import java.net.URI;
41 import java.util.Iterator;
42 import java.util.List;
43 import java.util.Map.Entry;
44
45 import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION;
46 import static io.netty.handler.codec.http.HttpHeaderNames.COOKIE;
47 import static io.netty.handler.codec.http.HttpHeaderNames.TE;
48 import static io.netty.handler.codec.http.HttpHeaderValues.TRAILERS;
49 import static io.netty.handler.codec.http.HttpResponseStatus.parseLine;
50 import static io.netty.handler.codec.http.HttpScheme.HTTP;
51 import static io.netty.handler.codec.http.HttpScheme.HTTPS;
52 import static io.netty.handler.codec.http.HttpUtil.isAsteriskForm;
53 import static io.netty.handler.codec.http.HttpUtil.isOriginForm;
54 import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
55 import static io.netty.handler.codec.http2.Http2Exception.connectionError;
56 import static io.netty.handler.codec.http2.Http2Exception.streamError;
57 import static io.netty.util.AsciiString.EMPTY_STRING;
58 import static io.netty.util.AsciiString.contentEqualsIgnoreCase;
59 import static io.netty.util.AsciiString.indexOf;
60 import static io.netty.util.AsciiString.trim;
61 import static io.netty.util.ByteProcessor.FIND_COMMA;
62 import static io.netty.util.ByteProcessor.FIND_SEMI_COLON;
63 import static io.netty.util.internal.ObjectUtil.checkNotNull;
64 import static io.netty.util.internal.StringUtil.isNullOrEmpty;
65 import static io.netty.util.internal.StringUtil.unescapeCsvFields;
66
67
68
69
70 public final class HttpConversionUtil {
71
72
73
74
75
76
77 private static final CharSequenceMap<AsciiString> HTTP_TO_HTTP2_HEADER_BLACKLIST =
78 new CharSequenceMap<AsciiString>();
79 static {
80 HTTP_TO_HTTP2_HEADER_BLACKLIST.add(CONNECTION, EMPTY_STRING);
81 @SuppressWarnings("deprecation")
82 AsciiString keepAlive = HttpHeaderNames.KEEP_ALIVE;
83 HTTP_TO_HTTP2_HEADER_BLACKLIST.add(keepAlive, EMPTY_STRING);
84 @SuppressWarnings("deprecation")
85 AsciiString proxyConnection = HttpHeaderNames.PROXY_CONNECTION;
86 HTTP_TO_HTTP2_HEADER_BLACKLIST.add(proxyConnection, EMPTY_STRING);
87 HTTP_TO_HTTP2_HEADER_BLACKLIST.add(HttpHeaderNames.TRANSFER_ENCODING, EMPTY_STRING);
88 HTTP_TO_HTTP2_HEADER_BLACKLIST.add(HttpHeaderNames.HOST, EMPTY_STRING);
89 HTTP_TO_HTTP2_HEADER_BLACKLIST.add(HttpHeaderNames.UPGRADE, EMPTY_STRING);
90 HTTP_TO_HTTP2_HEADER_BLACKLIST.add(ExtensionHeaderNames.STREAM_ID.text(), EMPTY_STRING);
91 HTTP_TO_HTTP2_HEADER_BLACKLIST.add(ExtensionHeaderNames.SCHEME.text(), EMPTY_STRING);
92 HTTP_TO_HTTP2_HEADER_BLACKLIST.add(ExtensionHeaderNames.PATH.text(), EMPTY_STRING);
93 }
94
95
96
97
98
99 public static final HttpMethod OUT_OF_MESSAGE_SEQUENCE_METHOD = HttpMethod.OPTIONS;
100
101
102
103
104
105 public static final String OUT_OF_MESSAGE_SEQUENCE_PATH = "";
106
107
108
109
110
111 public static final HttpResponseStatus OUT_OF_MESSAGE_SEQUENCE_RETURN_CODE = HttpResponseStatus.OK;
112
113
114
115
116
117 private static final AsciiString EMPTY_REQUEST_PATH = AsciiString.cached("/");
118
119 private HttpConversionUtil() {
120 }
121
122
123
124
125 public enum ExtensionHeaderNames {
126
127
128
129
130
131
132 STREAM_ID("x-http2-stream-id"),
133
134
135
136
137
138
139 SCHEME("x-http2-scheme"),
140
141
142
143
144
145
146 PATH("x-http2-path"),
147
148
149
150
151
152
153 STREAM_PROMISE_ID("x-http2-stream-promise-id"),
154
155
156
157
158
159
160 STREAM_DEPENDENCY_ID("x-http2-stream-dependency-id"),
161
162
163
164
165
166
167 STREAM_WEIGHT("x-http2-stream-weight");
168
169 private final AsciiString text;
170
171 ExtensionHeaderNames(String text) {
172 this.text = AsciiString.cached(text);
173 }
174
175 public AsciiString text() {
176 return text;
177 }
178 }
179
180
181
182
183
184
185
186
187 public static HttpResponseStatus parseStatus(CharSequence status) throws Http2Exception {
188 HttpResponseStatus result;
189 try {
190 result = parseLine(status);
191 if (result == HttpResponseStatus.SWITCHING_PROTOCOLS) {
192 throw connectionError(PROTOCOL_ERROR, "Invalid HTTP/2 status code '%d'", result.code());
193 }
194 } catch (Http2Exception e) {
195 throw e;
196 } catch (Throwable t) {
197 throw connectionError(PROTOCOL_ERROR, t,
198 "Unrecognized HTTP status code '%s' encountered in translation to HTTP/1.x", status);
199 }
200 return result;
201 }
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216 public static FullHttpResponse toFullHttpResponse(int streamId, Http2Headers http2Headers, ByteBufAllocator alloc,
217 boolean validateHttpHeaders) throws Http2Exception {
218 return toFullHttpResponse(streamId, http2Headers, alloc.buffer(), validateHttpHeaders);
219 }
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234 public static FullHttpResponse toFullHttpResponse(int streamId, Http2Headers http2Headers, ByteBuf content,
235 boolean validateHttpHeaders)
236 throws Http2Exception {
237 HttpResponseStatus status = parseStatus(http2Headers.status());
238
239
240 FullHttpResponse msg = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, content,
241 validateHttpHeaders);
242 try {
243 addHttp2ToHttpHeaders(streamId, http2Headers, msg, false);
244 } catch (Http2Exception e) {
245 msg.release();
246 throw e;
247 } catch (Throwable t) {
248 msg.release();
249 throw streamError(streamId, PROTOCOL_ERROR, t, "HTTP/2 to HTTP/1.x headers conversion error");
250 }
251 return msg;
252 }
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267 public static FullHttpRequest toFullHttpRequest(int streamId, Http2Headers http2Headers, ByteBufAllocator alloc,
268 boolean validateHttpHeaders) throws Http2Exception {
269 return toFullHttpRequest(streamId, http2Headers, alloc.buffer(), validateHttpHeaders);
270 }
271
272 private static String extractPath(CharSequence method, Http2Headers headers) {
273 if (HttpMethod.CONNECT.asciiName().contentEqualsIgnoreCase(method)) {
274
275 return checkNotNull(headers.authority(),
276 "authority header cannot be null in the conversion to HTTP/1.x").toString();
277 } else {
278 return checkNotNull(headers.path(),
279 "path header cannot be null in conversion to HTTP/1.x").toString();
280 }
281 }
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296 public static FullHttpRequest toFullHttpRequest(int streamId, Http2Headers http2Headers, ByteBuf content,
297 boolean validateHttpHeaders) throws Http2Exception {
298
299 final CharSequence method = checkNotNull(http2Headers.method(),
300 "method header cannot be null in conversion to HTTP/1.x");
301 final CharSequence path = extractPath(method, http2Headers);
302 FullHttpRequest msg = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.valueOf(method
303 .toString()), path.toString(), content, validateHttpHeaders);
304 try {
305 addHttp2ToHttpHeaders(streamId, http2Headers, msg, false);
306 } catch (Http2Exception e) {
307 msg.release();
308 throw e;
309 } catch (Throwable t) {
310 msg.release();
311 throw streamError(streamId, PROTOCOL_ERROR, t, "HTTP/2 to HTTP/1.x headers conversion error");
312 }
313 return msg;
314 }
315
316
317
318
319
320
321
322
323
324
325
326
327
328 public static HttpRequest toHttpRequest(int streamId, Http2Headers http2Headers, boolean validateHttpHeaders)
329 throws Http2Exception {
330
331 final CharSequence method = checkNotNull(http2Headers.method(),
332 "method header cannot be null in conversion to HTTP/1.x");
333 final CharSequence path = extractPath(method, http2Headers);
334 HttpRequest msg = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.valueOf(method.toString()),
335 path.toString(), validateHttpHeaders);
336 try {
337 addHttp2ToHttpHeaders(streamId, http2Headers, msg.headers(), msg.protocolVersion(), false, true);
338 } catch (Http2Exception e) {
339 throw e;
340 } catch (Throwable t) {
341 throw streamError(streamId, PROTOCOL_ERROR, t, "HTTP/2 to HTTP/1.x headers conversion error");
342 }
343 return msg;
344 }
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359 public static HttpResponse toHttpResponse(final int streamId,
360 final Http2Headers http2Headers,
361 final boolean validateHttpHeaders) throws Http2Exception {
362 final HttpResponseStatus status = parseStatus(http2Headers.status());
363
364
365 final HttpResponse msg = new DefaultHttpResponse(HttpVersion.HTTP_1_1, status, validateHttpHeaders);
366 try {
367 addHttp2ToHttpHeaders(streamId, http2Headers, msg.headers(), msg.protocolVersion(), false, false);
368 } catch (final Http2Exception e) {
369 throw e;
370 } catch (final Throwable t) {
371 throw streamError(streamId, PROTOCOL_ERROR, t, "HTTP/2 to HTTP/1.x headers conversion error");
372 }
373 return msg;
374 }
375
376
377
378
379
380
381
382
383
384
385
386 public static void addHttp2ToHttpHeaders(int streamId, Http2Headers inputHeaders,
387 FullHttpMessage destinationMessage, boolean addToTrailer) throws Http2Exception {
388 addHttp2ToHttpHeaders(streamId, inputHeaders,
389 addToTrailer ? destinationMessage.trailingHeaders() : destinationMessage.headers(),
390 destinationMessage.protocolVersion(), addToTrailer, destinationMessage instanceof HttpRequest);
391 }
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406 public static void addHttp2ToHttpHeaders(int streamId, Http2Headers inputHeaders, HttpHeaders outputHeaders,
407 HttpVersion httpVersion, boolean isTrailer, boolean isRequest) throws Http2Exception {
408 Http2ToHttpHeaderTranslator translator = new Http2ToHttpHeaderTranslator(streamId, outputHeaders, isRequest);
409 try {
410 translator.translateHeaders(inputHeaders);
411 } catch (Http2Exception ex) {
412 throw ex;
413 } catch (Throwable t) {
414 throw streamError(streamId, PROTOCOL_ERROR, t, "HTTP/2 to HTTP/1.x headers conversion error");
415 }
416
417 outputHeaders.remove(HttpHeaderNames.TRANSFER_ENCODING);
418 outputHeaders.remove(HttpHeaderNames.TRAILER);
419 if (!isTrailer) {
420 outputHeaders.setInt(ExtensionHeaderNames.STREAM_ID.text(), streamId);
421 HttpUtil.setKeepAlive(outputHeaders, httpVersion, true);
422 }
423 }
424
425
426
427
428
429
430
431
432
433
434 public static Http2Headers toHttp2Headers(HttpMessage in, boolean validateHeaders) {
435 HttpHeaders inHeaders = in.headers();
436 final Http2Headers out = new DefaultHttp2Headers(validateHeaders, inHeaders.size());
437 if (in instanceof HttpRequest) {
438 HttpRequest request = (HttpRequest) in;
439 String host = inHeaders.getAsString(HttpHeaderNames.HOST);
440 if (isOriginForm(request.uri()) || isAsteriskForm(request.uri())) {
441 out.path(new AsciiString(request.uri()));
442 setHttp2Scheme(inHeaders, out);
443 } else {
444 String requestTarget = request.uri();
445 out.path(toHttp2Path(requestTarget));
446 if (hasSchemeAndAuthority(requestTarget)) {
447 URI requestTargetUri = URI.create(http2PathlessRequestTarget(requestTarget));
448
449 host = isNullOrEmpty(host) ? requestTargetUri.getAuthority() : host;
450 setHttp2Scheme(inHeaders, requestTargetUri, out);
451 } else {
452 int schemeEnd = schemeEnd(requestTarget);
453 if (schemeEnd != -1) {
454 setHttp2Scheme(inHeaders, requestTarget.substring(0, schemeEnd), -1, out);
455 } else {
456 setHttp2Scheme(inHeaders, out);
457 }
458 }
459 }
460 setHttp2Authority(host, out);
461 out.method(request.method().asciiName());
462 } else if (in instanceof HttpResponse) {
463 HttpResponse response = (HttpResponse) in;
464 out.status(response.status().codeAsText());
465 }
466
467
468 toHttp2Headers(inHeaders, out);
469 return out;
470 }
471
472 public static Http2Headers toHttp2Headers(HttpHeaders inHeaders, boolean validateHeaders) {
473 if (inHeaders.isEmpty()) {
474 return EmptyHttp2Headers.INSTANCE;
475 }
476
477 final Http2Headers out = new DefaultHttp2Headers(validateHeaders, inHeaders.size());
478 toHttp2Headers(inHeaders, out);
479 return out;
480 }
481
482 private static CharSequenceMap<AsciiString> toLowercaseMap(Iterator<? extends CharSequence> valuesIter,
483 int arraySizeHint) {
484 UnsupportedValueConverter<AsciiString> valueConverter = UnsupportedValueConverter.<AsciiString>instance();
485 CharSequenceMap<AsciiString> result = new CharSequenceMap<AsciiString>(true, valueConverter, arraySizeHint);
486
487 while (valuesIter.hasNext()) {
488 AsciiString lowerCased = AsciiString.of(valuesIter.next()).toLowerCase();
489 try {
490 int index = lowerCased.forEachByte(FIND_COMMA);
491 if (index != -1) {
492 int start = 0;
493 do {
494 result.add(lowerCased.subSequence(start, index, false).trim(), EMPTY_STRING);
495 start = index + 1;
496 } while (start < lowerCased.length() &&
497 (index = lowerCased.forEachByte(start, lowerCased.length() - start, FIND_COMMA)) != -1);
498 result.add(lowerCased.subSequence(start, lowerCased.length(), false).trim(), EMPTY_STRING);
499 } else {
500 result.add(lowerCased.trim(), EMPTY_STRING);
501 }
502 } catch (Exception e) {
503
504
505 throw new IllegalStateException(e);
506 }
507 }
508 return result;
509 }
510
511
512
513
514
515
516
517 private static void toHttp2HeadersFilterTE(Entry<CharSequence, CharSequence> entry,
518 Http2Headers out) {
519 if (indexOf(entry.getValue(), ',', 0) == -1) {
520 if (contentEqualsIgnoreCase(trim(entry.getValue()), TRAILERS)) {
521 out.add(TE, TRAILERS);
522 }
523 } else {
524 List<CharSequence> teValues = unescapeCsvFields(entry.getValue());
525 for (CharSequence teValue : teValues) {
526 if (contentEqualsIgnoreCase(trim(teValue), TRAILERS)) {
527 out.add(TE, TRAILERS);
528 break;
529 }
530 }
531 }
532 }
533
534 public static void toHttp2Headers(HttpHeaders inHeaders, Http2Headers out) {
535 Iterator<Entry<CharSequence, CharSequence>> iter = inHeaders.iteratorCharSequence();
536
537
538 CharSequenceMap<AsciiString> connectionBlacklist =
539 toLowercaseMap(inHeaders.valueCharSequenceIterator(CONNECTION), 8);
540 while (iter.hasNext()) {
541 Entry<CharSequence, CharSequence> entry = iter.next();
542 final AsciiString aName = AsciiString.of(entry.getKey()).toLowerCase();
543 if (!HTTP_TO_HTTP2_HEADER_BLACKLIST.contains(aName) && !connectionBlacklist.contains(aName)) {
544
545 if (aName.contentEqualsIgnoreCase(TE)) {
546 toHttp2HeadersFilterTE(entry, out);
547 } else if (aName.contentEqualsIgnoreCase(COOKIE)) {
548 CharSequence valueCs = entry.getValue();
549
550 boolean invalid = false;
551 for (int i = 0; i < valueCs.length(); i++) {
552 char c = valueCs.charAt(i);
553 if (c == ';') {
554 if (i + 1 >= valueCs.length() || valueCs.charAt(i + 1) != ' ') {
555
556 invalid = true;
557 break;
558 }
559 i++;
560 } else if (c > 255) {
561
562 invalid = true;
563 break;
564 }
565 }
566
567 if (invalid) {
568 out.add(COOKIE, valueCs);
569 } else {
570 splitValidCookieHeader(out, valueCs);
571 }
572 } else {
573 out.add(aName, entry.getValue());
574 }
575 }
576 }
577 }
578
579 private static void splitValidCookieHeader(Http2Headers out, CharSequence valueCs) {
580 try {
581 AsciiString value = AsciiString.of(valueCs);
582
583
584 int index = value.forEachByte(FIND_SEMI_COLON);
585 if (index != -1) {
586 int start = 0;
587 do {
588 out.add(COOKIE, value.subSequence(start, index, false));
589 assert index + 1 < value.length();
590 assert value.charAt(index + 1) == ' ';
591
592 start = index + 2;
593 } while (start < value.length() &&
594 (index = value.forEachByte(start, value.length() - start, FIND_SEMI_COLON)) != -1);
595 assert start < value.length();
596 out.add(COOKIE, value.subSequence(start, value.length(), false));
597 } else {
598 out.add(COOKIE, value);
599 }
600 } catch (Exception e) {
601
602
603 throw new IllegalStateException(e);
604 }
605 }
606
607
608
609
610
611 private static AsciiString toHttp2Path(String uri) {
612 String path = dropEmptyFragment(parsePath(uri));
613 String query = parseQuery(uri);
614 if (isNullOrEmpty(query)) {
615 return path.isEmpty() ? EMPTY_REQUEST_PATH : new AsciiString(path);
616 }
617 StringBuilder pathBuilder = new StringBuilder(path.length() + query.length() + 1);
618 pathBuilder.append(path);
619 appendQuery(pathBuilder, query);
620 return new AsciiString(pathBuilder.toString());
621 }
622
623
624
625
626 private static String parsePath(String uri) {
627 if (uri.isEmpty()) {
628 return StringUtil.EMPTY_STRING;
629 }
630 int i;
631 if (uri.charAt(0) == '/') {
632 i = 0;
633 } else {
634 i = uri.indexOf("://");
635
636 if (!isValidScheme(uri, i)) {
637 i = 0;
638 } else {
639 int authorityStart = i + 3;
640
641 int queryOrFragmentStart = queryOrFragmentStart(uri, authorityStart);
642 i = uri.indexOf('/', authorityStart);
643 if (i == -1 || (queryOrFragmentStart != -1 && queryOrFragmentStart < i)) {
644
645 return "/";
646 }
647 }
648 }
649
650 int queryStart = uri.indexOf('?', i);
651 if (queryStart == -1) {
652 queryStart = uri.length();
653 if (i == 0) {
654 return uri;
655 }
656 }
657 return uri.substring(i, queryStart);
658 }
659
660
661
662
663 private static String parseQuery(String uri) {
664 int i = uri.indexOf('?');
665 if (i == -1) {
666 return null;
667 } else {
668 return uri.substring(i + 1);
669 }
670 }
671
672 private static String dropEmptyFragment(String path) {
673
674 return path.endsWith("#") ? path.substring(0, path.length() - 1) : path;
675 }
676
677 private static void appendQuery(StringBuilder pathBuilder, String query) {
678 int fragmentStart = query.indexOf('#');
679 if (fragmentStart == 0) {
680
681 pathBuilder.append(query);
682 } else if (fragmentStart == query.length() - 1) {
683
684 pathBuilder.append('?').append(query, 0, fragmentStart);
685 } else {
686 pathBuilder.append('?').append(query);
687 }
688 }
689
690 static int queryOrFragmentStart(String uri, int searchStart) {
691 int queryStart = uri.indexOf('?', searchStart);
692 int fragmentStart = uri.indexOf('#', searchStart);
693 return queryStart == -1 ? fragmentStart :
694 fragmentStart == -1 ? queryStart : Math.min(queryStart, fragmentStart);
695 }
696
697
698 static boolean hasSchemeAndAuthority(String requestTarget) {
699 int schemeEnd = requestTarget.indexOf("://");
700 return isValidScheme(requestTarget, schemeEnd);
701 }
702
703 private static int schemeEnd(String requestTarget) {
704 int schemeEnd = requestTarget.indexOf(':');
705 return isValidScheme(requestTarget, schemeEnd) ? schemeEnd : -1;
706 }
707
708
709 private static String http2PathlessRequestTarget(String requestTarget) {
710 int schemeEnd = requestTarget.indexOf("://");
711 int authorityStart = schemeEnd + 3;
712
713 int pathStart = requestTarget.indexOf('/', authorityStart);
714 int delimiter = queryOrFragmentStart(requestTarget, authorityStart);
715 if (pathStart != -1 && (delimiter == -1 || pathStart < delimiter)) {
716 delimiter = pathStart;
717 }
718 if (delimiter == -1) {
719 return requestTarget;
720 }
721 return delimiter == authorityStart ? requestTarget.substring(0, delimiter + 1) :
722 requestTarget.substring(0, delimiter);
723 }
724
725
726 static boolean isValidScheme(String uri, int schemeEnd) {
727 if (schemeEnd <= 0) {
728 return false;
729 }
730 char first = uri.charAt(0);
731 if (!isAlpha(first)) {
732 return false;
733 }
734 for (int i = 1; i < schemeEnd; ++i) {
735 char c = uri.charAt(i);
736 if (!isAlpha(c) && (c < '0' || c > '9') && c != '+' && c != '-' && c != '.') {
737 return false;
738 }
739 }
740 return true;
741 }
742
743 private static boolean isAlpha(char c) {
744 return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
745 }
746
747
748 static void setHttp2Authority(String authority, Http2Headers out) {
749
750 if (authority != null) {
751 if (authority.isEmpty()) {
752 out.authority(EMPTY_STRING);
753 } else {
754 int start = authority.indexOf('@') + 1;
755 int length = authority.length() - start;
756 if (length == 0) {
757 throw new IllegalArgumentException("authority: " + authority);
758 }
759 out.authority(new AsciiString(authority, start, length));
760 }
761 }
762 }
763
764 private static void setHttp2Scheme(HttpHeaders in, Http2Headers out) {
765 setHttp2Scheme(in, URI.create(""), out);
766 }
767
768 private static void setHttp2Scheme(HttpHeaders in, URI uri, Http2Headers out) {
769 setHttp2Scheme(in, uri.getScheme(), uri.getPort(), out);
770 }
771
772 private static void setHttp2Scheme(HttpHeaders in, String scheme, int port, Http2Headers out) {
773 if (!isNullOrEmpty(scheme)) {
774 out.scheme(new AsciiString(scheme));
775 return;
776 }
777
778
779 CharSequence cValue = in.get(ExtensionHeaderNames.SCHEME.text());
780 if (cValue != null) {
781 out.scheme(AsciiString.of(cValue));
782 return;
783 }
784
785 if (port == HTTPS.port()) {
786 out.scheme(HTTPS.name());
787 } else if (port == HTTP.port()) {
788 out.scheme(HTTP.name());
789 } else {
790 throw new IllegalArgumentException(":scheme must be specified. " +
791 "see https://tools.ietf.org/html/rfc7540#section-8.1.2.3");
792 }
793 }
794
795
796
797
798 private static final class Http2ToHttpHeaderTranslator {
799
800
801
802 private static final CharSequenceMap<AsciiString>
803 REQUEST_HEADER_TRANSLATIONS = new CharSequenceMap<AsciiString>();
804 private static final CharSequenceMap<AsciiString>
805 RESPONSE_HEADER_TRANSLATIONS = new CharSequenceMap<AsciiString>();
806 static {
807 RESPONSE_HEADER_TRANSLATIONS.add(Http2Headers.PseudoHeaderName.AUTHORITY.value(),
808 HttpHeaderNames.HOST);
809 RESPONSE_HEADER_TRANSLATIONS.add(Http2Headers.PseudoHeaderName.SCHEME.value(),
810 ExtensionHeaderNames.SCHEME.text());
811 REQUEST_HEADER_TRANSLATIONS.add(RESPONSE_HEADER_TRANSLATIONS);
812 RESPONSE_HEADER_TRANSLATIONS.add(Http2Headers.PseudoHeaderName.PATH.value(),
813 ExtensionHeaderNames.PATH.text());
814 }
815
816 private final int streamId;
817 private final HttpHeaders output;
818 private final CharSequenceMap<AsciiString> translations;
819
820
821
822
823
824
825
826
827 Http2ToHttpHeaderTranslator(int streamId, HttpHeaders output, boolean request) {
828 this.streamId = streamId;
829 this.output = output;
830 translations = request ? REQUEST_HEADER_TRANSLATIONS : RESPONSE_HEADER_TRANSLATIONS;
831 }
832
833 void translateHeaders(Iterable<Entry<CharSequence, CharSequence>> inputHeaders) throws Http2Exception {
834
835 StringBuilder cookies = null;
836
837 for (Entry<CharSequence, CharSequence> entry : inputHeaders) {
838 final CharSequence name = entry.getKey();
839 final CharSequence value = entry.getValue();
840 AsciiString translatedName = translations.get(name);
841 if (translatedName != null) {
842 output.add(translatedName, AsciiString.of(value));
843 } else if (!Http2Headers.PseudoHeaderName.isPseudoHeader(name)) {
844
845
846 if (name.length() == 0 || name.charAt(0) == ':') {
847 throw streamError(streamId, PROTOCOL_ERROR,
848 "Invalid HTTP/2 header '%s' encountered in translation to HTTP/1.x", name);
849 }
850 if (COOKIE.equals(name)) {
851
852
853 if (cookies == null) {
854 cookies = InternalThreadLocalMap.get().stringBuilder();
855 } else if (cookies.length() > 0) {
856 cookies.append("; ");
857 }
858 cookies.append(value);
859 } else {
860 output.add(name, value);
861 }
862 }
863 }
864 if (cookies != null) {
865 output.add(COOKIE, cookies.toString());
866 }
867 }
868 }
869 }