View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  package org.apache.hc.client5.http.impl;
28  
29  import java.util.Iterator;
30  import java.util.concurrent.atomic.AtomicReference;
31  
32  import org.apache.hc.core5.annotation.Internal;
33  import org.apache.hc.core5.http.FormattedHeader;
34  import org.apache.hc.core5.http.Header;
35  import org.apache.hc.core5.http.HttpHeaders;
36  import org.apache.hc.core5.http.HttpMessage;
37  import org.apache.hc.core5.http.HttpVersion;
38  import org.apache.hc.core5.http.ParseException;
39  import org.apache.hc.core5.http.ProtocolException;
40  import org.apache.hc.core5.http.ProtocolVersion;
41  import org.apache.hc.core5.http.ProtocolVersionParser;
42  import org.apache.hc.core5.http.message.ParserCursor;
43  import org.apache.hc.core5.http.ssl.TLS;
44  import org.apache.hc.core5.util.Args;
45  import org.apache.hc.core5.util.CharArrayBuffer;
46  import org.apache.hc.core5.util.Tokenizer;
47  
48  /**
49   * Protocol switch handler.
50   *
51   * @since 5.4
52   */
53  @Internal
54  public final class ProtocolSwitchStrategy {
55  
56      private static final ProtocolVersionParser PROTOCOL_VERSION_PARSER = ProtocolVersionParser.INSTANCE;
57      private static final Tokenizer TOKENIZER = Tokenizer.INSTANCE;
58      private static final Tokenizer.Delimiter UPGRADE_TOKEN_DELIMITER = Tokenizer.delimiters(',');
59      private static final Tokenizer.Delimiter LAX_PROTO_DELIMITER = Tokenizer.delimiters('/', ',');
60  
61      @FunctionalInterface
62      private interface HeaderConsumer {
63  
64          void accept(CharSequence buffer, ParserCursor cursor) throws ProtocolException;
65  
66      }
67  
68      public ProtocolVersion switchProtocol(final HttpMessage response) throws ProtocolException {
69          final AtomicReference<ProtocolVersion> tlsUpgrade = new AtomicReference<>();
70  
71          parseHeaders(response, HttpHeaders.UPGRADE, (buffer, cursor) -> {
72              final ProtocolVersion protocolVersion = parseProtocolVersion(buffer, cursor);
73              if (protocolVersion != null) {
74                  if ("TLS".equalsIgnoreCase(protocolVersion.getProtocol())) {
75                      tlsUpgrade.set(protocolVersion);
76                  } else if (!protocolVersion.equals(HttpVersion.HTTP_1_1)) {
77                      throw new ProtocolException("Unsupported protocol or HTTP version: " + protocolVersion);
78                  }
79              }
80          });
81  
82          final ProtocolVersion result = tlsUpgrade.get();
83          if (result != null) {
84              return result;
85          } else {
86              throw new ProtocolException("Invalid protocol switch response: no TLS version found");
87          }
88      }
89  
90      private ProtocolVersion parseProtocolVersion(final CharSequence buffer, final ParserCursor cursor) throws ProtocolException {
91          TOKENIZER.skipWhiteSpace(buffer, cursor);
92          final String proto = TOKENIZER.parseToken(buffer, cursor, LAX_PROTO_DELIMITER);
93          if (!cursor.atEnd()) {
94              final char ch = buffer.charAt(cursor.getPos());
95              if (ch == '/') {
96                  if (proto.isEmpty()) {
97                      throw new ParseException("Invalid protocol", buffer, cursor.getLowerBound(), cursor.getUpperBound(), cursor.getPos());
98                  }
99                  cursor.updatePos(cursor.getPos() + 1);
100                 return PROTOCOL_VERSION_PARSER.parse(proto, null, buffer, cursor, UPGRADE_TOKEN_DELIMITER);
101             }
102         }
103         if (proto.isEmpty()) {
104             return null;
105         } else if (proto.equalsIgnoreCase("TLS")) {
106             return TLS.V_1_2.getVersion();
107         } else {
108             throw new ProtocolException("Unsupported or invalid protocol: " + proto);
109         }
110     }
111 
112 
113     private void parseHeaders(final HttpMessage message, final String name, final HeaderConsumer consumer)
114             throws ProtocolException {
115         final Iterator<Header> it = message.headerIterator(name);
116         while (it.hasNext()) {
117             parseHeader(it.next(), consumer);
118         }
119     }
120 
121     private void parseHeader(final Header header, final HeaderConsumer consumer) throws ProtocolException {
122         Args.notNull(header, "Header");
123         if (header instanceof FormattedHeader) {
124             final CharArrayBuffer buf = ((FormattedHeader) header).getBuffer();
125             final ParserCursor cursor = new ParserCursor(0, buf.length());
126             cursor.updatePos(((FormattedHeader) header).getValuePos());
127             parseHeaderElements(buf, cursor, consumer);
128         } else {
129             final String value = header.getValue();
130             if (value == null) {
131                 return;
132             }
133             final ParserCursor cursor = new ParserCursor(0, value.length());
134             parseHeaderElements(value, cursor, consumer);
135         }
136     }
137 
138     private void parseHeaderElements(final CharSequence buffer,
139                                      final ParserCursor cursor,
140                                      final HeaderConsumer consumer) throws ProtocolException {
141         while (!cursor.atEnd()) {
142             consumer.accept(buffer, cursor);
143             if (!cursor.atEnd()) {
144                 final char ch = buffer.charAt(cursor.getPos());
145                 if (ch == ',') {
146                     cursor.updatePos(cursor.getPos() + 1);
147                 }
148             }
149         }
150     }
151 
152 }