1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.handler.codec.http.websocketx;
17
18 import io.netty.buffer.ByteBuf;
19 import io.netty.buffer.Unpooled;
20 import io.netty.handler.codec.http.DefaultFullHttpRequest;
21 import io.netty.handler.codec.http.FullHttpRequest;
22 import io.netty.handler.codec.http.FullHttpResponse;
23 import io.netty.handler.codec.http.HttpHeaderNames;
24 import io.netty.handler.codec.http.HttpHeaderValues;
25 import io.netty.handler.codec.http.HttpHeaders;
26 import io.netty.handler.codec.http.HttpMethod;
27 import io.netty.handler.codec.http.HttpResponseStatus;
28 import io.netty.handler.codec.http.HttpVersion;
29 import io.netty.util.internal.PlatformDependent;
30
31 import java.net.URI;
32 import java.nio.ByteBuffer;
33
34
35
36
37
38
39
40
41
42
43
44 public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker {
45
46 private ByteBuf expectedChallengeResponseBytes;
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63 public WebSocketClientHandshaker00(URI webSocketURL, WebSocketVersion version, String subprotocol,
64 HttpHeaders customHeaders, int maxFramePayloadLength) {
65 this(webSocketURL, version, subprotocol, customHeaders, maxFramePayloadLength,
66 DEFAULT_FORCE_CLOSE_TIMEOUT_MILLIS);
67 }
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86 public WebSocketClientHandshaker00(URI webSocketURL, WebSocketVersion version, String subprotocol,
87 HttpHeaders customHeaders, int maxFramePayloadLength,
88 long forceCloseTimeoutMillis) {
89 this(webSocketURL, version, subprotocol, customHeaders, maxFramePayloadLength, forceCloseTimeoutMillis, false);
90 }
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112 WebSocketClientHandshaker00(URI webSocketURL, WebSocketVersion version, String subprotocol,
113 HttpHeaders customHeaders, int maxFramePayloadLength,
114 long forceCloseTimeoutMillis, boolean absoluteUpgradeUrl) {
115 this(webSocketURL, version, subprotocol, customHeaders, maxFramePayloadLength, forceCloseTimeoutMillis,
116 absoluteUpgradeUrl, true);
117 }
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142 WebSocketClientHandshaker00(URI webSocketURL, WebSocketVersion version, String subprotocol,
143 HttpHeaders customHeaders, int maxFramePayloadLength,
144 long forceCloseTimeoutMillis, boolean absoluteUpgradeUrl,
145 boolean generateOriginHeader) {
146 super(webSocketURL, version, subprotocol, customHeaders, maxFramePayloadLength, forceCloseTimeoutMillis,
147 absoluteUpgradeUrl, generateOriginHeader);
148 }
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168 @Override
169 protected FullHttpRequest newHandshakeRequest() {
170
171 int spaces1 = WebSocketUtil.randomNumber(1, 12);
172 int spaces2 = WebSocketUtil.randomNumber(1, 12);
173
174 int max1 = Integer.MAX_VALUE / spaces1;
175 int max2 = Integer.MAX_VALUE / spaces2;
176
177 int number1 = WebSocketUtil.randomNumber(0, max1);
178 int number2 = WebSocketUtil.randomNumber(0, max2);
179
180 int product1 = number1 * spaces1;
181 int product2 = number2 * spaces2;
182
183 String key1 = Integer.toString(product1);
184 String key2 = Integer.toString(product2);
185
186 key1 = insertRandomCharacters(key1);
187 key2 = insertRandomCharacters(key2);
188
189 key1 = insertSpaces(key1, spaces1);
190 key2 = insertSpaces(key2, spaces2);
191
192 byte[] key3 = WebSocketUtil.randomBytes(8);
193
194 ByteBuffer buffer = ByteBuffer.allocate(4);
195 buffer.putInt(number1);
196 byte[] number1Array = buffer.array();
197 buffer = ByteBuffer.allocate(4);
198 buffer.putInt(number2);
199 byte[] number2Array = buffer.array();
200
201 byte[] challenge = new byte[16];
202 System.arraycopy(number1Array, 0, challenge, 0, 4);
203 System.arraycopy(number2Array, 0, challenge, 4, 4);
204 System.arraycopy(key3, 0, challenge, 8, 8);
205 expectedChallengeResponseBytes = Unpooled.wrappedBuffer(WebSocketUtil.md5(challenge));
206
207 URI wsURL = uri();
208
209
210 FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, upgradeUrl(wsURL),
211 Unpooled.wrappedBuffer(key3));
212 HttpHeaders headers = request.headers();
213
214 if (customHeaders != null) {
215 headers.add(customHeaders);
216 if (!headers.contains(HttpHeaderNames.HOST)) {
217
218
219
220 headers.set(HttpHeaderNames.HOST, websocketHostValue(wsURL));
221 }
222 } else {
223 headers.set(HttpHeaderNames.HOST, websocketHostValue(wsURL));
224 }
225
226 headers.set(HttpHeaderNames.UPGRADE, HttpHeaderValues.WEBSOCKET)
227 .set(HttpHeaderNames.CONNECTION, HttpHeaderValues.UPGRADE)
228 .set(HttpHeaderNames.SEC_WEBSOCKET_KEY1, key1)
229 .set(HttpHeaderNames.SEC_WEBSOCKET_KEY2, key2);
230
231 if (generateOriginHeader && !headers.contains(HttpHeaderNames.ORIGIN)) {
232 headers.set(HttpHeaderNames.ORIGIN, websocketOriginValue(wsURL));
233 }
234
235 String expectedSubprotocol = expectedSubprotocol();
236 if (expectedSubprotocol != null && !expectedSubprotocol.isEmpty()) {
237 headers.set(HttpHeaderNames.SEC_WEBSOCKET_PROTOCOL, expectedSubprotocol);
238 }
239
240
241
242 headers.set(HttpHeaderNames.CONTENT_LENGTH, key3.length);
243 return request;
244 }
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266 @Override
267 protected void verify(FullHttpResponse response) {
268 HttpResponseStatus status = response.status();
269 if (!HttpResponseStatus.SWITCHING_PROTOCOLS.equals(status)) {
270 throw new WebSocketClientHandshakeException("Invalid handshake response getStatus: " + status, response);
271 }
272
273 HttpHeaders headers = response.headers();
274 CharSequence upgrade = headers.get(HttpHeaderNames.UPGRADE);
275 if (!HttpHeaderValues.WEBSOCKET.contentEqualsIgnoreCase(upgrade)) {
276 throw new WebSocketClientHandshakeException("Invalid handshake response upgrade: " + upgrade, response);
277 }
278
279 if (!headers.containsValue(HttpHeaderNames.CONNECTION, HttpHeaderValues.UPGRADE, true)) {
280 throw new WebSocketClientHandshakeException("Invalid handshake response connection: "
281 + headers.get(HttpHeaderNames.CONNECTION), response);
282 }
283
284 ByteBuf challenge = response.content();
285 if (!challenge.equals(expectedChallengeResponseBytes)) {
286 throw new WebSocketClientHandshakeException("Invalid challenge", response);
287 }
288 }
289
290 private static String insertRandomCharacters(String key) {
291 int count = WebSocketUtil.randomNumber(1, 12);
292
293 char[] randomChars = new char[count];
294 int randCount = 0;
295 while (randCount < count) {
296 int rand = PlatformDependent.threadLocalRandom().nextInt(0x7e) + 0x21;
297 if (0x21 < rand && rand < 0x2f || 0x3a < rand && rand < 0x7e) {
298 randomChars[randCount] = (char) rand;
299 randCount += 1;
300 }
301 }
302
303 for (int i = 0; i < count; i++) {
304 int split = WebSocketUtil.randomNumber(0, key.length());
305 String part1 = key.substring(0, split);
306 String part2 = key.substring(split);
307 key = part1 + randomChars[i] + part2;
308 }
309
310 return key;
311 }
312
313 private static String insertSpaces(String key, int spaces) {
314 for (int i = 0; i < spaces; i++) {
315 int split = WebSocketUtil.randomNumber(1, key.length() - 1);
316 String part1 = key.substring(0, split);
317 String part2 = key.substring(split);
318 key = part1 + ' ' + part2;
319 }
320
321 return key;
322 }
323
324 @Override
325 protected WebSocketFrameDecoder newWebsocketDecoder() {
326 return new WebSocket00FrameDecoder(maxFramePayloadLength());
327 }
328
329 @Override
330 protected WebSocketFrameEncoder newWebSocketEncoder() {
331 return new WebSocket00FrameEncoder();
332 }
333
334 @Override
335 public WebSocketClientHandshaker00 setForceCloseTimeoutMillis(long forceCloseTimeoutMillis) {
336 super.setForceCloseTimeoutMillis(forceCloseTimeoutMillis);
337 return this;
338 }
339
340 }