2012-02-07 09:26:55 +01:00
|
|
|
/*
|
|
|
|
* Copyright 2012 The Netty Project
|
|
|
|
*
|
|
|
|
* The Netty Project licenses this file to you under the Apache License,
|
|
|
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
|
|
|
* with the License. You may obtain a copy of the License at:
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
|
|
* License for the specific language governing permissions and limitations
|
|
|
|
* under the License.
|
|
|
|
*/
|
2011-09-26 14:51:15 +02:00
|
|
|
// (BSD License: http://www.opensource.org/licenses/bsd-license)
|
|
|
|
//
|
|
|
|
// Copyright (c) 2011, Joe Walnes and contributors
|
|
|
|
// All rights reserved.
|
|
|
|
//
|
|
|
|
// Redistribution and use in source and binary forms, with or
|
|
|
|
// without modification, are permitted provided that the
|
|
|
|
// following conditions are met:
|
|
|
|
//
|
|
|
|
// * Redistributions of source code must retain the above
|
|
|
|
// copyright notice, this list of conditions and the
|
|
|
|
// following disclaimer.
|
|
|
|
//
|
|
|
|
// * Redistributions in binary form must reproduce the above
|
|
|
|
// copyright notice, this list of conditions and the
|
|
|
|
// following disclaimer in the documentation and/or other
|
|
|
|
// materials provided with the distribution.
|
|
|
|
//
|
|
|
|
// * Neither the name of the Webbit nor the names of
|
|
|
|
// its contributors may be used to endorse or promote products
|
|
|
|
// derived from this software without specific prior written
|
|
|
|
// permission.
|
|
|
|
//
|
|
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
|
|
|
// CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
|
|
|
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
|
|
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
|
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
|
|
|
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
|
|
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
|
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
|
|
|
// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
|
|
|
// BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
|
|
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
|
|
|
// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
|
|
// POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
|
2011-12-09 04:38:59 +01:00
|
|
|
package io.netty.handler.codec.http.websocketx;
|
2011-09-26 14:51:15 +02:00
|
|
|
|
2011-12-09 04:38:59 +01:00
|
|
|
import io.netty.buffer.ChannelBuffer;
|
|
|
|
import io.netty.buffer.ChannelBuffers;
|
|
|
|
import io.netty.channel.ChannelFutureListener;
|
2012-05-23 20:42:10 +02:00
|
|
|
import io.netty.channel.ChannelInboundHandlerContext;
|
2012-05-16 16:02:06 +02:00
|
|
|
import io.netty.handler.codec.CorruptedFrameException;
|
2012-05-17 05:37:37 +02:00
|
|
|
import io.netty.handler.codec.ReplayingDecoder;
|
2012-05-16 16:02:06 +02:00
|
|
|
import io.netty.handler.codec.TooLongFrameException;
|
2011-12-09 04:38:59 +01:00
|
|
|
import io.netty.logging.InternalLogger;
|
|
|
|
import io.netty.logging.InternalLoggerFactory;
|
2011-09-26 14:51:15 +02:00
|
|
|
|
|
|
|
/**
|
2011-12-15 06:09:09 +01:00
|
|
|
* Decodes a web socket frame from wire protocol version 8 format. This code was forked from <a
|
|
|
|
* href="https://github.com/joewalnes/webbit">webbit</a> and modified.
|
2011-09-26 14:51:15 +02:00
|
|
|
*/
|
2012-05-23 20:42:10 +02:00
|
|
|
public class WebSocket08FrameDecoder extends ReplayingDecoder<WebSocketFrame, WebSocket08FrameDecoder.State> {
|
2011-09-26 14:51:15 +02:00
|
|
|
|
2011-12-15 12:25:40 +01:00
|
|
|
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocket08FrameDecoder.class);
|
|
|
|
|
|
|
|
private static final byte OPCODE_CONT = 0x0;
|
|
|
|
private static final byte OPCODE_TEXT = 0x1;
|
|
|
|
private static final byte OPCODE_BINARY = 0x2;
|
|
|
|
private static final byte OPCODE_CLOSE = 0x8;
|
|
|
|
private static final byte OPCODE_PING = 0x9;
|
|
|
|
private static final byte OPCODE_PONG = 0xA;
|
|
|
|
|
2012-01-11 12:16:14 +01:00
|
|
|
private UTF8Output fragmentedFramesText;
|
|
|
|
private int fragmentedFramesCount;
|
2011-12-15 12:25:40 +01:00
|
|
|
|
2012-05-31 02:13:00 +02:00
|
|
|
private final long maxFramePayloadLength;
|
2011-12-15 12:25:40 +01:00
|
|
|
private boolean frameFinalFlag;
|
|
|
|
private int frameRsv;
|
|
|
|
private int frameOpcode;
|
|
|
|
private long framePayloadLength;
|
2012-01-11 12:16:14 +01:00
|
|
|
private ChannelBuffer framePayload;
|
|
|
|
private int framePayloadBytesRead;
|
2011-12-15 12:25:40 +01:00
|
|
|
private ChannelBuffer maskingKey;
|
|
|
|
|
2012-01-19 05:12:45 +01:00
|
|
|
private final boolean allowExtensions;
|
|
|
|
private final boolean maskedPayload;
|
2012-01-11 12:16:14 +01:00
|
|
|
private boolean receivedClosingHandshake;
|
2011-12-15 12:25:40 +01:00
|
|
|
|
|
|
|
public enum State {
|
|
|
|
FRAME_START, MASKING_KEY, PAYLOAD, CORRUPT
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Constructor
|
2012-05-23 20:42:10 +02:00
|
|
|
*
|
2011-12-15 12:25:40 +01:00
|
|
|
* @param maskedPayload
|
|
|
|
* Web socket servers must set this to true processed incoming masked payload. Client implementations
|
|
|
|
* must set this to false.
|
|
|
|
* @param allowExtensions
|
|
|
|
* Flag to allow reserved extension bits to be used or not
|
2012-05-31 02:13:00 +02:00
|
|
|
* @param maxFramePayloadLength
|
|
|
|
* Maximum length of a frame's payload. Setting this to an appropriate value for you application
|
|
|
|
* helps check for denial of services attacks.
|
2011-12-15 12:25:40 +01:00
|
|
|
*/
|
2012-05-31 02:13:00 +02:00
|
|
|
public WebSocket08FrameDecoder(boolean maskedPayload, boolean allowExtensions, long maxFramePayloadLength) {
|
2011-12-15 12:25:40 +01:00
|
|
|
super(State.FRAME_START);
|
|
|
|
this.maskedPayload = maskedPayload;
|
|
|
|
this.allowExtensions = allowExtensions;
|
2012-05-31 02:13:00 +02:00
|
|
|
this.maxFramePayloadLength = maxFramePayloadLength;
|
2011-12-15 12:25:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2012-05-23 20:42:10 +02:00
|
|
|
public WebSocketFrame decode(
|
|
|
|
ChannelInboundHandlerContext<Byte> ctx, ChannelBuffer in) throws Exception {
|
2011-12-15 12:25:40 +01:00
|
|
|
|
|
|
|
// Discard all data received if closing handshake was received before.
|
|
|
|
if (receivedClosingHandshake) {
|
2012-05-23 20:42:10 +02:00
|
|
|
in.skipBytes(actualReadableBytes());
|
2011-12-15 12:25:40 +01:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2012-05-23 20:42:10 +02:00
|
|
|
switch (state()) {
|
2011-12-15 12:25:40 +01:00
|
|
|
case FRAME_START:
|
|
|
|
framePayloadBytesRead = 0;
|
|
|
|
framePayloadLength = -1;
|
|
|
|
framePayload = null;
|
|
|
|
|
|
|
|
// FIN, RSV, OPCODE
|
2012-05-23 20:42:10 +02:00
|
|
|
byte b = in.readByte();
|
2011-12-15 12:25:40 +01:00
|
|
|
frameFinalFlag = (b & 0x80) != 0;
|
|
|
|
frameRsv = (b & 0x70) >> 4;
|
2012-01-11 12:16:14 +01:00
|
|
|
frameOpcode = b & 0x0F;
|
2011-12-15 12:25:40 +01:00
|
|
|
|
|
|
|
if (logger.isDebugEnabled()) {
|
|
|
|
logger.debug("Decoding WebSocket Frame opCode=" + frameOpcode);
|
|
|
|
}
|
|
|
|
|
|
|
|
// MASK, PAYLOAD LEN 1
|
2012-05-23 20:42:10 +02:00
|
|
|
b = in.readByte();
|
2011-12-15 12:25:40 +01:00
|
|
|
boolean frameMasked = (b & 0x80) != 0;
|
2012-01-11 12:16:14 +01:00
|
|
|
int framePayloadLen1 = b & 0x7F;
|
2011-12-15 12:25:40 +01:00
|
|
|
|
2012-01-19 05:12:45 +01:00
|
|
|
if (frameRsv != 0 && !allowExtensions) {
|
2012-05-23 20:42:10 +02:00
|
|
|
protocolViolation(ctx, "RSV != 0 and no extension negotiated, RSV:" + frameRsv);
|
2011-12-15 12:25:40 +01:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2012-01-19 05:12:45 +01:00
|
|
|
if (maskedPayload && !frameMasked) {
|
2012-05-23 20:42:10 +02:00
|
|
|
protocolViolation(ctx, "unmasked client to server frame");
|
2011-12-15 12:25:40 +01:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
if (frameOpcode > 7) { // control frame (have MSB in opcode set)
|
|
|
|
|
|
|
|
// control frames MUST NOT be fragmented
|
|
|
|
if (!frameFinalFlag) {
|
2012-05-23 20:42:10 +02:00
|
|
|
protocolViolation(ctx, "fragmented control frame");
|
2011-12-15 12:25:40 +01:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// control frames MUST have payload 125 octets or less
|
|
|
|
if (framePayloadLen1 > 125) {
|
2012-05-23 20:42:10 +02:00
|
|
|
protocolViolation(ctx, "control frame with payload length > 125 octets");
|
2011-12-15 12:25:40 +01:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// check for reserved control frame opcodes
|
|
|
|
if (!(frameOpcode == OPCODE_CLOSE || frameOpcode == OPCODE_PING || frameOpcode == OPCODE_PONG)) {
|
2012-05-23 20:42:10 +02:00
|
|
|
protocolViolation(ctx, "control frame using reserved opcode " + frameOpcode);
|
2011-12-15 12:25:40 +01:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// close frame : if there is a body, the first two bytes of the
|
|
|
|
// body MUST be a 2-byte unsigned integer (in network byte
|
|
|
|
// order) representing a status code
|
|
|
|
if (frameOpcode == 8 && framePayloadLen1 == 1) {
|
2012-05-23 20:42:10 +02:00
|
|
|
protocolViolation(ctx, "received close control frame with payload len 1");
|
2011-12-15 12:25:40 +01:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
} else { // data frame
|
|
|
|
// check for reserved data frame opcodes
|
|
|
|
if (!(frameOpcode == OPCODE_CONT || frameOpcode == OPCODE_TEXT || frameOpcode == OPCODE_BINARY)) {
|
2012-05-23 20:42:10 +02:00
|
|
|
protocolViolation(ctx, "data frame using reserved opcode " + frameOpcode);
|
2011-12-15 12:25:40 +01:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// check opcode vs message fragmentation state 1/2
|
|
|
|
if (fragmentedFramesCount == 0 && frameOpcode == OPCODE_CONT) {
|
2012-05-23 20:42:10 +02:00
|
|
|
protocolViolation(ctx, "received continuation data frame outside fragmented message");
|
2011-12-15 12:25:40 +01:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// check opcode vs message fragmentation state 2/2
|
|
|
|
if (fragmentedFramesCount != 0 && frameOpcode != OPCODE_CONT && frameOpcode != OPCODE_PING) {
|
2012-05-23 20:42:10 +02:00
|
|
|
protocolViolation(ctx, "received non-continuation data frame while inside fragmented message");
|
2011-12-15 12:25:40 +01:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read frame payload length
|
|
|
|
if (framePayloadLen1 == 126) {
|
2012-05-23 20:42:10 +02:00
|
|
|
framePayloadLength = in.readUnsignedShort();
|
2011-12-15 12:25:40 +01:00
|
|
|
if (framePayloadLength < 126) {
|
2012-05-23 20:42:10 +02:00
|
|
|
protocolViolation(ctx, "invalid data frame length (not using minimal length encoding)");
|
2011-12-15 12:25:40 +01:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
} else if (framePayloadLen1 == 127) {
|
2012-05-23 20:42:10 +02:00
|
|
|
framePayloadLength = in.readLong();
|
2011-12-15 12:25:40 +01:00
|
|
|
// TODO: check if it's bigger than 0x7FFFFFFFFFFFFFFF, Maybe
|
|
|
|
// just check if it's negative?
|
|
|
|
|
|
|
|
if (framePayloadLength < 65536) {
|
2012-05-23 20:42:10 +02:00
|
|
|
protocolViolation(ctx, "invalid data frame length (not using minimal length encoding)");
|
2011-12-15 12:25:40 +01:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
framePayloadLength = framePayloadLen1;
|
|
|
|
}
|
|
|
|
|
2012-05-31 02:13:00 +02:00
|
|
|
if (framePayloadLength > maxFramePayloadLength) {
|
|
|
|
protocolViolation(ctx, "Max frame length of " + maxFramePayloadLength + " has been exceeded.");
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2011-12-15 12:25:40 +01:00
|
|
|
if (logger.isDebugEnabled()) {
|
|
|
|
logger.debug("Decoding WebSocket Frame length=" + framePayloadLength);
|
|
|
|
}
|
|
|
|
|
|
|
|
checkpoint(State.MASKING_KEY);
|
|
|
|
case MASKING_KEY:
|
2012-01-19 05:12:45 +01:00
|
|
|
if (maskedPayload) {
|
2012-05-23 20:42:10 +02:00
|
|
|
maskingKey = in.readBytes(4);
|
2011-12-15 12:25:40 +01:00
|
|
|
}
|
|
|
|
checkpoint(State.PAYLOAD);
|
|
|
|
case PAYLOAD:
|
|
|
|
// Sometimes, the payload may not be delivered in 1 nice packet
|
|
|
|
// We need to accumulate the data until we have it all
|
|
|
|
int rbytes = actualReadableBytes();
|
|
|
|
ChannelBuffer payloadBuffer = null;
|
|
|
|
|
2012-05-31 02:13:00 +02:00
|
|
|
long willHaveReadByteCount = framePayloadBytesRead + rbytes;
|
2011-12-15 12:25:40 +01:00
|
|
|
// logger.debug("Frame rbytes=" + rbytes + " willHaveReadByteCount="
|
|
|
|
// + willHaveReadByteCount + " framePayloadLength=" +
|
|
|
|
// framePayloadLength);
|
|
|
|
if (willHaveReadByteCount == framePayloadLength) {
|
|
|
|
// We have all our content so proceed to process
|
2012-05-23 20:42:10 +02:00
|
|
|
payloadBuffer = in.readBytes(rbytes);
|
2011-12-15 12:25:40 +01:00
|
|
|
} else if (willHaveReadByteCount < framePayloadLength) {
|
|
|
|
// We don't have all our content so accumulate payload.
|
|
|
|
// Returning null means we will get called back
|
2012-05-23 20:42:10 +02:00
|
|
|
payloadBuffer = in.readBytes(rbytes);
|
2011-12-15 12:25:40 +01:00
|
|
|
if (framePayload == null) {
|
2012-05-23 20:42:10 +02:00
|
|
|
framePayload = ChannelBuffers.buffer(toFrameLength(framePayloadLength));
|
2011-12-15 12:25:40 +01:00
|
|
|
}
|
|
|
|
framePayload.writeBytes(payloadBuffer);
|
2012-01-19 05:12:45 +01:00
|
|
|
framePayloadBytesRead += rbytes;
|
2011-12-15 12:25:40 +01:00
|
|
|
|
|
|
|
// Return null to wait for more bytes to arrive
|
|
|
|
return null;
|
|
|
|
} else if (willHaveReadByteCount > framePayloadLength) {
|
|
|
|
// We have more than what we need so read up to the end of frame
|
|
|
|
// Leave the remainder in the buffer for next frame
|
2012-05-23 20:42:10 +02:00
|
|
|
payloadBuffer = in.readBytes(toFrameLength(framePayloadLength - framePayloadBytesRead));
|
2011-12-15 12:25:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Now we have all the data, the next checkpoint must be the next
|
|
|
|
// frame
|
|
|
|
checkpoint(State.FRAME_START);
|
|
|
|
|
|
|
|
// Take the data that we have in this packet
|
|
|
|
if (framePayload == null) {
|
|
|
|
framePayload = payloadBuffer;
|
|
|
|
} else {
|
|
|
|
framePayload.writeBytes(payloadBuffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unmask data if needed
|
2012-01-19 05:12:45 +01:00
|
|
|
if (maskedPayload) {
|
2011-12-15 12:25:40 +01:00
|
|
|
unmask(framePayload);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Processing ping/pong/close frames because they cannot be
|
|
|
|
// fragmented
|
|
|
|
if (frameOpcode == OPCODE_PING) {
|
|
|
|
return new PingWebSocketFrame(frameFinalFlag, frameRsv, framePayload);
|
|
|
|
} else if (frameOpcode == OPCODE_PONG) {
|
|
|
|
return new PongWebSocketFrame(frameFinalFlag, frameRsv, framePayload);
|
|
|
|
} else if (frameOpcode == OPCODE_CLOSE) {
|
2012-05-23 20:42:10 +02:00
|
|
|
checkCloseFrameBody(ctx, framePayload);
|
2012-01-19 05:12:45 +01:00
|
|
|
receivedClosingHandshake = true;
|
2012-04-09 06:33:45 +02:00
|
|
|
return new CloseWebSocketFrame(frameFinalFlag, frameRsv, framePayload);
|
2011-12-15 12:25:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Processing for possible fragmented messages for text and binary
|
|
|
|
// frames
|
|
|
|
String aggregatedText = null;
|
|
|
|
if (frameFinalFlag) {
|
|
|
|
// Final frame of the sequence. Apparently ping frames are
|
|
|
|
// allowed in the middle of a fragmented message
|
|
|
|
if (frameOpcode != OPCODE_PING) {
|
|
|
|
fragmentedFramesCount = 0;
|
|
|
|
|
|
|
|
// Check text for UTF8 correctness
|
|
|
|
if (frameOpcode == OPCODE_TEXT || fragmentedFramesText != null) {
|
|
|
|
// Check UTF-8 correctness for this payload
|
2012-05-23 20:42:10 +02:00
|
|
|
checkUTF8String(ctx, framePayload.array());
|
2011-12-15 12:25:40 +01:00
|
|
|
|
|
|
|
// This does a second check to make sure UTF-8
|
|
|
|
// correctness for entire text message
|
|
|
|
aggregatedText = fragmentedFramesText.toString();
|
|
|
|
|
|
|
|
fragmentedFramesText = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Not final frame so we can expect more frames in the
|
|
|
|
// fragmented sequence
|
|
|
|
if (fragmentedFramesCount == 0) {
|
|
|
|
// First text or binary frame for a fragmented set
|
|
|
|
fragmentedFramesText = null;
|
|
|
|
if (frameOpcode == OPCODE_TEXT) {
|
2012-05-23 20:42:10 +02:00
|
|
|
checkUTF8String(ctx, framePayload.array());
|
2011-12-15 12:25:40 +01:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Subsequent frames - only check if init frame is text
|
|
|
|
if (fragmentedFramesText != null) {
|
2012-05-23 20:42:10 +02:00
|
|
|
checkUTF8String(ctx, framePayload.array());
|
2011-12-15 12:25:40 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Increment counter
|
|
|
|
fragmentedFramesCount++;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return the frame
|
|
|
|
if (frameOpcode == OPCODE_TEXT) {
|
|
|
|
return new TextWebSocketFrame(frameFinalFlag, frameRsv, framePayload);
|
|
|
|
} else if (frameOpcode == OPCODE_BINARY) {
|
|
|
|
return new BinaryWebSocketFrame(frameFinalFlag, frameRsv, framePayload);
|
|
|
|
} else if (frameOpcode == OPCODE_CONT) {
|
|
|
|
return new ContinuationWebSocketFrame(frameFinalFlag, frameRsv, framePayload, aggregatedText);
|
|
|
|
} else {
|
|
|
|
throw new UnsupportedOperationException("Cannot decode web socket frame with opcode: " + frameOpcode);
|
|
|
|
}
|
|
|
|
case CORRUPT:
|
|
|
|
// If we don't keep reading Netty will throw an exception saying
|
|
|
|
// we can't return null if no bytes read and state not changed.
|
2012-05-23 20:42:10 +02:00
|
|
|
in.readByte();
|
2011-12-15 12:25:40 +01:00
|
|
|
return null;
|
|
|
|
default:
|
|
|
|
throw new Error("Shouldn't reach here.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void unmask(ChannelBuffer frame) {
|
|
|
|
byte[] bytes = frame.array();
|
|
|
|
for (int i = 0; i < bytes.length; i++) {
|
|
|
|
frame.setByte(i, frame.getByte(i) ^ maskingKey.getByte(i % 4));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-05-23 20:42:10 +02:00
|
|
|
private void protocolViolation(ChannelInboundHandlerContext<Byte> ctx, String reason) throws CorruptedFrameException {
|
2011-12-15 12:25:40 +01:00
|
|
|
checkpoint(State.CORRUPT);
|
2012-05-23 20:42:10 +02:00
|
|
|
if (ctx.channel().isActive()) {
|
|
|
|
ctx.flush().addListener(ChannelFutureListener.CLOSE);
|
2011-12-15 12:25:40 +01:00
|
|
|
}
|
|
|
|
throw new CorruptedFrameException(reason);
|
|
|
|
}
|
|
|
|
|
2012-05-23 20:42:10 +02:00
|
|
|
private static int toFrameLength(long l) throws TooLongFrameException {
|
2011-12-15 12:25:40 +01:00
|
|
|
if (l > Integer.MAX_VALUE) {
|
|
|
|
throw new TooLongFrameException("Length:" + l);
|
|
|
|
} else {
|
|
|
|
return (int) l;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-05-23 20:42:10 +02:00
|
|
|
private void checkUTF8String(ChannelInboundHandlerContext<Byte> ctx, byte[] bytes) throws CorruptedFrameException {
|
2011-12-15 12:25:40 +01:00
|
|
|
try {
|
|
|
|
// StringBuilder sb = new StringBuilder("UTF8 " + bytes.length +
|
|
|
|
// " bytes: ");
|
|
|
|
// for (byte b : bytes) {
|
|
|
|
// sb.append(Integer.toHexString(b)).append(" ");
|
|
|
|
// }
|
|
|
|
// logger.debug(sb.toString());
|
|
|
|
|
|
|
|
if (fragmentedFramesText == null) {
|
|
|
|
fragmentedFramesText = new UTF8Output(bytes);
|
|
|
|
} else {
|
|
|
|
fragmentedFramesText.write(bytes);
|
|
|
|
}
|
|
|
|
} catch (UTF8Exception ex) {
|
2012-05-23 20:42:10 +02:00
|
|
|
protocolViolation(ctx, "invalid UTF-8 bytes");
|
2011-12-15 12:25:40 +01:00
|
|
|
}
|
|
|
|
}
|
2012-04-09 06:33:45 +02:00
|
|
|
|
2012-05-23 20:42:10 +02:00
|
|
|
protected void checkCloseFrameBody(ChannelInboundHandlerContext<Byte> ctx, ChannelBuffer buffer) throws CorruptedFrameException {
|
2012-04-09 06:33:45 +02:00
|
|
|
if (buffer == null || buffer.capacity() == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (buffer.capacity() == 1) {
|
2012-05-23 20:42:10 +02:00
|
|
|
protocolViolation(ctx, "Invalid close frame body");
|
2012-04-09 06:33:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Save reader index
|
|
|
|
int idx = buffer.readerIndex();
|
|
|
|
buffer.readerIndex(0);
|
|
|
|
|
|
|
|
// Must have 2 byte integer within the valid range
|
|
|
|
int statusCode = buffer.readShort();
|
2012-05-23 20:42:10 +02:00
|
|
|
if (statusCode >= 0 && statusCode <= 999 || statusCode >= 1004 && statusCode <= 1006
|
|
|
|
|| statusCode >= 1012 && statusCode <= 2999) {
|
|
|
|
protocolViolation(ctx, "Invalid close frame status code: " + statusCode);
|
2012-04-09 06:33:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// May have UTF-8 message
|
|
|
|
if (buffer.readableBytes() > 0) {
|
|
|
|
byte[] b = new byte[buffer.readableBytes()];
|
|
|
|
buffer.readBytes(b);
|
|
|
|
try {
|
|
|
|
new UTF8Output(b);
|
|
|
|
} catch (UTF8Exception ex) {
|
2012-05-23 20:42:10 +02:00
|
|
|
protocolViolation(ctx, "Invalid close frame reason text. Invalid UTF-8 bytes");
|
2012-04-09 06:33:45 +02:00
|
|
|
}
|
|
|
|
}
|
2012-05-23 20:42:10 +02:00
|
|
|
|
2012-04-09 06:33:45 +02:00
|
|
|
// Restore reader index
|
|
|
|
buffer.readerIndex(idx);
|
|
|
|
}
|
2011-11-10 03:49:44 +01:00
|
|
|
}
|