View Javadoc

1   /*******************************************************************************
2    * Copyright (c) 2011 Intalio, Inc.
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    *
8    *   The Eclipse Public License is available at
9    *   http://www.eclipse.org/legal/epl-v10.html
10   *
11   *   The Apache License v2.0 is available at
12   *   http://www.opensource.org/licenses/apache2.0.php
13   *
14   * You may elect to redistribute this code under either of these licenses.
15   *******************************************************************************/
16  // ========================================================================
17  // Copyright (c) 2010 Mort Bay Consulting Pty. Ltd.
18  // ------------------------------------------------------------------------
19  // All rights reserved. This program and the accompanying materials
20  // are made available under the terms of the Eclipse Public License v1.0
21  // and Apache License v2.0 which accompanies this distribution.
22  // The Eclipse Public License is available at
23  // http://www.eclipse.org/legal/epl-v10.html
24  // The Apache License v2.0 is available at
25  // http://www.opensource.org/licenses/apache2.0.php
26  // You may elect to redistribute this code under either of these licenses.
27  // ========================================================================
28  
29  package org.eclipse.jetty.websocket;
30  
31  import java.io.IOException;
32  
33  import org.eclipse.jetty.io.Buffer;
34  import org.eclipse.jetty.io.Buffers;
35  import org.eclipse.jetty.io.EndPoint;
36  import org.eclipse.jetty.util.log.Log;
37  import org.eclipse.jetty.util.log.Logger;
38  
39  
40  
41  /* ------------------------------------------------------------ */
42  /**
43   * Parser the WebSocket protocol.
44   *
45   */
46  public class WebSocketParserD13 implements WebSocketParser
47  {
48      private static final Logger LOG = Log.getLogger(WebSocketParserD13.class);
49  
50      public enum State {
51  
52          START(0), OPCODE(1), LENGTH_7(1), LENGTH_16(2), LENGTH_63(8), MASK(4), PAYLOAD(0), DATA(0), SKIP(1), SEEK_EOF(1);
53  
54          int _needs;
55  
56          State(int needs)
57          {
58              _needs=needs;
59          }
60  
61          int getNeeds()
62          {
63              return _needs;
64          }
65      }
66  
67      private final WebSocketBuffers _buffers;
68      private final EndPoint _endp;
69      private final FrameHandler _handler;
70      private final boolean _shouldBeMasked;
71      private State _state;
72      private Buffer _buffer;
73      private byte _flags;
74      private byte _opcode;
75      private int _bytesNeeded;
76      private long _length;
77      private boolean _masked;
78      private final byte[] _mask = new byte[4];
79      private int _m;
80      private boolean _skip;
81      private boolean _fragmentFrames=true;
82  
83      /* ------------------------------------------------------------ */
84      /**
85       * @param buffers The buffers to use for parsing.  Only the {@link Buffers#getBuffer()} is used.
86       * This should be a direct buffer if binary data is mostly used or an indirect buffer if utf-8 data
87       * is mostly used.
88       * @param endp the endpoint
89       * @param handler the handler to notify when a parse event occurs
90       * @param shouldBeMasked whether masking should be handled
91       */
92      public WebSocketParserD13(WebSocketBuffers buffers, EndPoint endp, FrameHandler handler, boolean shouldBeMasked)
93      {
94          _buffers=buffers;
95          _endp=endp;
96          _handler=handler;
97          _shouldBeMasked=shouldBeMasked;
98          _state=State.START;
99      }
100 
101     /* ------------------------------------------------------------ */
102     /**
103      * @return True if fake fragments should be created for frames larger than the buffer.
104      */
105     public boolean isFakeFragments()
106     {
107         return _fragmentFrames;
108     }
109 
110     /* ------------------------------------------------------------ */
111     /**
112      * @param fakeFragments True if fake fragments should be created for frames larger than the buffer.
113      */
114     public void setFakeFragments(boolean fakeFragments)
115     {
116         _fragmentFrames = fakeFragments;
117     }
118 
119     /* ------------------------------------------------------------ */
120     public boolean isBufferEmpty()
121     {
122         return _buffer==null || _buffer.length()==0;
123     }
124 
125     /* ------------------------------------------------------------ */
126     public Buffer getBuffer()
127     {
128         return _buffer;
129     }
130 
131     /* ------------------------------------------------------------ */
132     /** Parse to next event.
133      * Parse to the next {@link WebSocketParser.FrameHandler} event or until no more data is
134      * available. Fill data from the {@link EndPoint} only as necessary.
135      * @return An indication of progress or otherwise. -1 indicates EOF, 0 indicates
136      * that no bytes were read and no messages parsed. A positive number indicates either
137      * the bytes filled or the messages parsed.
138      */
139     public int parseNext()
140     {
141         if (_buffer==null)
142             _buffer=_buffers.getBuffer();
143 
144         boolean progress=false;
145         int filled=-1;
146 
147         // Loop until a datagram call back or can't fill anymore
148         while(!progress && (!_endp.isInputShutdown()||_buffer.length()>0))
149         {
150             int available=_buffer.length();
151 
152             // Fill buffer if we need a byte or need length
153             while (available<(_state==State.SKIP?1:_bytesNeeded))
154             {
155                 // compact to mark (set at start of data)
156                 _buffer.compact();
157 
158                 // if no space, then the data is too big for buffer
159                 if (_buffer.space() == 0)
160                 {
161                     // Can we send a fake frame?
162                     if (_fragmentFrames && _state==State.DATA)
163                     {
164                         Buffer data =_buffer.get(4*(available/4));
165                         _buffer.compact();
166                         if (_masked)
167                         {
168                             if (data.array()==null)
169                                 data=_buffer.asMutableBuffer();
170                             byte[] array = data.array();
171                             final int end=data.putIndex();
172                             for (int i=data.getIndex();i<end;i++)
173                                 array[i]^=_mask[_m++%4];
174                         }
175 
176                         // System.err.printf("%s %s %s >>\n",TypeUtil.toHexString(_flags),TypeUtil.toHexString(_opcode),data.length());
177                         _bytesNeeded-=data.length();
178                         progress=true;
179                         _handler.onFrame((byte)(_flags&(0xff^WebSocketConnectionRFC6455.FLAG_FIN)), _opcode, data);
180 
181                         _opcode=WebSocketConnectionRFC6455.OP_CONTINUATION;
182                     }
183 
184                     if (_buffer.space() == 0)
185                         throw new IllegalStateException("FULL: "+_state+" "+_bytesNeeded+">"+_buffer.capacity());
186                 }
187 
188                 // catch IOExceptions (probably EOF) and try to parse what we have
189                 try
190                 {
191                     filled=_endp.isInputShutdown()?-1:_endp.fill(_buffer);
192                     available=_buffer.length();
193                     // System.err.printf(">> filled %d/%d%n",filled,available);
194                     if (filled<=0)
195                         break;
196                 }
197                 catch(IOException e)
198                 {
199                     LOG.debug(e);
200                     filled=-1;
201                     break;
202                 }
203             }
204             // Did we get enough?
205             if (available<(_state==State.SKIP?1:_bytesNeeded))
206                 break;
207 
208             // if we are here, then we have sufficient bytes to process the current state.
209             // Parse the buffer byte by byte (unless it is STATE_DATA)
210             byte b;
211             while (_state!=State.DATA && available>=(_state==State.SKIP?1:_bytesNeeded))
212             {
213                 switch (_state)
214                 {
215                     case START:
216                         _skip=false;
217                         _state=_opcode==WebSocketConnectionRFC6455.OP_CLOSE?State.SEEK_EOF:State.OPCODE;
218                         _bytesNeeded=_state.getNeeds();
219                         continue;
220 
221                     case OPCODE:
222                         b=_buffer.get();
223                         available--;
224                         _opcode=(byte)(b&0xf);
225                         _flags=(byte)(0xf&(b>>4));
226 
227                         if (WebSocketConnectionRFC6455.isControlFrame(_opcode)&&!WebSocketConnectionRFC6455.isLastFrame(_flags))
228                         {
229                             LOG.warn("Fragmented Control from "+_endp);
230                             _handler.close(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,"Fragmented control");
231                             progress=true;
232                             _skip=true;
233                         }
234 
235                         _state=State.LENGTH_7;
236                         _bytesNeeded=_state.getNeeds();
237 
238                         continue;
239 
240                     case LENGTH_7:
241                         b=_buffer.get();
242                         available--;
243                         _masked=(b&0x80)!=0;
244                         b=(byte)(0x7f&b);
245 
246                         switch(b)
247                         {
248                             case 0x7f:
249                                 _length=0;
250                                 _state=State.LENGTH_63;
251                                 break;
252                             case 0x7e:
253                                 _length=0;
254                                 _state=State.LENGTH_16;
255                                 break;
256                             default:
257                                 _length=(0x7f&b);
258                                 _state=_masked?State.MASK:State.PAYLOAD;
259                         }
260                         _bytesNeeded=_state.getNeeds();
261                         continue;
262 
263                     case LENGTH_16:
264                         b=_buffer.get();
265                         available--;
266                         _length = _length*0x100 + (0xff&b);
267                         if (--_bytesNeeded==0)
268                         {
269                             if (_length>_buffer.capacity() && !_fragmentFrames)
270                             {
271                                 progress=true;
272                                 _handler.close(WebSocketConnectionRFC6455.CLOSE_POLICY_VIOLATION,"frame size "+_length+">"+_buffer.capacity());
273                                 _skip=true;
274                             }
275 
276                             _state=_masked?State.MASK:State.PAYLOAD;
277                             _bytesNeeded=_state.getNeeds();
278                         }
279                         continue;
280 
281                     case LENGTH_63:
282                         b=_buffer.get();
283                         available--;
284                         _length = _length*0x100 + (0xff&b);
285                         if (--_bytesNeeded==0)
286                         {
287                             _bytesNeeded=(int)_length;
288                             if (_length>=_buffer.capacity() && !_fragmentFrames)
289                             {
290                                 progress=true;
291                                 _handler.close(WebSocketConnectionRFC6455.CLOSE_POLICY_VIOLATION,"frame size "+_length+">"+_buffer.capacity());
292                                 _skip=true;
293                             }
294 
295                             _state=_masked?State.MASK:State.PAYLOAD;
296                             _bytesNeeded=_state.getNeeds();
297                         }
298                         continue;
299 
300                     case MASK:
301                         _buffer.get(_mask,0,4);
302                         _m=0;
303                         available-=4;
304                         _state=State.PAYLOAD;
305                         _bytesNeeded=_state.getNeeds();
306                         break;
307 
308                     case PAYLOAD:
309                         _bytesNeeded=(int)_length;
310                         _state=_skip?State.SKIP:State.DATA;
311                         break;
312 
313                     case DATA:
314                         break;
315 
316                     case SKIP:
317                         int skip=Math.min(available,_bytesNeeded);
318                         progress=true;
319                         _buffer.skip(skip);
320                         available-=skip;
321                         _bytesNeeded-=skip;
322                         if (_bytesNeeded==0)
323                             _state=State.START;
324                         break;
325 
326                     case SEEK_EOF:
327                         progress=true;
328                         _buffer.skip(available);
329                         available=0;
330                         break;
331                 }
332             }
333 
334             if (_state==State.DATA && available>=_bytesNeeded)
335             {
336                 if ( _masked!=_shouldBeMasked)
337                 {
338                     _buffer.skip(_bytesNeeded);
339                     _state=State.START;
340                     progress=true;
341                     _handler.close(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,"Not masked");
342                 }
343                 else
344                 {
345                     Buffer data =_buffer.get(_bytesNeeded);
346                     if (_masked)
347                     {
348                         if (data.array()==null)
349                             data=_buffer.asMutableBuffer();
350                         byte[] array = data.array();
351                         final int end=data.putIndex();
352                         for (int i=data.getIndex();i<end;i++)
353                             array[i]^=_mask[_m++%4];
354                     }
355 
356                     // System.err.printf("%s %s %s >>\n",TypeUtil.toHexString(_flags),TypeUtil.toHexString(_opcode),data.length());
357 
358                     progress=true;
359                     _handler.onFrame(_flags, _opcode, data);
360                     _bytesNeeded=0;
361                     _state=State.START;
362                 }
363 
364                 break;
365             }
366         }
367 
368         return progress?1:filled;
369     }
370 
371     /* ------------------------------------------------------------ */
372     public void fill(Buffer buffer)
373     {
374         if (buffer!=null && buffer.length()>0)
375         {
376             if (_buffer==null)
377                 _buffer=_buffers.getBuffer();
378 
379             _buffer.put(buffer);
380             buffer.clear();
381         }
382     }
383 
384     /* ------------------------------------------------------------ */
385     public void returnBuffer()
386     {
387         if (_buffer!=null && _buffer.length()==0)
388         {
389             _buffers.returnBuffer(_buffer);
390             _buffer=null;
391         }
392     }
393 
394     /* ------------------------------------------------------------ */
395     @Override
396     public String toString()
397     {
398         return String.format("%s@%x state=%s buffer=%s",
399                 getClass().getSimpleName(),
400                 hashCode(),
401                 _state,
402                 _buffer);
403     }
404 }