diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java index e5ff31c6d94..2df036ac0a8 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java @@ -85,6 +85,8 @@ public abstract class WebSocketClientHandshaker { private final boolean absoluteUpgradeUrl; + protected final boolean generateOriginHeader; + /** * Base constructor * @@ -151,6 +153,36 @@ protected WebSocketClientHandshaker(URI uri, WebSocketVersion version, String su protected WebSocketClientHandshaker(URI uri, WebSocketVersion version, String subprotocol, HttpHeaders customHeaders, int maxFramePayloadLength, long forceCloseTimeoutMillis, boolean absoluteUpgradeUrl) { + this(uri, version, subprotocol, customHeaders, maxFramePayloadLength, forceCloseTimeoutMillis, + absoluteUpgradeUrl, true); + } + + /** + * Base constructor + * + * @param uri + * 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 forceCloseTimeoutMillis + * Close the connection if it was not closed by the server after timeout specified + * @param absoluteUpgradeUrl + * Use an absolute url for the Upgrade request, typically when connecting through an HTTP proxy over + * clear HTTP + * @param generateOriginHeader + * Allows to generate the `Origin`|`Sec-WebSocket-Origin` header value for handshake request + * according to the given webSocketURL + */ + protected WebSocketClientHandshaker(URI uri, WebSocketVersion version, String subprotocol, + HttpHeaders customHeaders, int maxFramePayloadLength, + long forceCloseTimeoutMillis, boolean absoluteUpgradeUrl, boolean generateOriginHeader) { this.uri = uri; this.version = version; expectedSubprotocol = subprotocol; @@ -158,6 +190,7 @@ protected WebSocketClientHandshaker(URI uri, WebSocketVersion version, String su this.maxFramePayloadLength = maxFramePayloadLength; this.forceCloseTimeoutMillis = forceCloseTimeoutMillis; this.absoluteUpgradeUrl = absoluteUpgradeUrl; + this.generateOriginHeader = generateOriginHeader; } /** @@ -265,6 +298,28 @@ public final ChannelFuture handshake(Channel channel, final ChannelPromise promi } } + if (uri.getHost() == null) { + if (customHeaders == null || !customHeaders.contains(HttpHeaderNames.HOST)) { + promise.setFailure(new IllegalArgumentException("Cannot generate the 'host' header value," + + " webSocketURI should contain host or passed through customHeaders")); + return promise; + } + + if (generateOriginHeader && !customHeaders.contains(HttpHeaderNames.ORIGIN)) { + final String originName; + if (version == WebSocketVersion.V07 || version == WebSocketVersion.V08) { + originName = HttpHeaderNames.SEC_WEBSOCKET_ORIGIN.toString(); + } else { + originName = HttpHeaderNames.ORIGIN.toString(); + } + + promise.setFailure(new IllegalArgumentException("Cannot generate the '" + originName + "' header" + + " value, webSocketURI should contain host or disable generateOriginHeader or pass value" + + " through customHeaders")); + return promise; + } + } + FullHttpRequest request = newHandshakeRequest(); channel.writeAndFlush(request).addListener(new ChannelFutureListener() { diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java index 9ccee77af1c..6b63e38f24f 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java @@ -109,11 +109,42 @@ public WebSocketClientHandshaker00(URI webSocketURL, WebSocketVersion version, S * Use an absolute url for the Upgrade request, typically when connecting through an HTTP proxy over * clear HTTP */ + WebSocketClientHandshaker00(URI webSocketURL, WebSocketVersion version, String subprotocol, + HttpHeaders customHeaders, int maxFramePayloadLength, + long forceCloseTimeoutMillis, boolean absoluteUpgradeUrl) { + this(webSocketURL, version, subprotocol, customHeaders, maxFramePayloadLength, forceCloseTimeoutMillis, + absoluteUpgradeUrl, true); + } + + /** + * Creates a new instance with the specified destination WebSocket location and version to initiate. + * + * @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 forceCloseTimeoutMillis + * Close the connection if it was not closed by the server after timeout specified + * @param absoluteUpgradeUrl + * Use an absolute url for the Upgrade request, typically when connecting through an HTTP proxy over + * clear HTTP + * @param generateOriginHeader + * Allows to generate the `Origin` header value for handshake request + * according to the given webSocketURL + */ WebSocketClientHandshaker00(URI webSocketURL, WebSocketVersion version, String subprotocol, HttpHeaders customHeaders, int maxFramePayloadLength, - long forceCloseTimeoutMillis, boolean absoluteUpgradeUrl) { + long forceCloseTimeoutMillis, boolean absoluteUpgradeUrl, + boolean generateOriginHeader) { super(webSocketURL, version, subprotocol, customHeaders, maxFramePayloadLength, forceCloseTimeoutMillis, - absoluteUpgradeUrl); + absoluteUpgradeUrl, generateOriginHeader); } /** @@ -182,15 +213,22 @@ protected FullHttpRequest newHandshakeRequest() { if (customHeaders != null) { headers.add(customHeaders); + if (!headers.contains(HttpHeaderNames.HOST)) { + // Only add HOST header if customHeaders did not contain it. + // + // See https://github.com/netty/netty/issues/10101 + headers.set(HttpHeaderNames.HOST, websocketHostValue(wsURL)); + } + } else { + headers.set(HttpHeaderNames.HOST, websocketHostValue(wsURL)); } headers.set(HttpHeaderNames.UPGRADE, HttpHeaderValues.WEBSOCKET) .set(HttpHeaderNames.CONNECTION, HttpHeaderValues.UPGRADE) - .set(HttpHeaderNames.HOST, websocketHostValue(wsURL)) .set(HttpHeaderNames.SEC_WEBSOCKET_KEY1, key1) .set(HttpHeaderNames.SEC_WEBSOCKET_KEY2, key2); - if (!headers.contains(HttpHeaderNames.ORIGIN)) { + if (generateOriginHeader && !headers.contains(HttpHeaderNames.ORIGIN)) { headers.set(HttpHeaderNames.ORIGIN, websocketOriginValue(wsURL)); } 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 924978a256b..b0d1ace3628 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 @@ -164,12 +164,52 @@ public WebSocketClientHandshaker07(URI webSocketURL, WebSocketVersion version, S * Use an absolute url for the Upgrade request, typically when connecting through an HTTP proxy over * clear HTTP */ + WebSocketClientHandshaker07(URI webSocketURL, WebSocketVersion version, String subprotocol, + boolean allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength, + boolean performMasking, boolean allowMaskMismatch, long forceCloseTimeoutMillis, + boolean absoluteUpgradeUrl) { + this(webSocketURL, version, subprotocol, allowExtensions, customHeaders, maxFramePayloadLength, performMasking, + allowMaskMismatch, forceCloseTimeoutMillis, absoluteUpgradeUrl, true); + } + + /** + * 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 + * When set to true, frames which are not masked properly according to the standard will still be + * accepted + * @param forceCloseTimeoutMillis + * Close the connection if it was not closed by the server after timeout specified. + * @param absoluteUpgradeUrl + * Use an absolute url for the Upgrade request, typically when connecting through an HTTP proxy over + * clear HTTP + * @param generateOriginHeader + * Allows to generate a `Sec-WebSocket-Origin` header value for handshake request + * according to the given webSocketURL + */ WebSocketClientHandshaker07(URI webSocketURL, WebSocketVersion version, String subprotocol, boolean allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength, boolean performMasking, boolean allowMaskMismatch, long forceCloseTimeoutMillis, - boolean absoluteUpgradeUrl) { + boolean absoluteUpgradeUrl, boolean generateOriginHeader) { super(webSocketURL, version, subprotocol, customHeaders, maxFramePayloadLength, forceCloseTimeoutMillis, - absoluteUpgradeUrl); + absoluteUpgradeUrl, generateOriginHeader); this.allowExtensions = allowExtensions; this.performMasking = performMasking; this.allowMaskMismatch = allowMaskMismatch; @@ -232,7 +272,7 @@ protected FullHttpRequest newHandshakeRequest() { .set(HttpHeaderNames.CONNECTION, HttpHeaderValues.UPGRADE) .set(HttpHeaderNames.SEC_WEBSOCKET_KEY, key); - if (!headers.contains(HttpHeaderNames.SEC_WEBSOCKET_ORIGIN)) { + if (generateOriginHeader && !headers.contains(HttpHeaderNames.SEC_WEBSOCKET_ORIGIN)) { headers.set(HttpHeaderNames.SEC_WEBSOCKET_ORIGIN, websocketOriginValue(wsURL)); } 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 f24e7c469ad..8f2f7b7c0cd 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 @@ -134,7 +134,7 @@ public WebSocketClientHandshaker08(URI webSocketURL, WebSocketVersion version, S boolean allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength, boolean performMasking, boolean allowMaskMismatch, long forceCloseTimeoutMillis) { this(webSocketURL, version, subprotocol, allowExtensions, customHeaders, maxFramePayloadLength, performMasking, - allowMaskMismatch, forceCloseTimeoutMillis, false); + allowMaskMismatch, forceCloseTimeoutMillis, false, true); } /** @@ -166,12 +166,52 @@ public WebSocketClientHandshaker08(URI webSocketURL, WebSocketVersion version, S * Use an absolute url for the Upgrade request, typically when connecting through an HTTP proxy over * clear HTTP */ + WebSocketClientHandshaker08(URI webSocketURL, WebSocketVersion version, String subprotocol, + boolean allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength, + boolean performMasking, boolean allowMaskMismatch, long forceCloseTimeoutMillis, + boolean absoluteUpgradeUrl) { + this(webSocketURL, version, subprotocol, allowExtensions, customHeaders, maxFramePayloadLength, performMasking, + allowMaskMismatch, forceCloseTimeoutMillis, absoluteUpgradeUrl, true); + } + + /** + * 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 + * When set to true, frames which are not masked properly according to the standard will still be + * accepted + * @param forceCloseTimeoutMillis + * Close the connection if it was not closed by the server after timeout specified. + * @param absoluteUpgradeUrl + * Use an absolute url for the Upgrade request, typically when connecting through an HTTP proxy over + * clear HTTP + * @param generateOriginHeader + * Allows to generate a `Sec-WebSocket-Origin` header value for handshake request + * according to the given webSocketURL + */ WebSocketClientHandshaker08(URI webSocketURL, WebSocketVersion version, String subprotocol, boolean allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength, boolean performMasking, boolean allowMaskMismatch, long forceCloseTimeoutMillis, - boolean absoluteUpgradeUrl) { + boolean absoluteUpgradeUrl, boolean generateOriginHeader) { super(webSocketURL, version, subprotocol, customHeaders, maxFramePayloadLength, forceCloseTimeoutMillis, - absoluteUpgradeUrl); + absoluteUpgradeUrl, generateOriginHeader); this.allowExtensions = allowExtensions; this.performMasking = performMasking; this.allowMaskMismatch = allowMaskMismatch; @@ -234,7 +274,7 @@ protected FullHttpRequest newHandshakeRequest() { .set(HttpHeaderNames.CONNECTION, HttpHeaderValues.UPGRADE) .set(HttpHeaderNames.SEC_WEBSOCKET_KEY, key); - if (!headers.contains(HttpHeaderNames.SEC_WEBSOCKET_ORIGIN)) { + if (generateOriginHeader && !headers.contains(HttpHeaderNames.SEC_WEBSOCKET_ORIGIN)) { headers.set(HttpHeaderNames.SEC_WEBSOCKET_ORIGIN, websocketOriginValue(wsURL)); } 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 70805611fa5..6e47607c02e 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 @@ -167,12 +167,53 @@ public WebSocketClientHandshaker13(URI webSocketURL, WebSocketVersion version, S * Use an absolute url for the Upgrade request, typically when connecting through an HTTP proxy over * clear HTTP */ + WebSocketClientHandshaker13(URI webSocketURL, WebSocketVersion version, String subprotocol, + boolean allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength, + boolean performMasking, boolean allowMaskMismatch, + long forceCloseTimeoutMillis, boolean absoluteUpgradeUrl) { + this(webSocketURL, version, subprotocol, allowExtensions, customHeaders, maxFramePayloadLength, performMasking, + allowMaskMismatch, forceCloseTimeoutMillis, absoluteUpgradeUrl, true); + } + + /** + * 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 + * When set to true, frames which are not masked properly according to the standard will still be + * accepted + * @param forceCloseTimeoutMillis + * Close the connection if it was not closed by the server after timeout specified. + * @param absoluteUpgradeUrl + * Use an absolute url for the Upgrade request, typically when connecting through an HTTP proxy over + * clear HTTP + * @param generateOriginHeader + * Allows to generate the `Origin` header value for handshake request + * according to the given webSocketURL + */ WebSocketClientHandshaker13(URI webSocketURL, WebSocketVersion version, String subprotocol, boolean allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength, boolean performMasking, boolean allowMaskMismatch, - long forceCloseTimeoutMillis, boolean absoluteUpgradeUrl) { + long forceCloseTimeoutMillis, boolean absoluteUpgradeUrl, + boolean generateOriginHeader) { super(webSocketURL, version, subprotocol, customHeaders, maxFramePayloadLength, forceCloseTimeoutMillis, - absoluteUpgradeUrl); + absoluteUpgradeUrl, generateOriginHeader); this.allowExtensions = allowExtensions; this.performMasking = performMasking; this.allowMaskMismatch = allowMaskMismatch; @@ -190,7 +231,6 @@ public WebSocketClientHandshaker13(URI webSocketURL, WebSocketVersion version, S * Upgrade: websocket * Connection: Upgrade * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== - * Origin: http://example.com * Sec-WebSocket-Protocol: chat, superchat * Sec-WebSocket-Version: 13 * @@ -235,7 +275,7 @@ protected FullHttpRequest newHandshakeRequest() { .set(HttpHeaderNames.CONNECTION, HttpHeaderValues.UPGRADE) .set(HttpHeaderNames.SEC_WEBSOCKET_KEY, key); - if (!headers.contains(HttpHeaderNames.ORIGIN)) { + if (generateOriginHeader && !headers.contains(HttpHeaderNames.ORIGIN)) { headers.set(HttpHeaderNames.ORIGIN, websocketOriginValue(wsURL)); } 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 a5dabdcbd55..ce915eb3986 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 @@ -222,4 +222,69 @@ public static WebSocketClientHandshaker newHandshaker( throw new WebSocketClientHandshakeException("Protocol version " + version + " not supported."); } + + /** + * 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 + * When set to true, frames which are not masked properly according to the standard will still be + * accepted. + * @param forceCloseTimeoutMillis + * Close the connection if it was not closed by the server after timeout specified + * @param absoluteUpgradeUrl + * Use an absolute url for the Upgrade request, typically when connecting through an HTTP proxy over + * clear HTTP + * @param generateOriginHeader + * Allows to generate the `Origin`|`Sec-WebSocket-Origin` header value for handshake request + * according to the given webSocketURL + */ + public static WebSocketClientHandshaker newHandshaker( + URI webSocketURL, WebSocketVersion version, String subprotocol, + boolean allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength, + boolean performMasking, boolean allowMaskMismatch, long forceCloseTimeoutMillis, + boolean absoluteUpgradeUrl, boolean generateOriginHeader) { + if (version == V13) { + return new WebSocketClientHandshaker13( + webSocketURL, V13, subprotocol, allowExtensions, customHeaders, + maxFramePayloadLength, performMasking, allowMaskMismatch, forceCloseTimeoutMillis, + absoluteUpgradeUrl, generateOriginHeader); + } + if (version == V08) { + return new WebSocketClientHandshaker08( + webSocketURL, V08, subprotocol, allowExtensions, customHeaders, + maxFramePayloadLength, performMasking, allowMaskMismatch, forceCloseTimeoutMillis, + absoluteUpgradeUrl, generateOriginHeader); + } + if (version == V07) { + return new WebSocketClientHandshaker07( + webSocketURL, V07, subprotocol, allowExtensions, customHeaders, + maxFramePayloadLength, performMasking, allowMaskMismatch, forceCloseTimeoutMillis, + absoluteUpgradeUrl, generateOriginHeader); + } + if (version == V00) { + return new WebSocketClientHandshaker00( + webSocketURL, V00, subprotocol, customHeaders, + maxFramePayloadLength, forceCloseTimeoutMillis, absoluteUpgradeUrl, generateOriginHeader); + } + + throw new WebSocketClientHandshakeException("Protocol version " + version + " not supported."); + } } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientProtocolConfig.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientProtocolConfig.java index 0cfd6c39db8..6700c7818ad 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientProtocolConfig.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientProtocolConfig.java @@ -34,6 +34,7 @@ public final class WebSocketClientProtocolConfig { static final boolean DEFAULT_ALLOW_MASK_MISMATCH = false; static final boolean DEFAULT_HANDLE_CLOSE_FRAMES = true; static final boolean DEFAULT_DROP_PONG_FRAMES = true; + static final boolean DEFAULT_GENERATE_ORIGIN_HEADER = true; private final URI webSocketUri; private final String subprotocol; @@ -49,6 +50,7 @@ public final class WebSocketClientProtocolConfig { private final long handshakeTimeoutMillis; private final long forceCloseTimeoutMillis; private final boolean absoluteUpgradeUrl; + private final boolean generateOriginHeader; private WebSocketClientProtocolConfig( URI webSocketUri, @@ -64,7 +66,8 @@ private WebSocketClientProtocolConfig( boolean dropPongFrames, long handshakeTimeoutMillis, long forceCloseTimeoutMillis, - boolean absoluteUpgradeUrl + boolean absoluteUpgradeUrl, + boolean generateOriginHeader ) { this.webSocketUri = webSocketUri; this.subprotocol = subprotocol; @@ -80,6 +83,7 @@ private WebSocketClientProtocolConfig( this.dropPongFrames = dropPongFrames; this.handshakeTimeoutMillis = checkPositive(handshakeTimeoutMillis, "handshakeTimeoutMillis"); this.absoluteUpgradeUrl = absoluteUpgradeUrl; + this.generateOriginHeader = generateOriginHeader; } public URI webSocketUri() { @@ -138,24 +142,29 @@ public boolean absoluteUpgradeUrl() { return absoluteUpgradeUrl; } + public boolean generateOriginHeader() { + return generateOriginHeader; + } + @Override public String toString() { return "WebSocketClientProtocolConfig" + - " {webSocketUri=" + webSocketUri + - ", subprotocol=" + subprotocol + - ", version=" + version + - ", allowExtensions=" + allowExtensions + - ", customHeaders=" + customHeaders + - ", maxFramePayloadLength=" + maxFramePayloadLength + - ", performMasking=" + performMasking + - ", allowMaskMismatch=" + allowMaskMismatch + - ", handleCloseFrames=" + handleCloseFrames + - ", sendCloseFrame=" + sendCloseFrame + - ", dropPongFrames=" + dropPongFrames + - ", handshakeTimeoutMillis=" + handshakeTimeoutMillis + - ", forceCloseTimeoutMillis=" + forceCloseTimeoutMillis + - ", absoluteUpgradeUrl=" + absoluteUpgradeUrl + - "}"; + " {webSocketUri=" + webSocketUri + + ", subprotocol=" + subprotocol + + ", version=" + version + + ", allowExtensions=" + allowExtensions + + ", customHeaders=" + customHeaders + + ", maxFramePayloadLength=" + maxFramePayloadLength + + ", performMasking=" + performMasking + + ", allowMaskMismatch=" + allowMaskMismatch + + ", handleCloseFrames=" + handleCloseFrames + + ", sendCloseFrame=" + sendCloseFrame + + ", dropPongFrames=" + dropPongFrames + + ", handshakeTimeoutMillis=" + handshakeTimeoutMillis + + ", forceCloseTimeoutMillis=" + forceCloseTimeoutMillis + + ", absoluteUpgradeUrl=" + absoluteUpgradeUrl + + ", generateOriginHeader=" + generateOriginHeader + + "}"; } public Builder toBuilder() { @@ -177,7 +186,8 @@ public static Builder newBuilder() { DEFAULT_DROP_PONG_FRAMES, DEFAULT_HANDSHAKE_TIMEOUT_MILLIS, -1, - false); + false, + DEFAULT_GENERATE_ORIGIN_HEADER); } public static final class Builder { @@ -195,6 +205,7 @@ public static final class Builder { private long handshakeTimeoutMillis; private long forceCloseTimeoutMillis; private boolean absoluteUpgradeUrl; + private boolean generateOriginHeader; private Builder(WebSocketClientProtocolConfig clientConfig) { this(ObjectUtil.checkNotNull(clientConfig, "clientConfig").webSocketUri(), @@ -210,7 +221,8 @@ private Builder(WebSocketClientProtocolConfig clientConfig) { clientConfig.dropPongFrames(), clientConfig.handshakeTimeoutMillis(), clientConfig.forceCloseTimeoutMillis(), - clientConfig.absoluteUpgradeUrl()); + clientConfig.absoluteUpgradeUrl(), + clientConfig.generateOriginHeader()); } private Builder(URI webSocketUri, @@ -226,7 +238,8 @@ private Builder(URI webSocketUri, boolean dropPongFrames, long handshakeTimeoutMillis, long forceCloseTimeoutMillis, - boolean absoluteUpgradeUrl) { + boolean absoluteUpgradeUrl, + boolean generateOriginHeader) { this.webSocketUri = webSocketUri; this.subprotocol = subprotocol; this.version = version; @@ -241,6 +254,7 @@ private Builder(URI webSocketUri, this.handshakeTimeoutMillis = handshakeTimeoutMillis; this.forceCloseTimeoutMillis = forceCloseTimeoutMillis; this.absoluteUpgradeUrl = absoluteUpgradeUrl; + this.generateOriginHeader = generateOriginHeader; } /** @@ -367,6 +381,16 @@ public Builder absoluteUpgradeUrl(boolean absoluteUpgradeUrl) { return this; } + /** + * Allows to generate the `Origin`|`Sec-WebSocket-Origin` header value for handshake request + * according the given webSocketURI. Usually it's not necessary and can be disabled, + * but for backward compatibility is set to {@code true} as default. + */ + public Builder generateOriginHeader(boolean generateOriginHeader) { + this.generateOriginHeader = generateOriginHeader; + return this; + } + /** * Build unmodifiable client protocol configuration. */ @@ -385,7 +409,8 @@ public WebSocketClientProtocolConfig build() { dropPongFrames, handshakeTimeoutMillis, forceCloseTimeoutMillis, - absoluteUpgradeUrl + absoluteUpgradeUrl, + generateOriginHeader ); } } 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 257d3f025c9..2f8e262ca0a 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 @@ -94,7 +94,8 @@ public WebSocketClientProtocolHandler(WebSocketClientProtocolConfig clientConfig clientConfig.performMasking(), clientConfig.allowMaskMismatch(), clientConfig.forceCloseTimeoutMillis(), - clientConfig.absoluteUpgradeUrl() + clientConfig.absoluteUpgradeUrl(), + clientConfig.generateOriginHeader() ); this.clientConfig = clientConfig; } diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00Test.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00Test.java index 9d1606f3707..d84c9032c2a 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00Test.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00Test.java @@ -23,9 +23,9 @@ public class WebSocketClientHandshaker00Test extends WebSocketClientHandshakerTest { @Override protected WebSocketClientHandshaker newHandshaker(URI uri, String subprotocol, HttpHeaders headers, - boolean absoluteUpgradeUrl) { + boolean absoluteUpgradeUrl, boolean generateOriginHeader) { return new WebSocketClientHandshaker00(uri, WebSocketVersion.V00, subprotocol, headers, - 1024, 10000, absoluteUpgradeUrl); + 1024, 10000, absoluteUpgradeUrl, generateOriginHeader); } @Override diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker07Test.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker07Test.java index 692bc3bbe64..e3f04da85f2 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker07Test.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker07Test.java @@ -31,7 +31,7 @@ public class WebSocketClientHandshaker07Test extends WebSocketClientHandshakerTe public void testHostHeaderPreserved() { URI uri = URI.create("ws://localhost:9999"); WebSocketClientHandshaker handshaker = newHandshaker(uri, null, - new DefaultHttpHeaders().set(HttpHeaderNames.HOST, "test.netty.io"), false); + new DefaultHttpHeaders().set(HttpHeaderNames.HOST, "test.netty.io"), false, true); FullHttpRequest request = handshaker.newHandshakeRequest(); try { @@ -44,10 +44,10 @@ public void testHostHeaderPreserved() { @Override protected WebSocketClientHandshaker newHandshaker(URI uri, String subprotocol, HttpHeaders headers, - boolean absoluteUpgradeUrl) { + boolean absoluteUpgradeUrl, boolean generateOriginHeader) { return new WebSocketClientHandshaker07(uri, WebSocketVersion.V07, subprotocol, false, headers, 1024, true, false, 10000, - absoluteUpgradeUrl); + absoluteUpgradeUrl, generateOriginHeader); } @Override diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08Test.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08Test.java index 34c5fb76d8b..aa8e11dd50a 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08Test.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08Test.java @@ -22,9 +22,9 @@ public class WebSocketClientHandshaker08Test extends WebSocketClientHandshaker07Test { @Override protected WebSocketClientHandshaker newHandshaker(URI uri, String subprotocol, HttpHeaders headers, - boolean absoluteUpgradeUrl) { + boolean absoluteUpgradeUrl, boolean generateOriginHeader) { return new WebSocketClientHandshaker08(uri, WebSocketVersion.V08, subprotocol, false, headers, 1024, true, true, 10000, - absoluteUpgradeUrl); + absoluteUpgradeUrl, generateOriginHeader); } } diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13Test.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13Test.java index 2371caed598..cd82f214546 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13Test.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13Test.java @@ -24,10 +24,10 @@ public class WebSocketClientHandshaker13Test extends WebSocketClientHandshaker07 @Override protected WebSocketClientHandshaker newHandshaker(URI uri, String subprotocol, HttpHeaders headers, - boolean absoluteUpgradeUrl) { + boolean absoluteUpgradeUrl, boolean generateOriginHeader) { return new WebSocketClientHandshaker13(uri, WebSocketVersion.V13, subprotocol, false, headers, 1024, true, true, 10000, - absoluteUpgradeUrl); + absoluteUpgradeUrl, generateOriginHeader); } @Override diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshakerTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshakerTest.java index 1be884e0b66..411e8ae9c2a 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshakerTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshakerTest.java @@ -51,6 +51,7 @@ import static io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker13.WEBSOCKET_13_ACCEPT_GUID; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; @@ -59,10 +60,11 @@ public abstract class WebSocketClientHandshakerTest { protected abstract WebSocketClientHandshaker newHandshaker(URI uri, String subprotocol, HttpHeaders headers, - boolean absoluteUpgradeUrl); + boolean absoluteUpgradeUrl, + boolean generateOriginHeader); protected WebSocketClientHandshaker newHandshaker(URI uri) { - return newHandshaker(uri, null, null, false); + return newHandshaker(uri, null, null, false, true); } protected abstract CharSequence getOriginHeaderName(); @@ -183,7 +185,7 @@ public void originHeaderWithoutScheme() { public void testSetOriginFromCustomHeaders() { HttpHeaders customHeaders = new DefaultHttpHeaders().set(getOriginHeaderName(), "http://example.com"); WebSocketClientHandshaker handshaker = newHandshaker(URI.create("ws://server.example.com/chat"), null, - customHeaders, false); + customHeaders, false, true); FullHttpRequest request = handshaker.newHandshakeRequest(); try { assertEquals("http://example.com", request.headers().get(getOriginHeaderName())); @@ -192,6 +194,50 @@ public void testSetOriginFromCustomHeaders() { } } + @Test + public void testOriginHeaderIsAbsentWhenGeneratingDisable() { + URI uri = URI.create("http://example.com/ws"); + WebSocketClientHandshaker handshaker = newHandshaker(uri, null, null, false, false); + FullHttpRequest request = handshaker.newHandshakeRequest(); + try { + assertFalse(request.headers().contains(getOriginHeaderName())); + assertEquals("/ws", request.uri()); + } finally { + request.release(); + } + } + + @Test + public void testInvalidHostWhenIncorrectWebSocketURI() { + URI uri = URI.create("/ws"); + EmbeddedChannel channel = new EmbeddedChannel(new HttpClientCodec()); + final WebSocketClientHandshaker handshaker = newHandshaker(uri, null, null, false, true); + final ChannelFuture handshakeFuture = handshaker.handshake(channel); + + assertFalse(handshakeFuture.isSuccess()); + assertInstanceOf(IllegalArgumentException.class, handshakeFuture.cause()); + assertEquals("Cannot generate the 'host' header value, webSocketURI should contain host" + + " or passed through customHeaders", handshakeFuture.cause().getMessage()); + assertFalse(channel.finish()); + } + + @Test + public void testInvalidOriginWhenIncorrectWebSocketURI() { + URI uri = URI.create("/ws"); + EmbeddedChannel channel = new EmbeddedChannel(new HttpClientCodec()); + HttpHeaders headers = new DefaultHttpHeaders(); + headers.set(HttpHeaderNames.HOST, "localhost:80"); + final WebSocketClientHandshaker handshaker = newHandshaker(uri, null, headers, false, true); + final ChannelFuture handshakeFuture = handshaker.handshake(channel); + + assertFalse(handshakeFuture.isSuccess()); + assertInstanceOf(IllegalArgumentException.class, handshakeFuture.cause()); + assertEquals("Cannot generate the '" + getOriginHeaderName() + "' header value," + + " webSocketURI should contain host or disable generateOriginHeader" + + " or pass value through customHeaders", handshakeFuture.cause().getMessage()); + assertFalse(channel.finish()); + } + private void testHostHeader(String uri, String expected) { testHeaderDefaultHttp(uri, HttpHeaderNames.HOST, expected); } @@ -262,7 +308,7 @@ public void testUpgradeUrlWithoutPathWithQuery() { @Test public void testAbsoluteUpgradeUrlWithQuery() { URI uri = URI.create("ws://localhost:9999/path%20with%20ws?a=b%20c"); - WebSocketClientHandshaker handshaker = newHandshaker(uri, null, null, true); + WebSocketClientHandshaker handshaker = newHandshaker(uri, null, null, true, true); FullHttpRequest request = handshaker.newHandshakeRequest(); try { assertEquals("ws://localhost:9999/path%20with%20ws?a=b%20c", request.uri()); @@ -392,7 +438,7 @@ public void testDuplicateWebsocketHandshakeHeaders() { inputHeaders.add(getProtocolHeaderName(), bogusSubProtocol); String realSubProtocol = "realSubProtocol"; - WebSocketClientHandshaker handshaker = newHandshaker(uri, realSubProtocol, inputHeaders, false); + WebSocketClientHandshaker handshaker = newHandshaker(uri, realSubProtocol, inputHeaders, false, true); FullHttpRequest request = handshaker.newHandshakeRequest(); HttpHeaders outputHeaders = request.headers(); @@ -412,7 +458,7 @@ public void testDuplicateWebsocketHandshakeHeaders() { @Test public void testWebSocketClientHandshakeException() { URI uri = URI.create("ws://localhost:9999/exception"); - WebSocketClientHandshaker handshaker = newHandshaker(uri, null, null, false); + WebSocketClientHandshaker handshaker = newHandshaker(uri, null, null, false, true); FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.UNAUTHORIZED); response.headers().set(HttpHeaderNames.WWW_AUTHENTICATE, "realm = access token required");