View Javadoc
1   /*
2    * Copyright 2019 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    *   https://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  package io.netty.handler.codec.http.websocketx;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.channel.ChannelHandlerContext;
20  import io.netty.handler.codec.ReplayingDecoder;
21  import io.netty.handler.codec.TooLongFrameException;
22  import io.netty.util.internal.ObjectUtil;
23  
24  import java.util.List;
25  
26  import static io.netty.buffer.ByteBufUtil.readBytes;
27  
28  /**
29   * Decodes {@link ByteBuf}s into {@link WebSocketFrame}s.
30   * <p>
31   * For the detailed instruction on adding add Web Socket support to your HTTP server, take a look into the
32   * <tt>WebSocketServer</tt> example located in the {@code io.netty.example.http.websocket} package.
33   */
34  public class WebSocket00FrameDecoder extends ReplayingDecoder<Void> implements WebSocketFrameDecoder {
35  
36      static final int DEFAULT_MAX_FRAME_SIZE = 16384;
37  
38      private final long maxFrameSize;
39      private boolean receivedClosingHandshake;
40  
41      public WebSocket00FrameDecoder() {
42          this(DEFAULT_MAX_FRAME_SIZE);
43      }
44  
45      /**
46       * Creates a new instance of {@code WebSocketFrameDecoder} with the specified {@code maxFrameSize}. If the client
47       * sends a frame size larger than {@code maxFrameSize}, the channel will be closed.
48       *
49       * @param maxFrameSize
50       *            the maximum frame size to decode
51       */
52      public WebSocket00FrameDecoder(int maxFrameSize) {
53          this.maxFrameSize = maxFrameSize;
54      }
55  
56      /**
57       * Creates a new instance of {@code WebSocketFrameDecoder} with the specified {@code maxFrameSize}. If the client
58       * sends a frame size larger than {@code maxFrameSize}, the channel will be closed.
59       *
60       * @param decoderConfig
61       *            Frames decoder configuration.
62       */
63      public WebSocket00FrameDecoder(WebSocketDecoderConfig decoderConfig) {
64          this.maxFrameSize = ObjectUtil.checkNotNull(decoderConfig, "decoderConfig").maxFramePayloadLength();
65      }
66  
67      @Override
68      protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
69          // Discard all data received if closing handshake was received before.
70          if (receivedClosingHandshake) {
71              in.skipBytes(actualReadableBytes());
72              return;
73          }
74  
75          // Decode a frame otherwise.
76          byte type = in.readByte();
77          WebSocketFrame frame;
78          if ((type & 0x80) == 0x80) {
79              // If the MSB on type is set, decode the frame length
80              frame = decodeBinaryFrame(ctx, type, in);
81          } else {
82              // Decode a 0xff terminated UTF-8 string
83              frame = decodeTextFrame(ctx, in);
84          }
85  
86          if (frame != null) {
87              out.add(frame);
88          }
89      }
90  
91      private WebSocketFrame decodeBinaryFrame(ChannelHandlerContext ctx, byte type, ByteBuf buffer) {
92          long frameSize = 0;
93          int lengthFieldSize = 0;
94          byte b;
95          do {
96              b = buffer.readByte();
97              frameSize <<= 7;
98              frameSize |= b & 0x7f;
99              if (frameSize > maxFrameSize) {
100                 throw new TooLongFrameException("frame length exceeds " + maxFrameSize + ": " + frameSize);
101             }
102             lengthFieldSize++;
103             if (lengthFieldSize > 8) {
104                 // Perhaps a malicious peer?
105                 throw new TooLongFrameException("frame length field size exceeds 8: " + lengthFieldSize);
106             }
107         } while ((b & 0x80) == 0x80);
108 
109         if (type == (byte) 0xFF && frameSize == 0) {
110             receivedClosingHandshake = true;
111             return new CloseWebSocketFrame(true, 0, ctx.alloc().buffer(0));
112         }
113         ByteBuf payload = readBytes(ctx.alloc(), buffer, (int) frameSize);
114         return new BinaryWebSocketFrame(payload);
115     }
116 
117     private WebSocketFrame decodeTextFrame(ChannelHandlerContext ctx, ByteBuf buffer) {
118         int ridx = buffer.readerIndex();
119         int rbytes = actualReadableBytes();
120         int delimPos = buffer.indexOf(ridx, ridx + rbytes, (byte) 0xFF);
121         if (delimPos == -1) {
122             // Frame delimiter (0xFF) not found
123             if (rbytes > maxFrameSize) {
124                 // Frame length exceeded the maximum
125                 throw new TooLongFrameException("frame length exceeds " + maxFrameSize + ": " + rbytes);
126             } else {
127                 // Wait until more data is received
128                 return null;
129             }
130         }
131 
132         int frameSize = delimPos - ridx;
133         if (frameSize > maxFrameSize) {
134             throw new TooLongFrameException("frame length exceeds " + maxFrameSize + ": " + frameSize);
135         }
136 
137         ByteBuf binaryData = readBytes(ctx.alloc(), buffer, frameSize);
138         buffer.skipBytes(1);
139 
140         int ffDelimPos = binaryData.indexOf(binaryData.readerIndex(), binaryData.writerIndex(), (byte) 0xFF);
141         if (ffDelimPos >= 0) {
142             binaryData.release();
143             throw new IllegalArgumentException("a text frame should not contain 0xFF.");
144         }
145 
146         return new TextWebSocketFrame(binaryData);
147     }
148 }