1 /*
2 * Copyright 2019 The Netty Project
3 *
4 * The Netty Project licenses this file to you under the Apache License,
5 * version 2.0 (the "License"); you may not use this file except in compliance
6 * with the License. You may obtain a copy of the License at:
7 *
8 * https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations
14 * under the License.
15 */
16 package io.netty.handler.codec.http.websocketx;
17
18 import io.netty.handler.codec.http.DefaultFullHttpResponse;
19 import io.netty.handler.codec.http.FullHttpRequest;
20 import io.netty.handler.codec.http.FullHttpResponse;
21 import io.netty.handler.codec.http.HttpHeaderNames;
22 import io.netty.handler.codec.http.HttpHeaderValues;
23 import io.netty.handler.codec.http.HttpHeaders;
24 import io.netty.handler.codec.http.HttpMethod;
25 import io.netty.handler.codec.http.HttpResponseStatus;
26 import io.netty.util.CharsetUtil;
27
28 import static io.netty.handler.codec.http.HttpMethod.GET;
29 import static io.netty.handler.codec.http.HttpVersion.*;
30
31 /**
32 * <p>
33 * Performs server side opening and closing handshakes for web socket specification version <a
34 * href="https://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10" >draft-ietf-hybi-thewebsocketprotocol-
35 * 10</a>
36 * </p>
37 */
38 public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker {
39
40 public static final String WEBSOCKET_08_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
41
42 /**
43 * Constructor specifying the destination web socket location
44 *
45 * @param webSocketURL
46 * URL for web socket communications. e.g "ws://myhost.com/mypath".
47 * Subsequent web socket frames will be sent to this URL.
48 * @param subprotocols
49 * CSV of supported protocols
50 * @param allowExtensions
51 * Allow extensions to be used in the reserved bits of the web socket frame
52 * @param maxFramePayloadLength
53 * Maximum allowable frame payload length. Setting this value to your application's
54 * requirement may reduce denial of service attacks using long data frames.
55 */
56 public WebSocketServerHandshaker08(
57 String webSocketURL, String subprotocols, boolean allowExtensions, int maxFramePayloadLength) {
58 this(webSocketURL, subprotocols, allowExtensions, maxFramePayloadLength, false);
59 }
60
61 /**
62 * Constructor specifying the destination web socket location
63 *
64 * @param webSocketURL
65 * URL for web socket communications. e.g "ws://myhost.com/mypath".
66 * Subsequent web socket frames will be sent to this URL.
67 * @param subprotocols
68 * CSV of supported protocols
69 * @param allowExtensions
70 * Allow extensions to be used in the reserved bits of the web socket frame
71 * @param maxFramePayloadLength
72 * Maximum allowable frame payload length. Setting this value to your application's
73 * requirement may reduce denial of service attacks using long data frames.
74 * @param allowMaskMismatch
75 * When set to true, frames which are not masked properly according to the standard will still be
76 * accepted.
77 */
78 public WebSocketServerHandshaker08(
79 String webSocketURL, String subprotocols, boolean allowExtensions, int maxFramePayloadLength,
80 boolean allowMaskMismatch) {
81 this(webSocketURL, subprotocols, WebSocketDecoderConfig.newBuilder()
82 .allowExtensions(allowExtensions)
83 .maxFramePayloadLength(maxFramePayloadLength)
84 .allowMaskMismatch(allowMaskMismatch)
85 .build());
86 }
87
88 /**
89 * Constructor specifying the destination web socket location
90 *
91 * @param webSocketURL
92 * URL for web socket communications. e.g "ws://myhost.com/mypath".
93 * Subsequent web socket frames will be sent to this URL.
94 * @param subprotocols
95 * CSV of supported protocols
96 * @param decoderConfig
97 * Frames decoder configuration.
98 */
99 public WebSocketServerHandshaker08(
100 String webSocketURL, String subprotocols, WebSocketDecoderConfig decoderConfig) {
101 super(WebSocketVersion.V08, webSocketURL, subprotocols, decoderConfig);
102 }
103
104 /**
105 * <p>
106 * Handle the web socket handshake for the web socket specification <a href=
107 * "https://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-08">HyBi version 8 to 10</a>. Version 8, 9 and
108 * 10 share the same wire protocol.
109 * </p>
110 *
111 * <p>
112 * Browser request to the server:
113 * </p>
114 *
115 * <pre>
116 * GET /chat HTTP/1.1
117 * Host: server.example.com
118 * Upgrade: websocket
119 * Connection: Upgrade
120 * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
121 * Sec-WebSocket-Origin: http://example.com
122 * Sec-WebSocket-Protocol: chat, superchat
123 * Sec-WebSocket-Version: 8
124 * </pre>
125 *
126 * <p>
127 * Server response:
128 * </p>
129 *
130 * <pre>
131 * HTTP/1.1 101 Switching Protocols
132 * Upgrade: websocket
133 * Connection: Upgrade
134 * Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
135 * Sec-WebSocket-Protocol: chat
136 * </pre>
137 */
138 @Override
139 protected FullHttpResponse newHandshakeResponse(FullHttpRequest req, HttpHeaders headers) {
140 HttpMethod method = req.method();
141 if (!GET.equals(method)) {
142 throw new WebSocketServerHandshakeException("Invalid WebSocket handshake method: " + method, req);
143 }
144
145 CharSequence key = req.headers().get(HttpHeaderNames.SEC_WEBSOCKET_KEY);
146 if (key == null) {
147 throw new WebSocketServerHandshakeException("not a WebSocket request: missing key", req);
148 }
149
150 FullHttpResponse res = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.SWITCHING_PROTOCOLS,
151 req.content().alloc().buffer(0));
152
153 if (headers != null) {
154 res.headers().add(headers);
155 }
156
157 String acceptSeed = key + WEBSOCKET_08_ACCEPT_GUID;
158 byte[] sha1 = WebSocketUtil.sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
159 String accept = WebSocketUtil.base64(sha1);
160
161 if (logger.isDebugEnabled()) {
162 logger.debug("WebSocket version 08 server handshake key: {}, response: {}", key, accept);
163 }
164
165 res.headers().set(HttpHeaderNames.UPGRADE, HttpHeaderValues.WEBSOCKET)
166 .set(HttpHeaderNames.CONNECTION, HttpHeaderValues.UPGRADE)
167 .set(HttpHeaderNames.SEC_WEBSOCKET_ACCEPT, accept);
168
169 String subprotocols = req.headers().get(HttpHeaderNames.SEC_WEBSOCKET_PROTOCOL);
170 if (subprotocols != null) {
171 String selectedSubprotocol = selectSubprotocol(subprotocols);
172 if (selectedSubprotocol == null) {
173 if (logger.isDebugEnabled()) {
174 logger.debug("Requested subprotocol(s) not supported: {}", subprotocols);
175 }
176 } else {
177 res.headers().set(HttpHeaderNames.SEC_WEBSOCKET_PROTOCOL, selectedSubprotocol);
178 }
179 }
180 return res;
181 }
182
183 @Override
184 protected WebSocketFrameDecoder newWebsocketDecoder() {
185 return new WebSocket08FrameDecoder(decoderConfig());
186 }
187
188 @Override
189 protected WebSocketFrameEncoder newWebSocketEncoder() {
190 return new WebSocket08FrameEncoder(false);
191 }
192 }