View Javadoc

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