WebSocket is closed without an error on protocol violations (#9116)

Motivation:

Incorrect WebSockets closure affects our production system.
Enforced 'close socket on any protocol violation' prevents our custom termination sequence from execution.
Huge number of parameters is a nightmare both in usage and in support (decoders configuration).

Modification:

- Fix violations handling - send proper response codes.
- Fix for messages leak.
- Introduce decoder's option to disable default behavior (send close frame) on protocol violations.
- Encapsulate WebSocket response codes - WebSocketCloseStatus.
- Encapsulate decoder's configuration into a separate class - WebSocketDecoderConfig.

Result:

Fixes #8295.
This commit is contained in:
ursa 2019-06-18 09:05:58 +01:00 committed by Norman Maurer
parent aaf5ec1fbb
commit d9db218291
19 changed files with 1039 additions and 158 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012 The Netty Project
* Copyright 2019 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
@ -33,7 +33,31 @@ public class CloseWebSocketFrame extends WebSocketFrame {
}
/**
* Creates a new empty close frame with closing getStatus code and reason text
* Creates a new empty close frame with closing status code and reason text
*
* @param status
* Status code as per <a href="http://tools.ietf.org/html/rfc6455#section-7.4">RFC 6455</a>. For
* example, <tt>1000</tt> indicates normal closure.
*/
public CloseWebSocketFrame(WebSocketCloseStatus status) {
this(status.code(), status.reasonText());
}
/**
* Creates a new empty close frame with closing status code and reason text
*
* @param status
* Status code as per <a href="http://tools.ietf.org/html/rfc6455#section-7.4">RFC 6455</a>. For
* example, <tt>1000</tt> indicates normal closure.
* @param reasonText
* Reason text. Set to null if no text.
*/
public CloseWebSocketFrame(WebSocketCloseStatus status, String reasonText) {
this(status.code(), reasonText);
}
/**
* Creates a new empty close frame with closing status code and reason text
*
* @param statusCode
* Integer status code as per <a href="http://tools.ietf.org/html/rfc6455#section-7.4">RFC 6455</a>. For
@ -46,7 +70,7 @@ public class CloseWebSocketFrame extends WebSocketFrame {
}
/**
* Creates a new close frame with no losing getStatus code and no reason text
* Creates a new close frame with no losing status code and no reason text
*
* @param finalFragment
* flag indicating if this frame is the final fragment
@ -105,7 +129,7 @@ public class CloseWebSocketFrame extends WebSocketFrame {
/**
* Returns the closing status code as per <a href="http://tools.ietf.org/html/rfc6455#section-7.4">RFC 6455</a>. If
* a getStatus code is set, -1 is returned.
* a status code is set, -1 is returned.
*/
public int statusCode() {
ByteBuf binaryData = content();

View File

@ -0,0 +1,64 @@
/*
* Copyright 2019 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.
*/
package io.netty.handler.codec.http.websocketx;
import io.netty.handler.codec.CorruptedFrameException;
import io.netty.handler.codec.DecoderException;
/**
* An {@link DecoderException} which is thrown when the received {@link WebSocketFrame} data could not be decoded by
* an inbound handler.
*/
public final class CorruptedWebSocketFrameException extends CorruptedFrameException {
private static final long serialVersionUID = 3918055132492988338L;
private final WebSocketCloseStatus closeStatus;
/**
* Creates a new instance.
*/
public CorruptedWebSocketFrameException() {
this(WebSocketCloseStatus.PROTOCOL_ERROR, null, null);
}
/**
* Creates a new instance.
*/
public CorruptedWebSocketFrameException(WebSocketCloseStatus status, String message, Throwable cause) {
super(message == null ? status.reasonText() : message, cause);
closeStatus = status;
}
/**
* Creates a new instance.
*/
public CorruptedWebSocketFrameException(WebSocketCloseStatus status, String message) {
this(status, message, null);
}
/**
* Creates a new instance.
*/
public CorruptedWebSocketFrameException(WebSocketCloseStatus status, Throwable cause) {
this(status, null, cause);
}
public WebSocketCloseStatus closeStatus() {
return closeStatus;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012 The Netty Project
* Copyright 2019 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
@ -36,7 +36,6 @@
package io.netty.handler.codec.http.websocketx;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.CorruptedFrameException;
import io.netty.util.ByteProcessor;
/**
@ -79,7 +78,8 @@ final class Utf8Validator implements ByteProcessor {
codep = 0;
if (state != UTF8_ACCEPT) {
state = UTF8_ACCEPT;
throw new CorruptedFrameException("bytes are not UTF-8");
throw new CorruptedWebSocketFrameException(
WebSocketCloseStatus.INVALID_PAYLOAD_DATA, "bytes are not UTF-8");
}
}
@ -93,7 +93,8 @@ final class Utf8Validator implements ByteProcessor {
if (state == UTF8_REJECT) {
checking = false;
throw new CorruptedFrameException("bytes are not UTF-8");
throw new CorruptedWebSocketFrameException(
WebSocketCloseStatus.INVALID_PAYLOAD_DATA, "bytes are not UTF-8");
}
return true;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012 The Netty Project
* Copyright 2019 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
@ -19,6 +19,7 @@ import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ReplayingDecoder;
import io.netty.handler.codec.TooLongFrameException;
import io.netty.util.internal.ObjectUtil;
import java.util.List;
@ -52,6 +53,17 @@ public class WebSocket00FrameDecoder extends ReplayingDecoder<Void> implements W
this.maxFrameSize = maxFrameSize;
}
/**
* Creates a new instance of {@code WebSocketFrameDecoder} with the specified {@code maxFrameSize}. If the client
* sends a frame size larger than {@code maxFrameSize}, the channel will be closed.
*
* @param decoderConfig
* Frames decoder configuration.
*/
public WebSocket00FrameDecoder(WebSocketDecoderConfig decoderConfig) {
this.maxFrameSize = ObjectUtil.checkNotNull(decoderConfig, "decoderConfig").maxFramePayloadLength();
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
// Discard all data received if closing handshake was received before.

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012 The Netty Project
* Copyright 2019 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
@ -71,7 +71,11 @@ public class WebSocket07FrameDecoder extends WebSocket08FrameDecoder {
* helps check for denial of services attacks.
*/
public WebSocket07FrameDecoder(boolean expectMaskedFrames, boolean allowExtensions, int maxFramePayloadLength) {
this(expectMaskedFrames, allowExtensions, maxFramePayloadLength, false);
this(WebSocketDecoderConfig.newBuilder()
.expectMaskedFrames(expectMaskedFrames)
.allowExtensions(allowExtensions)
.maxFramePayloadLength(maxFramePayloadLength)
.build());
}
/**
@ -91,6 +95,21 @@ public class WebSocket07FrameDecoder extends WebSocket08FrameDecoder {
*/
public WebSocket07FrameDecoder(boolean expectMaskedFrames, boolean allowExtensions, int maxFramePayloadLength,
boolean allowMaskMismatch) {
super(expectMaskedFrames, allowExtensions, maxFramePayloadLength, allowMaskMismatch);
this(WebSocketDecoderConfig.newBuilder()
.expectMaskedFrames(expectMaskedFrames)
.allowExtensions(allowExtensions)
.maxFramePayloadLength(maxFramePayloadLength)
.allowMaskMismatch(allowMaskMismatch)
.build());
}
/**
* Constructor
*
* @param decoderConfig
* Frames decoder configuration.
*/
public WebSocket07FrameDecoder(WebSocketDecoderConfig decoderConfig) {
super(decoderConfig);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012 The Netty Project
* Copyright 2019 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
@ -58,8 +58,8 @@ import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.CorruptedFrameException;
import io.netty.handler.codec.TooLongFrameException;
import io.netty.util.internal.ObjectUtil;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
@ -93,10 +93,7 @@ public class WebSocket08FrameDecoder extends ByteToMessageDecoder
private static final byte OPCODE_PING = 0x9;
private static final byte OPCODE_PONG = 0xA;
private final long maxFramePayloadLength;
private final boolean allowExtensions;
private final boolean expectMaskedFrames;
private final boolean allowMaskMismatch;
private final WebSocketDecoderConfig config;
private int fragmentedFramesCount;
private boolean frameFinalFlag;
@ -142,10 +139,22 @@ public class WebSocket08FrameDecoder extends ByteToMessageDecoder
*/
public WebSocket08FrameDecoder(boolean expectMaskedFrames, boolean allowExtensions, int maxFramePayloadLength,
boolean allowMaskMismatch) {
this.expectMaskedFrames = expectMaskedFrames;
this.allowMaskMismatch = allowMaskMismatch;
this.allowExtensions = allowExtensions;
this.maxFramePayloadLength = maxFramePayloadLength;
this(WebSocketDecoderConfig.newBuilder()
.expectMaskedFrames(expectMaskedFrames)
.allowExtensions(allowExtensions)
.maxFramePayloadLength(maxFramePayloadLength)
.allowMaskMismatch(allowMaskMismatch)
.build());
}
/**
* Constructor
*
* @param decoderConfig
* Frames decoder configuration.
*/
public WebSocket08FrameDecoder(WebSocketDecoderConfig decoderConfig) {
this.config = ObjectUtil.checkNotNull(decoderConfig, "decoderConfig");
}
@Override
@ -184,13 +193,13 @@ public class WebSocket08FrameDecoder extends ByteToMessageDecoder
frameMasked = (b & 0x80) != 0;
framePayloadLen1 = b & 0x7F;
if (frameRsv != 0 && !allowExtensions) {
protocolViolation(ctx, "RSV != 0 and no extension negotiated, RSV:" + frameRsv);
if (frameRsv != 0 && !config.allowExtensions()) {
protocolViolation(ctx, in, "RSV != 0 and no extension negotiated, RSV:" + frameRsv);
return;
}
if (!allowMaskMismatch && expectMaskedFrames != frameMasked) {
protocolViolation(ctx, "received a frame that is not masked as expected");
if (!config.allowMaskMismatch() && config.expectMaskedFrames() != frameMasked) {
protocolViolation(ctx, in, "received a frame that is not masked as expected");
return;
}
@ -198,20 +207,20 @@ public class WebSocket08FrameDecoder extends ByteToMessageDecoder
// control frames MUST NOT be fragmented
if (!frameFinalFlag) {
protocolViolation(ctx, "fragmented control frame");
protocolViolation(ctx, in, "fragmented control frame");
return;
}
// control frames MUST have payload 125 octets or less
if (framePayloadLen1 > 125) {
protocolViolation(ctx, "control frame with payload length > 125 octets");
protocolViolation(ctx, in, "control frame with payload length > 125 octets");
return;
}
// check for reserved control frame opcodes
if (!(frameOpcode == OPCODE_CLOSE || frameOpcode == OPCODE_PING
|| frameOpcode == OPCODE_PONG)) {
protocolViolation(ctx, "control frame using reserved opcode " + frameOpcode);
protocolViolation(ctx, in, "control frame using reserved opcode " + frameOpcode);
return;
}
@ -219,26 +228,26 @@ public class WebSocket08FrameDecoder extends ByteToMessageDecoder
// body MUST be a 2-byte unsigned integer (in network byte
// order) representing a getStatus code
if (frameOpcode == 8 && framePayloadLen1 == 1) {
protocolViolation(ctx, "received close control frame with payload len 1");
protocolViolation(ctx, in, "received close control frame with payload len 1");
return;
}
} else { // data frame
// check for reserved data frame opcodes
if (!(frameOpcode == OPCODE_CONT || frameOpcode == OPCODE_TEXT
|| frameOpcode == OPCODE_BINARY)) {
protocolViolation(ctx, "data frame using reserved opcode " + frameOpcode);
protocolViolation(ctx, in, "data frame using reserved opcode " + frameOpcode);
return;
}
// check opcode vs message fragmentation state 1/2
if (fragmentedFramesCount == 0 && frameOpcode == OPCODE_CONT) {
protocolViolation(ctx, "received continuation data frame outside fragmented message");
protocolViolation(ctx, in, "received continuation data frame outside fragmented message");
return;
}
// check opcode vs message fragmentation state 2/2
if (fragmentedFramesCount != 0 && frameOpcode != OPCODE_CONT && frameOpcode != OPCODE_PING) {
protocolViolation(ctx,
protocolViolation(ctx, in,
"received non-continuation data frame while inside fragmented message");
return;
}
@ -254,7 +263,7 @@ public class WebSocket08FrameDecoder extends ByteToMessageDecoder
}
framePayloadLength = in.readUnsignedShort();
if (framePayloadLength < 126) {
protocolViolation(ctx, "invalid data frame length (not using minimal length encoding)");
protocolViolation(ctx, in, "invalid data frame length (not using minimal length encoding)");
return;
}
} else if (framePayloadLen1 == 127) {
@ -266,15 +275,16 @@ public class WebSocket08FrameDecoder extends ByteToMessageDecoder
// just check if it's negative?
if (framePayloadLength < 65536) {
protocolViolation(ctx, "invalid data frame length (not using minimal length encoding)");
protocolViolation(ctx, in, "invalid data frame length (not using minimal length encoding)");
return;
}
} else {
framePayloadLength = framePayloadLen1;
}
if (framePayloadLength > maxFramePayloadLength) {
protocolViolation(ctx, "Max frame length of " + maxFramePayloadLength + " has been exceeded.");
if (framePayloadLength > config.maxFramePayloadLength()) {
protocolViolation(ctx, in, WebSocketCloseStatus.MESSAGE_TOO_BIG,
"Max frame length of " + config.maxFramePayloadLength() + " has been exceeded.");
return;
}
@ -408,18 +418,33 @@ public class WebSocket08FrameDecoder extends ByteToMessageDecoder
}
}
private void protocolViolation(ChannelHandlerContext ctx, String reason) {
protocolViolation(ctx, new CorruptedFrameException(reason));
private void protocolViolation(ChannelHandlerContext ctx, ByteBuf in, String reason) {
protocolViolation(ctx, in, WebSocketCloseStatus.PROTOCOL_ERROR, reason);
}
private void protocolViolation(ChannelHandlerContext ctx, CorruptedFrameException ex) {
private void protocolViolation(ChannelHandlerContext ctx, ByteBuf in, WebSocketCloseStatus status, String reason) {
protocolViolation(ctx, in, new CorruptedWebSocketFrameException(status, reason));
}
private void protocolViolation(ChannelHandlerContext ctx, ByteBuf in, CorruptedWebSocketFrameException ex) {
state = State.CORRUPT;
if (ctx.channel().isActive()) {
int readableBytes = in.readableBytes();
if (readableBytes > 0) {
// Fix for memory leak, caused by ByteToMessageDecoder#channelRead:
// buffer 'cumulation' is released ONLY when no more readable bytes available.
in.skipBytes(readableBytes);
}
if (ctx.channel().isActive() && config.closeOnProtocolViolation()) {
Object closeMessage;
if (receivedClosingHandshake) {
closeMessage = Unpooled.EMPTY_BUFFER;
} else {
closeMessage = new CloseWebSocketFrame(1002, null);
WebSocketCloseStatus closeStatus = ex.closeStatus();
String reasonText = ex.getMessage();
if (reasonText == null) {
reasonText = closeStatus.reasonText();
}
closeMessage = new CloseWebSocketFrame(closeStatus, reasonText);
}
ctx.writeAndFlush(closeMessage).addListener(ChannelFutureListener.CLOSE);
}
@ -441,7 +466,7 @@ public class WebSocket08FrameDecoder extends ByteToMessageDecoder
return;
}
if (buffer.readableBytes() == 1) {
protocolViolation(ctx, "Invalid close frame body");
protocolViolation(ctx, buffer, WebSocketCloseStatus.INVALID_PAYLOAD_DATA, "Invalid close frame body");
}
// Save reader index
@ -450,17 +475,16 @@ public class WebSocket08FrameDecoder extends ByteToMessageDecoder
// Must have 2 byte integer within the valid range
int statusCode = buffer.readShort();
if (statusCode >= 0 && statusCode <= 999 || statusCode >= 1004 && statusCode <= 1006
|| statusCode >= 1015 && statusCode <= 2999) {
protocolViolation(ctx, "Invalid close frame getStatus code: " + statusCode);
if (!WebSocketCloseStatus.isValidStatusCode(statusCode)) {
protocolViolation(ctx, buffer, "Invalid close frame getStatus code: " + statusCode);
}
// May have UTF-8 message
if (buffer.isReadable()) {
try {
new Utf8Validator().check(buffer);
} catch (CorruptedFrameException ex) {
protocolViolation(ctx, ex);
} catch (CorruptedWebSocketFrameException ex) {
protocolViolation(ctx, buffer, ex);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012 The Netty Project
* Copyright 2019 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
@ -91,6 +91,21 @@ public class WebSocket13FrameDecoder extends WebSocket08FrameDecoder {
*/
public WebSocket13FrameDecoder(boolean expectMaskedFrames, boolean allowExtensions, int maxFramePayloadLength,
boolean allowMaskMismatch) {
super(expectMaskedFrames, allowExtensions, maxFramePayloadLength, allowMaskMismatch);
this(WebSocketDecoderConfig.newBuilder()
.expectMaskedFrames(expectMaskedFrames)
.allowExtensions(allowExtensions)
.maxFramePayloadLength(maxFramePayloadLength)
.allowMaskMismatch(allowMaskMismatch)
.build());
}
/**
* Constructor
*
* @param decoderConfig
* Frames decoder configuration.
*/
public WebSocket13FrameDecoder(WebSocketDecoderConfig decoderConfig) {
super(decoderConfig);
}
}

View File

@ -0,0 +1,314 @@
/*
* Copyright 2019 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.
*/
package io.netty.handler.codec.http.websocketx;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
/**
* WebSocket status codes specified in RFC-6455.
* <pre>
*
* RFC-6455 The WebSocket Protocol, December 2011:
* <a href="https://tools.ietf.org/html/rfc6455#section-7.4.1"
* >https://tools.ietf.org/html/rfc6455#section-7.4.1</a>
*
* WebSocket Protocol Registries, April 2019:
* <a href="https://www.iana.org/assignments/websocket/websocket.xhtml#close-code-number"
* >https://www.iana.org/assignments/websocket/websocket.xhtml</a>
*
* 7.4.1. Defined Status Codes
*
* Endpoints MAY use the following pre-defined status codes when sending
* a Close frame.
*
* 1000
*
* 1000 indicates a normal closure, meaning that the purpose for
* which the connection was established has been fulfilled.
*
* 1001
*
* 1001 indicates that an endpoint is "going away", such as a server
* going down or a browser having navigated away from a page.
*
* 1002
*
* 1002 indicates that an endpoint is terminating the connection due
* to a protocol error.
*
* 1003
*
* 1003 indicates that an endpoint is terminating the connection
* because it has received a type of data it cannot accept (e.g., an
* endpoint that understands only text data MAY send this if it
* receives a binary message).
*
* 1004
*
* Reserved. The specific meaning might be defined in the future.
*
* 1005
*
* 1005 is a reserved value and MUST NOT be set as a status code in a
* Close control frame by an endpoint. It is designated for use in
* applications expecting a status code to indicate that no status
* code was actually present.
*
* 1006
*
* 1006 is a reserved value and MUST NOT be set as a status code in a
* Close control frame by an endpoint. It is designated for use in
* applications expecting a status code to indicate that the
* connection was closed abnormally, e.g., without sending or
* receiving a Close control frame.
*
* 1007
*
* 1007 indicates that an endpoint is terminating the connection
* because it has received data within a message that was not
* consistent with the type of the message (e.g., non-UTF-8 [RFC3629]
* data within a text message).
*
* 1008
*
* 1008 indicates that an endpoint is terminating the connection
* because it has received a message that violates its policy. This
* is a generic status code that can be returned when there is no
* other more suitable status code (e.g., 1003 or 1009) or if there
* is a need to hide specific details about the policy.
*
* 1009
*
* 1009 indicates that an endpoint is terminating the connection
* because it has received a message that is too big for it to
* process.
*
* 1010
*
* 1010 indicates that an endpoint (client) is terminating the
* connection because it has expected the server to negotiate one or
* more extension, but the server didn't return them in the response
* message of the WebSocket handshake. The list of extensions that
* are needed SHOULD appear in the /reason/ part of the Close frame.
* Note that this status code is not used by the server, because it
* can fail the WebSocket handshake instead.
*
* 1011
*
* 1011 indicates that a server is terminating the connection because
* it encountered an unexpected condition that prevented it from
* fulfilling the request.
*
* 1012 (IANA Registry, Non RFC-6455)
*
* 1012 indicates that the service is restarted. a client may reconnect,
* and if it choses to do, should reconnect using a randomized delay
* of 5 - 30 seconds.
*
* 1013 (IANA Registry, Non RFC-6455)
*
* 1013 indicates that the service is experiencing overload. a client
* should only connect to a different IP (when there are multiple for the
* target) or reconnect to the same IP upon user action.
*
* 1014 (IANA Registry, Non RFC-6455)
*
* The server was acting as a gateway or proxy and received an invalid
* response from the upstream server. This is similar to 502 HTTP Status Code.
*
* 1015
*
* 1015 is a reserved value and MUST NOT be set as a status code in a
* Close control frame by an endpoint. It is designated for use in
* applications expecting a status code to indicate that the
* connection was closed due to a failure to perform a TLS handshake
* (e.g., the server certificate can't be verified).
*
*
* 7.4.2. Reserved Status Code Ranges
*
* 0-999
*
* Status codes in the range 0-999 are not used.
*
* 1000-2999
*
* Status codes in the range 1000-2999 are reserved for definition by
* this protocol, its future revisions, and extensions specified in a
* permanent and readily available public specification.
*
* 3000-3999
*
* Status codes in the range 3000-3999 are reserved for use by
* libraries, frameworks, and applications. These status codes are
* registered directly with IANA. The interpretation of these codes
* is undefined by this protocol.
*
* 4000-4999
*
* Status codes in the range 4000-4999 are reserved for private use
* and thus can't be registered. Such codes can be used by prior
* agreements between WebSocket applications. The interpretation of
* these codes is undefined by this protocol.
* </pre>
* <p>
* While {@link WebSocketCloseStatus} is enum-like structure, its instances should NOT be compared by reference.
* Instead, either {@link #equals(Object)} should be used or direct comparison of {@link #code()} value.
*/
public final class WebSocketCloseStatus implements Comparable<WebSocketCloseStatus> {
public static final WebSocketCloseStatus NORMAL_CLOSURE =
new WebSocketCloseStatus(1000, "Bye");
public static final WebSocketCloseStatus ENDPOINT_UNAVAILABLE =
new WebSocketCloseStatus(1001, "Endpoint unavailable");
public static final WebSocketCloseStatus PROTOCOL_ERROR =
new WebSocketCloseStatus(1002, "Protocol error");
public static final WebSocketCloseStatus INVALID_MESSAGE_TYPE =
new WebSocketCloseStatus(1003, "Invalid message type");
public static final WebSocketCloseStatus INVALID_PAYLOAD_DATA =
new WebSocketCloseStatus(1007, "Invalid payload data");
public static final WebSocketCloseStatus POLICY_VIOLATION =
new WebSocketCloseStatus(1008, "Policy violation");
public static final WebSocketCloseStatus MESSAGE_TOO_BIG =
new WebSocketCloseStatus(1009, "Message too big");
public static final WebSocketCloseStatus MANDATORY_EXTENSION =
new WebSocketCloseStatus(1010, "Mandatory extension");
public static final WebSocketCloseStatus INTERNAL_SERVER_ERROR =
new WebSocketCloseStatus(1011, "Internal server error");
public static final WebSocketCloseStatus SERVICE_RESTART =
new WebSocketCloseStatus(1012, "Service Restart");
public static final WebSocketCloseStatus TRY_AGAIN_LATER =
new WebSocketCloseStatus(1013, "Try Again Later");
public static final WebSocketCloseStatus BAD_GATEWAY =
new WebSocketCloseStatus(1014, "Bad Gateway");
// 1004, 1005, 1006, 1015 are reserved and should never be used by user
//public static final WebSocketCloseStatus SPECIFIC_MEANING = register(1004, "...");
//public static final WebSocketCloseStatus EMPTY = register(1005, "Empty");
//public static final WebSocketCloseStatus ABNORMAL_CLOSURE = register(1006, "Abnormal closure");
//public static final WebSocketCloseStatus TLS_HANDSHAKE_FAILED(1015, "TLS handshake failed");
private final int statusCode;
private final String reasonText;
private String text;
public WebSocketCloseStatus(int statusCode, String reasonText) {
if (!isValidStatusCode(statusCode)) {
throw new IllegalArgumentException(
"WebSocket close status code does NOT comply with RFC-6455: " + statusCode);
}
this.statusCode = statusCode;
this.reasonText = checkNotNull(reasonText, "reasonText");
}
public int code() {
return statusCode;
}
public String reasonText() {
return reasonText;
}
/**
* Order of {@link WebSocketCloseStatus} only depends on {@link #code()}.
*/
@Override
public int compareTo(WebSocketCloseStatus o) {
return code() - o.code();
}
/**
* Equality of {@link WebSocketCloseStatus} only depends on {@link #code()}.
*/
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (null == o || getClass() != o.getClass()) {
return false;
}
WebSocketCloseStatus that = (WebSocketCloseStatus) o;
return statusCode == that.statusCode;
}
@Override
public int hashCode() {
return statusCode;
}
@Override
public String toString() {
String text = this.text;
if (text == null) {
// E.g.: "1000 Bye", "1009 Message too big"
this.text = text = code() + " " + reasonText();
}
return text;
}
public static boolean isValidStatusCode(int code) {
return code < 0 ||
1000 <= code && code <= 1003 ||
1007 <= code && code <= 1014 ||
3000 <= code;
}
public static WebSocketCloseStatus valueOf(int code) {
switch (code) {
case 1000:
return NORMAL_CLOSURE;
case 1001:
return ENDPOINT_UNAVAILABLE;
case 1002:
return PROTOCOL_ERROR;
case 1003:
return INVALID_MESSAGE_TYPE;
case 1007:
return INVALID_PAYLOAD_DATA;
case 1008:
return POLICY_VIOLATION;
case 1009:
return MESSAGE_TOO_BIG;
case 1010:
return MANDATORY_EXTENSION;
case 1011:
return INTERNAL_SERVER_ERROR;
case 1012:
return SERVICE_RESTART;
case 1013:
return TRY_AGAIN_LATER;
case 1014:
return BAD_GATEWAY;
default:
return new WebSocketCloseStatus(code, "Close status #" + code);
}
}
}

View File

@ -0,0 +1,147 @@
/*
* Copyright 2019 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.
*/
package io.netty.handler.codec.http.websocketx;
import io.netty.util.internal.ObjectUtil;
/**
* Frames decoder configuration.
*/
public final class WebSocketDecoderConfig {
private final int maxFramePayloadLength;
private final boolean expectMaskedFrames;
private final boolean allowMaskMismatch;
private final boolean allowExtensions;
private final boolean closeOnProtocolViolation;
/**
* Constructor
*
* @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.
* @param expectMaskedFrames
* Web socket servers must set this to true processed incoming masked payload. Client implementations
* must set this to false.
* @param allowMaskMismatch
* Allows to loosen the masking requirement on received frames. When this is set to false then also
* frames which are not masked properly according to the standard will still be accepted.
* @param allowExtensions
* Flag to allow reserved extension bits to be used or not
* @param closeOnProtocolViolation
* Flag to send close frame immediately on any protocol violation.ion.
*/
private WebSocketDecoderConfig(int maxFramePayloadLength, boolean expectMaskedFrames, boolean allowMaskMismatch,
boolean allowExtensions, boolean closeOnProtocolViolation) {
this.maxFramePayloadLength = maxFramePayloadLength;
this.expectMaskedFrames = expectMaskedFrames;
this.allowMaskMismatch = allowMaskMismatch;
this.allowExtensions = allowExtensions;
this.closeOnProtocolViolation = closeOnProtocolViolation;
}
public int maxFramePayloadLength() {
return maxFramePayloadLength;
}
public boolean expectMaskedFrames() {
return expectMaskedFrames;
}
public boolean allowMaskMismatch() {
return allowMaskMismatch;
}
public boolean allowExtensions() {
return allowExtensions;
}
public boolean closeOnProtocolViolation() {
return closeOnProtocolViolation;
}
@Override
public String toString() {
return "WebSocketDecoderConfig" +
" [maxFramePayloadLength=" + maxFramePayloadLength +
", expectMaskedFrames=" + expectMaskedFrames +
", allowMaskMismatch=" + allowMaskMismatch +
", allowExtensions=" + allowExtensions +
", closeOnProtocolViolation=" + closeOnProtocolViolation +
"]";
}
public Builder toBuilder() {
return new Builder(this);
}
public static Builder newBuilder() {
return new Builder();
}
public static final class Builder {
private int maxFramePayloadLength = 65536;
private boolean expectMaskedFrames = true;
private boolean allowMaskMismatch;
private boolean allowExtensions;
private boolean closeOnProtocolViolation = true;
private Builder() {
/* No-op */
}
private Builder(WebSocketDecoderConfig decoderConfig) {
ObjectUtil.checkNotNull(decoderConfig, "decoderConfig");
maxFramePayloadLength = decoderConfig.maxFramePayloadLength();
expectMaskedFrames = decoderConfig.expectMaskedFrames();
allowMaskMismatch = decoderConfig.allowMaskMismatch();
allowExtensions = decoderConfig.allowExtensions();
closeOnProtocolViolation = decoderConfig.closeOnProtocolViolation();
}
public Builder maxFramePayloadLength(int maxFramePayloadLength) {
this.maxFramePayloadLength = maxFramePayloadLength;
return this;
}
public Builder expectMaskedFrames(boolean expectMaskedFrames) {
this.expectMaskedFrames = expectMaskedFrames;
return this;
}
public Builder allowMaskMismatch(boolean allowMaskMismatch) {
this.allowMaskMismatch = allowMaskMismatch;
return this;
}
public Builder allowExtensions(boolean allowExtensions) {
this.allowExtensions = allowExtensions;
return this;
}
public Builder closeOnProtocolViolation(boolean closeOnProtocolViolation) {
this.closeOnProtocolViolation = closeOnProtocolViolation;
return this;
}
public WebSocketDecoderConfig build() {
return new WebSocketDecoderConfig(
maxFramePayloadLength, expectMaskedFrames, allowMaskMismatch,
allowExtensions, closeOnProtocolViolation);
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012 The Netty Project
* Copyright 2019 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
@ -35,7 +35,7 @@ import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.internal.EmptyArrays;
import io.netty.util.internal.ThrowableUtil;
import io.netty.util.internal.ObjectUtil;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
@ -56,7 +56,7 @@ public abstract class WebSocketServerHandshaker {
private final WebSocketVersion version;
private final int maxFramePayloadLength;
private final WebSocketDecoderConfig decoderConfig;
private String selectedSubprotocol;
@ -81,6 +81,26 @@ public abstract class WebSocketServerHandshaker {
protected WebSocketServerHandshaker(
WebSocketVersion version, String uri, String subprotocols,
int maxFramePayloadLength) {
this(version, uri, subprotocols, WebSocketDecoderConfig.newBuilder()
.maxFramePayloadLength(maxFramePayloadLength)
.build());
}
/**
* Constructor specifying the destination web socket location
*
* @param version
* the protocol version
* @param uri
* URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
* sent to this URL.
* @param subprotocols
* CSV of supported protocols. Null if sub protocols not supported.
* @param decoderConfig
* Frames decoder configuration.
*/
protected WebSocketServerHandshaker(
WebSocketVersion version, String uri, String subprotocols, WebSocketDecoderConfig decoderConfig) {
this.version = version;
this.uri = uri;
if (subprotocols != null) {
@ -92,7 +112,7 @@ public abstract class WebSocketServerHandshaker {
} else {
this.subprotocols = EmptyArrays.EMPTY_STRINGS;
}
this.maxFramePayloadLength = maxFramePayloadLength;
this.decoderConfig = ObjectUtil.checkNotNull(decoderConfig, "decoderConfig");
}
/**
@ -124,7 +144,16 @@ public abstract class WebSocketServerHandshaker {
* @return The maximum length for a frame's payload
*/
public int maxFramePayloadLength() {
return maxFramePayloadLength;
return decoderConfig.maxFramePayloadLength();
}
/**
* Gets this decoder configuration.
*
* @return This decoder configuration.
*/
public WebSocketDecoderConfig decoderConfig() {
return decoderConfig;
}
/**

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012 The Netty Project
* Copyright 2019 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
@ -60,7 +60,24 @@ public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker {
* reduce denial of service attacks using long data frames.
*/
public WebSocketServerHandshaker00(String webSocketURL, String subprotocols, int maxFramePayloadLength) {
super(WebSocketVersion.V00, webSocketURL, subprotocols, maxFramePayloadLength);
this(webSocketURL, subprotocols, WebSocketDecoderConfig.newBuilder()
.maxFramePayloadLength(maxFramePayloadLength)
.build());
}
/**
* Constructor specifying the destination web socket location
*
* @param webSocketURL
* URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
* sent to this URL.
* @param subprotocols
* CSV of supported protocols
* @param decoderConfig
* Frames decoder configuration.
*/
public WebSocketServerHandshaker00(String webSocketURL, String subprotocols, WebSocketDecoderConfig decoderConfig) {
super(WebSocketVersion.V00, webSocketURL, subprotocols, decoderConfig);
}
/**
@ -189,7 +206,7 @@ public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker {
@Override
protected WebSocketFrameDecoder newWebsocketDecoder() {
return new WebSocket00FrameDecoder(maxFramePayloadLength());
return new WebSocket00FrameDecoder(decoderConfig());
}
@Override

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012 The Netty Project
* Copyright 2019 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
@ -37,9 +37,6 @@ public class WebSocketServerHandshaker07 extends WebSocketServerHandshaker {
public static final String WEBSOCKET_07_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
private final boolean allowExtensions;
private final boolean allowMaskMismatch;
/**
* Constructor specifying the destination web socket location
*
@ -79,9 +76,21 @@ public class WebSocketServerHandshaker07 extends WebSocketServerHandshaker {
public WebSocketServerHandshaker07(
String webSocketURL, String subprotocols, boolean allowExtensions, int maxFramePayloadLength,
boolean allowMaskMismatch) {
super(WebSocketVersion.V07, webSocketURL, subprotocols, maxFramePayloadLength);
this.allowExtensions = allowExtensions;
this.allowMaskMismatch = allowMaskMismatch;
this(webSocketURL, subprotocols, WebSocketDecoderConfig.newBuilder()
.allowExtensions(allowExtensions)
.maxFramePayloadLength(maxFramePayloadLength)
.allowMaskMismatch(allowMaskMismatch)
.build());
}
/**
* Constructor specifying the destination web socket location
*
* @param decoderConfig
* Frames decoder configuration.
*/
public WebSocketServerHandshaker07(String webSocketURL, String subprotocols, WebSocketDecoderConfig decoderConfig) {
super(WebSocketVersion.V07, webSocketURL, subprotocols, decoderConfig);
}
/**
@ -159,7 +168,7 @@ public class WebSocketServerHandshaker07 extends WebSocketServerHandshaker {
@Override
protected WebSocketFrameDecoder newWebsocketDecoder() {
return new WebSocket07FrameDecoder(true, allowExtensions, maxFramePayloadLength(), allowMaskMismatch);
return new WebSocket07FrameDecoder(decoderConfig());
}
@Override

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012 The Netty Project
* Copyright 2019 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
@ -37,9 +37,6 @@ public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker {
public static final String WEBSOCKET_08_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
private final boolean allowExtensions;
private final boolean allowMaskMismatch;
/**
* Constructor specifying the destination web socket location
*
@ -79,9 +76,27 @@ public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker {
public WebSocketServerHandshaker08(
String webSocketURL, String subprotocols, boolean allowExtensions, int maxFramePayloadLength,
boolean allowMaskMismatch) {
super(WebSocketVersion.V08, webSocketURL, subprotocols, maxFramePayloadLength);
this.allowExtensions = allowExtensions;
this.allowMaskMismatch = allowMaskMismatch;
this(webSocketURL, subprotocols, WebSocketDecoderConfig.newBuilder()
.allowExtensions(allowExtensions)
.maxFramePayloadLength(maxFramePayloadLength)
.allowMaskMismatch(allowMaskMismatch)
.build());
}
/**
* Constructor specifying the destination web socket location
*
* @param webSocketURL
* URL for web socket communications. e.g "ws://myhost.com/mypath".
* Subsequent web socket frames will be sent to this URL.
* @param subprotocols
* CSV of supported protocols
* @param decoderConfig
* Frames decoder configuration.
*/
public WebSocketServerHandshaker08(
String webSocketURL, String subprotocols, WebSocketDecoderConfig decoderConfig) {
super(WebSocketVersion.V08, webSocketURL, subprotocols, decoderConfig);
}
/**
@ -158,7 +173,7 @@ public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker {
@Override
protected WebSocketFrameDecoder newWebsocketDecoder() {
return new WebSocket08FrameDecoder(true, allowExtensions, maxFramePayloadLength(), allowMaskMismatch);
return new WebSocket08FrameDecoder(decoderConfig());
}
@Override

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012 The Netty Project
* Copyright 2019 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
@ -36,9 +36,6 @@ public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker {
public static final String WEBSOCKET_13_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
private final boolean allowExtensions;
private final boolean allowMaskMismatch;
/**
* Constructor specifying the destination web socket location
*
@ -78,9 +75,27 @@ public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker {
public WebSocketServerHandshaker13(
String webSocketURL, String subprotocols, boolean allowExtensions, int maxFramePayloadLength,
boolean allowMaskMismatch) {
super(WebSocketVersion.V13, webSocketURL, subprotocols, maxFramePayloadLength);
this.allowExtensions = allowExtensions;
this.allowMaskMismatch = allowMaskMismatch;
this(webSocketURL, subprotocols, WebSocketDecoderConfig.newBuilder()
.allowExtensions(allowExtensions)
.maxFramePayloadLength(maxFramePayloadLength)
.allowMaskMismatch(allowMaskMismatch)
.build());
}
/**
* Constructor specifying the destination web socket location
*
* @param webSocketURL
* URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web
* socket frames will be sent to this URL.
* @param subprotocols
* CSV of supported protocols
* @param decoderConfig
* Frames decoder configuration.
*/
public WebSocketServerHandshaker13(
String webSocketURL, String subprotocols, WebSocketDecoderConfig decoderConfig) {
super(WebSocketVersion.V13, webSocketURL, subprotocols, decoderConfig);
}
/**
@ -156,7 +171,7 @@ public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker {
@Override
protected WebSocketFrameDecoder newWebsocketDecoder() {
return new WebSocket13FrameDecoder(true, allowExtensions, maxFramePayloadLength(), allowMaskMismatch);
return new WebSocket13FrameDecoder(decoderConfig());
}
@Override

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012 The Netty Project
* Copyright 2019 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
@ -25,6 +25,7 @@ import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.util.internal.ObjectUtil;
/**
* Auto-detects the version of the Web Socket protocol in use and creates a new proper
@ -36,11 +37,7 @@ public class WebSocketServerHandshakerFactory {
private final String subprotocols;
private final boolean allowExtensions;
private final int maxFramePayloadLength;
private final boolean allowMaskMismatch;
private final WebSocketDecoderConfig decoderConfig;
/**
* Constructor specifying the destination web socket location
@ -98,11 +95,29 @@ public class WebSocketServerHandshakerFactory {
public WebSocketServerHandshakerFactory(
String webSocketURL, String subprotocols, boolean allowExtensions,
int maxFramePayloadLength, boolean allowMaskMismatch) {
this(webSocketURL, subprotocols, WebSocketDecoderConfig.newBuilder()
.allowExtensions(allowExtensions)
.maxFramePayloadLength(maxFramePayloadLength)
.allowMaskMismatch(allowMaskMismatch)
.build());
}
/**
* Constructor specifying the destination web socket location
*
* @param webSocketURL
* URL for web socket communications. e.g "ws://myhost.com/mypath".
* Subsequent web socket frames will be sent to this URL.
* @param subprotocols
* CSV of supported protocols. Null if sub protocols not supported.
* @param decoderConfig
* Frames decoder options.
*/
public WebSocketServerHandshakerFactory(
String webSocketURL, String subprotocols, WebSocketDecoderConfig decoderConfig) {
this.webSocketURL = webSocketURL;
this.subprotocols = subprotocols;
this.allowExtensions = allowExtensions;
this.maxFramePayloadLength = maxFramePayloadLength;
this.allowMaskMismatch = allowMaskMismatch;
this.decoderConfig = ObjectUtil.checkNotNull(decoderConfig, "decoderConfig");
}
/**
@ -118,21 +133,21 @@ public class WebSocketServerHandshakerFactory {
if (version.equals(WebSocketVersion.V13.toHttpHeaderValue())) {
// Version 13 of the wire protocol - RFC 6455 (version 17 of the draft hybi specification).
return new WebSocketServerHandshaker13(
webSocketURL, subprotocols, allowExtensions, maxFramePayloadLength, allowMaskMismatch);
webSocketURL, subprotocols, decoderConfig);
} else if (version.equals(WebSocketVersion.V08.toHttpHeaderValue())) {
// Version 8 of the wire protocol - version 10 of the draft hybi specification.
return new WebSocketServerHandshaker08(
webSocketURL, subprotocols, allowExtensions, maxFramePayloadLength, allowMaskMismatch);
webSocketURL, subprotocols, decoderConfig);
} else if (version.equals(WebSocketVersion.V07.toHttpHeaderValue())) {
// Version 8 of the wire protocol - version 07 of the draft hybi specification.
return new WebSocketServerHandshaker07(
webSocketURL, subprotocols, allowExtensions, maxFramePayloadLength, allowMaskMismatch);
webSocketURL, subprotocols, decoderConfig);
} else {
return null;
}
} else {
// Assume version 00 where version header was not specified
return new WebSocketServerHandshaker00(webSocketURL, subprotocols, maxFramePayloadLength);
return new WebSocketServerHandshaker00(webSocketURL, subprotocols, decoderConfig);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012 The Netty Project
* Copyright 2019 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
@ -106,11 +106,9 @@ public class WebSocketServerProtocolHandler extends WebSocketProtocolHandler {
private final String websocketPath;
private final String subprotocols;
private final boolean allowExtensions;
private final int maxFramePayloadLength;
private final boolean allowMaskMismatch;
private final boolean checkStartsWith;
private final long handshakeTimeoutMillis;
private final WebSocketDecoderConfig decoderConfig;
public WebSocketServerProtocolHandler(String websocketPath) {
this(websocketPath, DEFAULT_HANDSHAKE_TIMEOUT_MS);
@ -190,14 +188,23 @@ public class WebSocketServerProtocolHandler extends WebSocketProtocolHandler {
public WebSocketServerProtocolHandler(String websocketPath, String subprotocols, boolean allowExtensions,
int maxFrameSize, boolean allowMaskMismatch, boolean checkStartsWith,
boolean dropPongFrames, long handshakeTimeoutMillis) {
this(websocketPath, subprotocols, checkStartsWith, dropPongFrames, handshakeTimeoutMillis,
WebSocketDecoderConfig.newBuilder()
.maxFramePayloadLength(maxFrameSize)
.allowMaskMismatch(allowMaskMismatch)
.allowExtensions(allowExtensions)
.build());
}
public WebSocketServerProtocolHandler(String websocketPath, String subprotocols, boolean checkStartsWith,
boolean dropPongFrames, long handshakeTimeoutMillis,
WebSocketDecoderConfig decoderConfig) {
super(dropPongFrames);
this.websocketPath = websocketPath;
this.subprotocols = subprotocols;
this.allowExtensions = allowExtensions;
maxFramePayloadLength = maxFrameSize;
this.allowMaskMismatch = allowMaskMismatch;
this.checkStartsWith = checkStartsWith;
this.handshakeTimeoutMillis = checkPositive(handshakeTimeoutMillis, "handshakeTimeoutMillis");
this.decoderConfig = checkNotNull(decoderConfig, "decoderConfig");
}
@Override
@ -206,11 +213,8 @@ public class WebSocketServerProtocolHandler extends WebSocketProtocolHandler {
if (cp.get(WebSocketServerProtocolHandshakeHandler.class) == null) {
// Add the WebSocketHandshakeHandler before this one.
ctx.pipeline().addBefore(ctx.name(), WebSocketServerProtocolHandshakeHandler.class.getName(),
new WebSocketServerProtocolHandshakeHandler(websocketPath, subprotocols,
allowExtensions, maxFramePayloadLength,
allowMaskMismatch,
checkStartsWith,
handshakeTimeoutMillis));
new WebSocketServerProtocolHandshakeHandler(
websocketPath, subprotocols, checkStartsWith, handshakeTimeoutMillis, decoderConfig));
}
if (cp.get(Utf8FrameValidator.class) == null) {
// Add the UFT8 checking before this one.

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012 The Netty Project
* Copyright 2019 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
@ -48,43 +48,19 @@ class WebSocketServerProtocolHandshakeHandler implements ChannelInboundHandler {
private final String websocketPath;
private final String subprotocols;
private final boolean allowExtensions;
private final int maxFramePayloadSize;
private final boolean allowMaskMismatch;
private final boolean checkStartsWith;
private final long handshakeTimeoutMillis;
private final WebSocketDecoderConfig decoderConfig;
private ChannelHandlerContext ctx;
private ChannelPromise handshakePromise;
WebSocketServerProtocolHandshakeHandler(String websocketPath, String subprotocols,
boolean allowExtensions, int maxFrameSize, boolean allowMaskMismatch) {
this(websocketPath, subprotocols, allowExtensions, maxFrameSize, allowMaskMismatch,
DEFAULT_HANDSHAKE_TIMEOUT_MS);
}
WebSocketServerProtocolHandshakeHandler(String websocketPath, String subprotocols,
boolean allowExtensions, int maxFrameSize,
boolean allowMaskMismatch, long handshakeTimeoutMillis) {
this(websocketPath, subprotocols, allowExtensions, maxFrameSize, allowMaskMismatch,
false, handshakeTimeoutMillis);
}
WebSocketServerProtocolHandshakeHandler(String websocketPath, String subprotocols,
boolean allowExtensions, int maxFrameSize, boolean allowMaskMismatch, boolean checkStartsWith) {
this(websocketPath, subprotocols, allowExtensions, maxFrameSize, allowMaskMismatch,
checkStartsWith, DEFAULT_HANDSHAKE_TIMEOUT_MS);
}
WebSocketServerProtocolHandshakeHandler(String websocketPath, String subprotocols,
boolean allowExtensions, int maxFrameSize, boolean allowMaskMismatch,
boolean checkStartsWith, long handshakeTimeoutMillis) {
boolean checkStartsWith, long handshakeTimeoutMillis, WebSocketDecoderConfig decoderConfig) {
this.websocketPath = websocketPath;
this.subprotocols = subprotocols;
this.allowExtensions = allowExtensions;
maxFramePayloadSize = maxFrameSize;
this.allowMaskMismatch = allowMaskMismatch;
this.checkStartsWith = checkStartsWith;
this.handshakeTimeoutMillis = checkPositive(handshakeTimeoutMillis, "handshakeTimeoutMillis");
this.decoderConfig = checkNotNull(decoderConfig, "decoderConfig");
}
@Override
@ -108,8 +84,7 @@ class WebSocketServerProtocolHandshakeHandler implements ChannelInboundHandler {
}
final WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(
getWebSocketLocation(ctx.pipeline(), req, websocketPath), subprotocols,
allowExtensions, maxFramePayloadSize, allowMaskMismatch);
getWebSocketLocation(ctx.pipeline(), req, websocketPath), subprotocols, decoderConfig);
final WebSocketServerHandshaker handshaker = wsFactory.newHandshaker(req);
final ChannelPromise localHandshakePromise = handshakePromise;
if (handshaker == null) {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2014 The Netty Project
* Copyright 2019 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
@ -53,6 +53,67 @@ public class WebSocket08EncoderDecoderTest {
strTestData = s.toString();
}
@Test
public void testWebSocketProtocolViolation() {
// Given
initTestData();
int maxPayloadLength = 255;
String errorMessage = "Max frame length of " + maxPayloadLength + " has been exceeded.";
WebSocketCloseStatus expectedStatus = WebSocketCloseStatus.MESSAGE_TOO_BIG;
// With auto-close
WebSocketDecoderConfig config = WebSocketDecoderConfig.newBuilder()
.maxFramePayloadLength(maxPayloadLength)
.closeOnProtocolViolation(true)
.build();
EmbeddedChannel inChannel = new EmbeddedChannel(new WebSocket08FrameDecoder(config));
EmbeddedChannel outChannel = new EmbeddedChannel(new WebSocket08FrameEncoder(true));
executeProtocolViolationTest(outChannel, inChannel, maxPayloadLength + 1, expectedStatus, errorMessage);
CloseWebSocketFrame response = inChannel.readOutbound();
Assert.assertNotNull(response);
Assert.assertEquals(expectedStatus.code(), response.statusCode());
Assert.assertEquals(errorMessage, response.reasonText());
// Without auto-close
config = WebSocketDecoderConfig.newBuilder()
.maxFramePayloadLength(maxPayloadLength)
.closeOnProtocolViolation(false)
.build();
inChannel = new EmbeddedChannel(new WebSocket08FrameDecoder(config));
outChannel = new EmbeddedChannel(new WebSocket08FrameEncoder(true));
executeProtocolViolationTest(outChannel, inChannel, maxPayloadLength + 1, expectedStatus, errorMessage);
response = inChannel.readOutbound();
Assert.assertNull(response);
// Release test data
binTestData.release();
Assert.assertFalse(inChannel.finish());
Assert.assertFalse(outChannel.finish());
}
private void executeProtocolViolationTest(EmbeddedChannel outChannel, EmbeddedChannel inChannel,
int testDataLength, WebSocketCloseStatus expectedStatus, String errorMessage) {
CorruptedWebSocketFrameException corrupted = null;
try {
testBinaryWithLen(outChannel, inChannel, testDataLength);
} catch (CorruptedWebSocketFrameException e) {
corrupted = e;
}
BinaryWebSocketFrame exceedingFrame = inChannel.readInbound();
Assert.assertNull(exceedingFrame);
Assert.assertNotNull(corrupted);
Assert.assertEquals(expectedStatus, corrupted.closeStatus());
Assert.assertEquals(errorMessage, corrupted.getMessage());
}
@Test
public void testWebSocketEncodingAndDecoding() {
initTestData();
@ -108,16 +169,7 @@ public class WebSocket08EncoderDecoderTest {
String testStr = strTestData.substring(0, testDataLength);
outChannel.writeOutbound(new TextWebSocketFrame(testStr));
// Transfer encoded data into decoder
// Loop because there might be multiple frames (gathering write)
while (true) {
ByteBuf encoded = outChannel.readOutbound();
if (encoded != null) {
inChannel.writeInbound(encoded);
} else {
break;
}
}
transfer(outChannel, inChannel);
Object decoded = inChannel.readInbound();
Assert.assertNotNull(decoded);
@ -132,16 +184,7 @@ public class WebSocket08EncoderDecoderTest {
binTestData.setIndex(0, testDataLength); // Send only len bytes
outChannel.writeOutbound(new BinaryWebSocketFrame(binTestData));
// Transfer encoded data into decoder
// Loop because there might be multiple frames (gathering write)
while (true) {
ByteBuf encoded = outChannel.readOutbound();
if (encoded != null) {
inChannel.writeInbound(encoded);
} else {
break;
}
}
transfer(outChannel, inChannel);
Object decoded = inChannel.readInbound();
Assert.assertNotNull(decoded);
@ -154,4 +197,16 @@ public class WebSocket08EncoderDecoderTest {
}
binFrame.release();
}
private void transfer(EmbeddedChannel outChannel, EmbeddedChannel inChannel) {
// Transfer encoded data into decoder
// Loop because there might be multiple frames (gathering write)
for (;;) {
ByteBuf encoded = outChannel.readOutbound();
if (encoded == null) {
return;
}
inChannel.writeInbound(encoded);
}
}
}

View File

@ -0,0 +1,127 @@
/*
* Copyright 2019 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.
*/
package io.netty.handler.codec.http.websocketx;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Test;
import static io.netty.handler.codec.http.websocketx.WebSocketCloseStatus.*;
public class WebSocketCloseStatusTest {
private final List<WebSocketCloseStatus> validCodes = Arrays.asList(
NORMAL_CLOSURE,
ENDPOINT_UNAVAILABLE,
PROTOCOL_ERROR,
INVALID_MESSAGE_TYPE,
INVALID_PAYLOAD_DATA,
POLICY_VIOLATION,
MESSAGE_TOO_BIG,
MANDATORY_EXTENSION,
INTERNAL_SERVER_ERROR,
SERVICE_RESTART,
TRY_AGAIN_LATER,
BAD_GATEWAY
);
@Test
public void testToString() {
Assert.assertEquals("1000 Bye", NORMAL_CLOSURE.toString());
}
@Test
public void testKnownStatuses() {
Assert.assertSame(NORMAL_CLOSURE, valueOf(1000));
Assert.assertSame(ENDPOINT_UNAVAILABLE, valueOf(1001));
Assert.assertSame(PROTOCOL_ERROR, valueOf(1002));
Assert.assertSame(INVALID_MESSAGE_TYPE, valueOf(1003));
Assert.assertSame(INVALID_PAYLOAD_DATA, valueOf(1007));
Assert.assertSame(POLICY_VIOLATION, valueOf(1008));
Assert.assertSame(MESSAGE_TOO_BIG, valueOf(1009));
Assert.assertSame(MANDATORY_EXTENSION, valueOf(1010));
Assert.assertSame(INTERNAL_SERVER_ERROR, valueOf(1011));
Assert.assertSame(SERVICE_RESTART, valueOf(1012));
Assert.assertSame(TRY_AGAIN_LATER, valueOf(1013));
Assert.assertSame(BAD_GATEWAY, valueOf(1014));
}
@Test
public void testNaturalOrder() {
Assert.assertThat(PROTOCOL_ERROR, Matchers.greaterThan(NORMAL_CLOSURE));
Assert.assertThat(PROTOCOL_ERROR, Matchers.greaterThan(valueOf(1001)));
Assert.assertThat(PROTOCOL_ERROR, Matchers.comparesEqualTo(PROTOCOL_ERROR));
Assert.assertThat(PROTOCOL_ERROR, Matchers.comparesEqualTo(valueOf(1002)));
Assert.assertThat(PROTOCOL_ERROR, Matchers.lessThan(INVALID_MESSAGE_TYPE));
Assert.assertThat(PROTOCOL_ERROR, Matchers.lessThan(valueOf(1007)));
}
@Test
public void testUserDefinedStatuses() {
// Given, when
WebSocketCloseStatus feedTimeot = new WebSocketCloseStatus(6033, "Feed timed out");
WebSocketCloseStatus untradablePrice = new WebSocketCloseStatus(6034, "Untradable price");
// Then
Assert.assertNotSame(feedTimeot, valueOf(6033));
Assert.assertEquals(feedTimeot.code(), 6033);
Assert.assertEquals(feedTimeot.reasonText(), "Feed timed out");
Assert.assertNotSame(untradablePrice, valueOf(6034));
Assert.assertEquals(untradablePrice.code(), 6034);
Assert.assertEquals(untradablePrice.reasonText(), "Untradable price");
}
@Test
public void testRfc6455CodeValidation() {
// Given
List<Integer> knownCodes = Arrays.asList(
NORMAL_CLOSURE.code(),
ENDPOINT_UNAVAILABLE.code(),
PROTOCOL_ERROR.code(),
INVALID_MESSAGE_TYPE.code(),
INVALID_PAYLOAD_DATA.code(),
POLICY_VIOLATION.code(),
MESSAGE_TOO_BIG.code(),
MANDATORY_EXTENSION.code(),
INTERNAL_SERVER_ERROR.code(),
SERVICE_RESTART.code(),
TRY_AGAIN_LATER.code(),
BAD_GATEWAY.code()
);
SortedSet<Integer> invalidCodes = new TreeSet<Integer>();
// When
for (int statusCode = Short.MIN_VALUE; statusCode < Short.MAX_VALUE; statusCode++) {
if (!isValidStatusCode(statusCode)) {
invalidCodes.add(statusCode);
}
}
// Then
Assert.assertEquals(0, invalidCodes.first().intValue());
Assert.assertEquals(2999, invalidCodes.last().intValue());
Assert.assertEquals(3000 - validCodes.size(), invalidCodes.size());
invalidCodes.retainAll(knownCodes);
Assert.assertEquals(invalidCodes, Collections.emptySet());
}
}