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.CorruptedFrameException;
61 import io.netty.handler.codec.ReplayingDecoder;
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 /**
70 * Decodes a web socket frame from wire protocol version 8 format. This code was forked from <a
71 * href="https://github.com/joewalnes/webbit">webbit</a> and modified.
72 */
73 public class WebSocket08FrameDecoder extends ReplayingDecoder<WebSocket08FrameDecoder.State>
74 implements WebSocketFrameDecoder {
75
76 private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocket08FrameDecoder.class);
77
78 private static final byte OPCODE_CONT = 0x0;
79 private static final byte OPCODE_TEXT = 0x1;
80 private static final byte OPCODE_BINARY = 0x2;
81 private static final byte OPCODE_CLOSE = 0x8;
82 private static final byte OPCODE_PING = 0x9;
83 private static final byte OPCODE_PONG = 0xA;
84
85 private int fragmentedFramesCount;
86 private final long maxFramePayloadLength;
87 private boolean frameFinalFlag;
88 private int frameRsv;
89 private int frameOpcode;
90 private long framePayloadLength;
91 private ByteBuf framePayload;
92 private int framePayloadBytesRead;
93 private byte[] maskingKey;
94 private ByteBuf payloadBuffer;
95 private final boolean allowExtensions;
96 private final boolean maskedPayload;
97 private boolean receivedClosingHandshake;
98 private Utf8Validator utf8Validator;
99
100 enum State {
101 FRAME_START, MASKING_KEY, PAYLOAD, CORRUPT
102 }
103
104 /**
105 * Constructor
106 *
107 * @param maskedPayload
108 * Web socket servers must set this to true processed incoming masked payload. Client implementations
109 * must set this to false.
110 * @param allowExtensions
111 * Flag to allow reserved extension bits to be used or not
112 * @param maxFramePayloadLength
113 * Maximum length of a frame's payload. Setting this to an appropriate value for you application
114 * helps check for denial of services attacks.
115 */
116 public WebSocket08FrameDecoder(boolean maskedPayload, boolean allowExtensions, int maxFramePayloadLength) {
117 super(State.FRAME_START);
118 this.maskedPayload = maskedPayload;
119 this.allowExtensions = allowExtensions;
120 this.maxFramePayloadLength = maxFramePayloadLength;
121 }
122
123 @Override
124 protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
125
126 // Discard all data received if closing handshake was received before.
127 if (receivedClosingHandshake) {
128 in.skipBytes(actualReadableBytes());
129 return;
130 }
131
132 try {
133 switch (state()) {
134 case FRAME_START:
135 framePayloadBytesRead = 0;
136 framePayloadLength = -1;
137 framePayload = null;
138 payloadBuffer = null;
139
140 // FIN, RSV, OPCODE
141 byte b = in.readByte();
142 frameFinalFlag = (b & 0x80) != 0;
143 frameRsv = (b & 0x70) >> 4;
144 frameOpcode = b & 0x0F;
145
146 if (logger.isDebugEnabled()) {
147 logger.debug("Decoding WebSocket Frame opCode={}", frameOpcode);
148 }
149
150 // MASK, PAYLOAD LEN 1
151 b = in.readByte();
152 boolean frameMasked = (b & 0x80) != 0;
153 int framePayloadLen1 = b & 0x7F;
154
155 if (frameRsv != 0 && !allowExtensions) {
156 protocolViolation(ctx, "RSV != 0 and no extension negotiated, RSV:" + frameRsv);
157 return;
158 }
159
160 if (maskedPayload && !frameMasked) {
161 protocolViolation(ctx, "unmasked client to server frame");
162 return;
163 }
164 if (frameOpcode > 7) { // control frame (have MSB in opcode set)
165
166 // control frames MUST NOT be fragmented
167 if (!frameFinalFlag) {
168 protocolViolation(ctx, "fragmented control frame");
169 return;
170 }
171
172 // control frames MUST have payload 125 octets or less
173 if (framePayloadLen1 > 125) {
174 protocolViolation(ctx, "control frame with payload length > 125 octets");
175 return;
176 }
177
178 // check for reserved control frame opcodes
179 if (!(frameOpcode == OPCODE_CLOSE || frameOpcode == OPCODE_PING
180 || frameOpcode == OPCODE_PONG)) {
181 protocolViolation(ctx, "control frame using reserved opcode " + frameOpcode);
182 return;
183 }
184
185 // close frame : if there is a body, the first two bytes of the
186 // body MUST be a 2-byte unsigned integer (in network byte
187 // order) representing a getStatus code
188 if (frameOpcode == 8 && framePayloadLen1 == 1) {
189 protocolViolation(ctx, "received close control frame with payload len 1");
190 return;
191 }
192 } else { // data frame
193 // check for reserved data frame opcodes
194 if (!(frameOpcode == OPCODE_CONT || frameOpcode == OPCODE_TEXT
195 || frameOpcode == OPCODE_BINARY)) {
196 protocolViolation(ctx, "data frame using reserved opcode " + frameOpcode);
197 return;
198 }
199
200 // check opcode vs message fragmentation state 1/2
201 if (fragmentedFramesCount == 0 && frameOpcode == OPCODE_CONT) {
202 protocolViolation(ctx, "received continuation data frame outside fragmented message");
203 return;
204 }
205
206 // check opcode vs message fragmentation state 2/2
207 if (fragmentedFramesCount != 0 && frameOpcode != OPCODE_CONT && frameOpcode != OPCODE_PING) {
208 protocolViolation(ctx,
209 "received non-continuation data frame while inside fragmented message");
210 return;
211 }
212 }
213
214 // Read frame payload length
215 if (framePayloadLen1 == 126) {
216 framePayloadLength = in.readUnsignedShort();
217 if (framePayloadLength < 126) {
218 protocolViolation(ctx, "invalid data frame length (not using minimal length encoding)");
219 return;
220 }
221 } else if (framePayloadLen1 == 127) {
222 framePayloadLength = in.readLong();
223 // TODO: check if it's bigger than 0x7FFFFFFFFFFFFFFF, Maybe
224 // just check if it's negative?
225
226 if (framePayloadLength < 65536) {
227 protocolViolation(ctx, "invalid data frame length (not using minimal length encoding)");
228 return;
229 }
230 } else {
231 framePayloadLength = framePayloadLen1;
232 }
233
234 if (framePayloadLength > maxFramePayloadLength) {
235 protocolViolation(ctx, "Max frame length of " + maxFramePayloadLength + " has been exceeded.");
236 return;
237 }
238
239 if (logger.isDebugEnabled()) {
240 logger.debug("Decoding WebSocket Frame length={}", framePayloadLength);
241 }
242
243 checkpoint(State.MASKING_KEY);
244 case MASKING_KEY:
245 if (maskedPayload) {
246 if (maskingKey == null) {
247 maskingKey = new byte[4];
248 }
249 in.readBytes(maskingKey);
250 }
251 checkpoint(State.PAYLOAD);
252 case PAYLOAD:
253 // Sometimes, the payload may not be delivered in 1 nice packet
254 // We need to accumulate the data until we have it all
255 int rbytes = actualReadableBytes();
256
257 long willHaveReadByteCount = framePayloadBytesRead + rbytes;
258 // logger.debug("Frame rbytes=" + rbytes + " willHaveReadByteCount="
259 // + willHaveReadByteCount + " framePayloadLength=" +
260 // framePayloadLength);
261 if (willHaveReadByteCount == framePayloadLength) {
262 // We have all our content so proceed to process
263 payloadBuffer = ctx.alloc().buffer(rbytes);
264 payloadBuffer.writeBytes(in, rbytes);
265 } else if (willHaveReadByteCount < framePayloadLength) {
266
267 // We don't have all our content so accumulate payload.
268 // Returning null means we will get called back
269 if (framePayload == null) {
270 framePayload = ctx.alloc().buffer(toFrameLength(framePayloadLength));
271 }
272 framePayload.writeBytes(in, rbytes);
273 framePayloadBytesRead += rbytes;
274
275 // Return null to wait for more bytes to arrive
276 return;
277 } else if (willHaveReadByteCount > framePayloadLength) {
278 // We have more than what we need so read up to the end of frame
279 // Leave the remainder in the buffer for next frame
280 if (framePayload == null) {
281 framePayload = ctx.alloc().buffer(toFrameLength(framePayloadLength));
282 }
283 framePayload.writeBytes(in, toFrameLength(framePayloadLength - framePayloadBytesRead));
284 }
285
286 // Now we have all the data, the next checkpoint must be the next
287 // frame
288 checkpoint(State.FRAME_START);
289
290 // Take the data that we have in this packet
291 if (framePayload == null) {
292 framePayload = payloadBuffer;
293 payloadBuffer = null;
294 } else if (payloadBuffer != null) {
295 framePayload.writeBytes(payloadBuffer);
296 payloadBuffer.release();
297 payloadBuffer = null;
298 }
299
300 // Unmask data if needed
301 if (maskedPayload) {
302 unmask(framePayload);
303 }
304
305 // Processing ping/pong/close frames because they cannot be
306 // fragmented
307 if (frameOpcode == OPCODE_PING) {
308 out.add(new PingWebSocketFrame(frameFinalFlag, frameRsv, framePayload));
309 framePayload = null;
310 return;
311 }
312 if (frameOpcode == OPCODE_PONG) {
313 out.add(new PongWebSocketFrame(frameFinalFlag, frameRsv, framePayload));
314 framePayload = null;
315 return;
316 }
317 if (frameOpcode == OPCODE_CLOSE) {
318 receivedClosingHandshake = true;
319 checkCloseFrameBody(ctx, framePayload);
320 out.add(new CloseWebSocketFrame(frameFinalFlag, frameRsv, framePayload));
321 framePayload = null;
322 return;
323 }
324
325 // Processing for possible fragmented messages for text and binary
326 // frames
327 if (frameFinalFlag) {
328 // Final frame of the sequence. Apparently ping frames are
329 // allowed in the middle of a fragmented message
330 if (frameOpcode != OPCODE_PING) {
331 fragmentedFramesCount = 0;
332
333 // Check text for UTF8 correctness
334 if (frameOpcode == OPCODE_TEXT ||
335 utf8Validator != null && utf8Validator.isChecking()) {
336 // Check UTF-8 correctness for this payload
337 checkUTF8String(ctx, framePayload);
338
339 // This does a second check to make sure UTF-8
340 // correctness for entire text message
341 utf8Validator.finish();
342 }
343 }
344 } else {
345 // Not final frame so we can expect more frames in the
346 // fragmented sequence
347 if (fragmentedFramesCount == 0) {
348 // First text or binary frame for a fragmented set
349 if (frameOpcode == OPCODE_TEXT) {
350 checkUTF8String(ctx, framePayload);
351 }
352 } else {
353 // Subsequent frames - only check if init frame is text
354 if (utf8Validator != null && utf8Validator.isChecking()) {
355 checkUTF8String(ctx, framePayload);
356 }
357 }
358
359 // Increment counter
360 fragmentedFramesCount++;
361 }
362
363 // Return the frame
364 if (frameOpcode == OPCODE_TEXT) {
365 out.add(new TextWebSocketFrame(frameFinalFlag, frameRsv, framePayload));
366 framePayload = null;
367 return;
368 } else if (frameOpcode == OPCODE_BINARY) {
369 out.add(new BinaryWebSocketFrame(frameFinalFlag, frameRsv, framePayload));
370 framePayload = null;
371 return;
372 } else if (frameOpcode == OPCODE_CONT) {
373 out.add(new ContinuationWebSocketFrame(frameFinalFlag, frameRsv, framePayload));
374 framePayload = null;
375 return;
376 } else {
377 throw new UnsupportedOperationException("Cannot decode web socket frame with opcode: "
378 + frameOpcode);
379 }
380 case CORRUPT:
381 // If we don't keep reading Netty will throw an exception saying
382 // we can't return null if no bytes read and state not changed.
383 in.readByte();
384 return;
385 default:
386 throw new Error("Shouldn't reach here.");
387 }
388 } catch (Exception e) {
389 if (payloadBuffer != null) {
390 if (payloadBuffer.refCnt() > 0) {
391 payloadBuffer.release();
392 }
393 payloadBuffer = null;
394 }
395 if (framePayload != null) {
396 if (framePayload.refCnt() > 0) {
397 framePayload.release();
398 }
399 framePayload = null;
400 }
401 throw e;
402 }
403 }
404
405 private void unmask(ByteBuf frame) {
406 int i = frame.readerIndex();
407 int end = frame.writerIndex();
408
409 ByteOrder order = frame.order();
410
411 // Remark: & 0xFF is necessary because Java will do signed expansion from
412 // byte to int which we don't want.
413 int intMask = ((maskingKey[0] & 0xFF) << 24)
414 | ((maskingKey[1] & 0xFF) << 16)
415 | ((maskingKey[2] & 0xFF) << 8)
416 | (maskingKey[3] & 0xFF);
417
418 // If the byte order of our buffers it little endian we have to bring our mask
419 // into the same format, because getInt() and writeInt() will use a reversed byte order
420 if (order == ByteOrder.LITTLE_ENDIAN) {
421 intMask = Integer.reverseBytes(intMask);
422 }
423
424 for (; i + 3 < end; i += 4) {
425 int unmasked = frame.getInt(i) ^ intMask;
426 frame.setInt(i, unmasked);
427 }
428 for (; i < end; i++) {
429 frame.setByte(i, frame.getByte(i) ^ maskingKey[i % 4]);
430 }
431 }
432
433 private void protocolViolation(ChannelHandlerContext ctx, String reason) {
434 protocolViolation(ctx, new CorruptedFrameException(reason));
435 }
436
437 private void protocolViolation(ChannelHandlerContext ctx, CorruptedFrameException ex) {
438 checkpoint(State.CORRUPT);
439 if (ctx.channel().isActive()) {
440 Object closeMessage;
441 if (receivedClosingHandshake) {
442 closeMessage = Unpooled.EMPTY_BUFFER;
443 } else {
444 closeMessage = new CloseWebSocketFrame(1002, null);
445 }
446 ctx.writeAndFlush(closeMessage).addListener(ChannelFutureListener.CLOSE);
447 }
448 throw ex;
449 }
450
451 private static int toFrameLength(long l) {
452 if (l > Integer.MAX_VALUE) {
453 throw new TooLongFrameException("Length:" + l);
454 } else {
455 return (int) l;
456 }
457 }
458
459 private void checkUTF8String(ChannelHandlerContext ctx, ByteBuf buffer) {
460 try {
461 if (utf8Validator == null) {
462 utf8Validator = new Utf8Validator();
463 }
464 utf8Validator.check(buffer);
465 } catch (CorruptedFrameException ex) {
466 protocolViolation(ctx, ex);
467 }
468 }
469
470 /** */
471 protected void checkCloseFrameBody(
472 ChannelHandlerContext ctx, ByteBuf buffer) {
473 if (buffer == null || !buffer.isReadable()) {
474 return;
475 }
476 if (buffer.readableBytes() == 1) {
477 protocolViolation(ctx, "Invalid close frame body");
478 }
479
480 // Save reader index
481 int idx = buffer.readerIndex();
482 buffer.readerIndex(0);
483
484 // Must have 2 byte integer within the valid range
485 int statusCode = buffer.readShort();
486 if (statusCode >= 0 && statusCode <= 999 || statusCode >= 1004 && statusCode <= 1006
487 || statusCode >= 1012 && statusCode <= 2999) {
488 protocolViolation(ctx, "Invalid close frame getStatus code: " + statusCode);
489 }
490
491 // May have UTF-8 message
492 if (buffer.isReadable()) {
493 try {
494 new Utf8Validator().check(buffer);
495 } catch (CorruptedFrameException ex) {
496 protocolViolation(ctx, ex);
497 }
498 }
499
500 // Restore reader index
501 buffer.readerIndex(idx);
502 }
503
504 @Override
505 public void channelInactive(ChannelHandlerContext ctx) throws Exception {
506 super.channelInactive(ctx);
507
508 // release all not complete frames data to prevent leaks.
509 // https://github.com/netty/netty/issues/1874
510 if (framePayload != null) {
511 framePayload.release();
512 }
513 if (payloadBuffer != null) {
514 payloadBuffer.release();
515 }
516 }
517 }