From: Daniel Stenberg Date: Fri, 9 Sep 2022 13:11:14 +0000 (+0200) Subject: tests: add websockets tests X-Git-Tag: curl-7_86_0~274 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=0aaebf62ec281286afb9e51f2a8088302463df3f;p=thirdparty%2Fcurl.git tests: add websockets tests - add websockets support to sws - 2300: first very basic websockets test - 2301: first libcurl test for ws (not working yet) - 2302: use the ws callback - 2303: test refused upgrade --- diff --git a/tests/data/DISABLED b/tests/data/DISABLED index c2907bcfec..d4a451b1d1 100644 --- a/tests/data/DISABLED +++ b/tests/data/DISABLED @@ -79,6 +79,8 @@ 1941 1942 1943 +2301 +2302 %endif 2043 # Tests that are disabled here for rustls are SUPPOSED to work diff --git a/tests/data/Makefile.inc b/tests/data/Makefile.inc index dd7aab82b7..a060a803a6 100644 --- a/tests/data/Makefile.inc +++ b/tests/data/Makefile.inc @@ -242,6 +242,8 @@ test2100 \ \ test2200 test2201 test2202 test2203 test2204 test2205 \ \ +test2300 test2301 test2302 test2303 \ +\ test3000 test3001 test3002 test3003 test3004 test3005 test3006 test3007 \ test3008 test3009 test3010 test3011 test3012 test3013 test3014 test3015 \ test3016 test3017 test3018 test3019 test3020 test3021 test3022 test3023 \ diff --git a/tests/data/test2300 b/tests/data/test2300 new file mode 100644 index 0000000000..997acfad29 --- /dev/null +++ b/tests/data/test2300 @@ -0,0 +1,62 @@ + + + +WebSockets + + + +# +# Server-side + + +HTTP/1.1 101 Switching to WebSockets swsclose +Server: test-server/fake +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs= + + +# allow upgrade + +upgrade + + + +# +# Client-side + +# for the forced CURL_ENTROPY + +debug +ws + + +http + + +WebSockets upgrade only + + +ws://%HOSTIP:%HTTPPORT/%TESTNUMBER + + + +# +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Version: 13 +Sec-WebSocket-Key: NDMyMTUzMjE2MzIxNzMyMQ== + + + +52 + + + diff --git a/tests/data/test2301 b/tests/data/test2301 new file mode 100644 index 0000000000..1f8ed662b1 --- /dev/null +++ b/tests/data/test2301 @@ -0,0 +1,65 @@ + + + +WebSockets + + + +# +# Server-side + + +HTTP/1.1 101 Switching to WebSockets +Server: test-server/fake +Upgrade: websocket +Connection: Upgrade +Something: else +Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs= + +%hex[%89%00]hex% + +# allow upgrade + +upgrade + + + +# +# Client-side + +# require debug for the forced CURL_ENTROPY + +debug +ws + + +http + + +WebSockets via callback (raw mode) + curl_ws_send() + + +lib%TESTNUMBER + + +ws://%HOSTIP:%HTTPPORT/%TESTNUMBER + + + +# +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: webbie-sox/3 +Accept: */* +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Version: 13 +Sec-WebSocket-Key: NDMyMTUzMjE2MzIxNzMyMQ== + +%hex[%8a%00]hex% + + + diff --git a/tests/data/test2302 b/tests/data/test2302 new file mode 100644 index 0000000000..013c324e85 --- /dev/null +++ b/tests/data/test2302 @@ -0,0 +1,70 @@ + + + +WebSockets + + + +# +# Sends a PING + a 5 byte hello TEXT + + +HTTP/1.1 101 Switching to WebSockets +Server: test-server/fake +Upgrade: websocket +Connection: Upgrade +Something: else +Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs= + +%hex[%89%00%81%05hello]hex% + +# allow upgrade + +upgrade + + + +# +# Client-side + +# require debug for the forced CURL_ENTROPY + +debug +ws + + +http + + +WebSockets via callback (frame mode) + curl_ws_send() + + +lib%TESTNUMBER + + +ws://%HOSTIP:%HTTPPORT/%TESTNUMBER + + + +# +# PONG with no data and the 32 bit mask +# + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: webbie-sox/3 +Accept: */* +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Version: 13 +Sec-WebSocket-Key: NDMyMTUzMjE2MzIxNzMyMQ== + +%hex[%8a%808321]hex% + + +68 65 6c 6c 6f +RECFLAGS: 1 + + + diff --git a/tests/data/test2303 b/tests/data/test2303 new file mode 100644 index 0000000000..dbd1115c80 --- /dev/null +++ b/tests/data/test2303 @@ -0,0 +1,59 @@ + + + +WebSockets + + + +# + + +HTTP/1.1 200 Oblivious +Server: test-server/fake +Something: else +Content-Length: 6 + +hello + + + +# +# Client-side + +# require debug for the forced CURL_ENTROPY + +debug +ws + + +http + + +WebSockets but gets a 200 back + + +lib2302 + + +ws://%HOSTIP:%HTTPPORT/%TESTNUMBER + + + + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: webbie-sox/3 +Accept: */* +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Version: 13 +Sec-WebSocket-Key: NDMyMTUzMjE2MzIxNzMyMQ== + + +# 22 == CURLE_HTTP_RETURNED_ERROR + +22 + + + diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc index 6e6bfa9f86..0ca599cfa8 100644 --- a/tests/libtest/Makefile.inc +++ b/tests/libtest/Makefile.inc @@ -65,6 +65,8 @@ noinst_PROGRAMS = chkhostname libauthretry libntlmconnect \ lib1915 lib1916 lib1917 lib1918 lib1919 \ lib1933 lib1934 lib1935 lib1936 lib1937 lib1938 lib1939 lib1940 \ lib1945 lib1946 lib1947 \ + lib2301 lib2302 \ +>>>>>>> 265a739f6 (tests: add websockets tests) lib3010 lib3025 lib3026 lib3027 chkdecimalpoint_SOURCES = chkdecimalpoint.c ../../lib/mprintf.c \ @@ -752,6 +754,12 @@ lib1947_SOURCES = lib1947.c $(SUPPORTFILES) lib1947_LDADD = $(TESTUTIL_LIBS) lib1947_CPPFLAGS = $(AM_CPPFLAGS) +lib2301_SOURCES = lib2301.c $(SUPPORTFILES) +lib2301_LDADD = $(TESTUTIL_LIBS) + +lib2302_SOURCES = lib2302.c $(SUPPORTFILES) +lib2302_LDADD = $(TESTUTIL_LIBS) + lib3010_SOURCES = lib3010.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS) lib3010_LDADD = $(TESTUTIL_LIBS) lib3010_CPPFLAGS = $(AM_CPPFLAGS) diff --git a/tests/libtest/lib2301.c b/tests/libtest/lib2301.c new file mode 100644 index 0000000000..95247933af --- /dev/null +++ b/tests/libtest/lib2301.c @@ -0,0 +1,154 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2022, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "test.h" + +#ifdef USE_WEBSOCKETS +#if 0 + +static int ping(CURL *curl, const char *send_payload) +{ + size_t sent; + CURLcode result = + curl_ws_send(curl, send_payload, strlen(send_payload), &sent, CURLWS_PING); + fprintf(stderr, + "ws: curl_ws_send returned %u, sent %u\n", (int)result, (int)sent); + + return (int)result; +} + +static int recv_pong(CURL *curl, const char *exected_payload) +{ + size_t rlen; + unsigned int rflags; + char buffer[256]; + CURLcode result = + curl_ws_recv(curl, buffer, sizeof(buffer), &rlen, &rflags); + if(rflags & CURLWS_PONG) { + int same = 0; + fprintf(stderr, "ws: got PONG back\n"); + if(rlen == strlen(exected_payload)) { + if(!memcmp(exected_payload, buffer, rlen)) { + fprintf(stderr, "ws: got the same payload back\n"); + same = 1; + } + } + if(!same) + fprintf(stderr, "ws: did NOT get the same payload back\n"); + } + else { + fprintf(stderr, "recv_pong: got %u bytes rflags %x\n", (int)rlen, rflags); + } + fprintf(stderr, "ws: curl_ws_recv returned %u, received %u\n", (int)result, + rlen); + return (int)result; +} + +/* just close the connection */ +static void websocket_close(CURL *curl) +{ + size_t sent; + CURLcode result = + curl_ws_send(curl, "", 0, &sent, CURLWS_CLOSE); + fprintf(stderr, + "ws: curl_ws_send returned %u, sent %u\n", (int)result, (int)sent); +} + +static void websocket(CURL *curl) +{ + int i = 0; + fprintf(stderr, "ws: websocket() starts\n"); + do { + if(ping(curl, "foobar")) + return; + if(recv_pong(curl, "foobar")) + return; + sleep(2); + } while(i++ < 10); + websocket_close(curl); +} + +#endif + +static size_t writecb(char *b, size_t size, size_t nitems, void *p) +{ + CURL *easy = p; + unsigned char *buffer = (unsigned char *)b; + size_t i; + size_t sent; + unsigned char pong[] = { + 0x8a, 0x0 + }; + size_t incoming = nitems; + fprintf(stderr, "Called CURLOPT_WRITEFUNCTION with %u bytes: ", + (int)nitems); + for(i = 0; i < nitems; i++) + fprintf(stderr, "%02x ", (unsigned char)buffer[i]); + fprintf(stderr, "\n"); + (void)size; + if(buffer[0] == 0x89) { + CURLcode result; + fprintf(stderr, "send back a simple PONG\n"); + result = curl_ws_send(easy, pong, 2, &sent, 0); + if(result) + nitems = 0; + } + if(nitems != incoming) + fprintf(stderr, "returns error from callback\n"); + return nitems; +} + +int test(char *URL) +{ + CURL *curl; + CURLcode res = CURLE_OK; + + global_init(CURL_GLOBAL_ALL); + + curl = curl_easy_init(); + if(curl) { + curl_easy_setopt(curl, CURLOPT_URL, URL); + + /* use the callback style */ + curl_easy_setopt(curl, CURLOPT_USERAGENT, "webbie-sox/3"); + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + curl_easy_setopt(curl, CURLOPT_WS_OPTIONS, CURLWS_RAW_MODE); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writecb); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, curl); + res = curl_easy_perform(curl); + fprintf(stderr, "curl_easy_perform() returned %u\n", (int)res); +#if 0 + if(res == CURLE_OK) + websocket(curl); +#endif + /* always cleanup */ + curl_easy_cleanup(curl); + } + curl_global_cleanup(); + return (int)res; +} + +#else /* no websockets */ +NO_SUPPORT_BUILT_IN +#endif diff --git a/tests/libtest/lib2302.c b/tests/libtest/lib2302.c new file mode 100644 index 0000000000..920f83f99f --- /dev/null +++ b/tests/libtest/lib2302.c @@ -0,0 +1,157 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2022, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "test.h" + +#ifdef USE_WEBSOCKETS + +#if 0 + +static int ping(CURL *curl, const char *send_payload) +{ + size_t sent; + CURLcode result = + curl_ws_send(curl, send_payload, strlen(send_payload), &sent, CURLWS_PING); + fprintf(stderr, + "ws: curl_ws_send returned %u, sent %u\n", (int)result, (int)sent); + + return (int)result; +} + +static int recv_pong(CURL *curl, const char *exected_payload) +{ + size_t rlen; + unsigned int rflags; + char buffer[256]; + CURLcode result = + curl_ws_recv(curl, buffer, sizeof(buffer), &rlen, &rflags); + if(rflags & CURLWS_PONG) { + int same = 0; + fprintf(stderr, "ws: got PONG back\n"); + if(rlen == strlen(exected_payload)) { + if(!memcmp(exected_payload, buffer, rlen)) { + fprintf(stderr, "ws: got the same payload back\n"); + same = 1; + } + } + if(!same) + fprintf(stderr, "ws: did NOT get the same payload back\n"); + } + else { + fprintf(stderr, "recv_pong: got %u bytes rflags %x\n", (int)rlen, rflags); + } + fprintf(stderr, "ws: curl_ws_recv returned %u, received %u\n", (int)result, + rlen); + return (int)result; +} + +/* just close the connection */ +static void websocket_close(CURL *curl) +{ + size_t sent; + CURLcode result = + curl_ws_send(curl, "", 0, &sent, CURLWS_CLOSE); + fprintf(stderr, + "ws: curl_ws_send returned %u, sent %u\n", (int)result, (int)sent); +} + +static void websocket(CURL *curl) +{ + int i = 0; + fprintf(stderr, "ws: websocket() starts\n"); + do { + if(ping(curl, "foobar")) + return; + if(recv_pong(curl, "foobar")) + return; + sleep(2); + } while(i++ < 10); + websocket_close(curl); +} + +#endif + +static size_t writecb(char *buffer, size_t size, size_t nitems, void *p) +{ + CURL *easy = p; + size_t i; + size_t incoming = nitems; + struct curl_ws_metadata *meta; + (void)size; + for(i = 0; i < nitems; i++) + printf("%02x ", (unsigned char)buffer[i]); + printf("\n"); + + meta = curl_ws_meta(easy); + if(meta) + printf("RECFLAGS: %x\n", meta->recvflags); + else + fprintf(stderr, "RECFLAGS: NULL\n"); + + /* this assumes we get a simple TEXT frame first */ + { + CURLcode result = CURLE_OK; + fprintf(stderr, "send back a TEXT\n"); + (void)easy; + /*result = curl_ws_send(easy, pong, 2, &sent, 0);*/ + if(result) + nitems = 0; + } + if(nitems != incoming) + fprintf(stderr, "returns error from callback\n"); + return nitems; +} + +int test(char *URL) +{ + CURL *curl; + CURLcode res = CURLE_OK; + + global_init(CURL_GLOBAL_ALL); + + curl = curl_easy_init(); + if(curl) { + curl_easy_setopt(curl, CURLOPT_URL, URL); + + /* use the callback style */ + curl_easy_setopt(curl, CURLOPT_USERAGENT, "webbie-sox/3"); + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writecb); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, curl); + res = curl_easy_perform(curl); + fprintf(stderr, "curl_easy_perform() returned %u\n", (int)res); +#if 0 + if(res == CURLE_OK) + websocket(curl); +#endif + /* always cleanup */ + curl_easy_cleanup(curl); + } + curl_global_cleanup(); + return (int)res; +} + +#else +NO_SUPPORT_BUILT_IN +#endif diff --git a/tests/libtest/test.h b/tests/libtest/test.h index f52e05f90a..badad1d109 100644 --- a/tests/libtest/test.h +++ b/tests/libtest/test.h @@ -485,4 +485,12 @@ extern int unitfail; #define global_init(A) \ chk_global_init((A), (__FILE__), (__LINE__)) +#define NO_SUPPORT_BUILT_IN \ + int test(char *URL) \ + { \ + (void)URL; \ + fprintf(stderr, "Missing support\n"); \ + return 1; \ + } + /* ---------------------------------------------------------------- */ diff --git a/tests/server/sws.c b/tests/server/sws.c index 9f8c4a858f..55c1c1d601 100644 --- a/tests/server/sws.c +++ b/tests/server/sws.c @@ -124,7 +124,7 @@ struct httprequest { bool skipall; /* skip all incoming data */ bool noexpect; /* refuse Expect: (don't read the body) */ bool connmon; /* monitor the state of the connection, log disconnects */ - bool upgrade; /* test case allows upgrade to http2 */ + bool upgrade; /* test case allows upgrade */ bool upgrade_request; /* upgrade request found and allowed */ bool close; /* similar to swsclose in response: close connection after response is sent */ @@ -182,7 +182,7 @@ const char *cmdfile = DEFAULT_CMDFILE; proper point - like with NTLM */ #define CMD_CONNECTIONMONITOR "connection-monitor" -/* upgrade to http2 */ +/* upgrade to http2/websocket/xxxx */ #define CMD_UPGRADE "upgrade" /* close connection */ @@ -311,7 +311,7 @@ static int parse_servercmd(struct httprequest *req) req->connmon = TRUE; } else if(!strncmp(CMD_UPGRADE, cmd, strlen(CMD_UPGRADE))) { - logmsg("enabled upgrade to http2"); + logmsg("enabled upgrade"); req->upgrade = TRUE; } else if(!strncmp(CMD_SWSCLOSE, cmd, strlen(CMD_SWSCLOSE))) { @@ -541,8 +541,9 @@ static int ProcessRequest(struct httprequest *req) parse_servercmd(req); } else if((req->offset >= 3)) { + unsigned char *l = (unsigned char *)line; logmsg("** Unusual request. Starts with %02x %02x %02x (%c%c%c)", - line[0], line[1], line[2], line[0], line[1], line[2]); + l[0], l[1], l[2], l[0], l[1], l[2]); } } @@ -763,8 +764,9 @@ static int ProcessRequest(struct httprequest *req) if(req->upgrade && strstr(req->reqbuf, "Upgrade:")) { /* we allow upgrade and there was one! */ - logmsg("Found Upgrade: in request and allows it"); + logmsg("Found Upgrade: in request and allow it"); req->upgrade_request = TRUE; + return 0; /* not done */ } if(req->cl > 0) { @@ -857,6 +859,8 @@ static void init_httprequest(struct httprequest *req) req->upgrade_request = 0; } +static int send_doc(curl_socket_t sock, struct httprequest *req); + /* returns 1 if the connection should be serviced again immediately, 0 if there is no data waiting, or < 0 if it should be closed */ static int get_request(curl_socket_t sock, struct httprequest *req) @@ -866,6 +870,56 @@ static int get_request(curl_socket_t sock, struct httprequest *req) ssize_t got = 0; int overflow = 0; + if(req->upgrade_request) { + /* upgraded connection, work it differently until end of connection */ + logmsg("Upgraded connection, this is a no longer HTTP/1"); + send_doc(sock, req); + + /* dump the request received so far to the external file */ + reqbuf[req->offset] = '\0'; + storerequest(reqbuf, req->offset); + req->offset = 0; + + /* read websocket traffic */ + do { + + got = sread(sock, reqbuf + req->offset, REQBUFSIZ - req->offset); + if(got > 0) + req->offset += got; + logmsg("Got: %d", (int)got); + + if((got == -1) && ((EAGAIN == errno) || (EWOULDBLOCK == errno))) { + int rc; + fd_set input; + fd_set output; + struct timeval timeout = {1, 0}; /* 1000 ms */ + + FD_ZERO(&input); + FD_ZERO(&output); + got = 0; + FD_SET(sock, &input); + do { + logmsg("Wait until readable"); + rc = select((int)sock + 1, &input, &output, NULL, &timeout); + } while(rc < 0 && errno == EINTR && !got_exit_signal); + logmsg("readable %d", rc); + if(rc) + got = 1; + } + } while(got > 0); + + if(req->offset) { + logmsg("log the websocket traffic"); + /* dump the incoming websocket traffic to the external file */ + reqbuf[req->offset] = '\0'; + storerequest(reqbuf, req->offset); + req->offset = 0; + } + init_httprequest(req); + + return -1; + } + if(req->offset >= REQBUFSIZ-1) { /* buffer is already full; do nothing */ overflow = 1; @@ -1708,10 +1762,10 @@ http_connect_cleanup: *infdp = CURL_SOCKET_BAD; } -static void http2(struct httprequest *req) +static void http_upgrade(struct httprequest *req) { (void)req; - logmsg("switched to http2"); + logmsg("Upgraded to ... %u", req->upgrade_request); /* left to implement */ } @@ -1852,9 +1906,9 @@ static int service_connection(curl_socket_t msgsock, struct httprequest *req, } if(req->upgrade_request) { - /* an upgrade request, switch to http2 here */ - http2(req); - return -1; + /* an upgrade request, switch to another protocol here */ + http_upgrade(req); + return 1; } /* if we got a CONNECT, loop and get another request as well! */ @@ -2273,7 +2327,7 @@ int main(int argc, char *argv[]) } /* Reset the request, unless we're still in the middle of reading */ - if(rc) + if(rc && !req->upgrade_request) init_httprequest(req); } while(rc > 0); }