1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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
50
51
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 }