1
2
3
4
5
6
7
8
9
10
11
12
13
14 package org.eclipse.jetty.websocket;
15
16 import java.io.IOException;
17 import java.security.MessageDigest;
18 import java.security.NoSuchAlgorithmException;
19 import java.util.Collections;
20 import java.util.List;
21 import javax.servlet.http.HttpServletRequest;
22 import javax.servlet.http.HttpServletResponse;
23
24 import org.eclipse.jetty.http.HttpURI;
25 import org.eclipse.jetty.io.AbstractConnection;
26 import org.eclipse.jetty.io.AsyncEndPoint;
27 import org.eclipse.jetty.io.Buffer;
28 import org.eclipse.jetty.io.ByteArrayBuffer;
29 import org.eclipse.jetty.io.Connection;
30 import org.eclipse.jetty.io.EndPoint;
31 import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
32 import org.eclipse.jetty.io.nio.SelectChannelEndPoint;
33 import org.eclipse.jetty.util.QuotedStringTokenizer;
34 import org.eclipse.jetty.util.StringUtil;
35 import org.eclipse.jetty.util.log.Log;
36 import org.eclipse.jetty.util.log.Logger;
37 import org.eclipse.jetty.websocket.WebSocket.OnFrame;
38
39 public class WebSocketConnectionD00 extends AbstractConnection implements WebSocketConnection, WebSocket.FrameConnection
40 {
41 private static final Logger LOG = Log.getLogger(WebSocketConnectionD00.class);
42
43 public final static byte LENGTH_FRAME=(byte)0x80;
44 public final static byte SENTINEL_FRAME=(byte)0x00;
45
46 final IdleCheck _idle;
47 final WebSocketParser _parser;
48 final WebSocketGenerator _generator;
49 final WebSocket _websocket;
50 final String _protocol;
51 String _key1;
52 String _key2;
53 ByteArrayBuffer _hixieBytes;
54
55 public WebSocketConnectionD00(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol)
56 throws IOException
57 {
58 super(endpoint,timestamp);
59 if (endpoint instanceof AsyncEndPoint)
60 ((AsyncEndPoint)endpoint).cancelIdle();
61
62 _endp.setMaxIdleTime(maxIdleTime);
63
64 _websocket = websocket;
65 _protocol=protocol;
66
67 _generator = new WebSocketGeneratorD00(buffers, _endp);
68 _parser = new WebSocketParserD00(buffers, endpoint, new FrameHandlerD00(_websocket));
69
70 if (_endp instanceof SelectChannelEndPoint)
71 {
72 final SelectChannelEndPoint scep=(SelectChannelEndPoint)_endp;
73 scep.cancelIdle();
74 _idle=new IdleCheck()
75 {
76 public void access(EndPoint endp)
77 {
78 scep.scheduleIdle();
79 }
80 };
81 scep.scheduleIdle();
82 }
83 else
84 {
85 _idle = new IdleCheck()
86 {
87 public void access(EndPoint endp)
88 {}
89 };
90 }
91 }
92
93
94 public org.eclipse.jetty.websocket.WebSocket.Connection getConnection()
95 {
96 return this;
97 }
98
99
100
101 public void setHixieKeys(String key1,String key2)
102 {
103 _key1=key1;
104 _key2=key2;
105 _hixieBytes=new IndirectNIOBuffer(16);
106 }
107
108
109 public Connection handle() throws IOException
110 {
111 try
112 {
113
114 if (_hixieBytes!=null)
115 {
116
117
118 Buffer buffer=_parser.getBuffer();
119 if (buffer!=null && buffer.length()>0)
120 {
121 int l=buffer.length();
122 if (l>(8-_hixieBytes.length()))
123 l=8-_hixieBytes.length();
124 _hixieBytes.put(buffer.peek(buffer.getIndex(),l));
125 buffer.skip(l);
126 }
127
128
129 while(_endp.isOpen())
130 {
131
132 if (_hixieBytes.length()==8)
133 {
134
135
136 doTheHixieHixieShake();
137 _endp.flush(_hixieBytes);
138 _hixieBytes=null;
139 _endp.flush();
140 break;
141 }
142
143
144 int filled=_endp.fill(_hixieBytes);
145 if (filled<0)
146 {
147 _endp.close();
148 break;
149 }
150 }
151
152 if (_websocket instanceof OnFrame)
153 ((OnFrame)_websocket).onHandshake(this);
154 _websocket.onOpen(this);
155 return this;
156 }
157
158
159 boolean progress=true;
160
161 while (progress)
162 {
163 int flushed=_generator.flush();
164 int filled=_parser.parseNext();
165
166 progress = flushed>0 || filled>0;
167
168 if (filled<0 || flushed<0)
169 {
170 _endp.close();
171 break;
172 }
173 }
174 }
175 catch(IOException e)
176 {
177 LOG.debug(e);
178 try
179 {
180 _endp.close();
181 }
182 catch(IOException e2)
183 {
184 LOG.ignore(e2);
185 }
186 throw e;
187 }
188 finally
189 {
190 if (_endp.isOpen())
191 {
192 _idle.access(_endp);
193
194 if (_endp.isInputShutdown() && _generator.isBufferEmpty())
195 _endp.close();
196 else
197 checkWriteable();
198
199 checkWriteable();
200 }
201 }
202 return this;
203 }
204
205
206 private void doTheHixieHixieShake()
207 {
208 byte[] result=WebSocketConnectionD00.doTheHixieHixieShake(
209 WebSocketConnectionD00.hixieCrypt(_key1),
210 WebSocketConnectionD00.hixieCrypt(_key2),
211 _hixieBytes.asArray());
212 _hixieBytes.clear();
213 _hixieBytes.put(result);
214 }
215
216
217 public boolean isOpen()
218 {
219 return _endp!=null&&_endp.isOpen();
220 }
221
222
223 public boolean isIdle()
224 {
225 return _parser.isBufferEmpty() && _generator.isBufferEmpty();
226 }
227
228
229 public boolean isSuspended()
230 {
231 return false;
232 }
233
234
235 public void closed()
236 {
237 _websocket.onClose(WebSocketConnectionD06.CLOSE_NORMAL,"");
238 }
239
240
241
242
243 public void sendMessage(String content) throws IOException
244 {
245 byte[] data = content.getBytes(StringUtil.__UTF8);
246 _generator.addFrame((byte)0,SENTINEL_FRAME,data,0,data.length);
247 _generator.flush();
248 checkWriteable();
249 _idle.access(_endp);
250 }
251
252
253 public void sendMessage(byte[] data, int offset, int length) throws IOException
254 {
255 _generator.addFrame((byte)0,LENGTH_FRAME,data,offset,length);
256 _generator.flush();
257 checkWriteable();
258 _idle.access(_endp);
259 }
260
261
262 public boolean isMore(byte flags)
263 {
264 return (flags&0x8) != 0;
265 }
266
267
268
269
270
271 public void sendControl(byte code, byte[] content, int offset, int length) throws IOException
272 {
273 }
274
275
276 public void sendFrame(byte flags,byte opcode, byte[] content, int offset, int length) throws IOException
277 {
278 _generator.addFrame((byte)0,opcode,content,offset,length);
279 _generator.flush();
280 checkWriteable();
281 _idle.access(_endp);
282 }
283
284
285 public void close(int code, String message)
286 {
287 throw new UnsupportedOperationException();
288 }
289
290
291 public void disconnect()
292 {
293 try
294 {
295 _generator.flush();
296 _endp.close();
297 }
298 catch(IOException e)
299 {
300 LOG.ignore(e);
301 }
302 }
303
304
305 public void fillBuffersFrom(Buffer buffer)
306 {
307 _parser.fill(buffer);
308 }
309
310
311
312 private void checkWriteable()
313 {
314 if (!_generator.isBufferEmpty() && _endp instanceof AsyncEndPoint)
315 ((AsyncEndPoint)_endp).scheduleWrite();
316 }
317
318
319 static long hixieCrypt(String key)
320 {
321
322
323
324 long number=0;
325 int spaces=0;
326 for (char c : key.toCharArray())
327 {
328 if (Character.isDigit(c))
329 number=number*10+(c-'0');
330 else if (c==' ')
331 spaces++;
332 }
333 return number/spaces;
334 }
335
336 public static byte[] doTheHixieHixieShake(long key1,long key2,byte[] key3)
337 {
338 try
339 {
340 MessageDigest md = MessageDigest.getInstance("MD5");
341 byte [] fodder = new byte[16];
342
343 fodder[0]=(byte)(0xff&(key1>>24));
344 fodder[1]=(byte)(0xff&(key1>>16));
345 fodder[2]=(byte)(0xff&(key1>>8));
346 fodder[3]=(byte)(0xff&key1);
347 fodder[4]=(byte)(0xff&(key2>>24));
348 fodder[5]=(byte)(0xff&(key2>>16));
349 fodder[6]=(byte)(0xff&(key2>>8));
350 fodder[7]=(byte)(0xff&key2);
351 System.arraycopy(key3, 0, fodder, 8, 8);
352 md.update(fodder);
353 return md.digest();
354 }
355 catch (NoSuchAlgorithmException e)
356 {
357 throw new IllegalStateException(e);
358 }
359 }
360
361 private interface IdleCheck
362 {
363 void access(EndPoint endp);
364 }
365
366 public void handshake(HttpServletRequest request, HttpServletResponse response, String subprotocol) throws IOException
367 {
368 String uri=request.getRequestURI();
369 String query=request.getQueryString();
370 if (query!=null && query.length()>0)
371 uri+="?"+query;
372 uri=new HttpURI(uri).toString();
373 String host=request.getHeader("Host");
374
375 String origin=request.getHeader("Sec-WebSocket-Origin");
376 if (origin==null)
377 origin=request.getHeader("Origin");
378 if (origin!=null)
379 origin= QuotedStringTokenizer.quoteIfNeeded(origin, "\r\n");
380
381
382 String key1 = request.getHeader("Sec-WebSocket-Key1");
383
384 if (key1!=null)
385 {
386 String key2 = request.getHeader("Sec-WebSocket-Key2");
387 setHixieKeys(key1,key2);
388
389 response.setHeader("Upgrade","WebSocket");
390 response.addHeader("Connection","Upgrade");
391 if (origin!=null)
392 response.addHeader("Sec-WebSocket-Origin",origin);
393 response.addHeader("Sec-WebSocket-Location",(request.isSecure()?"wss://":"ws://")+host+uri);
394 if (subprotocol!=null)
395 response.addHeader("Sec-WebSocket-Protocol",subprotocol);
396 response.sendError(101,"WebSocket Protocol Handshake");
397 }
398 else
399 {
400 response.setHeader("Upgrade","WebSocket");
401 response.addHeader("Connection","Upgrade");
402 response.addHeader("WebSocket-Origin",origin);
403 response.addHeader("WebSocket-Location",(request.isSecure()?"wss://":"ws://")+host+uri);
404 if (subprotocol!=null)
405 response.addHeader("WebSocket-Protocol",subprotocol);
406 response.sendError(101,"Web Socket Protocol Handshake");
407 response.flushBuffer();
408 if (_websocket instanceof OnFrame)
409 ((OnFrame)_websocket).onHandshake(this);
410 _websocket.onOpen(this);
411 }
412 }
413
414 public void setMaxTextMessageSize(int size)
415 {
416 }
417
418 public void setMaxIdleTime(int ms)
419 {
420 try
421 {
422 _endp.setMaxIdleTime(ms);
423 }
424 catch(IOException e)
425 {
426 LOG.warn(e);
427 }
428 }
429
430 public void setMaxBinaryMessageSize(int size)
431 {
432 }
433
434 public int getMaxTextMessageSize()
435 {
436 return -1;
437 }
438
439 public int getMaxIdleTime()
440 {
441 return _endp.getMaxIdleTime();
442 }
443
444 public int getMaxBinaryMessageSize()
445 {
446 return -1;
447 }
448
449 public String getProtocol()
450 {
451 return _protocol;
452 }
453
454 static class FrameHandlerD00 implements WebSocketParser.FrameHandler
455 {
456 final WebSocket _websocket;
457
458 FrameHandlerD00(WebSocket websocket)
459 {
460 _websocket=websocket;
461 }
462
463 public void onFrame(byte flags, byte opcode, Buffer buffer)
464 {
465 try
466 {
467 byte[] array=buffer.array();
468
469 if (opcode==0)
470 {
471 if (_websocket instanceof WebSocket.OnTextMessage)
472 ((WebSocket.OnTextMessage)_websocket).onMessage(buffer.toString(StringUtil.__UTF8));
473 }
474 else
475 {
476 if (_websocket instanceof WebSocket.OnBinaryMessage)
477 ((WebSocket.OnBinaryMessage)_websocket).onMessage(array,buffer.getIndex(),buffer.length());
478 }
479 }
480 catch(ThreadDeath th)
481 {
482 throw th;
483 }
484 catch(Throwable th)
485 {
486 LOG.warn(th);
487 }
488 }
489
490 public void close(int code,String message)
491 {
492 }
493 }
494
495 public boolean isMessageComplete(byte flags)
496 {
497 return true;
498 }
499
500 public byte binaryOpcode()
501 {
502 return LENGTH_FRAME;
503 }
504
505 public byte textOpcode()
506 {
507 return SENTINEL_FRAME;
508 }
509
510 public boolean isControl(byte opcode)
511 {
512 return false;
513 }
514
515 public boolean isText(byte opcode)
516 {
517 return (opcode&LENGTH_FRAME)==0;
518 }
519
520 public boolean isBinary(byte opcode)
521 {
522 return (opcode&LENGTH_FRAME)!=0;
523 }
524
525 public boolean isContinuation(byte opcode)
526 {
527 return false;
528 }
529
530 public boolean isClose(byte opcode)
531 {
532 return false;
533 }
534
535 public boolean isPing(byte opcode)
536 {
537 return false;
538 }
539
540 public boolean isPong(byte opcode)
541 {
542 return false;
543 }
544
545 public List<Extension> getExtensions()
546 {
547 return Collections.emptyList();
548 }
549
550 public byte continuationOpcode()
551 {
552 return 0;
553 }
554
555 public byte finMask()
556 {
557 return 0;
558 }
559
560 public void setAllowFrameFragmentation(boolean allowFragmentation)
561 {
562 }
563
564 public boolean isAllowFrameFragmentation()
565 {
566 return false;
567 }
568 }