/src/easywsclient/easywsclient.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | |
2 | | #ifdef _WIN32 |
3 | | #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) |
4 | | #define _CRT_SECURE_NO_WARNINGS // _CRT_SECURE_NO_WARNINGS for sscanf errors in MSVC2013 Express |
5 | | #endif |
6 | | #ifndef WIN32_LEAN_AND_MEAN |
7 | | #define WIN32_LEAN_AND_MEAN |
8 | | #endif |
9 | | #include <fcntl.h> |
10 | | #include <WinSock2.h> |
11 | | #include <WS2tcpip.h> |
12 | | #pragma comment( lib, "ws2_32" ) |
13 | | #include <stdio.h> |
14 | | #include <stdlib.h> |
15 | | #include <string.h> |
16 | | #include <sys/types.h> |
17 | | #include <io.h> |
18 | | #ifndef _SSIZE_T_DEFINED |
19 | | typedef int ssize_t; |
20 | | #define _SSIZE_T_DEFINED |
21 | | #endif |
22 | | #ifndef _SOCKET_T_DEFINED |
23 | | typedef SOCKET socket_t; |
24 | | #define _SOCKET_T_DEFINED |
25 | | #endif |
26 | | #ifndef snprintf |
27 | | #define snprintf _snprintf_s |
28 | | #endif |
29 | | #if _MSC_VER >=1600 |
30 | | // vs2010 or later |
31 | | #include <stdint.h> |
32 | | #else |
33 | | typedef __int8 int8_t; |
34 | | typedef unsigned __int8 uint8_t; |
35 | | typedef __int32 int32_t; |
36 | | typedef unsigned __int32 uint32_t; |
37 | | typedef __int64 int64_t; |
38 | | typedef unsigned __int64 uint64_t; |
39 | | #endif |
40 | | #define socketerrno WSAGetLastError() |
41 | | #define SOCKET_EAGAIN_EINPROGRESS WSAEINPROGRESS |
42 | | #define SOCKET_EWOULDBLOCK WSAEWOULDBLOCK |
43 | | #else |
44 | | #include <fcntl.h> |
45 | | #include <netdb.h> |
46 | | #include <netinet/in.h> |
47 | | #include <netinet/tcp.h> |
48 | | #include <stdio.h> |
49 | | #include <stdlib.h> |
50 | | #include <string.h> |
51 | | #include <sys/socket.h> |
52 | | #include <sys/time.h> |
53 | | #include <sys/types.h> |
54 | | #include <unistd.h> |
55 | | #include <stdint.h> |
56 | | #ifndef _SOCKET_T_DEFINED |
57 | | typedef int socket_t; |
58 | | #define _SOCKET_T_DEFINED |
59 | | #endif |
60 | | #ifndef INVALID_SOCKET |
61 | 234 | #define INVALID_SOCKET (-1) |
62 | | #endif |
63 | | #ifndef SOCKET_ERROR |
64 | 14 | #define SOCKET_ERROR (-1) |
65 | | #endif |
66 | 14 | #define closesocket(s) ::close(s) |
67 | | #include <errno.h> |
68 | 0 | #define socketerrno errno |
69 | 0 | #define SOCKET_EAGAIN_EINPROGRESS EAGAIN |
70 | 0 | #define SOCKET_EWOULDBLOCK EWOULDBLOCK |
71 | | #endif |
72 | | |
73 | | #include <vector> |
74 | | #include <string> |
75 | | |
76 | | #include "easywsclient.hpp" |
77 | | |
78 | | using easywsclient::Callback_Imp; |
79 | | using easywsclient::BytesCallback_Imp; |
80 | | |
81 | | namespace { // private module-only namespace |
82 | | |
83 | 103 | socket_t hostname_connect(const std::string& hostname, int port) { |
84 | 103 | struct addrinfo hints; |
85 | 103 | struct addrinfo *result; |
86 | 103 | struct addrinfo *p; |
87 | 103 | int ret; |
88 | 103 | socket_t sockfd = INVALID_SOCKET; |
89 | 103 | char sport[16]; |
90 | 103 | memset(&hints, 0, sizeof(hints)); |
91 | 103 | hints.ai_family = AF_UNSPEC; |
92 | 103 | hints.ai_socktype = SOCK_STREAM; |
93 | 103 | snprintf(sport, 16, "%d", port); |
94 | 103 | if ((ret = getaddrinfo(hostname.c_str(), sport, &hints, &result)) != 0) |
95 | 94 | { |
96 | 94 | fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(ret)); |
97 | 94 | return 1; |
98 | 94 | } |
99 | 23 | for(p = result; p != NULL; p = p->ai_next) |
100 | 14 | { |
101 | 14 | sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol); |
102 | 14 | if (sockfd == INVALID_SOCKET) { continue; } |
103 | 14 | if (connect(sockfd, p->ai_addr, p->ai_addrlen) != SOCKET_ERROR) { |
104 | 0 | break; |
105 | 0 | } |
106 | 14 | closesocket(sockfd); |
107 | 14 | sockfd = INVALID_SOCKET; |
108 | 14 | } |
109 | 9 | freeaddrinfo(result); |
110 | 9 | return sockfd; |
111 | 103 | } |
112 | | |
113 | | |
114 | | class _DummyWebSocket : public easywsclient::WebSocket |
115 | | { |
116 | | public: |
117 | 0 | void poll(int timeout) { } |
118 | 0 | void send(const std::string& message) { } |
119 | 0 | void sendBinary(const std::string& message) { } |
120 | 0 | void sendBinary(const std::vector<uint8_t>& message) { } |
121 | 0 | void sendPing() { } |
122 | 0 | void close() { } |
123 | 0 | readyStateValues getReadyState() const { return CLOSED; } |
124 | 0 | void _dispatch(Callback_Imp & callable) { } |
125 | 0 | void _dispatchBinary(BytesCallback_Imp& callable) { } |
126 | | }; |
127 | | |
128 | | |
129 | | class _RealWebSocket : public easywsclient::WebSocket |
130 | | { |
131 | | public: |
132 | | // http://tools.ietf.org/html/rfc6455#section-5.2 Base Framing Protocol |
133 | | // |
134 | | // 0 1 2 3 |
135 | | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 |
136 | | // +-+-+-+-+-------+-+-------------+-------------------------------+ |
137 | | // |F|R|R|R| opcode|M| Payload len | Extended payload length | |
138 | | // |I|S|S|S| (4) |A| (7) | (16/64) | |
139 | | // |N|V|V|V| |S| | (if payload len==126/127) | |
140 | | // | |1|2|3| |K| | | |
141 | | // +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + |
142 | | // | Extended payload length continued, if payload len == 127 | |
143 | | // + - - - - - - - - - - - - - - - +-------------------------------+ |
144 | | // | |Masking-key, if MASK set to 1 | |
145 | | // +-------------------------------+-------------------------------+ |
146 | | // | Masking-key (continued) | Payload Data | |
147 | | // +-------------------------------- - - - - - - - - - - - - - - - + |
148 | | // : Payload Data continued ... : |
149 | | // + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + |
150 | | // | Payload Data continued ... | |
151 | | // +---------------------------------------------------------------+ |
152 | | struct wsheader_type { |
153 | | unsigned header_size; |
154 | | bool fin; |
155 | | bool mask; |
156 | | enum opcode_type { |
157 | | CONTINUATION = 0x0, |
158 | | TEXT_FRAME = 0x1, |
159 | | BINARY_FRAME = 0x2, |
160 | | CLOSE = 8, |
161 | | PING = 9, |
162 | | PONG = 0xa, |
163 | | } opcode; |
164 | | int N0; |
165 | | uint64_t N; |
166 | | uint8_t masking_key[4]; |
167 | | }; |
168 | | |
169 | | std::vector<uint8_t> rxbuf; |
170 | | std::vector<uint8_t> txbuf; |
171 | | std::vector<uint8_t> receivedData; |
172 | | |
173 | | socket_t sockfd; |
174 | | readyStateValues readyState; |
175 | | bool useMask; |
176 | | bool isRxBad; |
177 | | |
178 | | _RealWebSocket(socket_t sockfd, bool useMask) |
179 | 0 | : sockfd(sockfd) |
180 | 0 | , readyState(OPEN) |
181 | 0 | , useMask(useMask) |
182 | 0 | , isRxBad(false) { |
183 | 0 | } |
184 | | |
185 | 0 | readyStateValues getReadyState() const { |
186 | 0 | return readyState; |
187 | 0 | } |
188 | | |
189 | 0 | void poll(int timeout) { // timeout in milliseconds |
190 | 0 | if (readyState == CLOSED) { |
191 | 0 | if (timeout > 0) { |
192 | 0 | timeval tv = { timeout/1000, (timeout%1000) * 1000 }; |
193 | 0 | select(0, NULL, NULL, NULL, &tv); |
194 | 0 | } |
195 | 0 | return; |
196 | 0 | } |
197 | 0 | if (timeout != 0) { |
198 | 0 | fd_set rfds; |
199 | 0 | fd_set wfds; |
200 | 0 | timeval tv = { timeout/1000, (timeout%1000) * 1000 }; |
201 | 0 | FD_ZERO(&rfds); |
202 | 0 | FD_ZERO(&wfds); |
203 | 0 | FD_SET(sockfd, &rfds); |
204 | 0 | if (txbuf.size()) { FD_SET(sockfd, &wfds); } |
205 | 0 | select(sockfd + 1, &rfds, &wfds, 0, timeout > 0 ? &tv : 0); |
206 | 0 | } |
207 | 0 | while (true) { |
208 | | // FD_ISSET(0, &rfds) will be true |
209 | 0 | int N = rxbuf.size(); |
210 | 0 | ssize_t ret; |
211 | 0 | rxbuf.resize(N + 1500); |
212 | 0 | ret = recv(sockfd, (char*)&rxbuf[0] + N, 1500, 0); |
213 | 0 | if (false) { } |
214 | 0 | else if (ret < 0 && (socketerrno == SOCKET_EWOULDBLOCK || socketerrno == SOCKET_EAGAIN_EINPROGRESS)) { |
215 | 0 | rxbuf.resize(N); |
216 | 0 | break; |
217 | 0 | } |
218 | 0 | else if (ret <= 0) { |
219 | 0 | rxbuf.resize(N); |
220 | 0 | closesocket(sockfd); |
221 | 0 | readyState = CLOSED; |
222 | 0 | fputs(ret < 0 ? "Connection error!\n" : "Connection closed!\n", stderr); |
223 | 0 | break; |
224 | 0 | } |
225 | 0 | else { |
226 | 0 | rxbuf.resize(N + ret); |
227 | 0 | } |
228 | 0 | } |
229 | 0 | while (txbuf.size()) { |
230 | 0 | int ret = ::send(sockfd, (char*)&txbuf[0], txbuf.size(), 0); |
231 | 0 | if (false) { } // ?? |
232 | 0 | else if (ret < 0 && (socketerrno == SOCKET_EWOULDBLOCK || socketerrno == SOCKET_EAGAIN_EINPROGRESS)) { |
233 | 0 | break; |
234 | 0 | } |
235 | 0 | else if (ret <= 0) { |
236 | 0 | closesocket(sockfd); |
237 | 0 | readyState = CLOSED; |
238 | 0 | fputs(ret < 0 ? "Connection error!\n" : "Connection closed!\n", stderr); |
239 | 0 | break; |
240 | 0 | } |
241 | 0 | else { |
242 | 0 | txbuf.erase(txbuf.begin(), txbuf.begin() + ret); |
243 | 0 | } |
244 | 0 | } |
245 | 0 | if (!txbuf.size() && readyState == CLOSING) { |
246 | 0 | closesocket(sockfd); |
247 | 0 | readyState = CLOSED; |
248 | 0 | } |
249 | 0 | } |
250 | | |
251 | | // Callable must have signature: void(const std::string & message). |
252 | | // Should work with C functions, C++ functors, and C++11 std::function and |
253 | | // lambda: |
254 | | //template<class Callable> |
255 | | //void dispatch(Callable callable) |
256 | 0 | virtual void _dispatch(Callback_Imp & callable) { |
257 | 0 | struct CallbackAdapter : public BytesCallback_Imp |
258 | | // Adapt void(const std::string<uint8_t>&) to void(const std::string&) |
259 | 0 | { |
260 | 0 | Callback_Imp& callable; |
261 | 0 | CallbackAdapter(Callback_Imp& callable) : callable(callable) { } |
262 | 0 | void operator()(const std::vector<uint8_t>& message) { |
263 | 0 | std::string stringMessage(message.begin(), message.end()); |
264 | 0 | callable(stringMessage); |
265 | 0 | } |
266 | 0 | }; |
267 | 0 | CallbackAdapter bytesCallback(callable); |
268 | 0 | _dispatchBinary(bytesCallback); |
269 | 0 | } |
270 | | |
271 | 0 | virtual void _dispatchBinary(BytesCallback_Imp & callable) { |
272 | | // TODO: consider acquiring a lock on rxbuf... |
273 | 0 | if (isRxBad) { |
274 | 0 | return; |
275 | 0 | } |
276 | 0 | while (true) { |
277 | 0 | wsheader_type ws; |
278 | 0 | if (rxbuf.size() < 2) { return; /* Need at least 2 */ } |
279 | 0 | const uint8_t * data = (uint8_t *) &rxbuf[0]; // peek, but don't consume |
280 | 0 | ws.fin = (data[0] & 0x80) == 0x80; |
281 | 0 | ws.opcode = (wsheader_type::opcode_type) (data[0] & 0x0f); |
282 | 0 | ws.mask = (data[1] & 0x80) == 0x80; |
283 | 0 | ws.N0 = (data[1] & 0x7f); |
284 | 0 | ws.header_size = 2 + (ws.N0 == 126? 2 : 0) + (ws.N0 == 127? 8 : 0) + (ws.mask? 4 : 0); |
285 | 0 | if (rxbuf.size() < ws.header_size) { return; /* Need: ws.header_size - rxbuf.size() */ } |
286 | 0 | int i = 0; |
287 | 0 | if (ws.N0 < 126) { |
288 | 0 | ws.N = ws.N0; |
289 | 0 | i = 2; |
290 | 0 | } |
291 | 0 | else if (ws.N0 == 126) { |
292 | 0 | ws.N = 0; |
293 | 0 | ws.N |= ((uint64_t) data[2]) << 8; |
294 | 0 | ws.N |= ((uint64_t) data[3]) << 0; |
295 | 0 | i = 4; |
296 | 0 | } |
297 | 0 | else if (ws.N0 == 127) { |
298 | 0 | ws.N = 0; |
299 | 0 | ws.N |= ((uint64_t) data[2]) << 56; |
300 | 0 | ws.N |= ((uint64_t) data[3]) << 48; |
301 | 0 | ws.N |= ((uint64_t) data[4]) << 40; |
302 | 0 | ws.N |= ((uint64_t) data[5]) << 32; |
303 | 0 | ws.N |= ((uint64_t) data[6]) << 24; |
304 | 0 | ws.N |= ((uint64_t) data[7]) << 16; |
305 | 0 | ws.N |= ((uint64_t) data[8]) << 8; |
306 | 0 | ws.N |= ((uint64_t) data[9]) << 0; |
307 | 0 | i = 10; |
308 | 0 | if (ws.N & 0x8000000000000000ull) { |
309 | | // https://tools.ietf.org/html/rfc6455 writes the "the most |
310 | | // significant bit MUST be 0." |
311 | | // |
312 | | // We can't drop the frame, because (1) we don't we don't |
313 | | // know how much data to skip over to find the next header, |
314 | | // and (2) this would be an impractically long length, even |
315 | | // if it were valid. So just close() and return immediately |
316 | | // for now. |
317 | 0 | isRxBad = true; |
318 | 0 | fprintf(stderr, "ERROR: Frame has invalid frame length. Closing.\n"); |
319 | 0 | close(); |
320 | 0 | return; |
321 | 0 | } |
322 | 0 | } |
323 | 0 | if (ws.mask) { |
324 | 0 | ws.masking_key[0] = ((uint8_t) data[i+0]) << 0; |
325 | 0 | ws.masking_key[1] = ((uint8_t) data[i+1]) << 0; |
326 | 0 | ws.masking_key[2] = ((uint8_t) data[i+2]) << 0; |
327 | 0 | ws.masking_key[3] = ((uint8_t) data[i+3]) << 0; |
328 | 0 | } |
329 | 0 | else { |
330 | 0 | ws.masking_key[0] = 0; |
331 | 0 | ws.masking_key[1] = 0; |
332 | 0 | ws.masking_key[2] = 0; |
333 | 0 | ws.masking_key[3] = 0; |
334 | 0 | } |
335 | | |
336 | | // Note: The checks above should hopefully ensure this addition |
337 | | // cannot overflow: |
338 | 0 | if (rxbuf.size() < ws.header_size+ws.N) { return; /* Need: ws.header_size+ws.N - rxbuf.size() */ } |
339 | | |
340 | | // We got a whole message, now do something with it: |
341 | 0 | if (false) { } |
342 | 0 | else if ( |
343 | 0 | ws.opcode == wsheader_type::TEXT_FRAME |
344 | 0 | || ws.opcode == wsheader_type::BINARY_FRAME |
345 | 0 | || ws.opcode == wsheader_type::CONTINUATION |
346 | 0 | ) { |
347 | 0 | if (ws.mask) { for (size_t i = 0; i != ws.N; ++i) { rxbuf[i+ws.header_size] ^= ws.masking_key[i&0x3]; } } |
348 | 0 | receivedData.insert(receivedData.end(), rxbuf.begin()+ws.header_size, rxbuf.begin()+ws.header_size+(size_t)ws.N);// just feed |
349 | 0 | if (ws.fin) { |
350 | 0 | callable((const std::vector<uint8_t>) receivedData); |
351 | 0 | receivedData.erase(receivedData.begin(), receivedData.end()); |
352 | 0 | std::vector<uint8_t> ().swap(receivedData);// free memory |
353 | 0 | } |
354 | 0 | } |
355 | 0 | else if (ws.opcode == wsheader_type::PING) { |
356 | 0 | if (ws.mask) { for (size_t i = 0; i != ws.N; ++i) { rxbuf[i+ws.header_size] ^= ws.masking_key[i&0x3]; } } |
357 | 0 | std::string data(rxbuf.begin()+ws.header_size, rxbuf.begin()+ws.header_size+(size_t)ws.N); |
358 | 0 | sendData(wsheader_type::PONG, data.size(), data.begin(), data.end()); |
359 | 0 | } |
360 | 0 | else if (ws.opcode == wsheader_type::PONG) { } |
361 | 0 | else if (ws.opcode == wsheader_type::CLOSE) { close(); } |
362 | 0 | else { fprintf(stderr, "ERROR: Got unexpected WebSocket message.\n"); close(); } |
363 | |
|
364 | 0 | rxbuf.erase(rxbuf.begin(), rxbuf.begin() + ws.header_size+(size_t)ws.N); |
365 | 0 | } |
366 | 0 | } |
367 | | |
368 | 0 | void sendPing() { |
369 | 0 | std::string empty; |
370 | 0 | sendData(wsheader_type::PING, empty.size(), empty.begin(), empty.end()); |
371 | 0 | } |
372 | | |
373 | 0 | void send(const std::string& message) { |
374 | 0 | sendData(wsheader_type::TEXT_FRAME, message.size(), message.begin(), message.end()); |
375 | 0 | } |
376 | | |
377 | 0 | void sendBinary(const std::string& message) { |
378 | 0 | sendData(wsheader_type::BINARY_FRAME, message.size(), message.begin(), message.end()); |
379 | 0 | } |
380 | | |
381 | 0 | void sendBinary(const std::vector<uint8_t>& message) { |
382 | 0 | sendData(wsheader_type::BINARY_FRAME, message.size(), message.begin(), message.end()); |
383 | 0 | } |
384 | | |
385 | | template<class Iterator> |
386 | 0 | void sendData(wsheader_type::opcode_type type, uint64_t message_size, Iterator message_begin, Iterator message_end) { |
387 | | // TODO: |
388 | | // Masking key should (must) be derived from a high quality random |
389 | | // number generator, to mitigate attacks on non-WebSocket friendly |
390 | | // middleware: |
391 | 0 | const uint8_t masking_key[4] = { 0x12, 0x34, 0x56, 0x78 }; |
392 | | // TODO: consider acquiring a lock on txbuf... |
393 | 0 | if (readyState == CLOSING || readyState == CLOSED) { return; } |
394 | 0 | std::vector<uint8_t> header; |
395 | 0 | header.assign(2 + (message_size >= 126 ? 2 : 0) + (message_size >= 65536 ? 6 : 0) + (useMask ? 4 : 0), 0); |
396 | 0 | header[0] = 0x80 | type; |
397 | 0 | if (false) { } |
398 | 0 | else if (message_size < 126) { |
399 | 0 | header[1] = (message_size & 0xff) | (useMask ? 0x80 : 0); |
400 | 0 | if (useMask) { |
401 | 0 | header[2] = masking_key[0]; |
402 | 0 | header[3] = masking_key[1]; |
403 | 0 | header[4] = masking_key[2]; |
404 | 0 | header[5] = masking_key[3]; |
405 | 0 | } |
406 | 0 | } |
407 | 0 | else if (message_size < 65536) { |
408 | 0 | header[1] = 126 | (useMask ? 0x80 : 0); |
409 | 0 | header[2] = (message_size >> 8) & 0xff; |
410 | 0 | header[3] = (message_size >> 0) & 0xff; |
411 | 0 | if (useMask) { |
412 | 0 | header[4] = masking_key[0]; |
413 | 0 | header[5] = masking_key[1]; |
414 | 0 | header[6] = masking_key[2]; |
415 | 0 | header[7] = masking_key[3]; |
416 | 0 | } |
417 | 0 | } |
418 | 0 | else { // TODO: run coverage testing here |
419 | 0 | header[1] = 127 | (useMask ? 0x80 : 0); |
420 | 0 | header[2] = (message_size >> 56) & 0xff; |
421 | 0 | header[3] = (message_size >> 48) & 0xff; |
422 | 0 | header[4] = (message_size >> 40) & 0xff; |
423 | 0 | header[5] = (message_size >> 32) & 0xff; |
424 | 0 | header[6] = (message_size >> 24) & 0xff; |
425 | 0 | header[7] = (message_size >> 16) & 0xff; |
426 | 0 | header[8] = (message_size >> 8) & 0xff; |
427 | 0 | header[9] = (message_size >> 0) & 0xff; |
428 | 0 | if (useMask) { |
429 | 0 | header[10] = masking_key[0]; |
430 | 0 | header[11] = masking_key[1]; |
431 | 0 | header[12] = masking_key[2]; |
432 | 0 | header[13] = masking_key[3]; |
433 | 0 | } |
434 | 0 | } |
435 | | // N.B. - txbuf will keep growing until it can be transmitted over the socket: |
436 | 0 | txbuf.insert(txbuf.end(), header.begin(), header.end()); |
437 | 0 | txbuf.insert(txbuf.end(), message_begin, message_end); |
438 | 0 | if (useMask) { |
439 | 0 | size_t message_offset = txbuf.size() - message_size; |
440 | 0 | for (size_t i = 0; i != message_size; ++i) { |
441 | 0 | txbuf[message_offset + i] ^= masking_key[i&0x3]; |
442 | 0 | } |
443 | 0 | } |
444 | 0 | } Unexecuted instantiation: easywsclient.cpp:void (anonymous namespace)::_RealWebSocket::sendData<std::__1::__wrap_iter<char const*> >((anonymous namespace)::_RealWebSocket::wsheader_type::opcode_type, unsigned long, std::__1::__wrap_iter<char const*>, std::__1::__wrap_iter<char const*>) Unexecuted instantiation: easywsclient.cpp:void (anonymous namespace)::_RealWebSocket::sendData<std::__1::__wrap_iter<unsigned char const*> >((anonymous namespace)::_RealWebSocket::wsheader_type::opcode_type, unsigned long, std::__1::__wrap_iter<unsigned char const*>, std::__1::__wrap_iter<unsigned char const*>) Unexecuted instantiation: easywsclient.cpp:void (anonymous namespace)::_RealWebSocket::sendData<std::__1::__wrap_iter<char*> >((anonymous namespace)::_RealWebSocket::wsheader_type::opcode_type, unsigned long, std::__1::__wrap_iter<char*>, std::__1::__wrap_iter<char*>) |
445 | | |
446 | 0 | void close() { |
447 | 0 | if(readyState == CLOSING || readyState == CLOSED) { return; } |
448 | 0 | readyState = CLOSING; |
449 | 0 | uint8_t closeFrame[6] = {0x88, 0x80, 0x00, 0x00, 0x00, 0x00}; // last 4 bytes are a masking key |
450 | 0 | std::vector<uint8_t> header(closeFrame, closeFrame+6); |
451 | 0 | txbuf.insert(txbuf.end(), header.begin(), header.end()); |
452 | 0 | } |
453 | | |
454 | | }; |
455 | | |
456 | | |
457 | 168 | easywsclient::WebSocket::pointer from_url(const std::string& url, bool useMask, const std::string& origin) { |
458 | 168 | char host[512]; |
459 | 168 | int port; |
460 | 168 | char path[512]; |
461 | 168 | if (url.size() >= 512) { |
462 | 51 | fprintf(stderr, "ERROR: url size limit exceeded: %s\n", url.c_str()); |
463 | 51 | return NULL; |
464 | 51 | } |
465 | 117 | if (origin.size() >= 200) { |
466 | 0 | fprintf(stderr, "ERROR: origin size limit exceeded: %s\n", origin.c_str()); |
467 | 0 | return NULL; |
468 | 0 | } |
469 | 117 | if (false) { } |
470 | 117 | else if (sscanf(url.c_str(), "ws://%[^:/]:%d/%s", host, &port, path) == 3) { |
471 | 2 | } |
472 | 115 | else if (sscanf(url.c_str(), "ws://%[^:/]/%s", host, path) == 2) { |
473 | 3 | port = 80; |
474 | 3 | } |
475 | 112 | else if (sscanf(url.c_str(), "ws://%[^:/]:%d", host, &port) == 2) { |
476 | 61 | path[0] = '\0'; |
477 | 61 | } |
478 | 51 | else if (sscanf(url.c_str(), "ws://%[^:/]", host) == 1) { |
479 | 37 | port = 80; |
480 | 37 | path[0] = '\0'; |
481 | 37 | } |
482 | 14 | else { |
483 | 14 | fprintf(stderr, "ERROR: Could not parse WebSocket url: %s\n", url.c_str()); |
484 | 14 | return NULL; |
485 | 14 | } |
486 | | //fprintf(stderr, "easywsclient: connecting: host=%s port=%d path=/%s\n", host, port, path); |
487 | 103 | socket_t sockfd = hostname_connect(host, port); |
488 | 103 | if (sockfd == INVALID_SOCKET) { |
489 | 9 | fprintf(stderr, "Unable to connect to %s:%d\n", host, port); |
490 | 9 | return NULL; |
491 | 9 | } |
492 | 94 | { |
493 | | // XXX: this should be done non-blocking, |
494 | 94 | char line[1024]; |
495 | 94 | int status; |
496 | 94 | int i; |
497 | 94 | snprintf(line, 1024, "GET /%s HTTP/1.1\r\n", path); ::send(sockfd, line, strlen(line), 0); |
498 | 94 | if (port == 80) { |
499 | 32 | snprintf(line, 1024, "Host: %s\r\n", host); ::send(sockfd, line, strlen(line), 0); |
500 | 32 | } |
501 | 62 | else { |
502 | 62 | snprintf(line, 1024, "Host: %s:%d\r\n", host, port); ::send(sockfd, line, strlen(line), 0); |
503 | 62 | } |
504 | 94 | snprintf(line, 1024, "Upgrade: websocket\r\n"); ::send(sockfd, line, strlen(line), 0); |
505 | 94 | snprintf(line, 1024, "Connection: Upgrade\r\n"); ::send(sockfd, line, strlen(line), 0); |
506 | 94 | if (!origin.empty()) { |
507 | 0 | snprintf(line, 1024, "Origin: %s\r\n", origin.c_str()); ::send(sockfd, line, strlen(line), 0); |
508 | 0 | } |
509 | 94 | snprintf(line, 1024, "Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n"); ::send(sockfd, line, strlen(line), 0); |
510 | 94 | snprintf(line, 1024, "Sec-WebSocket-Version: 13\r\n"); ::send(sockfd, line, strlen(line), 0); |
511 | 94 | snprintf(line, 1024, "\r\n"); ::send(sockfd, line, strlen(line), 0); |
512 | 282 | for (i = 0; i < 2 || (i < 1023 && line[i-2] != '\r' && line[i-1] != '\n'); ++i) { if (recv(sockfd, line+i, 1, 0) == 0) { return NULL; } } |
513 | 94 | line[i] = 0; |
514 | 94 | if (i == 1023) { fprintf(stderr, "ERROR: Got invalid status line connecting to: %s\n", url.c_str()); return NULL; } |
515 | 94 | if (sscanf(line, "HTTP/1.1 %d", &status) != 1 || status != 101) { fprintf(stderr, "ERROR: Got bad status connecting to %s: %s", url.c_str(), line); return NULL; } |
516 | | // TODO: verify response headers, |
517 | 0 | while (true) { |
518 | 0 | for (i = 0; i < 2 || (i < 1023 && line[i-2] != '\r' && line[i-1] != '\n'); ++i) { if (recv(sockfd, line+i, 1, 0) == 0) { return NULL; } } |
519 | 0 | if (line[0] == '\r' && line[1] == '\n') { break; } |
520 | 0 | } |
521 | 0 | } |
522 | 0 | int flag = 1; |
523 | 0 | setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (char*) &flag, sizeof(flag)); // Disable Nagle's algorithm |
524 | | #ifdef _WIN32 |
525 | | u_long on = 1; |
526 | | ioctlsocket(sockfd, FIONBIO, &on); |
527 | | #else |
528 | 0 | fcntl(sockfd, F_SETFL, O_NONBLOCK); |
529 | 0 | #endif |
530 | | //fprintf(stderr, "Connected to: %s\n", url.c_str()); |
531 | 0 | return easywsclient::WebSocket::pointer(new _RealWebSocket(sockfd, useMask)); |
532 | 0 | } |
533 | | |
534 | | } // end of module-only namespace |
535 | | |
536 | | |
537 | | |
538 | | namespace easywsclient { |
539 | | |
540 | 0 | WebSocket::pointer WebSocket::create_dummy() { |
541 | 0 | static pointer dummy = pointer(new _DummyWebSocket); |
542 | 0 | return dummy; |
543 | 0 | } |
544 | | |
545 | | |
546 | 168 | WebSocket::pointer WebSocket::from_url(const std::string& url, const std::string& origin) { |
547 | 168 | return ::from_url(url, true, origin); |
548 | 168 | } |
549 | | |
550 | 0 | WebSocket::pointer WebSocket::from_url_no_mask(const std::string& url, const std::string& origin) { |
551 | 0 | return ::from_url(url, false, origin); |
552 | 0 | } |
553 | | |
554 | | |
555 | | } // namespace easywsclient |