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.Unpooled;
19 import io.netty.handler.codec.http.DefaultFullHttpRequest;
20 import io.netty.handler.codec.http.FullHttpRequest;
21 import io.netty.handler.codec.http.FullHttpResponse;
22 import io.netty.handler.codec.http.HttpHeaderNames;
23 import io.netty.handler.codec.http.HttpHeaderValues;
24 import io.netty.handler.codec.http.HttpHeaders;
25 import io.netty.handler.codec.http.HttpMethod;
26 import io.netty.handler.codec.http.HttpResponseStatus;
27 import io.netty.handler.codec.http.HttpVersion;
28 import io.netty.util.CharsetUtil;
29 import io.netty.util.internal.logging.InternalLogger;
30 import io.netty.util.internal.logging.InternalLoggerFactory;
31
32 import java.net.URI;
33
34
35
36
37
38
39
40
41 public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker {
42
43 private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketClientHandshaker13.class);
44
45 public static final String MAGIC_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
46
47 private String expectedChallengeResponseString;
48
49 private final boolean allowExtensions;
50 private final boolean performMasking;
51 private final boolean allowMaskMismatch;
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70 public WebSocketClientHandshaker13(URI webSocketURL, WebSocketVersion version, String subprotocol,
71 boolean allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength) {
72 this(webSocketURL, version, subprotocol, allowExtensions, customHeaders, maxFramePayloadLength,
73 true, false);
74 }
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100 public WebSocketClientHandshaker13(URI webSocketURL, WebSocketVersion version, String subprotocol,
101 boolean allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength,
102 boolean performMasking, boolean allowMaskMismatch) {
103 this(webSocketURL, version, subprotocol, allowExtensions, customHeaders, maxFramePayloadLength,
104 performMasking, allowMaskMismatch, DEFAULT_FORCE_CLOSE_TIMEOUT_MILLIS);
105 }
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133 public WebSocketClientHandshaker13(URI webSocketURL, WebSocketVersion version, String subprotocol,
134 boolean allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength,
135 boolean performMasking, boolean allowMaskMismatch,
136 long forceCloseTimeoutMillis) {
137 this(webSocketURL, version, subprotocol, allowExtensions, customHeaders, maxFramePayloadLength, performMasking,
138 allowMaskMismatch, forceCloseTimeoutMillis, false);
139 }
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170 WebSocketClientHandshaker13(URI webSocketURL, WebSocketVersion version, String subprotocol,
171 boolean allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength,
172 boolean performMasking, boolean allowMaskMismatch,
173 long forceCloseTimeoutMillis, boolean absoluteUpgradeUrl) {
174 this(webSocketURL, version, subprotocol, allowExtensions, customHeaders, maxFramePayloadLength, performMasking,
175 allowMaskMismatch, forceCloseTimeoutMillis, absoluteUpgradeUrl, true);
176 }
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210 WebSocketClientHandshaker13(URI webSocketURL, WebSocketVersion version, String subprotocol,
211 boolean allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength,
212 boolean performMasking, boolean allowMaskMismatch,
213 long forceCloseTimeoutMillis, boolean absoluteUpgradeUrl,
214 boolean generateOriginHeader) {
215 super(webSocketURL, version, subprotocol, customHeaders, maxFramePayloadLength, forceCloseTimeoutMillis,
216 absoluteUpgradeUrl, generateOriginHeader);
217 this.allowExtensions = allowExtensions;
218 this.performMasking = performMasking;
219 this.allowMaskMismatch = allowMaskMismatch;
220 }
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239 @Override
240 protected FullHttpRequest newHandshakeRequest() {
241 URI wsURL = uri();
242
243
244 byte[] nonce = WebSocketUtil.randomBytes(16);
245 String key = WebSocketUtil.base64(nonce);
246
247 String acceptSeed = key + MAGIC_GUID;
248 byte[] sha1 = WebSocketUtil.sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
249 expectedChallengeResponseString = WebSocketUtil.base64(sha1);
250
251 if (logger.isDebugEnabled()) {
252 logger.debug(
253 "WebSocket version 13 client handshake key: {}, expected response: {}",
254 key, expectedChallengeResponseString);
255 }
256
257
258 FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, upgradeUrl(wsURL),
259 Unpooled.EMPTY_BUFFER);
260 HttpHeaders headers = request.headers();
261
262 if (customHeaders != null) {
263 headers.add(customHeaders);
264 if (!headers.contains(HttpHeaderNames.HOST)) {
265
266
267
268 headers.set(HttpHeaderNames.HOST, websocketHostValue(wsURL));
269 }
270 } else {
271 headers.set(HttpHeaderNames.HOST, websocketHostValue(wsURL));
272 }
273
274 headers.set(HttpHeaderNames.UPGRADE, HttpHeaderValues.WEBSOCKET)
275 .set(HttpHeaderNames.CONNECTION, HttpHeaderValues.UPGRADE)
276 .set(HttpHeaderNames.SEC_WEBSOCKET_KEY, key);
277
278 if (generateOriginHeader && !headers.contains(HttpHeaderNames.ORIGIN)) {
279 headers.set(HttpHeaderNames.ORIGIN, websocketOriginValue(wsURL));
280 }
281
282 String expectedSubprotocol = expectedSubprotocol();
283 if (expectedSubprotocol != null && !expectedSubprotocol.isEmpty()) {
284 headers.set(HttpHeaderNames.SEC_WEBSOCKET_PROTOCOL, expectedSubprotocol);
285 }
286
287 headers.set(HttpHeaderNames.SEC_WEBSOCKET_VERSION, version().toAsciiString());
288 return request;
289 }
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308 @Override
309 protected void verify(FullHttpResponse response) {
310 HttpResponseStatus status = response.status();
311 if (!HttpResponseStatus.SWITCHING_PROTOCOLS.equals(status)) {
312 throw new WebSocketClientHandshakeException("Invalid handshake response getStatus: " + status, response);
313 }
314
315 HttpHeaders headers = response.headers();
316 CharSequence upgrade = headers.get(HttpHeaderNames.UPGRADE);
317 if (!HttpHeaderValues.WEBSOCKET.contentEqualsIgnoreCase(upgrade)) {
318 throw new WebSocketClientHandshakeException("Invalid handshake response upgrade: " + upgrade, response);
319 }
320
321 if (!headers.containsValue(HttpHeaderNames.CONNECTION, HttpHeaderValues.UPGRADE, true)) {
322 throw new WebSocketClientHandshakeException("Invalid handshake response connection: "
323 + headers.get(HttpHeaderNames.CONNECTION), response);
324 }
325
326 CharSequence accept = headers.get(HttpHeaderNames.SEC_WEBSOCKET_ACCEPT);
327 if (accept == null || !accept.equals(expectedChallengeResponseString)) {
328 throw new WebSocketClientHandshakeException(String.format(
329 "Invalid challenge. Actual: %s. Expected: %s", accept, expectedChallengeResponseString), response);
330 }
331 }
332
333 @Override
334 protected WebSocketFrameDecoder newWebsocketDecoder() {
335 return new WebSocket13FrameDecoder(false, allowExtensions, maxFramePayloadLength(), allowMaskMismatch);
336 }
337
338 @Override
339 protected WebSocketFrameEncoder newWebSocketEncoder() {
340 return new WebSocket13FrameEncoder(performMasking);
341 }
342
343 @Override
344 public WebSocketClientHandshaker13 setForceCloseTimeoutMillis(long forceCloseTimeoutMillis) {
345 super.setForceCloseTimeoutMillis(forceCloseTimeoutMillis);
346 return this;
347 }
348
349 public boolean isAllowExtensions() {
350 return allowExtensions;
351 }
352
353 public boolean isPerformMasking() {
354 return performMasking;
355 }
356
357 public boolean isAllowMaskMismatch() {
358 return allowMaskMismatch;
359 }
360 }