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