aboutsummaryrefslogtreecommitdiff
path: root/src/microhttpd_ws/test_websocket_browser.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/microhttpd_ws/test_websocket_browser.c')
-rw-r--r--src/microhttpd_ws/test_websocket_browser.c563
1 files changed, 563 insertions, 0 deletions
diff --git a/src/microhttpd_ws/test_websocket_browser.c b/src/microhttpd_ws/test_websocket_browser.c
new file mode 100644
index 00000000..dfbcd116
--- /dev/null
+++ b/src/microhttpd_ws/test_websocket_browser.c
@@ -0,0 +1,563 @@
1/*
2 This file is part of libmicrohttpd
3 Copyright (C) 2021 David Gausmann
4
5 libmicrohttpd is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published
7 by the Free Software Foundation; either version 3, or (at your
8 option) any later version.
9
10 libmicrohttpd is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with libmicrohttpd; see the file COPYING. If not, write to the
17 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
19*/
20/**
21 * @file test_websocket_browser.c
22 * @brief Testcase for WebSocket decoding/encoding with external browser
23 * @author David Gausmann
24 */
25#include <sys/types.h>
26#ifndef _WIN32
27#include <sys/select.h>
28#include <sys/socket.h>
29#include <fcntl.h>
30#else
31#include <winsock2.h>
32#endif
33#include "microhttpd.h"
34#include "microhttpd_ws.h"
35#include <stdlib.h>
36#include <string.h>
37#include <stdio.h>
38#include <stdint.h>
39#include <time.h>
40#include <errno.h>
41
42#define PORT 80
43
44#define PAGE \
45 "<!DOCTYPE html>\n" \
46 "<html>\n" \
47 "<head>\n" \
48 "<meta charset=\"UTF-8\">\n" \
49 "<title>Websocket External Test with Webbrowser</title>\n" \
50 "<script>\n" \
51 "\n" \
52 "let current_mode = 0;\n" \
53 "let current_step = 0;\n" \
54 "let sent_payload = null;\n" \
55 "let charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_!@%&/\\\\';\n" \
56 "let step_to_bytes = [ 0, 1, 2, 3, 122, 123, 124, 125, 126, 127, 128, 32766, 32767, 32768, 65534, 65535, 65536, 65537, 1048576, 10485760 ];\n" \
57 "let url = 'ws' + (window.location.protocol === 'https:' ? 's' : '')" \
58 " + '://' +\n" \
59 " window.location.host + '/websocket';\n" \
60 "let socket = null;\n" \
61 "\n" \
62 "window.onload = function (event) {\n" \
63 " if (!window.WebSocket) {\n" \
64 " document.write ('ERROR: The WebSocket class is not supported by your browser.<br>');\n" \
65 " }\n" \
66 " if (!window.fetch) {\n" \
67 " document.write ('ERROR: The fetch-API is not supported by your browser.<br>');\n" \
68 " }\n" \
69 " document.write ('Starting tests.<br>');\n" \
70 " runTest ();\n" \
71 "}\n" \
72 "\n" \
73 "function runTest () {\n" \
74 " switch (current_mode) {\n" \
75 " case 0:\n" \
76 " document.write ('TEXT');\n" \
77 " break;\n" \
78 " case 1:\n" \
79 " document.write ('BINARY');\n" \
80 " break;\n" \
81 " }\n" \
82 " document.write (', ' + step_to_bytes[current_step] + ' Bytes: ');\n" \
83 " socket = new WebSocket(url);\n" \
84 " socket.binaryType = 'arraybuffer';\n" \
85 " socket.onopen = function (event) {\n" \
86 " switch (current_mode) {\n" \
87 " case 0:\n" \
88 " sent_payload = randomText (step_to_bytes[current_step]);\n" \
89 " socket.send (sent_payload);\n" \
90 " break;\n" \
91 " case 1:\n" \
92 " sent_payload = randomBinary (step_to_bytes[current_step]);\n" \
93 " socket.send (sent_payload);\n" \
94 " break;\n" \
95 " }\n" \
96 " }\n" \
97 "\n" \
98 " socket.onclose = function (event) {\n" \
99 " socket.onmessage = null;\n" \
100 " socket.onclose = null;\n" \
101 " socket.onerror = null;\n" \
102 " document.write ('CLOSED unexpectedly.<br>');\n" \
103 " notifyError ();\n" \
104 " }\n" \
105 "\n" \
106 " socket.onerror = function (event) {\n" \
107 " socket.onmessage = null;\n" \
108 " socket.onclose = null;\n" \
109 " socket.onerror = null;\n" \
110 " document.write ('ERROR.<br>');\n" \
111 " notifyError ();\n" \
112 " }\n" \
113 "\n" \
114 " socket.onmessage = async function (event) {\n" \
115 " if (compareData (event.data, sent_payload)) {\n" \
116 " document.write ('SUCCESS.<br>');\n" \
117 " socket.onmessage = null;\n" \
118 " socket.onclose = null;\n" \
119 " socket.onerror = null;\n" \
120 " socket.close();\n" \
121 " socket = null;\n" \
122 " if (step_to_bytes.length <= ++current_step) {\n" \
123 " current_step = 0;\n" \
124 " if (1 < ++current_mode) {\n" \
125 " document.write ('FINISHED ALL TESTS.<br>');\n" \
126 " return;\n" \
127 " }\n" \
128 " }\n" \
129 " runTest ();\n" \
130 " }" \
131 " }\n" \
132 "}\n" \
133 "\n" \
134 "function compareData (data, data2) {\n" \
135 " if (typeof (data) === 'string' && typeof (data2) === 'string') {\n" \
136 " return (data === data2); \n" \
137 " } \n" \
138 " else if ((data instanceof ArrayBuffer) && (data2 instanceof ArrayBuffer)) {\n" \
139 " let view1 = new Uint8Array (data);\n" \
140 " let view2 = new Uint8Array (data2);\n" \
141 " if (view1.length != view2.length)\n" \
142 " return false;\n" \
143 " for (let i = 0; i < view1.length; ++i) {\n" \
144 " if (view1[i] !== view2[i])\n" \
145 " return false;\n" \
146 " }\n" \
147 " return true;\n" \
148 " }\n" \
149 " else\n" \
150 " {\n" \
151 " return false;\n" \
152 " }\n" \
153 "}\n" \
154 "\n" \
155 "function randomText (length) {\n" \
156 " let result = new Array (length);\n" \
157 " for (let i = 0; i < length; ++i)\n" \
158 " result [i] = charset [~~(Math.random () * charset.length)];\n" \
159 " return result.join ('');\n" \
160 "}\n" \
161 "\n" \
162 "function randomBinary (length) {\n" \
163 " let buffer = new ArrayBuffer (length);\n" \
164 " let view = new Uint8Array (buffer);\n" \
165 " for (let i = 0; i < length; ++i)\n" \
166 " view [i] = ~~(Math.random () * 256);\n" \
167 " return buffer;\n" \
168 "}\n" \
169 "\n" \
170 "function notifyError () {\n" \
171 " fetch('error/' + (0 == current_mode ? 'text' : 'binary') + '/' + step_to_bytes[current_step]);\n" \
172 "}\n" \
173 "\n" \
174 "</script>\n" \
175 "</head>\n" \
176 "<body>\n" \
177 "</body>\n" \
178 "</html>"
179
180#define PAGE_NOT_FOUND \
181 "404 Not Found"
182
183#define PAGE_INVALID_WEBSOCKET_REQUEST \
184 "Invalid WebSocket request!"
185
186static void
187send_all (MHD_socket fd,
188 const char *buf,
189 size_t len);
190static void
191make_blocking (MHD_socket fd);
192
193static void
194upgrade_handler (void *cls,
195 struct MHD_Connection *connection,
196 void *con_cls,
197 const char *extra_in,
198 size_t extra_in_size,
199 MHD_socket fd,
200 struct MHD_UpgradeResponseHandle *urh)
201{
202 /* make the socket blocking (operating-system-dependent code) */
203 make_blocking (fd);
204
205 /* create a websocket stream for this connection */
206 struct MHD_WebSocketStream* ws;
207 int result = MHD_websocket_stream_init (&ws,
208 0,
209 0);
210 if (0 != result)
211 {
212 /* Couldn't create the websocket stream.
213 * So we close the socket and leave
214 */
215 MHD_upgrade_action (urh,
216 MHD_UPGRADE_ACTION_CLOSE);
217 return;
218 }
219
220 /* Let's wait for incoming data */
221 const size_t buf_len = 256;
222 char buf[buf_len];
223 ssize_t got;
224 while (MHD_WEBSOCKET_VALIDITY_VALID == MHD_websocket_stream_is_valid (ws))
225 {
226 got = recv (fd,
227 buf,
228 sizeof (buf),
229 0);
230 if (0 >= got)
231 {
232 /* the TCP/IP socket has been closed */
233 fprintf (stderr,
234 "Error (The socket has been closed unexpectedly)\n");
235 break;
236 }
237
238 /* parse the entire received data */
239 size_t buf_offset = 0;
240 while (buf_offset < (size_t) got)
241 {
242 size_t new_offset = 0;
243 char *payload_data = NULL;
244 size_t payload_len = 0;
245 char *frame_data = NULL;
246 size_t frame_len = 0;
247 int status = MHD_websocket_decode (ws,
248 buf + buf_offset,
249 ((size_t) got) - buf_offset,
250 &new_offset,
251 &payload_data,
252 &payload_len);
253 if (0 > status)
254 {
255 /* an error occurred and the connection must be closed */
256 printf ("Decoding failed: status=%d, passed=%u\n", status, ((size_t) got) - buf_offset);
257 if (NULL != payload_data)
258 {
259 MHD_websocket_free (ws, payload_data);
260 }
261 break;
262 }
263 else
264 {
265 buf_offset += new_offset;
266 if (0 < status)
267 {
268 /* the frame is complete */
269 printf ("Decoding succeeded: type=%d, passed=%u, parsed=%u, payload_len=%d\n", status, ((size_t) got) - buf_offset, new_offset, payload_len);
270 switch (status)
271 {
272 case MHD_WEBSOCKET_STATUS_TEXT_FRAME:
273 case MHD_WEBSOCKET_STATUS_BINARY_FRAME:
274 /* The client has sent some data. */
275 if (NULL != payload_data || 0 == payload_len)
276 {
277 /* Send the received data back to the client */
278 if (MHD_WEBSOCKET_STATUS_TEXT_FRAME == status)
279 {
280 result = MHD_websocket_encode_text (ws,
281 payload_data,
282 payload_len,
283 0,
284 &frame_data,
285 &frame_len,
286 NULL);
287 }
288 else
289 {
290 result = MHD_websocket_encode_binary (ws,
291 payload_data,
292 payload_len,
293 0,
294 &frame_data,
295 &frame_len);
296 }
297 if (0 == result)
298 {
299 send_all (fd,
300 frame_data,
301 frame_len);
302 }
303 }
304 else
305 {
306 /* should never happen */
307 fprintf (stderr,
308 "Error (Empty buffer with payload_len != 0)\n");
309 }
310 break;
311
312 default:
313 /* Other frame types are ignored
314 * in this test script.
315 */
316 break;
317 }
318 }
319 if (NULL != payload_data)
320 {
321 MHD_websocket_free (ws, payload_data);
322 }
323 if (NULL != frame_data)
324 {
325 MHD_websocket_free (ws, frame_data);
326 }
327 }
328 }
329 }
330
331 /* free the websocket stream */
332 MHD_websocket_stream_free (ws);
333
334 /* close the socket when it is not needed anymore */
335 MHD_upgrade_action (urh,
336 MHD_UPGRADE_ACTION_CLOSE);
337}
338
339/* This helper function is used for the case that
340 * we need to resend some data
341 */
342static void
343send_all (MHD_socket fd,
344 const char *buf,
345 size_t len)
346{
347 ssize_t ret;
348 size_t off;
349
350 for (off = 0; off < len; off += ret)
351 {
352 ret = send (fd,
353 &buf[off],
354 (int) (len - off),
355 0);
356 if (0 > ret)
357 {
358 if (EAGAIN == errno)
359 {
360 ret = 0;
361 continue;
362 }
363 break;
364 }
365 if (0 == ret)
366 break;
367 }
368}
369
370/* This helper function contains operating-system-dependent code and
371 * is used to make a socket blocking.
372 */
373static void
374make_blocking (MHD_socket fd)
375{
376#ifndef _WIN32
377 int flags;
378
379 flags = fcntl (fd, F_GETFL);
380 if (-1 == flags)
381 return;
382 if ((flags & ~O_NONBLOCK) != flags)
383 if (-1 == fcntl (fd, F_SETFL, flags & ~O_NONBLOCK))
384 abort ();
385#else
386 unsigned long flags = 0;
387
388 ioctlsocket (fd, FIONBIO, &flags);
389#endif
390}
391
392static enum MHD_Result
393access_handler (void *cls,
394 struct MHD_Connection *connection,
395 const char *url,
396 const char *method,
397 const char *version,
398 const char *upload_data,
399 size_t *upload_data_size,
400 void **ptr)
401{
402 static int aptr;
403 struct MHD_Response *response;
404 int ret;
405
406 (void) cls; /* Unused. Silent compiler warning. */
407 (void) upload_data; /* Unused. Silent compiler warning. */
408 (void) upload_data_size; /* Unused. Silent compiler warning. */
409
410 if (0 != strcmp (method, "GET"))
411 return MHD_NO; /* unexpected method */
412 if (&aptr != *ptr)
413 {
414 /* do never respond on first call */
415 *ptr = &aptr;
416 return MHD_YES;
417 }
418 *ptr = NULL; /* reset when done */
419
420 if (0 == strcmp (url, "/"))
421 {
422 /* Default page for visiting the server */
423 struct MHD_Response *response = MHD_create_response_from_buffer (
424 strlen (PAGE),
425 PAGE,
426 MHD_RESPMEM_PERSISTENT);
427 ret = MHD_queue_response (connection,
428 MHD_HTTP_OK,
429 response);
430 MHD_destroy_response (response);
431 }
432 else if (0 == strncmp (url, "/error/", 7))
433 {
434 /* Report error */
435 fprintf (stderr, "Error in test (%s)\n", url + 7);
436
437 struct MHD_Response *response = MHD_create_response_from_buffer (
438 0,
439 "",
440 MHD_RESPMEM_PERSISTENT);
441 ret = MHD_queue_response (connection,
442 MHD_HTTP_OK,
443 response);
444 MHD_destroy_response (response);
445 }
446 else if (0 == strcmp (url, "/websocket"))
447 {
448 char is_valid = 1;
449 const char* value = NULL;
450 char sec_websocket_accept[29];
451
452 if (0 != MHD_websocket_check_http_version (version))
453 {
454 is_valid = 0;
455 }
456 value = MHD_lookup_connection_value (connection,
457 MHD_HEADER_KIND,
458 MHD_HTTP_HEADER_CONNECTION);
459 if (0 != MHD_websocket_check_connection_header (value))
460 {
461 is_valid = 0;
462 }
463 value = MHD_lookup_connection_value (connection,
464 MHD_HEADER_KIND,
465 MHD_HTTP_HEADER_UPGRADE);
466 if (0 != MHD_websocket_check_upgrade_header (value))
467 {
468 is_valid = 0;
469 }
470 value = MHD_lookup_connection_value (connection,
471 MHD_HEADER_KIND,
472 MHD_HTTP_HEADER_SEC_WEBSOCKET_VERSION);
473 if (0 != MHD_websocket_check_version_header (value))
474 {
475 is_valid = 0;
476 }
477 value = MHD_lookup_connection_value (connection,
478 MHD_HEADER_KIND,
479 MHD_HTTP_HEADER_SEC_WEBSOCKET_KEY);
480 if (0 != MHD_websocket_create_accept_header (value, sec_websocket_accept))
481 {
482 is_valid = 0;
483 }
484
485 if (1 == is_valid)
486 {
487 /* upgrade the connection */
488 response = MHD_create_response_for_upgrade (&upgrade_handler,
489 NULL);
490 MHD_add_response_header (response,
491 MHD_HTTP_HEADER_CONNECTION,
492 "Upgrade");
493 MHD_add_response_header (response,
494 MHD_HTTP_HEADER_UPGRADE,
495 "websocket");
496 MHD_add_response_header (response,
497 MHD_HTTP_HEADER_SEC_WEBSOCKET_ACCEPT,
498 sec_websocket_accept);
499 ret = MHD_queue_response (connection,
500 MHD_HTTP_SWITCHING_PROTOCOLS,
501 response);
502 MHD_destroy_response (response);
503 }
504 else
505 {
506 /* return error page */
507 struct MHD_Response*response = MHD_create_response_from_buffer (
508 strlen (PAGE_INVALID_WEBSOCKET_REQUEST),
509 PAGE_INVALID_WEBSOCKET_REQUEST,
510 MHD_RESPMEM_PERSISTENT);
511 ret = MHD_queue_response (connection,
512 MHD_HTTP_BAD_REQUEST,
513 response);
514 MHD_destroy_response (response);
515 }
516 }
517 else
518 {
519 struct MHD_Response*response = MHD_create_response_from_buffer (
520 strlen (PAGE_NOT_FOUND),
521 PAGE_NOT_FOUND,
522 MHD_RESPMEM_PERSISTENT);
523 ret = MHD_queue_response (connection,
524 MHD_HTTP_NOT_FOUND,
525 response);
526 MHD_destroy_response (response);
527 }
528
529 return ret;
530}
531
532int
533main (int argc,
534 char *const *argv)
535{
536 (void) argc; /* Unused. Silent compiler warning. */
537 (void) argv; /* Unused. Silent compiler warning. */
538 struct MHD_Daemon *daemon;
539
540 daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD |
541 MHD_USE_THREAD_PER_CONNECTION |
542 MHD_ALLOW_UPGRADE |
543 MHD_USE_ERROR_LOG,
544 PORT, NULL, NULL,
545 &access_handler, NULL,
546 MHD_OPTION_END);
547
548 if (NULL == daemon)
549 {
550 fprintf (stderr, "Error (Couldn't start daemon for testing)\n");
551 return 1;
552 }
553 printf("The server is listening now.\n");
554 printf("Access the server now with a websocket-capable webbrowser.\n\n");
555 printf("Press return to close.\n");
556
557 (void) getc (stdin);
558
559 MHD_stop_daemon (daemon);
560
561 return 0;
562}
563