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 HttpHeaders reqHeaders = req.headers();
146 if (!reqHeaders.containsValue(HttpHeaderNames.CONNECTION, HttpHeaderValues.UPGRADE, true)) {
147 throw new WebSocketServerHandshakeException(
148 "not a WebSocket request: a |Connection| header must include a token 'Upgrade'", req);
149 }
150 if (!reqHeaders.contains(HttpHeaderNames.UPGRADE, HttpHeaderValues.WEBSOCKET, true)) {
151 throw new WebSocketServerHandshakeException(
152 "not a WebSocket request: an |Upgrade| header must containing the value 'websocket'", req);
153 }
154 CharSequence key = reqHeaders.get(HttpHeaderNames.SEC_WEBSOCKET_KEY);
155 if (key == null) {
156 throw new WebSocketServerHandshakeException("not a WebSocket request: missing key", req);
157 }
158
159 FullHttpResponse res = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.SWITCHING_PROTOCOLS,
160 req.content().alloc().buffer(0));
161
162 if (headers != null) {
163 res.headers().add(headers);
164 }
165
166 String acceptSeed = key + WEBSOCKET_08_ACCEPT_GUID;
167 byte[] sha1 = WebSocketUtil.sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
168 String accept = WebSocketUtil.base64(sha1);
169
170 if (logger.isDebugEnabled()) {
171 logger.debug("WebSocket version 08 server handshake key: {}, response: {}", key, accept);
172 }
173
174 res.headers().set(HttpHeaderNames.UPGRADE, HttpHeaderValues.WEBSOCKET)
175 .set(HttpHeaderNames.CONNECTION, HttpHeaderValues.UPGRADE)
176 .set(HttpHeaderNames.SEC_WEBSOCKET_ACCEPT, accept);
177
178 String subprotocols = req.headers().get(HttpHeaderNames.SEC_WEBSOCKET_PROTOCOL);
179 if (subprotocols != null) {
180 String selectedSubprotocol = selectSubprotocol(subprotocols);
181 if (selectedSubprotocol == null) {
182 if (logger.isDebugEnabled()) {
183 logger.debug("Requested subprotocol(s) not supported: {}", subprotocols);
184 }
185 } else {
186 res.headers().set(HttpHeaderNames.SEC_WEBSOCKET_PROTOCOL, selectedSubprotocol);
187 }
188 }
189 return res;
190 }
191
192 @Override
193 protected WebSocketFrameDecoder newWebsocketDecoder() {
194 return new WebSocket08FrameDecoder(decoderConfig());
195 }
196
197 @Override
198 protected WebSocketFrameEncoder newWebSocketEncoder() {
199 return new WebSocket08FrameEncoder(false);
200 }
201 }