View Javadoc
1   /*
2    * Copyright 2014 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
5    * "License"); you may not use this file except in compliance with the License. You may obtain a
6    * copy of the License at:
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software distributed under the License
11   * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12   * or implied. See the License for the specific language governing permissions and limitations under
13   * the License.
14   */
15  package io.netty.handler.codec.http2;
16  
17  import io.netty.buffer.ByteBufAllocator;
18  import io.netty.handler.codec.UnsupportedValueConverter;
19  import io.netty.handler.codec.http.DefaultFullHttpRequest;
20  import io.netty.handler.codec.http.DefaultFullHttpResponse;
21  import io.netty.handler.codec.http.DefaultHttpRequest;
22  import io.netty.handler.codec.http.DefaultHttpResponse;
23  import io.netty.handler.codec.http.FullHttpMessage;
24  import io.netty.handler.codec.http.FullHttpRequest;
25  import io.netty.handler.codec.http.FullHttpResponse;
26  import io.netty.handler.codec.http.HttpHeaderNames;
27  import io.netty.handler.codec.http.HttpHeaders;
28  import io.netty.handler.codec.http.HttpMessage;
29  import io.netty.handler.codec.http.HttpMethod;
30  import io.netty.handler.codec.http.HttpRequest;
31  import io.netty.handler.codec.http.HttpResponse;
32  import io.netty.handler.codec.http.HttpResponseStatus;
33  import io.netty.handler.codec.http.HttpUtil;
34  import io.netty.handler.codec.http.HttpVersion;
35  import io.netty.util.AsciiString;
36  import io.netty.util.internal.UnstableApi;
37  
38  import java.net.URI;
39  import java.util.Iterator;
40  import java.util.List;
41  import java.util.Map.Entry;
42  
43  import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION;
44  import static io.netty.handler.codec.http.HttpHeaderNames.COOKIE;
45  import static io.netty.handler.codec.http.HttpHeaderNames.TE;
46  import static io.netty.handler.codec.http.HttpHeaderValues.TRAILERS;
47  import static io.netty.handler.codec.http.HttpScheme.HTTP;
48  import static io.netty.handler.codec.http.HttpScheme.HTTPS;
49  import static io.netty.handler.codec.http.HttpUtil.isAsteriskForm;
50  import static io.netty.handler.codec.http.HttpUtil.isOriginForm;
51  import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
52  import static io.netty.handler.codec.http2.Http2Exception.connectionError;
53  import static io.netty.handler.codec.http2.Http2Exception.streamError;
54  import static io.netty.util.AsciiString.EMPTY_STRING;
55  import static io.netty.util.AsciiString.contentEqualsIgnoreCase;
56  import static io.netty.util.AsciiString.indexOf;
57  import static io.netty.util.AsciiString.trim;
58  import static io.netty.util.ByteProcessor.FIND_COMMA;
59  import static io.netty.util.ByteProcessor.FIND_SEMI_COLON;
60  import static io.netty.util.internal.ObjectUtil.checkNotNull;
61  import static io.netty.util.internal.StringUtil.isNullOrEmpty;
62  import static io.netty.util.internal.StringUtil.length;
63  import static io.netty.util.internal.StringUtil.unescapeCsvFields;
64  
65  /**
66   * Provides utility methods and constants for the HTTP/2 to HTTP conversion
67   */
68  @UnstableApi
69  public final class HttpConversionUtil {
70      /**
71       * The set of headers that should not be directly copied when converting headers from HTTP to HTTP/2.
72       */
73      private static final CharSequenceMap<AsciiString> HTTP_TO_HTTP2_HEADER_BLACKLIST =
74              new CharSequenceMap<AsciiString>();
75      static {
76          HTTP_TO_HTTP2_HEADER_BLACKLIST.add(CONNECTION, EMPTY_STRING);
77          @SuppressWarnings("deprecation")
78          AsciiString keepAlive = HttpHeaderNames.KEEP_ALIVE;
79          HTTP_TO_HTTP2_HEADER_BLACKLIST.add(keepAlive, EMPTY_STRING);
80          @SuppressWarnings("deprecation")
81          AsciiString proxyConnection = HttpHeaderNames.PROXY_CONNECTION;
82          HTTP_TO_HTTP2_HEADER_BLACKLIST.add(proxyConnection, EMPTY_STRING);
83          HTTP_TO_HTTP2_HEADER_BLACKLIST.add(HttpHeaderNames.TRANSFER_ENCODING, EMPTY_STRING);
84          HTTP_TO_HTTP2_HEADER_BLACKLIST.add(HttpHeaderNames.HOST, EMPTY_STRING);
85          HTTP_TO_HTTP2_HEADER_BLACKLIST.add(HttpHeaderNames.UPGRADE, EMPTY_STRING);
86          HTTP_TO_HTTP2_HEADER_BLACKLIST.add(ExtensionHeaderNames.STREAM_ID.text(), EMPTY_STRING);
87          HTTP_TO_HTTP2_HEADER_BLACKLIST.add(ExtensionHeaderNames.SCHEME.text(), EMPTY_STRING);
88          HTTP_TO_HTTP2_HEADER_BLACKLIST.add(ExtensionHeaderNames.PATH.text(), EMPTY_STRING);
89      }
90  
91      /**
92       * This will be the method used for {@link HttpRequest} objects generated out of the HTTP message flow defined in <a
93       * href="http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-8.1.">HTTP/2 Spec Message Flow</a>
94       */
95      public static final HttpMethod OUT_OF_MESSAGE_SEQUENCE_METHOD = HttpMethod.OPTIONS;
96  
97      /**
98       * This will be the path used for {@link HttpRequest} objects generated out of the HTTP message flow defined in <a
99       * href="http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-8.1.">HTTP/2 Spec Message Flow</a>
100      */
101     public static final String OUT_OF_MESSAGE_SEQUENCE_PATH = "";
102 
103     /**
104      * This will be the status code used for {@link HttpResponse} objects generated out of the HTTP message flow defined
105      * in <a href="http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-8.1.">HTTP/2 Spec Message Flow</a>
106      */
107     public static final HttpResponseStatus OUT_OF_MESSAGE_SEQUENCE_RETURN_CODE = HttpResponseStatus.OK;
108 
109     /**
110      * <a href="https://tools.ietf.org/html/rfc7540#section-8.1.2.3">rfc7540, 8.1.2.3</a> states the path must not
111      * be empty, and instead should be {@code /}.
112      */
113     private static final AsciiString EMPTY_REQUEST_PATH = AsciiString.cached("/");
114 
115     private HttpConversionUtil() {
116     }
117 
118     /**
119      * Provides the HTTP header extensions used to carry HTTP/2 information in HTTP objects
120      */
121     public enum ExtensionHeaderNames {
122         /**
123          * HTTP extension header which will identify the stream id from the HTTP/2 event(s) responsible for generating a
124          * {@code HttpObject}
125          * <p>
126          * {@code "x-http2-stream-id"}
127          */
128         STREAM_ID("x-http2-stream-id"),
129         /**
130          * HTTP extension header which will identify the scheme pseudo header from the HTTP/2 event(s) responsible for
131          * generating a {@code HttpObject}
132          * <p>
133          * {@code "x-http2-scheme"}
134          */
135         SCHEME("x-http2-scheme"),
136         /**
137          * HTTP extension header which will identify the path pseudo header from the HTTP/2 event(s) responsible for
138          * generating a {@code HttpObject}
139          * <p>
140          * {@code "x-http2-path"}
141          */
142         PATH("x-http2-path"),
143         /**
144          * HTTP extension header which will identify the stream id used to create this stream in a HTTP/2 push promise
145          * frame
146          * <p>
147          * {@code "x-http2-stream-promise-id"}
148          */
149         STREAM_PROMISE_ID("x-http2-stream-promise-id"),
150         /**
151          * HTTP extension header which will identify the stream id which this stream is dependent on. This stream will
152          * be a child node of the stream id associated with this header value.
153          * <p>
154          * {@code "x-http2-stream-dependency-id"}
155          */
156         STREAM_DEPENDENCY_ID("x-http2-stream-dependency-id"),
157         /**
158          * HTTP extension header which will identify the weight (if non-default and the priority is not on the default
159          * stream) of the associated HTTP/2 stream responsible responsible for generating a {@code HttpObject}
160          * <p>
161          * {@code "x-http2-stream-weight"}
162          */
163         STREAM_WEIGHT("x-http2-stream-weight");
164 
165         private final AsciiString text;
166 
167         ExtensionHeaderNames(String text) {
168             this.text = AsciiString.cached(text);
169         }
170 
171         public AsciiString text() {
172             return text;
173         }
174     }
175 
176     /**
177      * Apply HTTP/2 rules while translating status code to {@link HttpResponseStatus}
178      *
179      * @param status The status from an HTTP/2 frame
180      * @return The HTTP/1.x status
181      * @throws Http2Exception If there is a problem translating from HTTP/2 to HTTP/1.x
182      */
183     public static HttpResponseStatus parseStatus(CharSequence status) throws Http2Exception {
184         HttpResponseStatus result;
185         try {
186             result = HttpResponseStatus.parseLine(status);
187             if (result == HttpResponseStatus.SWITCHING_PROTOCOLS) {
188                 throw connectionError(PROTOCOL_ERROR, "Invalid HTTP/2 status code '%d'", result.code());
189             }
190         } catch (Http2Exception e) {
191             throw e;
192         } catch (Throwable t) {
193             throw connectionError(PROTOCOL_ERROR, t,
194                             "Unrecognized HTTP status code '%s' encountered in translation to HTTP/1.x", status);
195         }
196         return result;
197     }
198 
199     /**
200      * Create a new object to contain the response data
201      *
202      * @param streamId The stream associated with the response
203      * @param http2Headers The initial set of HTTP/2 headers to create the response with
204      * @param alloc The {@link ByteBufAllocator} to use to generate the content of the message
205      * @param validateHttpHeaders <ul>
206      *        <li>{@code true} to validate HTTP headers in the http-codec</li>
207      *        <li>{@code false} not to validate HTTP headers in the http-codec</li>
208      *        </ul>
209      * @return A new response object which represents headers/data
210      * @throws Http2Exception see {@link #addHttp2ToHttpHeaders(int, Http2Headers, FullHttpMessage, boolean)}
211      */
212     public static FullHttpResponse toFullHttpResponse(int streamId, Http2Headers http2Headers, ByteBufAllocator alloc,
213                                                       boolean validateHttpHeaders)
214                     throws Http2Exception {
215         HttpResponseStatus status = parseStatus(http2Headers.status());
216         // HTTP/2 does not define a way to carry the version or reason phrase that is included in an
217         // HTTP/1.1 status line.
218         FullHttpResponse msg = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, alloc.buffer(),
219                                                            validateHttpHeaders);
220         try {
221             addHttp2ToHttpHeaders(streamId, http2Headers, msg, false);
222         } catch (Http2Exception e) {
223             msg.release();
224             throw e;
225         } catch (Throwable t) {
226             msg.release();
227             throw streamError(streamId, PROTOCOL_ERROR, t, "HTTP/2 to HTTP/1.x headers conversion error");
228         }
229         return msg;
230     }
231 
232     /**
233      * Create a new object to contain the request data
234      *
235      * @param streamId The stream associated with the request
236      * @param http2Headers The initial set of HTTP/2 headers to create the request with
237      * @param alloc The {@link ByteBufAllocator} to use to generate the content of the message
238      * @param validateHttpHeaders <ul>
239      *        <li>{@code true} to validate HTTP headers in the http-codec</li>
240      *        <li>{@code false} not to validate HTTP headers in the http-codec</li>
241      *        </ul>
242      * @return A new request object which represents headers/data
243      * @throws Http2Exception see {@link #addHttp2ToHttpHeaders(int, Http2Headers, FullHttpMessage, boolean)}
244      */
245     public static FullHttpRequest toFullHttpRequest(int streamId, Http2Headers http2Headers, ByteBufAllocator alloc,
246                                                 boolean validateHttpHeaders)
247                     throws Http2Exception {
248         // HTTP/2 does not define a way to carry the version identifier that is included in the HTTP/1.1 request line.
249         final CharSequence method = checkNotNull(http2Headers.method(),
250                 "method header cannot be null in conversion to HTTP/1.x");
251         final CharSequence path = checkNotNull(http2Headers.path(),
252                 "path header cannot be null in conversion to HTTP/1.x");
253         FullHttpRequest msg = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.valueOf(method
254                         .toString()), path.toString(), alloc.buffer(), validateHttpHeaders);
255         try {
256             addHttp2ToHttpHeaders(streamId, http2Headers, msg, false);
257         } catch (Http2Exception e) {
258             msg.release();
259             throw e;
260         } catch (Throwable t) {
261             msg.release();
262             throw streamError(streamId, PROTOCOL_ERROR, t, "HTTP/2 to HTTP/1.x headers conversion error");
263         }
264         return msg;
265     }
266 
267     /**
268      * Create a new object to contain the request data.
269      *
270      * @param streamId The stream associated with the request
271      * @param http2Headers The initial set of HTTP/2 headers to create the request with
272      * @param validateHttpHeaders <ul>
273      *        <li>{@code true} to validate HTTP headers in the http-codec</li>
274      *        <li>{@code false} not to validate HTTP headers in the http-codec</li>
275      *        </ul>
276      * @return A new request object which represents headers for a chunked request
277      * @throws Http2Exception see {@link #addHttp2ToHttpHeaders(int, Http2Headers, FullHttpMessage, boolean)}
278      */
279     public static HttpRequest toHttpRequest(int streamId, Http2Headers http2Headers, boolean validateHttpHeaders)
280                     throws Http2Exception {
281         // HTTP/2 does not define a way to carry the version identifier that is included in the HTTP/1.1 request line.
282         final CharSequence method = checkNotNull(http2Headers.method(),
283                 "method header cannot be null in conversion to HTTP/1.x");
284         final CharSequence path = checkNotNull(http2Headers.path(),
285                 "path header cannot be null in conversion to HTTP/1.x");
286         HttpRequest msg = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.valueOf(method.toString()),
287                 path.toString(), validateHttpHeaders);
288         try {
289             addHttp2ToHttpHeaders(streamId, http2Headers, msg.headers(), msg.protocolVersion(), false, true);
290         } catch (Http2Exception e) {
291             throw e;
292         } catch (Throwable t) {
293             throw streamError(streamId, PROTOCOL_ERROR, t, "HTTP/2 to HTTP/1.x headers conversion error");
294         }
295         return msg;
296     }
297 
298     /**
299      * Create a new object to contain the response data.
300      *
301      * @param streamId The stream associated with the response
302      * @param http2Headers The initial set of HTTP/2 headers to create the response with
303      * @param validateHttpHeaders <ul>
304      *        <li>{@code true} to validate HTTP headers in the http-codec</li>
305      *        <li>{@code false} not to validate HTTP headers in the http-codec</li>
306      *        </ul>
307      * @return A new response object which represents headers for a chunked response
308      * @throws Http2Exception see {@link #addHttp2ToHttpHeaders(int, Http2Headers,
309      *         HttpHeaders, HttpVersion, boolean, boolean)}
310      */
311     public static HttpResponse toHttpResponse(final int streamId,
312                                               final Http2Headers http2Headers,
313                                               final boolean validateHttpHeaders) throws Http2Exception {
314         final HttpResponseStatus status = parseStatus(http2Headers.status());
315         // HTTP/2 does not define a way to carry the version or reason phrase that is included in an
316         // HTTP/1.1 status line.
317         final HttpResponse msg = new DefaultHttpResponse(HttpVersion.HTTP_1_1, status, validateHttpHeaders);
318         try {
319             addHttp2ToHttpHeaders(streamId, http2Headers, msg.headers(), msg.protocolVersion(), false, true);
320         } catch (final Http2Exception e) {
321             throw e;
322         } catch (final Throwable t) {
323             throw streamError(streamId, PROTOCOL_ERROR, t, "HTTP/2 to HTTP/1.x headers conversion error");
324         }
325         return msg;
326     }
327 
328     /**
329      * Translate and add HTTP/2 headers to HTTP/1.x headers.
330      *
331      * @param streamId The stream associated with {@code sourceHeaders}.
332      * @param sourceHeaders The HTTP/2 headers to convert.
333      * @param destinationMessage The object which will contain the resulting HTTP/1.x headers.
334      * @param addToTrailer {@code true} to add to trailing headers. {@code false} to add to initial headers.
335      * @throws Http2Exception If not all HTTP/2 headers can be translated to HTTP/1.x.
336      * @see #addHttp2ToHttpHeaders(int, Http2Headers, HttpHeaders, HttpVersion, boolean, boolean)
337      */
338     public static void addHttp2ToHttpHeaders(int streamId, Http2Headers sourceHeaders,
339                     FullHttpMessage destinationMessage, boolean addToTrailer) throws Http2Exception {
340         addHttp2ToHttpHeaders(streamId, sourceHeaders,
341                 addToTrailer ? destinationMessage.trailingHeaders() : destinationMessage.headers(),
342                 destinationMessage.protocolVersion(), addToTrailer, destinationMessage instanceof HttpRequest);
343     }
344 
345     /**
346      * Translate and add HTTP/2 headers to HTTP/1.x headers.
347      *
348      * @param streamId The stream associated with {@code sourceHeaders}.
349      * @param inputHeaders The HTTP/2 headers to convert.
350      * @param outputHeaders The object which will contain the resulting HTTP/1.x headers..
351      * @param httpVersion What HTTP/1.x version {@code outputHeaders} should be treated as when doing the conversion.
352      * @param isTrailer {@code true} if {@code outputHeaders} should be treated as trailing headers.
353      * {@code false} otherwise.
354      * @param isRequest {@code true} if the {@code outputHeaders} will be used in a request message.
355      * {@code false} for response message.
356      * @throws Http2Exception If not all HTTP/2 headers can be translated to HTTP/1.x.
357      */
358     public static void addHttp2ToHttpHeaders(int streamId, Http2Headers inputHeaders, HttpHeaders outputHeaders,
359             HttpVersion httpVersion, boolean isTrailer, boolean isRequest) throws Http2Exception {
360         Http2ToHttpHeaderTranslator translator = new Http2ToHttpHeaderTranslator(streamId, outputHeaders, isRequest);
361         try {
362             for (Entry<CharSequence, CharSequence> entry : inputHeaders) {
363                 translator.translate(entry);
364             }
365         } catch (Http2Exception ex) {
366             throw ex;
367         } catch (Throwable t) {
368             throw streamError(streamId, PROTOCOL_ERROR, t, "HTTP/2 to HTTP/1.x headers conversion error");
369         }
370 
371         outputHeaders.remove(HttpHeaderNames.TRANSFER_ENCODING);
372         outputHeaders.remove(HttpHeaderNames.TRAILER);
373         if (!isTrailer) {
374             outputHeaders.setInt(ExtensionHeaderNames.STREAM_ID.text(), streamId);
375             HttpUtil.setKeepAlive(outputHeaders, httpVersion, true);
376         }
377     }
378 
379     /**
380      * Converts the given HTTP/1.x headers into HTTP/2 headers.
381      * The following headers are only used if they can not be found in from the {@code HOST} header or the
382      * {@code Request-Line} as defined by <a href="https://tools.ietf.org/html/rfc7230">rfc7230</a>
383      * <ul>
384      * <li>{@link ExtensionHeaderNames#SCHEME}</li>
385      * </ul>
386      * {@link ExtensionHeaderNames#PATH} is ignored and instead extracted from the {@code Request-Line}.
387      */
388     public static Http2Headers toHttp2Headers(HttpMessage in, boolean validateHeaders) {
389         HttpHeaders inHeaders = in.headers();
390         final Http2Headers out = new DefaultHttp2Headers(validateHeaders, inHeaders.size());
391         if (in instanceof HttpRequest) {
392             HttpRequest request = (HttpRequest) in;
393             URI requestTargetUri = URI.create(request.uri());
394             out.path(toHttp2Path(requestTargetUri));
395             out.method(request.method().asciiName());
396             setHttp2Scheme(inHeaders, requestTargetUri, out);
397 
398             if (!isOriginForm(requestTargetUri) && !isAsteriskForm(requestTargetUri)) {
399                 // Attempt to take from HOST header before taking from the request-line
400                 String host = inHeaders.getAsString(HttpHeaderNames.HOST);
401                 setHttp2Authority((host == null || host.isEmpty()) ? requestTargetUri.getAuthority() : host, out);
402             }
403         } else if (in instanceof HttpResponse) {
404             HttpResponse response = (HttpResponse) in;
405             out.status(response.status().codeAsText());
406         }
407 
408         // Add the HTTP headers which have not been consumed above
409         toHttp2Headers(inHeaders, out);
410         return out;
411     }
412 
413     public static Http2Headers toHttp2Headers(HttpHeaders inHeaders, boolean validateHeaders) {
414         if (inHeaders.isEmpty()) {
415             return EmptyHttp2Headers.INSTANCE;
416         }
417 
418         final Http2Headers out = new DefaultHttp2Headers(validateHeaders, inHeaders.size());
419         toHttp2Headers(inHeaders, out);
420         return out;
421     }
422 
423     private static CharSequenceMap<AsciiString> toLowercaseMap(Iterator<? extends CharSequence> valuesIter,
424                                                                int arraySizeHint) {
425         UnsupportedValueConverter<AsciiString> valueConverter = UnsupportedValueConverter.<AsciiString>instance();
426         CharSequenceMap<AsciiString> result = new CharSequenceMap<AsciiString>(true, valueConverter, arraySizeHint);
427 
428         while (valuesIter.hasNext()) {
429             AsciiString lowerCased = AsciiString.of(valuesIter.next()).toLowerCase();
430             try {
431                 int index = lowerCased.forEachByte(FIND_COMMA);
432                 if (index != -1) {
433                     int start = 0;
434                     do {
435                         result.add(lowerCased.subSequence(start, index, false).trim(), EMPTY_STRING);
436                         start = index + 1;
437                     } while (start < lowerCased.length() &&
438                              (index = lowerCased.forEachByte(start, lowerCased.length() - start, FIND_COMMA)) != -1);
439                     result.add(lowerCased.subSequence(start, lowerCased.length(), false).trim(), EMPTY_STRING);
440                 } else {
441                     result.add(lowerCased.trim(), EMPTY_STRING);
442                 }
443             } catch (Exception e) {
444                 // This is not expect to happen because FIND_COMMA never throws but must be caught
445                 // because of the ByteProcessor interface.
446                 throw new IllegalStateException(e);
447             }
448         }
449         return result;
450     }
451 
452     /**
453      * Filter the {@link HttpHeaderNames#TE} header according to the
454      * <a href="https://tools.ietf.org/html/rfc7540#section-8.1.2.2">special rules in the HTTP/2 RFC</a>.
455      * @param entry An entry whose name is {@link HttpHeaderNames#TE}.
456      * @param out the resulting HTTP/2 headers.
457      */
458     private static void toHttp2HeadersFilterTE(Entry<CharSequence, CharSequence> entry,
459                                                Http2Headers out) {
460         if (indexOf(entry.getValue(), ',', 0) == -1) {
461             if (contentEqualsIgnoreCase(trim(entry.getValue()), TRAILERS)) {
462                 out.add(TE, TRAILERS);
463             }
464         } else {
465             List<CharSequence> teValues = unescapeCsvFields(entry.getValue());
466             for (CharSequence teValue : teValues) {
467                 if (contentEqualsIgnoreCase(trim(teValue), TRAILERS)) {
468                     out.add(TE, TRAILERS);
469                     break;
470                 }
471             }
472         }
473     }
474 
475     public static void toHttp2Headers(HttpHeaders inHeaders, Http2Headers out) {
476         Iterator<Entry<CharSequence, CharSequence>> iter = inHeaders.iteratorCharSequence();
477         // Choose 8 as a default size because it is unlikely we will see more than 4 Connection headers values, but
478         // still allowing for "enough" space in the map to reduce the chance of hash code collision.
479         CharSequenceMap<AsciiString> connectionBlacklist =
480             toLowercaseMap(inHeaders.valueCharSequenceIterator(CONNECTION), 8);
481         while (iter.hasNext()) {
482             Entry<CharSequence, CharSequence> entry = iter.next();
483             final AsciiString aName = AsciiString.of(entry.getKey()).toLowerCase();
484             if (!HTTP_TO_HTTP2_HEADER_BLACKLIST.contains(aName) && !connectionBlacklist.contains(aName)) {
485                 // https://tools.ietf.org/html/rfc7540#section-8.1.2.2 makes a special exception for TE
486                 if (aName.contentEqualsIgnoreCase(TE)) {
487                     toHttp2HeadersFilterTE(entry, out);
488                 } else if (aName.contentEqualsIgnoreCase(COOKIE)) {
489                     AsciiString value = AsciiString.of(entry.getValue());
490                     // split up cookies to allow for better compression
491                     // https://tools.ietf.org/html/rfc7540#section-8.1.2.5
492                     try {
493                         int index = value.forEachByte(FIND_SEMI_COLON);
494                         if (index != -1) {
495                             int start = 0;
496                             do {
497                                 out.add(COOKIE, value.subSequence(start, index, false));
498                                 // skip 2 characters "; " (see https://tools.ietf.org/html/rfc6265#section-4.2.1)
499                                 start = index + 2;
500                             } while (start < value.length() &&
501                                     (index = value.forEachByte(start, value.length() - start, FIND_SEMI_COLON)) != -1);
502                             if (start >= value.length()) {
503                                 throw new IllegalArgumentException("cookie value is of unexpected format: " + value);
504                             }
505                             out.add(COOKIE, value.subSequence(start, value.length(), false));
506                         } else {
507                             out.add(COOKIE, value);
508                         }
509                     } catch (Exception e) {
510                         // This is not expect to happen because FIND_SEMI_COLON never throws but must be caught
511                         // because of the ByteProcessor interface.
512                         throw new IllegalStateException(e);
513                     }
514                 } else {
515                     out.add(aName, entry.getValue());
516                 }
517             }
518         }
519     }
520 
521     /**
522      * Generate a HTTP/2 {code :path} from a URI in accordance with
523      * <a href="https://tools.ietf.org/html/rfc7230#section-5.3">rfc7230, 5.3</a>.
524      */
525     private static AsciiString toHttp2Path(URI uri) {
526         StringBuilder pathBuilder = new StringBuilder(length(uri.getRawPath()) +
527                 length(uri.getRawQuery()) + length(uri.getRawFragment()) + 2);
528         if (!isNullOrEmpty(uri.getRawPath())) {
529             pathBuilder.append(uri.getRawPath());
530         }
531         if (!isNullOrEmpty(uri.getRawQuery())) {
532             pathBuilder.append('?');
533             pathBuilder.append(uri.getRawQuery());
534         }
535         if (!isNullOrEmpty(uri.getRawFragment())) {
536             pathBuilder.append('#');
537             pathBuilder.append(uri.getRawFragment());
538         }
539         String path = pathBuilder.toString();
540         return path.isEmpty() ? EMPTY_REQUEST_PATH : new AsciiString(path);
541     }
542 
543     // package-private for testing only
544     static void setHttp2Authority(String authority, Http2Headers out) {
545         // The authority MUST NOT include the deprecated "userinfo" subcomponent
546         if (authority != null) {
547             if (authority.isEmpty()) {
548                 out.authority(EMPTY_STRING);
549             } else {
550                 int start = authority.indexOf('@') + 1;
551                 int length = authority.length() - start;
552                 if (length == 0) {
553                     throw new IllegalArgumentException("authority: " + authority);
554                 }
555                 out.authority(new AsciiString(authority, start, length));
556             }
557         }
558     }
559 
560     private static void setHttp2Scheme(HttpHeaders in, URI uri, Http2Headers out) {
561         String value = uri.getScheme();
562         if (value != null) {
563             out.scheme(new AsciiString(value));
564             return;
565         }
566 
567         // Consume the Scheme extension header if present
568         CharSequence cValue = in.get(ExtensionHeaderNames.SCHEME.text());
569         if (cValue != null) {
570             out.scheme(AsciiString.of(cValue));
571             return;
572         }
573 
574         if (uri.getPort() == HTTPS.port()) {
575             out.scheme(HTTPS.name());
576         } else if (uri.getPort() == HTTP.port()) {
577             out.scheme(HTTP.name());
578         } else {
579             throw new IllegalArgumentException(":scheme must be specified. " +
580                     "see https://tools.ietf.org/html/rfc7540#section-8.1.2.3");
581         }
582     }
583 
584     /**
585      * Utility which translates HTTP/2 headers to HTTP/1 headers.
586      */
587     private static final class Http2ToHttpHeaderTranslator {
588         /**
589          * Translations from HTTP/2 header name to the HTTP/1.x equivalent.
590          */
591         private static final CharSequenceMap<AsciiString>
592             REQUEST_HEADER_TRANSLATIONS = new CharSequenceMap<AsciiString>();
593         private static final CharSequenceMap<AsciiString>
594             RESPONSE_HEADER_TRANSLATIONS = new CharSequenceMap<AsciiString>();
595         static {
596             RESPONSE_HEADER_TRANSLATIONS.add(Http2Headers.PseudoHeaderName.AUTHORITY.value(),
597                             HttpHeaderNames.HOST);
598             RESPONSE_HEADER_TRANSLATIONS.add(Http2Headers.PseudoHeaderName.SCHEME.value(),
599                             ExtensionHeaderNames.SCHEME.text());
600             REQUEST_HEADER_TRANSLATIONS.add(RESPONSE_HEADER_TRANSLATIONS);
601             RESPONSE_HEADER_TRANSLATIONS.add(Http2Headers.PseudoHeaderName.PATH.value(),
602                             ExtensionHeaderNames.PATH.text());
603         }
604 
605         private final int streamId;
606         private final HttpHeaders output;
607         private final CharSequenceMap<AsciiString> translations;
608 
609         /**
610          * Create a new instance
611          *
612          * @param output The HTTP/1.x headers object to store the results of the translation
613          * @param request if {@code true}, translates headers using the request translation map. Otherwise uses the
614          *        response translation map.
615          */
616         Http2ToHttpHeaderTranslator(int streamId, HttpHeaders output, boolean request) {
617             this.streamId = streamId;
618             this.output = output;
619             translations = request ? REQUEST_HEADER_TRANSLATIONS : RESPONSE_HEADER_TRANSLATIONS;
620         }
621 
622         public void translate(Entry<CharSequence, CharSequence> entry) throws Http2Exception {
623             final CharSequence name = entry.getKey();
624             final CharSequence value = entry.getValue();
625             AsciiString translatedName = translations.get(name);
626             if (translatedName != null) {
627                 output.add(translatedName, AsciiString.of(value));
628             } else if (!Http2Headers.PseudoHeaderName.isPseudoHeader(name)) {
629                 // https://tools.ietf.org/html/rfc7540#section-8.1.2.3
630                 // All headers that start with ':' are only valid in HTTP/2 context
631                 if (name.length() == 0 || name.charAt(0) == ':') {
632                     throw streamError(streamId, PROTOCOL_ERROR,
633                             "Invalid HTTP/2 header '%s' encountered in translation to HTTP/1.x", name);
634                 }
635                 if (COOKIE.equals(name)) {
636                     // combine the cookie values into 1 header entry.
637                     // https://tools.ietf.org/html/rfc7540#section-8.1.2.5
638                     String existingCookie = output.get(COOKIE);
639                     output.set(COOKIE,
640                                (existingCookie != null) ? (existingCookie + "; " + value) : value);
641                 } else {
642                     output.add(name, value);
643                 }
644             }
645         }
646     }
647 }