1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.jboss.netty.handler.codec.http.websocketx;
17
18 import org.jboss.netty.buffer.ChannelBuffer;
19 import org.jboss.netty.buffer.ChannelBuffers;
20 import org.jboss.netty.channel.Channel;
21 import org.jboss.netty.channel.ChannelFuture;
22 import org.jboss.netty.channel.ChannelFutureListener;
23 import org.jboss.netty.channel.ChannelPipeline;
24 import org.jboss.netty.channel.DefaultChannelFuture;
25 import org.jboss.netty.handler.codec.http.DefaultHttpRequest;
26 import org.jboss.netty.handler.codec.http.HttpHeaders.Names;
27 import org.jboss.netty.handler.codec.http.HttpHeaders.Values;
28 import org.jboss.netty.handler.codec.http.HttpMethod;
29 import org.jboss.netty.handler.codec.http.HttpRequest;
30 import org.jboss.netty.handler.codec.http.HttpRequestEncoder;
31 import org.jboss.netty.handler.codec.http.HttpResponse;
32 import org.jboss.netty.handler.codec.http.HttpResponseDecoder;
33 import org.jboss.netty.handler.codec.http.HttpResponseStatus;
34 import org.jboss.netty.handler.codec.http.HttpVersion;
35
36 import java.net.URI;
37 import java.nio.ByteBuffer;
38 import java.util.Map;
39
40
41
42
43
44
45
46
47
48
49
50 public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker {
51
52 private ChannelBuffer expectedChallengeResponseBytes;
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67 public WebSocketClientHandshaker00(URI webSocketURL, WebSocketVersion version, String subprotocol,
68 Map<String, String> customHeaders) {
69 this(webSocketURL, version, subprotocol, customHeaders, Long.MAX_VALUE);
70 }
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87 public WebSocketClientHandshaker00(URI webSocketURL, WebSocketVersion version, String subprotocol,
88 Map<String, String> customHeaders, long maxFramePayloadLength) {
89 super(webSocketURL, version, subprotocol, customHeaders, maxFramePayloadLength);
90 }
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113 @Override
114 public ChannelFuture handshake(Channel channel) {
115
116 int spaces1 = WebSocketUtil.randomNumber(1, 12);
117 int spaces2 = WebSocketUtil.randomNumber(1, 12);
118
119 int max1 = Integer.MAX_VALUE / spaces1;
120 int max2 = Integer.MAX_VALUE / spaces2;
121
122 int number1 = WebSocketUtil.randomNumber(0, max1);
123 int number2 = WebSocketUtil.randomNumber(0, max2);
124
125 int product1 = number1 * spaces1;
126 int product2 = number2 * spaces2;
127
128 String key1 = Integer.toString(product1);
129 String key2 = Integer.toString(product2);
130
131 key1 = insertRandomCharacters(key1);
132 key2 = insertRandomCharacters(key2);
133
134 key1 = insertSpaces(key1, spaces1);
135 key2 = insertSpaces(key2, spaces2);
136
137 byte[] key3 = WebSocketUtil.randomBytes(8);
138
139 ByteBuffer buffer = ByteBuffer.allocate(4);
140 buffer.putInt(number1);
141 byte[] number1Array = buffer.array();
142 buffer = ByteBuffer.allocate(4);
143 buffer.putInt(number2);
144 byte[] number2Array = buffer.array();
145
146 byte[] challenge = new byte[16];
147 System.arraycopy(number1Array, 0, challenge, 0, 4);
148 System.arraycopy(number2Array, 0, challenge, 4, 4);
149 System.arraycopy(key3, 0, challenge, 8, 8);
150 expectedChallengeResponseBytes = WebSocketUtil.md5(ChannelBuffers.wrappedBuffer(challenge));
151
152
153 URI wsURL = getWebSocketUrl();
154 String path = wsURL.getPath();
155 if (wsURL.getQuery() != null && wsURL.getQuery().length() > 0) {
156 path = wsURL.getPath() + '?' + wsURL.getQuery();
157 }
158
159 if (path == null || path.length() == 0) {
160 path = "/";
161 }
162
163
164 HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, path);
165 request.addHeader(Names.UPGRADE, Values.WEBSOCKET);
166 request.addHeader(Names.CONNECTION, Values.UPGRADE);
167 request.addHeader(Names.HOST, wsURL.getHost());
168
169 int wsPort = wsURL.getPort();
170 String originValue = "http://" + wsURL.getHost();
171 if (wsPort != 80 && wsPort != 443) {
172
173
174 originValue = originValue + ':' + wsPort;
175 }
176 request.addHeader(Names.ORIGIN, originValue);
177
178 request.addHeader(Names.SEC_WEBSOCKET_KEY1, key1);
179 request.addHeader(Names.SEC_WEBSOCKET_KEY2, key2);
180 String expectedSubprotocol = getExpectedSubprotocol();
181 if (expectedSubprotocol != null && expectedSubprotocol.length() != 0) {
182 request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, expectedSubprotocol);
183 }
184
185 if (customHeaders != null) {
186 for (Map.Entry<String, String> e: customHeaders.entrySet()) {
187 request.addHeader(e.getKey(), e.getValue());
188 }
189 }
190
191
192
193 request.setHeader(Names.CONTENT_LENGTH, key3.length);
194 request.setContent(ChannelBuffers.copiedBuffer(key3));
195
196 final ChannelFuture handshakeFuture = new DefaultChannelFuture(channel, false);
197 ChannelFuture future = channel.write(request);
198
199 future.addListener(new ChannelFutureListener() {
200 public void operationComplete(ChannelFuture future) {
201 ChannelPipeline p = future.getChannel().getPipeline();
202 p.replace(HttpRequestEncoder.class, "ws-encoder", new WebSocket00FrameEncoder());
203
204 if (future.isSuccess()) {
205 handshakeFuture.setSuccess();
206 } else {
207 handshakeFuture.setFailure(future.getCause());
208 }
209 }
210 });
211
212 return handshakeFuture;
213 }
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237 @Override
238 public void finishHandshake(Channel channel, HttpResponse response) {
239 final HttpResponseStatus status = new HttpResponseStatus(101, "WebSocket Protocol Handshake");
240
241 if (!response.getStatus().equals(status)) {
242 throw new WebSocketHandshakeException("Invalid handshake response status: " + response.getStatus());
243 }
244
245 String upgrade = response.getHeader(Names.UPGRADE);
246 if (!Values.WEBSOCKET.equals(upgrade)) {
247 throw new WebSocketHandshakeException("Invalid handshake response upgrade: "
248 + upgrade);
249 }
250
251 String connection = response.getHeader(Names.CONNECTION);
252 if (!Values.UPGRADE.equals(connection)) {
253 throw new WebSocketHandshakeException("Invalid handshake response connection: "
254 + connection);
255 }
256
257 ChannelBuffer challenge = response.getContent();
258 if (!challenge.equals(expectedChallengeResponseBytes)) {
259 throw new WebSocketHandshakeException("Invalid challenge");
260 }
261
262 String subprotocol = response.getHeader(Names.SEC_WEBSOCKET_PROTOCOL);
263 setActualSubprotocol(subprotocol);
264
265 setHandshakeComplete();
266
267 channel.getPipeline().get(HttpResponseDecoder.class).replace("ws-decoder",
268 new WebSocket00FrameDecoder(getMaxFramePayloadLength()));
269
270
271 }
272
273 private static String insertRandomCharacters(String key) {
274 int count = WebSocketUtil.randomNumber(1, 12);
275
276 char[] randomChars = new char[count];
277 int randCount = 0;
278 while (randCount < count) {
279 int rand = (int) (Math.random() * 0x7e + 0x21);
280 if (0x21 < rand && rand < 0x2f || 0x3a < rand && rand < 0x7e) {
281 randomChars[randCount] = (char) rand;
282 randCount += 1;
283 }
284 }
285
286 for (int i = 0; i < count; i++) {
287 int split = WebSocketUtil.randomNumber(0, key.length());
288 String part1 = key.substring(0, split);
289 String part2 = key.substring(split);
290 key = part1 + randomChars[i] + part2;
291 }
292
293 return key;
294 }
295
296 private static String insertSpaces(String key, int spaces) {
297 for (int i = 0; i < spaces; i++) {
298 int split = WebSocketUtil.randomNumber(1, key.length() - 1);
299 String part1 = key.substring(0, split);
300 String part2 = key.substring(split);
301 key = part1 + ' ' + part2;
302 }
303
304 return key;
305 }
306
307 }