1 | // |
2 | // WebSocket.cpp |
3 | // |
4 | // Library: Net |
5 | // Package: WebSocket |
6 | // Module: WebSocket |
7 | // |
8 | // Copyright (c) 2012, Applied Informatics Software Engineering GmbH. |
9 | // and Contributors. |
10 | // |
11 | // SPDX-License-Identifier: BSL-1.0 |
12 | // |
13 | |
14 | |
15 | #include "Poco/Net/WebSocket.h" |
16 | #include "Poco/Net/WebSocketImpl.h" |
17 | #include "Poco/Net/HTTPServerRequestImpl.h" |
18 | #include "Poco/Net/HTTPServerResponse.h" |
19 | #include "Poco/Net/HTTPClientSession.h" |
20 | #include "Poco/Net/HTTPServerSession.h" |
21 | #include "Poco/Net/NetException.h" |
22 | #include "Poco/MemoryStream.h" |
23 | #include "Poco/NullStream.h" |
24 | #include "Poco/BinaryWriter.h" |
25 | #include "Poco/SHA1Engine.h" |
26 | #include "Poco/Base64Encoder.h" |
27 | #include "Poco/String.h" |
28 | #include "Poco/Random.h" |
29 | #include "Poco/StreamCopier.h" |
30 | #include <sstream> |
31 | |
32 | |
33 | namespace Poco { |
34 | namespace Net { |
35 | |
36 | |
37 | const std::string WebSocket::WEBSOCKET_GUID("258EAFA5-E914-47DA-95CA-C5AB0DC85B11" ); |
38 | const std::string WebSocket::WEBSOCKET_VERSION("13" ); |
39 | HTTPCredentials WebSocket::_defaultCreds; |
40 | |
41 | |
42 | WebSocket::WebSocket(HTTPServerRequest& request, HTTPServerResponse& response): |
43 | StreamSocket(accept(request, response)) |
44 | { |
45 | } |
46 | |
47 | |
48 | WebSocket::WebSocket(HTTPClientSession& cs, HTTPRequest& request, HTTPResponse& response): |
49 | StreamSocket(connect(cs, request, response, _defaultCreds)) |
50 | { |
51 | } |
52 | |
53 | |
54 | WebSocket::WebSocket(HTTPClientSession& cs, HTTPRequest& request, HTTPResponse& response, HTTPCredentials& credentials): |
55 | StreamSocket(connect(cs, request, response, credentials)) |
56 | { |
57 | } |
58 | |
59 | |
60 | WebSocket::WebSocket(const Socket& socket): |
61 | StreamSocket(socket) |
62 | { |
63 | if (!dynamic_cast<WebSocketImpl*>(impl())) |
64 | throw InvalidArgumentException("Cannot assign incompatible socket" ); |
65 | } |
66 | |
67 | |
68 | WebSocket::~WebSocket() |
69 | { |
70 | } |
71 | |
72 | |
73 | WebSocket& WebSocket::operator = (const Socket& socket) |
74 | { |
75 | if (dynamic_cast<WebSocketImpl*>(socket.impl())) |
76 | Socket::operator = (socket); |
77 | else |
78 | throw InvalidArgumentException("Cannot assign incompatible socket" ); |
79 | return *this; |
80 | } |
81 | |
82 | |
83 | void WebSocket::shutdown() |
84 | { |
85 | shutdown(WS_NORMAL_CLOSE); |
86 | } |
87 | |
88 | |
89 | void WebSocket::shutdown(Poco::UInt16 statusCode, const std::string& statusMessage) |
90 | { |
91 | Poco::Buffer<char> buffer(statusMessage.size() + 2); |
92 | Poco::MemoryOutputStream ostr(buffer.begin(), buffer.size()); |
93 | Poco::BinaryWriter writer(ostr, Poco::BinaryWriter::NETWORK_BYTE_ORDER); |
94 | writer << statusCode; |
95 | writer.writeRaw(statusMessage); |
96 | sendFrame(buffer.begin(), static_cast<int>(ostr.charsWritten()), FRAME_FLAG_FIN | FRAME_OP_CLOSE); |
97 | } |
98 | |
99 | |
100 | int WebSocket::sendFrame(const void* buffer, int length, int flags) |
101 | { |
102 | flags |= FRAME_OP_SETRAW; |
103 | return static_cast<WebSocketImpl*>(impl())->sendBytes(buffer, length, flags); |
104 | } |
105 | |
106 | |
107 | int WebSocket::receiveFrame(void* buffer, int length, int& flags) |
108 | { |
109 | int n = static_cast<WebSocketImpl*>(impl())->receiveBytes(buffer, length, 0); |
110 | flags = static_cast<WebSocketImpl*>(impl())->frameFlags(); |
111 | return n; |
112 | } |
113 | |
114 | |
115 | int WebSocket::receiveFrame(Poco::Buffer<char>& buffer, int& flags) |
116 | { |
117 | int n = static_cast<WebSocketImpl*>(impl())->receiveBytes(buffer, 0); |
118 | flags = static_cast<WebSocketImpl*>(impl())->frameFlags(); |
119 | return n; |
120 | } |
121 | |
122 | |
123 | WebSocket::Mode WebSocket::mode() const |
124 | { |
125 | return static_cast<WebSocketImpl*>(impl())->mustMaskPayload() ? WS_CLIENT : WS_SERVER; |
126 | } |
127 | |
128 | |
129 | WebSocketImpl* WebSocket::accept(HTTPServerRequest& request, HTTPServerResponse& response) |
130 | { |
131 | if (request.hasToken("Connection" , "upgrade" ) && icompare(request.get("Upgrade" , "" ), "websocket" ) == 0) |
132 | { |
133 | std::string version = request.get("Sec-WebSocket-Version" , "" ); |
134 | if (version.empty()) throw WebSocketException("Missing Sec-WebSocket-Version in handshake request" , WS_ERR_HANDSHAKE_NO_VERSION); |
135 | if (version != WEBSOCKET_VERSION) throw WebSocketException("Unsupported WebSocket version requested" , version, WS_ERR_HANDSHAKE_UNSUPPORTED_VERSION); |
136 | std::string key = request.get("Sec-WebSocket-Key" , "" ); |
137 | Poco::trimInPlace(key); |
138 | if (key.empty()) throw WebSocketException("Missing Sec-WebSocket-Key in handshake request" , WS_ERR_HANDSHAKE_NO_KEY); |
139 | |
140 | response.setStatusAndReason(HTTPResponse::HTTP_SWITCHING_PROTOCOLS); |
141 | response.set("Upgrade" , "websocket" ); |
142 | response.set("Connection" , "Upgrade" ); |
143 | response.set("Sec-WebSocket-Accept" , computeAccept(key)); |
144 | response.setContentLength(0); |
145 | response.send().flush(); |
146 | |
147 | HTTPServerRequestImpl& requestImpl = static_cast<HTTPServerRequestImpl&>(request); |
148 | return new WebSocketImpl(static_cast<StreamSocketImpl*>(requestImpl.detachSocket().impl()), requestImpl.session(), false); |
149 | } |
150 | else throw WebSocketException("No WebSocket handshake" , WS_ERR_NO_HANDSHAKE); |
151 | } |
152 | |
153 | |
154 | WebSocketImpl* WebSocket::connect(HTTPClientSession& cs, HTTPRequest& request, HTTPResponse& response, HTTPCredentials& credentials) |
155 | { |
156 | if (!cs.getProxyHost().empty() && !cs.secure()) |
157 | { |
158 | cs.proxyTunnel(); |
159 | } |
160 | std::string key = createKey(); |
161 | request.set("Connection" , "Upgrade" ); |
162 | request.set("Upgrade" , "websocket" ); |
163 | request.set("Sec-WebSocket-Version" , WEBSOCKET_VERSION); |
164 | request.set("Sec-WebSocket-Key" , key); |
165 | request.setChunkedTransferEncoding(false); |
166 | cs.setKeepAlive(true); |
167 | cs.sendRequest(request); |
168 | std::istream& istr = cs.receiveResponse(response); |
169 | if (response.getStatus() == HTTPResponse::HTTP_SWITCHING_PROTOCOLS) |
170 | { |
171 | return completeHandshake(cs, response, key); |
172 | } |
173 | else if (response.getStatus() == HTTPResponse::HTTP_UNAUTHORIZED) |
174 | { |
175 | Poco::NullOutputStream null; |
176 | Poco::StreamCopier::copyStream(istr, null); |
177 | credentials.authenticate(request, response); |
178 | if (!cs.getProxyHost().empty() && !cs.secure()) |
179 | { |
180 | cs.reset(); |
181 | cs.proxyTunnel(); |
182 | } |
183 | cs.sendRequest(request); |
184 | cs.receiveResponse(response); |
185 | if (response.getStatus() == HTTPResponse::HTTP_SWITCHING_PROTOCOLS) |
186 | { |
187 | return completeHandshake(cs, response, key); |
188 | } |
189 | else if (response.getStatus() == HTTPResponse::HTTP_UNAUTHORIZED) |
190 | { |
191 | throw WebSocketException("Not authorized" , WS_ERR_UNAUTHORIZED); |
192 | } |
193 | } |
194 | if (response.getStatus() == HTTPResponse::HTTP_OK) |
195 | { |
196 | throw WebSocketException("The server does not understand the WebSocket protocol" , WS_ERR_NO_HANDSHAKE); |
197 | } |
198 | else |
199 | { |
200 | throw WebSocketException("Cannot upgrade to WebSocket connection" , response.getReason(), WS_ERR_NO_HANDSHAKE); |
201 | } |
202 | } |
203 | |
204 | |
205 | WebSocketImpl* WebSocket::completeHandshake(HTTPClientSession& cs, HTTPResponse& response, const std::string& key) |
206 | { |
207 | std::string connection = response.get("Connection" , "" ); |
208 | if (Poco::icompare(connection, "Upgrade" ) != 0) |
209 | throw WebSocketException("No Connection: Upgrade header in handshake response" , WS_ERR_NO_HANDSHAKE); |
210 | std::string upgrade = response.get("Upgrade" , "" ); |
211 | if (Poco::icompare(upgrade, "websocket" ) != 0) |
212 | throw WebSocketException("No Upgrade: websocket header in handshake response" , WS_ERR_NO_HANDSHAKE); |
213 | std::string accept = response.get("Sec-WebSocket-Accept" , "" ); |
214 | if (accept != computeAccept(key)) |
215 | throw WebSocketException("Invalid or missing Sec-WebSocket-Accept header in handshake response" , WS_ERR_HANDSHAKE_ACCEPT); |
216 | return new WebSocketImpl(static_cast<StreamSocketImpl*>(cs.detachSocket().impl()), cs, true); |
217 | } |
218 | |
219 | |
220 | std::string WebSocket::createKey() |
221 | { |
222 | Poco::Random rnd; |
223 | std::ostringstream ostr; |
224 | Poco::Base64Encoder base64(ostr); |
225 | Poco::BinaryWriter writer(base64); |
226 | writer << rnd.next() << rnd.next() << rnd.next() << rnd.next(); |
227 | base64.close(); |
228 | return ostr.str(); |
229 | } |
230 | |
231 | |
232 | std::string WebSocket::computeAccept(const std::string& key) |
233 | { |
234 | std::string accept(key); |
235 | accept += WEBSOCKET_GUID; |
236 | Poco::SHA1Engine sha1; |
237 | sha1.update(accept); |
238 | Poco::DigestEngine::Digest d = sha1.digest(); |
239 | std::ostringstream ostr; |
240 | Poco::Base64Encoder base64(ostr); |
241 | base64.write(reinterpret_cast<const char*>(&d[0]), d.size()); |
242 | base64.close(); |
243 | return ostr.str(); |
244 | } |
245 | |
246 | |
247 | } } // namespace Poco::Net |
248 | |