From 82461f051172ec0b7635107e7bb400455fadaa7c Mon Sep 17 00:00:00 2001 From: Matthias Einwag Date: Wed, 22 Oct 2014 21:59:45 +0200 Subject: [PATCH] Added an option to use websockets without masking Motivation: The requirement for the masking of frames and for checks of correct masking in the websocket specifiation have a large impact on performance. While it is mandatory for browsers to use masking there are other applications (like IPC protocols) that want to user websocket framing and proxy-traversing characteristics without the overhead of masking. The websocket standard also mentions that the requirement for mask verification on server side might be dropped in future. Modifications: Added an optional parameter allowMaskMismatch for the websocket decoder that allows a server to also accept unmasked frames (and clients to accept masked frames). Allowed to set this option through the websocket handshaker constructors as well as the websocket client and server handlers. The public API for existing components doesn't change, it will be forwarded to functions which implicetly set masking as required in the specification. For websocket clients an additional parameter is added that allows to disable the masking of frames that are sent by the client. Result: This update gives netty users the ability to create and use completely unmasked websocket connections in addition to the normal masked channels that the standard describes. --- .../websocketx/WebSocket07FrameDecoder.java | 26 ++++++++++-- .../websocketx/WebSocket08FrameDecoder.java | 42 +++++++++++++++---- .../websocketx/WebSocket13FrameDecoder.java | 26 ++++++++++-- .../WebSocketClientHandshaker07.java | 40 ++++++++++++++++-- .../WebSocketClientHandshaker08.java | 40 ++++++++++++++++-- .../WebSocketClientHandshaker13.java | 40 ++++++++++++++++-- .../WebSocketClientHandshakerFactory.java | 42 +++++++++++++++++-- .../WebSocketClientProtocolHandler.java | 37 +++++++++++++++- .../WebSocketServerHandshaker07.java | 27 +++++++++++- .../WebSocketServerHandshaker08.java | 27 +++++++++++- .../WebSocketServerHandshaker13.java | 27 +++++++++++- .../WebSocketServerHandshakerFactory.java | 32 ++++++++++++-- .../WebSocketServerProtocolHandler.java | 11 ++++- ...bSocketServerProtocolHandshakeHandler.java | 6 ++- .../WebSocket08EncoderDecoderTest.java | 9 +++- .../WebSocket08FrameDecoderTest.java | 2 +- .../WebSocketServerHandshaker08Test.java | 4 +- .../WebSocketServerHandshaker13Test.java | 4 +- 18 files changed, 396 insertions(+), 46 deletions(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket07FrameDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket07FrameDecoder.java index 55ef8d941b..457cfbb740 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket07FrameDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket07FrameDecoder.java @@ -61,7 +61,7 @@ public class WebSocket07FrameDecoder extends WebSocket08FrameDecoder { /** * Constructor * - * @param maskedPayload + * @param expectMaskedFrames * Web socket servers must set this to true processed incoming masked payload. Client implementations * must set this to false. * @param allowExtensions @@ -70,7 +70,27 @@ public class WebSocket07FrameDecoder extends WebSocket08FrameDecoder { * Maximum length of a frame's payload. Setting this to an appropriate value for you application * helps check for denial of services attacks. */ - public WebSocket07FrameDecoder(boolean maskedPayload, boolean allowExtensions, int maxFramePayloadLength) { - super(maskedPayload, allowExtensions, maxFramePayloadLength); + public WebSocket07FrameDecoder(boolean expectMaskedFrames, boolean allowExtensions, int maxFramePayloadLength) { + this(expectMaskedFrames, allowExtensions, maxFramePayloadLength, false); + } + + /** + * Constructor + * + * @param expectMaskedFrames + * 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 + * @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 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. + */ + public WebSocket07FrameDecoder(boolean expectMaskedFrames, boolean allowExtensions, int maxFramePayloadLength, + boolean allowMaskMismatch) { + super(expectMaskedFrames, allowExtensions, maxFramePayloadLength, allowMaskMismatch); } } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket08FrameDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket08FrameDecoder.java index 0c0528c22e..35d2930fd8 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket08FrameDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket08FrameDecoder.java @@ -95,10 +95,12 @@ public class WebSocket08FrameDecoder extends ByteToMessageDecoder private final long maxFramePayloadLength; private final boolean allowExtensions; - private final boolean maskedPayload; + private final boolean expectMaskedFrames; + private final boolean allowMaskMismatch; private int fragmentedFramesCount; private boolean frameFinalFlag; + private boolean frameMasked; private int frameRsv; private int frameOpcode; private long framePayloadLength; @@ -110,7 +112,7 @@ public class WebSocket08FrameDecoder extends ByteToMessageDecoder /** * Constructor * - * @param maskedPayload + * @param expectMaskedFrames * Web socket servers must set this to true processed incoming masked payload. Client implementations * must set this to false. * @param allowExtensions @@ -119,8 +121,29 @@ public class WebSocket08FrameDecoder extends ByteToMessageDecoder * Maximum length of a frame's payload. Setting this to an appropriate value for you application * helps check for denial of services attacks. */ - public WebSocket08FrameDecoder(boolean maskedPayload, boolean allowExtensions, int maxFramePayloadLength) { - this.maskedPayload = maskedPayload; + public WebSocket08FrameDecoder(boolean expectMaskedFrames, boolean allowExtensions, int maxFramePayloadLength) { + this(expectMaskedFrames, allowExtensions, maxFramePayloadLength, false); + } + + /** + * Constructor + * + * @param expectMaskedFrames + * 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 + * @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 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. + */ + public WebSocket08FrameDecoder(boolean expectMaskedFrames, boolean allowExtensions, int maxFramePayloadLength, + boolean allowMaskMismatch) { + this.expectMaskedFrames = expectMaskedFrames; + this.allowMaskMismatch = allowMaskMismatch; this.allowExtensions = allowExtensions; this.maxFramePayloadLength = maxFramePayloadLength; } @@ -158,7 +181,7 @@ public class WebSocket08FrameDecoder extends ByteToMessageDecoder } // MASK, PAYLOAD LEN 1 b = in.readByte(); - boolean frameMasked = (b & 0x80) != 0; + frameMasked = (b & 0x80) != 0; framePayloadLen1 = b & 0x7F; if (frameRsv != 0 && !allowExtensions) { @@ -166,10 +189,11 @@ public class WebSocket08FrameDecoder extends ByteToMessageDecoder return; } - if (maskedPayload && !frameMasked) { - protocolViolation(ctx, "unmasked client to server frame"); + if (!allowMaskMismatch && expectMaskedFrames != frameMasked) { + protocolViolation(ctx, "received a frame that is not masked as expected"); return; } + if (frameOpcode > 7) { // control frame (have MSB in opcode set) // control frames MUST NOT be fragmented @@ -260,7 +284,7 @@ public class WebSocket08FrameDecoder extends ByteToMessageDecoder state = State.MASKING_KEY; case MASKING_KEY: - if (maskedPayload) { + if (frameMasked) { if (in.readableBytes() < 4) { return; } @@ -284,7 +308,7 @@ public class WebSocket08FrameDecoder extends ByteToMessageDecoder state = State.READING_FIRST; // Unmask data if needed - if (maskedPayload) { + if (frameMasked) { unmask(payloadBuffer); } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket13FrameDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket13FrameDecoder.java index 6a222d885d..dc605123e4 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket13FrameDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket13FrameDecoder.java @@ -61,7 +61,7 @@ public class WebSocket13FrameDecoder extends WebSocket08FrameDecoder { /** * Constructor * - * @param maskedPayload + * @param expectMaskedFrames * Web socket servers must set this to true processed incoming masked payload. Client implementations * must set this to false. * @param allowExtensions @@ -70,7 +70,27 @@ public class WebSocket13FrameDecoder extends WebSocket08FrameDecoder { * Maximum length of a frame's payload. Setting this to an appropriate value for you application * helps check for denial of services attacks. */ - public WebSocket13FrameDecoder(boolean maskedPayload, boolean allowExtensions, int maxFramePayloadLength) { - super(maskedPayload, allowExtensions, maxFramePayloadLength); + public WebSocket13FrameDecoder(boolean expectMaskedFrames, boolean allowExtensions, int maxFramePayloadLength) { + this(expectMaskedFrames, allowExtensions, maxFramePayloadLength, false); + } + + /** + * Constructor + * + * @param expectMaskedFrames + * 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 + * @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 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. + */ + public WebSocket13FrameDecoder(boolean expectMaskedFrames, boolean allowExtensions, int maxFramePayloadLength, + boolean allowMaskMismatch) { + super(expectMaskedFrames, allowExtensions, maxFramePayloadLength, allowMaskMismatch); } } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker07.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker07.java index 76b690316c..cbf4157a0e 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker07.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker07.java @@ -47,6 +47,8 @@ public class WebSocketClientHandshaker07 extends WebSocketClientHandshaker { private String expectedChallengeResponseString; private final boolean allowExtensions; + private final boolean performMasking; + private final boolean allowMaskMismatch; /** * Creates a new instance. @@ -66,9 +68,41 @@ public class WebSocketClientHandshaker07 extends WebSocketClientHandshaker { * Maximum length of a frame's payload */ public WebSocketClientHandshaker07(URI webSocketURL, WebSocketVersion version, String subprotocol, - boolean allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength) { + boolean allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength) { + this(webSocketURL, version, subprotocol, allowExtensions, customHeaders, maxFramePayloadLength, true, false); + } + + /** + * Creates a new instance. + * + * @param webSocketURL + * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be + * sent to this URL. + * @param version + * Version of web socket specification to use to connect to the server + * @param subprotocol + * Sub protocol request sent to the server. + * @param allowExtensions + * Allow extensions to be used in the reserved bits of the web socket frame + * @param customHeaders + * Map of custom headers to add to the client request + * @param maxFramePayloadLength + * Maximum length of a frame's payload + * @param performMasking + * Whether to mask all written websocket frames. This must be set to true in order to be fully compatible + * with the websocket specifications. Client applications that communicate with a non-standard server + * which doesn't require masking might set this to false to achieve a higher performance. + * @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. + */ + public WebSocketClientHandshaker07(URI webSocketURL, WebSocketVersion version, String subprotocol, + boolean allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength, + boolean performMasking, boolean allowMaskMismatch) { super(webSocketURL, version, subprotocol, customHeaders, maxFramePayloadLength); this.allowExtensions = allowExtensions; + this.performMasking = performMasking; + this.allowMaskMismatch = allowMaskMismatch; } /** @@ -192,11 +226,11 @@ public class WebSocketClientHandshaker07 extends WebSocketClientHandshaker { @Override protected WebSocketFrameDecoder newWebsocketDecoder() { - return new WebSocket07FrameDecoder(false, allowExtensions, maxFramePayloadLength()); + return new WebSocket07FrameDecoder(false, allowExtensions, maxFramePayloadLength(), allowMaskMismatch); } @Override protected WebSocketFrameEncoder newWebSocketEncoder() { - return new WebSocket07FrameEncoder(true); + return new WebSocket07FrameEncoder(performMasking); } } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java index 821d2d31f5..df81e86f43 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java @@ -48,6 +48,8 @@ public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker { private String expectedChallengeResponseString; private final boolean allowExtensions; + private final boolean performMasking; + private final boolean allowMaskMismatch; /** * Creates a new instance. @@ -67,9 +69,41 @@ public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker { * Maximum length of a frame's payload */ public WebSocketClientHandshaker08(URI webSocketURL, WebSocketVersion version, String subprotocol, - boolean allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength) { + boolean allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength) { + this(webSocketURL, version, subprotocol, allowExtensions, customHeaders, maxFramePayloadLength, true, false); + } + + /** + * Creates a new instance. + * + * @param webSocketURL + * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be + * sent to this URL. + * @param version + * Version of web socket specification to use to connect to the server + * @param subprotocol + * Sub protocol request sent to the server. + * @param allowExtensions + * Allow extensions to be used in the reserved bits of the web socket frame + * @param customHeaders + * Map of custom headers to add to the client request + * @param maxFramePayloadLength + * Maximum length of a frame's payload + * @param performMasking + * Whether to mask all written websocket frames. This must be set to true in order to be fully compatible + * with the websocket specifications. Client applications that communicate with a non-standard server + * which doesn't require masking might set this to false to achieve a higher performance. + * @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. + */ + public WebSocketClientHandshaker08(URI webSocketURL, WebSocketVersion version, String subprotocol, + boolean allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength, + boolean performMasking, boolean allowMaskMismatch) { super(webSocketURL, version, subprotocol, customHeaders, maxFramePayloadLength); this.allowExtensions = allowExtensions; + this.performMasking = performMasking; + this.allowMaskMismatch = allowMaskMismatch; } /** @@ -193,11 +227,11 @@ public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker { @Override protected WebSocketFrameDecoder newWebsocketDecoder() { - return new WebSocket08FrameDecoder(false, allowExtensions, maxFramePayloadLength()); + return new WebSocket08FrameDecoder(false, allowExtensions, maxFramePayloadLength(), allowMaskMismatch); } @Override protected WebSocketFrameEncoder newWebSocketEncoder() { - return new WebSocket08FrameEncoder(true); + return new WebSocket08FrameEncoder(performMasking); } } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java index 25e25c9bf4..90d9efa4f2 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java @@ -48,6 +48,8 @@ public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker { private String expectedChallengeResponseString; private final boolean allowExtensions; + private final boolean performMasking; + private final boolean allowMaskMismatch; /** * Creates a new instance. @@ -67,9 +69,41 @@ public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker { * Maximum length of a frame's payload */ public WebSocketClientHandshaker13(URI webSocketURL, WebSocketVersion version, String subprotocol, - boolean allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength) { + boolean allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength) { + this(webSocketURL, version, subprotocol, allowExtensions, customHeaders, maxFramePayloadLength, true, false); + } + + /** + * Creates a new instance. + * + * @param webSocketURL + * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be + * sent to this URL. + * @param version + * Version of web socket specification to use to connect to the server + * @param subprotocol + * Sub protocol request sent to the server. + * @param allowExtensions + * Allow extensions to be used in the reserved bits of the web socket frame + * @param customHeaders + * Map of custom headers to add to the client request + * @param maxFramePayloadLength + * Maximum length of a frame's payload + * @param performMasking + * Whether to mask all written websocket frames. This must be set to true in order to be fully compatible + * with the websocket specifications. Client applications that communicate with a non-standard server + * which doesn't require masking might set this to false to achieve a higher performance. + * @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. + */ + public WebSocketClientHandshaker13(URI webSocketURL, WebSocketVersion version, String subprotocol, + boolean allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength, + boolean performMasking, boolean allowMaskMismatch) { super(webSocketURL, version, subprotocol, customHeaders, maxFramePayloadLength); this.allowExtensions = allowExtensions; + this.performMasking = performMasking; + this.allowMaskMismatch = allowMaskMismatch; } /** @@ -203,11 +237,11 @@ public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker { @Override protected WebSocketFrameDecoder newWebsocketDecoder() { - return new WebSocket13FrameDecoder(false, allowExtensions, maxFramePayloadLength()); + return new WebSocket13FrameDecoder(false, allowExtensions, maxFramePayloadLength(), allowMaskMismatch); } @Override protected WebSocketFrameEncoder newWebSocketEncoder() { - return new WebSocket13FrameEncoder(true); + return new WebSocket13FrameEncoder(performMasking); } } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshakerFactory.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshakerFactory.java index 6e616bf1f8..9df7294adc 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshakerFactory.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshakerFactory.java @@ -74,17 +74,53 @@ public final class WebSocketClientHandshakerFactory { public static WebSocketClientHandshaker newHandshaker( URI webSocketURL, WebSocketVersion version, String subprotocol, boolean allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength) { + return newHandshaker(webSocketURL, version, subprotocol, allowExtensions, customHeaders, + maxFramePayloadLength, true, false); + } + + /** + * Creates a new handshaker. + * + * @param webSocketURL + * URL for web socket communications. e.g "ws://myhost.com/mypath". + * Subsequent web socket frames will be sent to this URL. + * @param version + * Version of web socket specification to use to connect to the server + * @param subprotocol + * Sub protocol request sent to the server. Null if no sub-protocol support is required. + * @param allowExtensions + * Allow extensions to be used in the reserved bits of the web socket frame + * @param customHeaders + * Custom HTTP headers to send during the handshake + * @param maxFramePayloadLength + * Maximum allowable frame payload length. Setting this value to your application's + * requirement may reduce denial of service attacks using long data frames. + * @param performMasking + * Whether to mask all written websocket frames. This must be set to true in order to be fully compatible + * with the websocket specifications. Client applications that communicate with a non-standard server + * which doesn't require masking might set this to false to achieve a higher performance. + * @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. + */ + public static WebSocketClientHandshaker newHandshaker( + URI webSocketURL, WebSocketVersion version, String subprotocol, + boolean allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength, + boolean performMasking, boolean allowMaskMismatch) { if (version == V13) { return new WebSocketClientHandshaker13( - webSocketURL, V13, subprotocol, allowExtensions, customHeaders, maxFramePayloadLength); + webSocketURL, V13, subprotocol, allowExtensions, customHeaders, + maxFramePayloadLength, performMasking, allowMaskMismatch); } if (version == V08) { return new WebSocketClientHandshaker08( - webSocketURL, V08, subprotocol, allowExtensions, customHeaders, maxFramePayloadLength); + webSocketURL, V08, subprotocol, allowExtensions, customHeaders, + maxFramePayloadLength, performMasking, allowMaskMismatch); } if (version == V07) { return new WebSocketClientHandshaker07( - webSocketURL, V07, subprotocol, allowExtensions, customHeaders, maxFramePayloadLength); + webSocketURL, V07, subprotocol, allowExtensions, customHeaders, + maxFramePayloadLength, performMasking, allowMaskMismatch); } if (version == V00) { return new WebSocketClientHandshaker00( diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientProtocolHandler.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientProtocolHandler.java index 8c4743be43..bdcfe9dcc4 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientProtocolHandler.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientProtocolHandler.java @@ -62,6 +62,39 @@ public class WebSocketClientProtocolHandler extends WebSocketProtocolHandler { HANDSHAKE_COMPLETE } + /** + * Base constructor + * + * @param webSocketURL + * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be + * sent to this URL. + * @param version + * Version of web socket specification to use to connect to the server + * @param subprotocol + * Sub protocol request sent to the server. + * @param customHeaders + * Map of custom headers to add to the client request + * @param maxFramePayloadLength + * Maximum length of a frame's payload + * @param handleCloseFrames + * {@code true} if close frames should not be forwarded and just close the channel + * @param performMasking + * Whether to mask all written websocket frames. This must be set to true in order to be fully compatible + * with the websocket specifications. Client applications that communicate with a non-standard server + * which doesn't require masking might set this to false to achieve a higher performance. + * @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. + */ + public WebSocketClientProtocolHandler(URI webSocketURL, WebSocketVersion version, String subprotocol, + boolean allowExtensions, HttpHeaders customHeaders, + int maxFramePayloadLength, boolean handleCloseFrames, + boolean performMasking, boolean allowMaskMismatch) { + this(WebSocketClientHandshakerFactory.newHandshaker(webSocketURL, version, subprotocol, + allowExtensions, customHeaders, maxFramePayloadLength, + performMasking, allowMaskMismatch), handleCloseFrames); + } + /** * Base constructor * @@ -82,8 +115,8 @@ public class WebSocketClientProtocolHandler extends WebSocketProtocolHandler { public WebSocketClientProtocolHandler(URI webSocketURL, WebSocketVersion version, String subprotocol, boolean allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength, boolean handleCloseFrames) { - this(WebSocketClientHandshakerFactory.newHandshaker(webSocketURL, version, subprotocol, - allowExtensions, customHeaders, maxFramePayloadLength), handleCloseFrames); + this(webSocketURL, version, subprotocol, allowExtensions, customHeaders, maxFramePayloadLength, + handleCloseFrames, true, false); } /** diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker07.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker07.java index 0e76202dcd..cd2be70bf0 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker07.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker07.java @@ -41,6 +41,7 @@ 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 @@ -58,8 +59,32 @@ public class WebSocketServerHandshaker07 extends WebSocketServerHandshaker { */ public WebSocketServerHandshaker07( String webSocketURL, String subprotocols, boolean allowExtensions, int maxFramePayloadLength) { + this(webSocketURL, subprotocols, allowExtensions, maxFramePayloadLength, false); + } + + /** + * 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 allowExtensions + * Allow extensions to be used in the reserved bits of the web socket frame + * @param maxFramePayloadLength + * Maximum allowable frame payload length. Setting this value to your application's + * requirement may reduce denial of service attacks using long data frames. + * @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. + */ + public WebSocketServerHandshaker07( + String webSocketURL, String subprotocols, boolean allowExtensions, int maxFramePayloadLength, + boolean allowMaskMismatch) { super(WebSocketVersion.V07, webSocketURL, subprotocols, maxFramePayloadLength); this.allowExtensions = allowExtensions; + this.allowMaskMismatch = allowMaskMismatch; } /** @@ -136,7 +161,7 @@ public class WebSocketServerHandshaker07 extends WebSocketServerHandshaker { @Override protected WebSocketFrameDecoder newWebsocketDecoder() { - return new WebSocket07FrameDecoder(true, allowExtensions, maxFramePayloadLength()); + return new WebSocket07FrameDecoder(true, allowExtensions, maxFramePayloadLength(), allowMaskMismatch); } @Override diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker08.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker08.java index c7882f24e2..cfe95a4a6f 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker08.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker08.java @@ -41,6 +41,7 @@ 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 @@ -58,8 +59,32 @@ public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker { */ public WebSocketServerHandshaker08( String webSocketURL, String subprotocols, boolean allowExtensions, int maxFramePayloadLength) { + this(webSocketURL, subprotocols, allowExtensions, maxFramePayloadLength, false); + } + + /** + * 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 allowExtensions + * Allow extensions to be used in the reserved bits of the web socket frame + * @param maxFramePayloadLength + * Maximum allowable frame payload length. Setting this value to your application's + * requirement may reduce denial of service attacks using long data frames. + * @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. + */ + public WebSocketServerHandshaker08( + String webSocketURL, String subprotocols, boolean allowExtensions, int maxFramePayloadLength, + boolean allowMaskMismatch) { super(WebSocketVersion.V08, webSocketURL, subprotocols, maxFramePayloadLength); this.allowExtensions = allowExtensions; + this.allowMaskMismatch = allowMaskMismatch; } /** @@ -135,7 +160,7 @@ public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker { @Override protected WebSocketFrameDecoder newWebsocketDecoder() { - return new WebSocket08FrameDecoder(true, allowExtensions, maxFramePayloadLength()); + return new WebSocket08FrameDecoder(true, allowExtensions, maxFramePayloadLength(), allowMaskMismatch); } @Override diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker13.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker13.java index 937a8df8cd..140343910c 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker13.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker13.java @@ -40,6 +40,7 @@ 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 @@ -57,8 +58,32 @@ public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker { */ public WebSocketServerHandshaker13( String webSocketURL, String subprotocols, boolean allowExtensions, int maxFramePayloadLength) { + this(webSocketURL, subprotocols, allowExtensions, maxFramePayloadLength, false); + } + + /** + * 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 allowExtensions + * Allow extensions to be used in the reserved bits of the web socket frame + * @param maxFramePayloadLength + * Maximum allowable frame payload length. Setting this value to your application's + * requirement may reduce denial of service attacks using long data frames. + * @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. + */ + public WebSocketServerHandshaker13( + String webSocketURL, String subprotocols, boolean allowExtensions, int maxFramePayloadLength, + boolean allowMaskMismatch) { super(WebSocketVersion.V13, webSocketURL, subprotocols, maxFramePayloadLength); this.allowExtensions = allowExtensions; + this.allowMaskMismatch = allowMaskMismatch; } /** @@ -133,7 +158,7 @@ public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker { @Override protected WebSocketFrameDecoder newWebsocketDecoder() { - return new WebSocket13FrameDecoder(true, allowExtensions, maxFramePayloadLength()); + return new WebSocket13FrameDecoder(true, allowExtensions, maxFramePayloadLength(), allowMaskMismatch); } @Override diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshakerFactory.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshakerFactory.java index d140680e4c..0446786baf 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshakerFactory.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshakerFactory.java @@ -39,6 +39,8 @@ public class WebSocketServerHandshakerFactory { private final int maxFramePayloadLength; + private final boolean allowMaskMismatch; + /** * Constructor specifying the destination web socket location * @@ -72,10 +74,34 @@ public class WebSocketServerHandshakerFactory { public WebSocketServerHandshakerFactory( String webSocketURL, String subprotocols, boolean allowExtensions, int maxFramePayloadLength) { + this(webSocketURL, subprotocols, allowExtensions, maxFramePayloadLength, false); + } + + /** + * 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 allowExtensions + * Allow extensions to be used in the reserved bits of the web socket frame + * @param maxFramePayloadLength + * Maximum allowable frame payload length. Setting this value to your application's + * requirement may reduce denial of service attacks using long data frames. + * @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. + */ + public WebSocketServerHandshakerFactory( + String webSocketURL, String subprotocols, boolean allowExtensions, + int maxFramePayloadLength, boolean allowMaskMismatch) { this.webSocketURL = webSocketURL; this.subprotocols = subprotocols; this.allowExtensions = allowExtensions; this.maxFramePayloadLength = maxFramePayloadLength; + this.allowMaskMismatch = allowMaskMismatch; } /** @@ -91,15 +117,15 @@ 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); + webSocketURL, subprotocols, allowExtensions, maxFramePayloadLength, allowMaskMismatch); } 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); + webSocketURL, subprotocols, allowExtensions, maxFramePayloadLength, allowMaskMismatch); } 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); + webSocketURL, subprotocols, allowExtensions, maxFramePayloadLength, allowMaskMismatch); } else { return null; } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandler.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandler.java index 62ce893bc9..afa0757db8 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandler.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandler.java @@ -66,6 +66,7 @@ public class WebSocketServerProtocolHandler extends WebSocketProtocolHandler { private final String subprotocols; private final boolean allowExtensions; private final int maxFramePayloadLength; + private final boolean allowMaskMismatch; public WebSocketServerProtocolHandler(String websocketPath) { this(websocketPath, null, false); @@ -80,11 +81,17 @@ public class WebSocketServerProtocolHandler extends WebSocketProtocolHandler { } public WebSocketServerProtocolHandler(String websocketPath, String subprotocols, - boolean allowExtensions, int maxFrameSize) { + boolean allowExtensions, int maxFrameSize) { + this(websocketPath, subprotocols, allowExtensions, maxFrameSize, false); + } + + public WebSocketServerProtocolHandler(String websocketPath, String subprotocols, + boolean allowExtensions, int maxFrameSize, boolean allowMaskMismatch) { this.websocketPath = websocketPath; this.subprotocols = subprotocols; this.allowExtensions = allowExtensions; maxFramePayloadLength = maxFrameSize; + this.allowMaskMismatch = allowMaskMismatch; } @Override @@ -94,7 +101,7 @@ public class WebSocketServerProtocolHandler extends WebSocketProtocolHandler { // Add the WebSocketHandshakeHandler before this one. ctx.pipeline().addBefore(ctx.name(), WebSocketServerProtocolHandshakeHandler.class.getName(), new WebSocketServerProtocolHandshakeHandler(websocketPath, subprotocols, - allowExtensions, maxFramePayloadLength)); + allowExtensions, maxFramePayloadLength, allowMaskMismatch)); } if (cp.get(Utf8FrameValidator.class) == null) { // Add the UFT8 checking before this one. diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandshakeHandler.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandshakeHandler.java index fbed35ad4a..3f3e2e171d 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandshakeHandler.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandshakeHandler.java @@ -41,13 +41,15 @@ class WebSocketServerProtocolHandshakeHandler extends ChannelHandlerAdapter { private final String subprotocols; private final boolean allowExtensions; private final int maxFramePayloadSize; + private final boolean allowMaskMismatch; WebSocketServerProtocolHandshakeHandler(String websocketPath, String subprotocols, - boolean allowExtensions, int maxFrameSize) { + boolean allowExtensions, int maxFrameSize, boolean allowMaskMismatch) { this.websocketPath = websocketPath; this.subprotocols = subprotocols; this.allowExtensions = allowExtensions; maxFramePayloadSize = maxFrameSize; + this.allowMaskMismatch = allowMaskMismatch; } @Override @@ -61,7 +63,7 @@ class WebSocketServerProtocolHandshakeHandler extends ChannelHandlerAdapter { final WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory( getWebSocketLocation(ctx.pipeline(), req, websocketPath), subprotocols, - allowExtensions, maxFramePayloadSize); + allowExtensions, maxFramePayloadSize, allowMaskMismatch); final WebSocketServerHandshaker handshaker = wsFactory.newHandshaker(req); if (handshaker == null) { WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel()); diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocket08EncoderDecoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocket08EncoderDecoderTest.java index edfdfa35ac..a84a81a2f7 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocket08EncoderDecoderTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocket08EncoderDecoderTest.java @@ -59,12 +59,17 @@ public class WebSocket08EncoderDecoderTest { // Test without masking EmbeddedChannel outChannel = new EmbeddedChannel(new WebSocket08FrameEncoder(false)); - EmbeddedChannel inChannel = new EmbeddedChannel(new WebSocket08FrameDecoder(false, false, 1024 * 1024)); + EmbeddedChannel inChannel = new EmbeddedChannel(new WebSocket08FrameDecoder(false, false, 1024 * 1024, false)); executeTests(outChannel, inChannel); // Test with activated masking outChannel = new EmbeddedChannel(new WebSocket08FrameEncoder(true)); - inChannel = new EmbeddedChannel(new WebSocket08FrameDecoder(true, false, 1024 * 1024)); + inChannel = new EmbeddedChannel(new WebSocket08FrameDecoder(true, false, 1024 * 1024, false)); + executeTests(outChannel, inChannel); + + // Test with activated masking and an unmasked expecting but forgiving decoder + outChannel = new EmbeddedChannel(new WebSocket08FrameEncoder(true)); + inChannel = new EmbeddedChannel(new WebSocket08FrameDecoder(false, false, 1024 * 1024, true)); executeTests(outChannel, inChannel); // Release test data diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocket08FrameDecoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocket08FrameDecoderTest.java index 86816560bb..bb90241023 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocket08FrameDecoderTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocket08FrameDecoderTest.java @@ -21,7 +21,7 @@ public class WebSocket08FrameDecoderTest { @Test public void channelInactive() throws Exception { - final WebSocket08FrameDecoder decoder = new WebSocket08FrameDecoder(true, true, 65535); + final WebSocket08FrameDecoder decoder = new WebSocket08FrameDecoder(true, true, 65535, false); final ChannelHandlerContext ctx = EasyMock.createMock(ChannelHandlerContext.class); decoder.channelInactive(ctx); } diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker08Test.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker08Test.java index 4b48d0898e..a03f0c6881 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker08Test.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker08Test.java @@ -61,10 +61,10 @@ public class WebSocketServerHandshaker08Test { if (subProtocol) { new WebSocketServerHandshaker08( - "ws://example.com/chat", "chat", false, Integer.MAX_VALUE).handshake(ch, req); + "ws://example.com/chat", "chat", false, Integer.MAX_VALUE, false).handshake(ch, req); } else { new WebSocketServerHandshaker08( - "ws://example.com/chat", null, false, Integer.MAX_VALUE).handshake(ch, req); + "ws://example.com/chat", null, false, Integer.MAX_VALUE, false).handshake(ch, req); } ByteBuf resBuf = ch.readOutbound(); diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker13Test.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker13Test.java index da8854ae00..5526bbf901 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker13Test.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker13Test.java @@ -61,10 +61,10 @@ public class WebSocketServerHandshaker13Test { if (subProtocol) { new WebSocketServerHandshaker13( - "ws://example.com/chat", "chat", false, Integer.MAX_VALUE).handshake(ch, req); + "ws://example.com/chat", "chat", false, Integer.MAX_VALUE, false).handshake(ch, req); } else { new WebSocketServerHandshaker13( - "ws://example.com/chat", null, false, Integer.MAX_VALUE).handshake(ch, req); + "ws://example.com/chat", null, false, Integer.MAX_VALUE, false).handshake(ch, req); } ByteBuf resBuf = ch.readOutbound();