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 WebSocketServerHandshaker07 extends WebSocketServerHandshaker {
39
40 public static final String WEBSOCKET_07_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 WebSocketServerHandshaker07(
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 WebSocketServerHandshaker07(
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 decoderConfig
92 * Frames decoder configuration.
93 */
94 public WebSocketServerHandshaker07(String webSocketURL, String subprotocols, WebSocketDecoderConfig decoderConfig) {
95 super(WebSocketVersion.V07, webSocketURL, subprotocols, decoderConfig);
96 }
97
98 /**
99 * <p>
100 * Handle the web socket handshake for the web socket specification <a href=
101 * "https://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-07">HyBi version 7</a>.
102 * </p>
103 *
104 * <p>
105 * Browser request to the server:
106 * </p>
107 *
108 * <pre>
109 * GET /chat HTTP/1.1
110 * Host: server.example.com
111 * Upgrade: websocket
112 * Connection: Upgrade
113 * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
114 * Sec-WebSocket-Origin: http://example.com
115 * Sec-WebSocket-Protocol: chat, superchat
116 * Sec-WebSocket-Version: 7
117 * </pre>
118 *
119 * <p>
120 * Server response:
121 * </p>
122 *
123 * <pre>
124 * HTTP/1.1 101 Switching Protocols
125 * Upgrade: websocket
126 * Connection: Upgrade
127 * Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
128 * Sec-WebSocket-Protocol: chat
129 * </pre>
130 */
131 @Override
132 protected FullHttpResponse newHandshakeResponse(FullHttpRequest req, HttpHeaders headers) {
133 HttpMethod method = req.method();
134 if (!GET.equals(method)) {
135 throw new WebSocketServerHandshakeException("Invalid WebSocket handshake method: " + method, req);
136 }
137
138 CharSequence key = req.headers().get(HttpHeaderNames.SEC_WEBSOCKET_KEY);
139 if (key == null) {
140 throw new WebSocketServerHandshakeException("not a WebSocket request: missing key", req);
141 }
142
143 FullHttpResponse res =
144 new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.SWITCHING_PROTOCOLS,
145 req.content().alloc().buffer(0));
146
147 if (headers != null) {
148 res.headers().add(headers);
149 }
150
151 String acceptSeed = key + WEBSOCKET_07_ACCEPT_GUID;
152 byte[] sha1 = WebSocketUtil.sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
153 String accept = WebSocketUtil.base64(sha1);
154
155 if (logger.isDebugEnabled()) {
156 logger.debug("WebSocket version 07 server handshake key: {}, response: {}.", key, accept);
157 }
158
159 res.headers().set(HttpHeaderNames.UPGRADE, HttpHeaderValues.WEBSOCKET)
160 .set(HttpHeaderNames.CONNECTION, HttpHeaderValues.UPGRADE)
161 .set(HttpHeaderNames.SEC_WEBSOCKET_ACCEPT, accept);
162
163 String subprotocols = req.headers().get(HttpHeaderNames.SEC_WEBSOCKET_PROTOCOL);
164 if (subprotocols != null) {
165 String selectedSubprotocol = selectSubprotocol(subprotocols);
166 if (selectedSubprotocol == null) {
167 if (logger.isDebugEnabled()) {
168 logger.debug("Requested subprotocol(s) not supported: {}", subprotocols);
169 }
170 } else {
171 res.headers().set(HttpHeaderNames.SEC_WEBSOCKET_PROTOCOL, selectedSubprotocol);
172 }
173 }
174 return res;
175 }
176
177 @Override
178 protected WebSocketFrameDecoder newWebsocketDecoder() {
179 return new WebSocket07FrameDecoder(decoderConfig());
180 }
181
182 @Override
183 protected WebSocketFrameEncoder newWebSocketEncoder() {
184 return new WebSocket07FrameEncoder(false);
185 }
186 }