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  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             // handle stupid hixie random bytes
114             if (_hixieBytes!=null)
115             {
116 
117                 // take any available bytes from the parser buffer, which may have already been read
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                 // while we are not blocked
129                 while(_endp.isOpen())
130                 {
131                     // do we now have enough
132                     if (_hixieBytes.length()==8)
133                     {
134                         // we have the silly random bytes
135                         // so let's work out the stupid 16 byte reply.
136                         doTheHixieHixieShake();
137                         _endp.flush(_hixieBytes);
138                         _hixieBytes=null;
139                         _endp.flush();
140                         break;
141                     }
142 
143                     // no, then let's fill
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             // handle the framing protocol
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      * {@inheritDoc}
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         // Don't ask me what all this is about.
322         // I think it's pretend secret stuff, kind of
323         // like talking in pig latin!
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 }