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
18 import org.eclipse.jetty.io.Buffer;
19 import org.eclipse.jetty.io.Buffers;
20 import org.eclipse.jetty.io.EndPoint;
21 import org.eclipse.jetty.util.TypeUtil;
22 import org.eclipse.jetty.util.Utf8StringBuilder;
23 import org.eclipse.jetty.util.log.Log;
24
25
26
27 /* ------------------------------------------------------------ */
28 /**
29 * Parser the WebSocket protocol.
30 *
31 */
32 public class WebSocketParserD07 implements WebSocketParser
33 {
34 public enum State {
35
36 START(0), OPCODE(1), LENGTH_7(1), LENGTH_16(2), LENGTH_63(8), MASK(4), PAYLOAD(0), DATA(0), SKIP(1);
37
38 int _needs;
39
40 State(int needs)
41 {
42 _needs=needs;
43 }
44
45 int getNeeds()
46 {
47 return _needs;
48 }
49 };
50
51
52 private final WebSocketBuffers _buffers;
53 private final EndPoint _endp;
54 private final FrameHandler _handler;
55 private final boolean _shouldBeMasked;
56 private State _state;
57 private Buffer _buffer;
58 private byte _flags;
59 private byte _opcode;
60 private int _bytesNeeded;
61 private long _length;
62 private boolean _masked;
63 private final byte[] _mask = new byte[4];
64 private int _m;
65 private boolean _skip;
66
67 /* ------------------------------------------------------------ */
68 /**
69 * @param buffers The buffers to use for parsing. Only the {@link Buffers#getBuffer()} is used.
70 * This should be a direct buffer if binary data is mostly used or an indirect buffer if utf-8 data
71 * is mostly used.
72 * @param endp
73 * @param handler
74 */
75 public WebSocketParserD07(WebSocketBuffers buffers, EndPoint endp, FrameHandler handler, boolean shouldBeMasked)
76 {
77 _buffers=buffers;
78 _endp=endp;
79 _handler=handler;
80 _shouldBeMasked=shouldBeMasked;
81 _state=State.START;
82 }
83
84 /* ------------------------------------------------------------ */
85 public boolean isBufferEmpty()
86 {
87 return _buffer==null || _buffer.length()==0;
88 }
89
90 /* ------------------------------------------------------------ */
91 public Buffer getBuffer()
92 {
93 return _buffer;
94 }
95
96 /* ------------------------------------------------------------ */
97 /** Parse to next event.
98 * Parse to the next {@link WebSocketParser.FrameHandler} event or until no more data is
99 * available. Fill data from the {@link EndPoint} only as necessary.
100 * @return An indication of progress or otherwise. -1 indicates EOF, 0 indicates
101 * that no bytes were read and no messages parsed. A positive number indicates either
102 * the bytes filled or the messages parsed.
103 */
104 public int parseNext()
105 {
106 if (_buffer==null)
107 _buffer=_buffers.getBuffer();
108
109 int total_filled=0;
110 int events=0;
111
112 // Loop until a datagram call back or can't fill anymore
113 while(true)
114 {
115 int available=_buffer.length();
116
117 // Fill buffer if we need a byte or need length
118 while (available<(_state==State.SKIP?1:_bytesNeeded))
119 {
120 // compact to mark (set at start of data)
121 _buffer.compact();
122
123 // if no space, then the data is too big for buffer
124 if (_buffer.space() == 0)
125 throw new IllegalStateException("FULL: "+_state+" "+_bytesNeeded+">"+_buffer.capacity());
126
127 // catch IOExceptions (probably EOF) and try to parse what we have
128 try
129 {
130 int filled=_endp.isOpen()?_endp.fill(_buffer):-1;
131 if (filled<=0)
132 return (total_filled+events)>0?(total_filled+events):filled;
133 total_filled+=filled;
134 available=_buffer.length();
135 }
136 catch(IOException e)
137 {
138 Log.debug(e);
139 return (total_filled+events)>0?(total_filled+events):-1;
140 }
141 }
142
143 // if we are here, then we have sufficient bytes to process the current state.
144
145 // Parse the buffer byte by byte (unless it is STATE_DATA)
146 byte b;
147 while (_state!=State.DATA && available>=(_state==State.SKIP?1:_bytesNeeded))
148 {
149 switch (_state)
150 {
151 case START:
152 _skip=false;
153 _state=State.OPCODE;
154 _bytesNeeded=_state.getNeeds();
155 continue;
156
157 case OPCODE:
158 b=_buffer.get();
159 available--;
160 _opcode=(byte)(b&0xf);
161 _flags=(byte)(0xf&(b>>4));
162
163 if (WebSocketConnectionD07.isControlFrame(_opcode)&&!WebSocketConnectionD07.isLastFrame(_flags))
164 {
165 events++;
166 Log.warn("Fragmented Control from "+_endp);
167 _handler.close(WebSocketConnectionD07.CLOSE_PROTOCOL,"Fragmented control");
168 _skip=true;
169 }
170
171 _state=State.LENGTH_7;
172 _bytesNeeded=_state.getNeeds();
173
174 continue;
175
176 case LENGTH_7:
177 b=_buffer.get();
178 available--;
179 _masked=(b&0x80)!=0;
180 b=(byte)(0x7f&b);
181
182 switch(b)
183 {
184 case 0x7f:
185 _length=0;
186 _state=State.LENGTH_63;
187 break;
188 case 0x7e:
189 _length=0;
190 _state=State.LENGTH_16;
191 break;
192 default:
193 _length=(0x7f&b);
194 _state=_masked?State.MASK:State.PAYLOAD;
195 }
196 _bytesNeeded=_state.getNeeds();
197 continue;
198
199 case LENGTH_16:
200 b=_buffer.get();
201 available--;
202 _length = _length*0x100 + (0xff&b);
203 if (--_bytesNeeded==0)
204 {
205 if (_length>_buffer.capacity())
206 {
207 events++;
208 _handler.close(WebSocketConnectionD07.CLOSE_LARGE,"frame size "+_length+">"+_buffer.capacity());
209 _skip=true;
210 }
211
212 _state=_masked?State.MASK:State.PAYLOAD;
213 _bytesNeeded=_state.getNeeds();
214 }
215 continue;
216
217 case LENGTH_63:
218 b=_buffer.get();
219 available--;
220 _length = _length*0x100 + (0xff&b);
221 if (--_bytesNeeded==0)
222 {
223 _bytesNeeded=(int)_length;
224 if (_length>=_buffer.capacity())
225 {
226 events++;
227 _handler.close(WebSocketConnectionD07.CLOSE_LARGE,"frame size "+_length+">"+_buffer.capacity());
228 _skip=true;
229 }
230
231 _state=_masked?State.MASK:State.PAYLOAD;
232 _bytesNeeded=_state.getNeeds();
233 }
234 continue;
235
236 case MASK:
237 _buffer.get(_mask,0,4);
238 _m=0;
239 available-=4;
240 _state=State.PAYLOAD;
241 _bytesNeeded=_state.getNeeds();
242 break;
243
244 case PAYLOAD:
245 _bytesNeeded=(int)_length;
246 _state=_skip?State.SKIP:State.DATA;
247 break;
248
249 case DATA:
250 break;
251
252 case SKIP:
253 int skip=Math.min(available,_bytesNeeded);
254 _buffer.skip(skip);
255 available-=skip;
256 _bytesNeeded-=skip;
257 if (_bytesNeeded==0)
258 _state=State.START;
259
260 }
261 }
262
263 if (_state==State.DATA && available>=_bytesNeeded)
264 {
265 if ( _masked!=_shouldBeMasked)
266 {
267 _buffer.skip(_bytesNeeded);
268 _state=State.START;
269 events++;
270 _handler.close(WebSocketConnectionD07.CLOSE_PROTOCOL,"bad mask");
271 }
272 else
273 {
274 Buffer data =_buffer.get(_bytesNeeded);
275 if (_masked)
276 {
277 if (data.array()==null)
278 data=_buffer.asMutableBuffer();
279 byte[] array = data.array();
280 final int end=data.putIndex();
281 for (int i=data.getIndex();i<end;i++)
282 array[i]^=_mask[_m++%4];
283 }
284
285 // System.err.printf("%s %s %s >>\n",TypeUtil.toHexString(_flags),TypeUtil.toHexString(_opcode),data.length());
286 events++;
287 _handler.onFrame(_flags, _opcode, data);
288 _bytesNeeded=0;
289 _state=State.START;
290 }
291
292 if (_buffer.length()==0)
293 {
294 _buffers.returnBuffer(_buffer);
295 _buffer=null;
296 }
297
298 return total_filled+events;
299 }
300 }
301 }
302
303 /* ------------------------------------------------------------ */
304 public void fill(Buffer buffer)
305 {
306 if (buffer!=null && buffer.length()>0)
307 {
308 if (_buffer==null)
309 _buffer=_buffers.getBuffer();
310 _buffer.put(buffer);
311 buffer.clear();
312 }
313 }
314
315 }