View Javadoc
1   /*
2    * Copyright 2012 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  // (BSD License: http://www.opensource.org/licenses/bsd-license)
17  //
18  // Copyright (c) 2011, Joe Walnes and contributors
19  // All rights reserved.
20  //
21  // Redistribution and use in source and binary forms, with or
22  // without modification, are permitted provided that the
23  // following conditions are met:
24  //
25  // * Redistributions of source code must retain the above
26  // copyright notice, this list of conditions and the
27  // following disclaimer.
28  //
29  // * Redistributions in binary form must reproduce the above
30  // copyright notice, this list of conditions and the
31  // following disclaimer in the documentation and/or other
32  // materials provided with the distribution.
33  //
34  // * Neither the name of the Webbit nor the names of
35  // its contributors may be used to endorse or promote products
36  // derived from this software without specific prior written
37  // permission.
38  //
39  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
40  // CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
41  // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
42  // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
43  // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
44  // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
45  // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
46  // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
47  // GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
48  // BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
49  // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
50  // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
51  // OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
52  // POSSIBILITY OF SUCH DAMAGE.
53  
54  package io.netty.handler.codec.http.websocketx;
55  
56  import io.netty.buffer.ByteBuf;
57  import io.netty.buffer.Unpooled;
58  import io.netty.channel.ChannelFutureListener;
59  import io.netty.channel.ChannelHandlerContext;
60  import io.netty.handler.codec.ByteToMessageDecoder;
61  import io.netty.handler.codec.CorruptedFrameException;
62  import io.netty.handler.codec.TooLongFrameException;
63  import io.netty.util.internal.logging.InternalLogger;
64  import io.netty.util.internal.logging.InternalLoggerFactory;
65  
66  import java.nio.ByteOrder;
67  import java.util.List;
68  
69  import static io.netty.buffer.ByteBufUtil.readBytes;
70  
71  /**
72   * Decodes a web socket frame from wire protocol version 8 format. This code was forked from <a
73   * href="https://github.com/joewalnes/webbit">webbit</a> and modified.
74   */
75  public class WebSocket08FrameDecoder extends ByteToMessageDecoder
76          implements WebSocketFrameDecoder {
77  
78      enum State {
79          READING_FIRST,
80          READING_SECOND,
81          READING_SIZE,
82          MASKING_KEY,
83          PAYLOAD,
84          CORRUPT
85      }
86  
87      private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocket08FrameDecoder.class);
88  
89      private static final byte OPCODE_CONT = 0x0;
90      private static final byte OPCODE_TEXT = 0x1;
91      private static final byte OPCODE_BINARY = 0x2;
92      private static final byte OPCODE_CLOSE = 0x8;
93      private static final byte OPCODE_PING = 0x9;
94      private static final byte OPCODE_PONG = 0xA;
95  
96      private final long maxFramePayloadLength;
97      private final boolean allowExtensions;
98      private final boolean expectMaskedFrames;
99      private final boolean allowMaskMismatch;
100 
101     private int fragmentedFramesCount;
102     private boolean frameFinalFlag;
103     private boolean frameMasked;
104     private int frameRsv;
105     private int frameOpcode;
106     private long framePayloadLength;
107     private byte[] maskingKey;
108     private int framePayloadLen1;
109     private boolean receivedClosingHandshake;
110     private State state = State.READING_FIRST;
111 
112     /**
113      * Constructor
114      *
115      * @param expectMaskedFrames
116      *            Web socket servers must set this to true processed incoming masked payload. Client implementations
117      *            must set this to false.
118      * @param allowExtensions
119      *            Flag to allow reserved extension bits to be used or not
120      * @param maxFramePayloadLength
121      *            Maximum length of a frame's payload. Setting this to an appropriate value for you application
122      *            helps check for denial of services attacks.
123      */
124     public WebSocket08FrameDecoder(boolean expectMaskedFrames, boolean allowExtensions, int maxFramePayloadLength) {
125         this(expectMaskedFrames, allowExtensions, maxFramePayloadLength, false);
126     }
127 
128     /**
129      * Constructor
130      *
131      * @param expectMaskedFrames
132      *            Web socket servers must set this to true processed incoming masked payload. Client implementations
133      *            must set this to false.
134      * @param allowExtensions
135      *            Flag to allow reserved extension bits to be used or not
136      * @param maxFramePayloadLength
137      *            Maximum length of a frame's payload. Setting this to an appropriate value for you application
138      *            helps check for denial of services attacks.
139      * @param allowMaskMismatch
140      *            When set to true, frames which are not masked properly according to the standard will still be
141      *            accepted.
142      */
143     public WebSocket08FrameDecoder(boolean expectMaskedFrames, boolean allowExtensions, int maxFramePayloadLength,
144                                    boolean allowMaskMismatch) {
145         this.expectMaskedFrames = expectMaskedFrames;
146         this.allowMaskMismatch = allowMaskMismatch;
147         this.allowExtensions = allowExtensions;
148         this.maxFramePayloadLength = maxFramePayloadLength;
149     }
150 
151     @Override
152     protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
153 
154         // Discard all data received if closing handshake was received before.
155         if (receivedClosingHandshake) {
156             in.skipBytes(actualReadableBytes());
157             return;
158         }
159             switch (state) {
160                 case READING_FIRST:
161                     if (!in.isReadable()) {
162                         return;
163                     }
164 
165                     framePayloadLength = 0;
166 
167                     // FIN, RSV, OPCODE
168                     byte b = in.readByte();
169                     frameFinalFlag = (b & 0x80) != 0;
170                     frameRsv = (b & 0x70) >> 4;
171                     frameOpcode = b & 0x0F;
172 
173                     if (logger.isDebugEnabled()) {
174                         logger.debug("Decoding WebSocket Frame opCode={}", frameOpcode);
175                     }
176 
177                     state = State.READING_SECOND;
178                 case READING_SECOND:
179                     if (!in.isReadable()) {
180                         return;
181                     }
182                     // MASK, PAYLOAD LEN 1
183                     b = in.readByte();
184                     frameMasked = (b & 0x80) != 0;
185                     framePayloadLen1 = b & 0x7F;
186 
187                     if (frameRsv != 0 && !allowExtensions) {
188                         protocolViolation(ctx, "RSV != 0 and no extension negotiated, RSV:" + frameRsv);
189                         return;
190                     }
191 
192                     if (!allowMaskMismatch && expectMaskedFrames != frameMasked) {
193                         protocolViolation(ctx, "received a frame that is not masked as expected");
194                         return;
195                     }
196 
197                     if (frameOpcode > 7) { // control frame (have MSB in opcode set)
198 
199                         // control frames MUST NOT be fragmented
200                         if (!frameFinalFlag) {
201                             protocolViolation(ctx, "fragmented control frame");
202                             return;
203                         }
204 
205                         // control frames MUST have payload 125 octets or less
206                         if (framePayloadLen1 > 125) {
207                             protocolViolation(ctx, "control frame with payload length > 125 octets");
208                             return;
209                         }
210 
211                         // check for reserved control frame opcodes
212                         if (!(frameOpcode == OPCODE_CLOSE || frameOpcode == OPCODE_PING
213                                 || frameOpcode == OPCODE_PONG)) {
214                             protocolViolation(ctx, "control frame using reserved opcode " + frameOpcode);
215                             return;
216                         }
217 
218                         // close frame : if there is a body, the first two bytes of the
219                         // body MUST be a 2-byte unsigned integer (in network byte
220                         // order) representing a getStatus code
221                         if (frameOpcode == 8 && framePayloadLen1 == 1) {
222                             protocolViolation(ctx, "received close control frame with payload len 1");
223                             return;
224                         }
225                     } else { // data frame
226                         // check for reserved data frame opcodes
227                         if (!(frameOpcode == OPCODE_CONT || frameOpcode == OPCODE_TEXT
228                                 || frameOpcode == OPCODE_BINARY)) {
229                             protocolViolation(ctx, "data frame using reserved opcode " + frameOpcode);
230                             return;
231                         }
232 
233                         // check opcode vs message fragmentation state 1/2
234                         if (fragmentedFramesCount == 0 && frameOpcode == OPCODE_CONT) {
235                             protocolViolation(ctx, "received continuation data frame outside fragmented message");
236                             return;
237                         }
238 
239                         // check opcode vs message fragmentation state 2/2
240                         if (fragmentedFramesCount != 0 && frameOpcode != OPCODE_CONT && frameOpcode != OPCODE_PING) {
241                             protocolViolation(ctx,
242                                     "received non-continuation data frame while inside fragmented message");
243                             return;
244                         }
245                     }
246 
247                     state = State.READING_SIZE;
248                  case READING_SIZE:
249 
250                     // Read frame payload length
251                     if (framePayloadLen1 == 126) {
252                         if (in.readableBytes() < 2) {
253                             return;
254                         }
255                         framePayloadLength = in.readUnsignedShort();
256                         if (framePayloadLength < 126) {
257                             protocolViolation(ctx, "invalid data frame length (not using minimal length encoding)");
258                             return;
259                         }
260                     } else if (framePayloadLen1 == 127) {
261                         if (in.readableBytes() < 8) {
262                             return;
263                         }
264                         framePayloadLength = in.readLong();
265                         // TODO: check if it's bigger than 0x7FFFFFFFFFFFFFFF, Maybe
266                         // just check if it's negative?
267 
268                         if (framePayloadLength < 65536) {
269                             protocolViolation(ctx, "invalid data frame length (not using minimal length encoding)");
270                             return;
271                         }
272                     } else {
273                         framePayloadLength = framePayloadLen1;
274                     }
275 
276                     if (framePayloadLength > maxFramePayloadLength) {
277                         protocolViolation(ctx, "Max frame length of " + maxFramePayloadLength + " has been exceeded.");
278                         return;
279                     }
280 
281                     if (logger.isDebugEnabled()) {
282                         logger.debug("Decoding WebSocket Frame length={}", framePayloadLength);
283                     }
284 
285                     state = State.MASKING_KEY;
286                 case MASKING_KEY:
287                     if (frameMasked) {
288                         if (in.readableBytes() < 4) {
289                             return;
290                         }
291                         if (maskingKey == null) {
292                             maskingKey = new byte[4];
293                         }
294                         in.readBytes(maskingKey);
295                     }
296                     state = State.PAYLOAD;
297                 case PAYLOAD:
298                     if (in.readableBytes() < framePayloadLength) {
299                         return;
300                     }
301 
302                     ByteBuf payloadBuffer = null;
303                     try {
304                         payloadBuffer = readBytes(ctx.alloc(), in, toFrameLength(framePayloadLength));
305 
306                         // Now we have all the data, the next checkpoint must be the next
307                         // frame
308                         state = State.READING_FIRST;
309 
310                         // Unmask data if needed
311                         if (frameMasked) {
312                             unmask(payloadBuffer);
313                         }
314 
315                         // Processing ping/pong/close frames because they cannot be
316                         // fragmented
317                         if (frameOpcode == OPCODE_PING) {
318                             out.add(new PingWebSocketFrame(frameFinalFlag, frameRsv, payloadBuffer));
319                             payloadBuffer = null;
320                             return;
321                         }
322                         if (frameOpcode == OPCODE_PONG) {
323                             out.add(new PongWebSocketFrame(frameFinalFlag, frameRsv, payloadBuffer));
324                             payloadBuffer = null;
325                             return;
326                         }
327                         if (frameOpcode == OPCODE_CLOSE) {
328                             receivedClosingHandshake = true;
329                             checkCloseFrameBody(ctx, payloadBuffer);
330                             out.add(new CloseWebSocketFrame(frameFinalFlag, frameRsv, payloadBuffer));
331                             payloadBuffer = null;
332                             return;
333                         }
334 
335                         // Processing for possible fragmented messages for text and binary
336                         // frames
337                         if (frameFinalFlag) {
338                             // Final frame of the sequence. Apparently ping frames are
339                             // allowed in the middle of a fragmented message
340                             if (frameOpcode != OPCODE_PING) {
341                                 fragmentedFramesCount = 0;
342                             }
343                         } else {
344                             // Increment counter
345                             fragmentedFramesCount++;
346                         }
347 
348                         // Return the frame
349                         if (frameOpcode == OPCODE_TEXT) {
350                             out.add(new TextWebSocketFrame(frameFinalFlag, frameRsv, payloadBuffer));
351                             payloadBuffer = null;
352                             return;
353                         } else if (frameOpcode == OPCODE_BINARY) {
354                             out.add(new BinaryWebSocketFrame(frameFinalFlag, frameRsv, payloadBuffer));
355                             payloadBuffer = null;
356                             return;
357                         } else if (frameOpcode == OPCODE_CONT) {
358                             out.add(new ContinuationWebSocketFrame(frameFinalFlag, frameRsv,
359                                     payloadBuffer));
360                             payloadBuffer = null;
361                             return;
362                         } else {
363                             throw new UnsupportedOperationException("Cannot decode web socket frame with opcode: "
364                                     + frameOpcode);
365                         }
366                     } finally {
367                         if (payloadBuffer != null) {
368                             payloadBuffer.release();
369                         }
370                     }
371                 case CORRUPT:
372                     if (in.isReadable()) {
373                         // If we don't keep reading Netty will throw an exception saying
374                         // we can't return null if no bytes read and state not changed.
375                         in.readByte();
376                     }
377                     return;
378                 default:
379                     throw new Error("Shouldn't reach here.");
380             }
381     }
382 
383     private void unmask(ByteBuf frame) {
384         int i = frame.readerIndex();
385         int end = frame.writerIndex();
386 
387         ByteOrder order = frame.order();
388 
389         // Remark: & 0xFF is necessary because Java will do signed expansion from
390         // byte to int which we don't want.
391         int intMask = ((maskingKey[0] & 0xFF) << 24)
392                     | ((maskingKey[1] & 0xFF) << 16)
393                     | ((maskingKey[2] & 0xFF) << 8)
394                     | (maskingKey[3] & 0xFF);
395 
396         // If the byte order of our buffers it little endian we have to bring our mask
397         // into the same format, because getInt() and writeInt() will use a reversed byte order
398         if (order == ByteOrder.LITTLE_ENDIAN) {
399             intMask = Integer.reverseBytes(intMask);
400         }
401 
402         for (; i + 3 < end; i += 4) {
403             int unmasked = frame.getInt(i) ^ intMask;
404             frame.setInt(i, unmasked);
405         }
406         for (; i < end; i++) {
407             frame.setByte(i, frame.getByte(i) ^ maskingKey[i % 4]);
408         }
409     }
410 
411     private void protocolViolation(ChannelHandlerContext ctx, String reason) {
412         protocolViolation(ctx, new CorruptedFrameException(reason));
413     }
414 
415     private void protocolViolation(ChannelHandlerContext ctx, CorruptedFrameException ex) {
416         state = State.CORRUPT;
417         if (ctx.channel().isActive()) {
418             Object closeMessage;
419             if (receivedClosingHandshake) {
420                 closeMessage = Unpooled.EMPTY_BUFFER;
421             } else {
422                 closeMessage = new CloseWebSocketFrame(1002, null);
423             }
424             ctx.writeAndFlush(closeMessage).addListener(ChannelFutureListener.CLOSE);
425         }
426         throw ex;
427     }
428 
429     private static int toFrameLength(long l) {
430         if (l > Integer.MAX_VALUE) {
431             throw new TooLongFrameException("Length:" + l);
432         } else {
433             return (int) l;
434         }
435     }
436 
437     /** */
438     protected void checkCloseFrameBody(
439             ChannelHandlerContext ctx, ByteBuf buffer) {
440         if (buffer == null || !buffer.isReadable()) {
441             return;
442         }
443         if (buffer.readableBytes() == 1) {
444             protocolViolation(ctx, "Invalid close frame body");
445         }
446 
447         // Save reader index
448         int idx = buffer.readerIndex();
449         buffer.readerIndex(0);
450 
451         // Must have 2 byte integer within the valid range
452         int statusCode = buffer.readShort();
453         if (statusCode >= 0 && statusCode <= 999 || statusCode >= 1004 && statusCode <= 1006
454                 || statusCode >= 1012 && statusCode <= 2999) {
455             protocolViolation(ctx, "Invalid close frame getStatus code: " + statusCode);
456         }
457 
458         // May have UTF-8 message
459         if (buffer.isReadable()) {
460             try {
461                 new Utf8Validator().check(buffer);
462             } catch (CorruptedFrameException ex) {
463                 protocolViolation(ctx, ex);
464             }
465         }
466 
467         // Restore reader index
468         buffer.readerIndex(idx);
469     }
470 }