1 /*
2 * Copyright 2012 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 * http://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 org.jboss.netty.handler.codec.http.websocketx;
17
18 import org.jboss.netty.buffer.ChannelBuffer;
19 import org.jboss.netty.buffer.ChannelBuffers;
20 import org.jboss.netty.channel.Channel;
21 import org.jboss.netty.channel.ChannelFuture;
22 import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
23 import org.jboss.netty.handler.codec.http.HttpHeaders.Names;
24 import org.jboss.netty.handler.codec.http.HttpHeaders.Values;
25 import org.jboss.netty.handler.codec.http.HttpRequest;
26 import org.jboss.netty.handler.codec.http.HttpResponse;
27 import org.jboss.netty.handler.codec.http.HttpResponseStatus;
28 import org.jboss.netty.logging.InternalLogger;
29 import org.jboss.netty.logging.InternalLoggerFactory;
30
31 import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.*;
32 import static org.jboss.netty.handler.codec.http.HttpHeaders.Values.*;
33 import static org.jboss.netty.handler.codec.http.HttpVersion.*;
34
35 /**
36 * <p>
37 * Performs server side opening and closing handshakes for web socket specification version <a
38 * href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00" >draft-ietf-hybi-thewebsocketprotocol-
39 * 00</a>
40 * </p>
41 * <p>
42 * A very large portion of this code was taken from the Netty 3.2 HTTP example.
43 * </p>
44 */
45 public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker {
46
47 private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketServerHandshaker00.class);
48
49 /**
50 * Constructor with default values
51 *
52 * @param webSocketURL
53 * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
54 * sent to this URL.
55 * @param subprotocols
56 * CSV of supported protocols
57 */
58 public WebSocketServerHandshaker00(String webSocketURL, String subprotocols) {
59 this(webSocketURL, subprotocols, Long.MAX_VALUE);
60 }
61
62 /**
63 * Constructor specifying the destination web socket location
64 *
65 * @param webSocketURL
66 * URL for web socket communications. e.g "ws://myhost.com/mypath".
67 * Subsequent web socket frames will be sent to this URL.
68 * @param subprotocols
69 * CSV of supported protocols
70 * @param maxFramePayloadLength
71 * Maximum allowable frame payload length. Setting this value to your application's requirement may
72 * reduce denial of service attacks using long data frames.
73 */
74 public WebSocketServerHandshaker00(String webSocketURL, String subprotocols, long maxFramePayloadLength) {
75 super(WebSocketVersion.V00, webSocketURL, subprotocols, maxFramePayloadLength);
76 }
77
78 /**
79 * <p>
80 * Handle the web socket handshake for the web socket specification <a href=
81 * "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00">HyBi version 0</a> and lower. This standard
82 * is really a rehash of <a href="http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76" >hixie-76</a> and
83 * <a href="http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75" >hixie-75</a>.
84 * </p>
85 *
86 * <p>
87 * Browser request to the server:
88 * </p>
89 *
90 * <pre>
91 * GET /demo HTTP/1.1
92 * Upgrade: WebSocket
93 * Connection: Upgrade
94 * Host: example.com
95 * Origin: http://example.com
96 * Sec-WebSocket-Protocol: chat, sample
97 * Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5
98 * Sec-WebSocket-Key2: 12998 5 Y3 1 .P00
99 *
100 * ^n:ds[4U
101 * </pre>
102 *
103 * <p>
104 * Server response:
105 * </p>
106 *
107 * <pre>
108 * HTTP/1.1 101 WebSocket Protocol Handshake
109 * Upgrade: WebSocket
110 * Connection: Upgrade
111 * Sec-WebSocket-Origin: http://example.com
112 * Sec-WebSocket-Location: ws://example.com/demo
113 * Sec-WebSocket-Protocol: sample
114 *
115 * 8jKS'y:G*Co,Wxa-
116 * </pre>
117 *
118 * @param channel
119 * Channel
120 * @param req
121 * HTTP request
122 */
123 @Override
124 public ChannelFuture handshake(Channel channel, HttpRequest req) {
125
126 if (logger.isDebugEnabled()) {
127 logger.debug(String.format("Channel %s WS Version 00 server handshake", channel.getId()));
128 }
129
130 // Serve the WebSocket handshake request.
131 if (!Values.UPGRADE.equalsIgnoreCase(req.headers().get(CONNECTION))
132 || !WEBSOCKET.equalsIgnoreCase(req.headers().get(Names.UPGRADE))) {
133 throw new WebSocketHandshakeException("not a WebSocket handshake request: missing upgrade");
134 }
135
136 // Hixie 75 does not contain these headers while Hixie 76 does
137 boolean isHixie76 = req.headers().contains(SEC_WEBSOCKET_KEY1) && req.headers().contains(SEC_WEBSOCKET_KEY2);
138
139 // Create the WebSocket handshake response.
140 HttpResponse res = new DefaultHttpResponse(HTTP_1_1, new HttpResponseStatus(101,
141 isHixie76 ? "WebSocket Protocol Handshake" : "Web Socket Protocol Handshake"));
142 res.headers().add(Names.UPGRADE, WEBSOCKET);
143 res.headers().add(CONNECTION, Values.UPGRADE);
144
145 // Fill in the headers and contents depending on handshake method.
146 if (isHixie76) {
147 // New handshake method with a challenge:
148 res.headers().add(SEC_WEBSOCKET_ORIGIN, req.headers().get(ORIGIN));
149 res.headers().add(SEC_WEBSOCKET_LOCATION, getWebSocketUrl());
150 String subprotocols = req.headers().get(SEC_WEBSOCKET_PROTOCOL);
151 if (subprotocols != null) {
152 String selectedSubprotocol = selectSubprotocol(subprotocols);
153 if (selectedSubprotocol == null) {
154 throw new WebSocketHandshakeException("Requested subprotocol(s) not supported: " + subprotocols);
155 } else {
156 res.headers().add(SEC_WEBSOCKET_PROTOCOL, selectedSubprotocol);
157 setSelectedSubprotocol(selectedSubprotocol);
158 }
159 }
160
161 // Calculate the answer of the challenge.
162 String key1 = req.headers().get(SEC_WEBSOCKET_KEY1);
163 String key2 = req.headers().get(SEC_WEBSOCKET_KEY2);
164 int a = (int) (Long.parseLong(key1.replaceAll("[^0-9]", "")) / key1.replaceAll("[^ ]", "").length());
165 int b = (int) (Long.parseLong(key2.replaceAll("[^0-9]", "")) / key2.replaceAll("[^ ]", "").length());
166 long c = req.getContent().readLong();
167 ChannelBuffer input = ChannelBuffers.buffer(16);
168 input.writeInt(a);
169 input.writeInt(b);
170 input.writeLong(c);
171 res.setContent(WebSocketUtil.md5(input));
172 } else {
173 // Old Hixie 75 handshake method with no challenge:
174 res.headers().add(WEBSOCKET_ORIGIN, req.headers().get(ORIGIN));
175 res.headers().add(WEBSOCKET_LOCATION, getWebSocketUrl());
176 String protocol = req.headers().get(WEBSOCKET_PROTOCOL);
177 if (protocol != null) {
178 res.headers().add(WEBSOCKET_PROTOCOL, selectSubprotocol(protocol));
179 }
180 }
181
182 return writeHandshakeResponse(
183 channel, res, new WebSocket00FrameEncoder(), new WebSocket00FrameDecoder(getMaxFramePayloadLength()));
184 }
185
186 /**
187 * Echo back the closing frame
188 *
189 * @param channel
190 * Channel
191 * @param frame
192 * Web Socket frame that was received
193 */
194 @Override
195 public ChannelFuture close(Channel channel, CloseWebSocketFrame frame) {
196 return channel.write(frame);
197 }
198 }