diff --git a/CMakeLists.txt b/CMakeLists.txt index 99ea9ba8be0682978f026c7ce4b1eba3e170bb64..8efdb08041507f1d9382de158dfbe0eb93f6b30b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -748,7 +748,7 @@ if (NOT LWS_WITHOUT_TESTAPPS) # # Helper function for adding a test app. # - macro(create_test_app TEST_NAME MAIN_SRC S2 S3 S4 S5) + macro(create_test_app TEST_NAME MAIN_SRC S2 S3 S4 S5 S6) set(TEST_SRCS ${MAIN_SRC}) set(TEST_HDR) @@ -768,6 +768,10 @@ if (NOT LWS_WITHOUT_TESTAPPS) else() list(APPEND TEST_SRCS ${S5}) endif() + if ("${S6}" STREQUAL "") + else() + list(APPEND TEST_SRCS ${S6}) + endif() if (WIN32) list(APPEND TEST_SRCS ${WIN32_HELPERS_PATH}/getopt.c @@ -829,12 +833,14 @@ if (NOT LWS_WITHOUT_TESTAPPS) "test-server/test-server-http.c" "test-server/test-server-dumb-increment.c" "test-server/test-server-mirror.c" + "test-server/test-server-status.c" "test-server/test-server-echogen.c") if (UNIX) create_test_app(test-fuzxy "test-server/fuzxy.c" "" "" "" + "" "") endif() if (UNIX AND NOT ((CMAKE_C_COMPILER_ID MATCHES "Clang") OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang"))) @@ -843,6 +849,7 @@ if (NOT LWS_WITHOUT_TESTAPPS) "test-server/test-server-http.c" "test-server/test-server-dumb-increment.c" "test-server/test-server-mirror.c" + "test-server/test-server-status.c" "test-server/test-server-echogen.c") endif() if (UNIX AND NOT ((CMAKE_C_COMPILER_ID MATCHES "Clang") OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang")) @@ -852,6 +859,7 @@ if (NOT LWS_WITHOUT_TESTAPPS) "test-server/test-server-http.c" "test-server/test-server-dumb-increment.c" "test-server/test-server-mirror.c" + "test-server/test-server-status.c" "test-server/test-server-echogen.c") endif() if (UNIX AND NOT ((CMAKE_C_COMPILER_ID MATCHES "Clang") OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang")) @@ -861,6 +869,7 @@ if (NOT LWS_WITHOUT_TESTAPPS) "test-server/test-server-http.c" "test-server/test-server-dumb-increment.c" "test-server/test-server-mirror.c" + "test-server/test-server-status.c" "test-server/test-server-echogen.c") endif() endif() @@ -873,6 +882,7 @@ if (NOT LWS_WITHOUT_TESTAPPS) "test-server/test-server-http.c" "test-server/test-server-dumb-increment.c" "test-server/test-server-mirror.c" + "test-server/test-server-status.c" "test-server/test-server-echogen.c") # Set defines for this executable only. set_property( @@ -978,27 +988,27 @@ if (NOT LWS_WITHOUT_TESTAPPS) # test-client # if (NOT LWS_WITHOUT_TEST_CLIENT) - create_test_app(test-client "test-server/test-client.c" "" "" "" "") + create_test_app(test-client "test-server/test-client.c" "" "" "" "" "") endif() # # test-fraggle # if (NOT LWS_WITHOUT_TEST_FRAGGLE) - create_test_app(test-fraggle "test-server/test-fraggle.c" "" "" "" "") + create_test_app(test-fraggle "test-server/test-fraggle.c" "" "" "" "" "") endif() # # test-ping # if (NOT LWS_WITHOUT_TEST_PING) - create_test_app(test-ping "test-server/test-ping.c" "" "" "" "") + create_test_app(test-ping "test-server/test-ping.c" "" "" "" "" "") endif() # # test-echo # if (NOT LWS_WITHOUT_TEST_ECHO) - create_test_app(test-echo "test-server/test-echo.c" "" "" "" "") + create_test_app(test-echo "test-server/test-echo.c" "" "" "" "" "") endif() endif(NOT LWS_WITHOUT_CLIENT) diff --git a/changelog b/changelog index f068cf2aa9595a1d60f9a448d0d425391dac5cde..9267d20ce3093f36072cd06cbcedd794561daca7 100644 --- a/changelog +++ b/changelog @@ -40,6 +40,9 @@ with systemd 5) MINOR example systemd .service file now provided for test server (not installed by default) +6) test server html is updated with tabs and a new live server monitoring +feature. Input sanitization added to the js. + User API additions ------------------ diff --git a/test-server/test-server-status.c b/test-server/test-server-status.c new file mode 100644 index 0000000000000000000000000000000000000000..5fb599a27fe23b240af5e66f2cc8d4177d29c47d --- /dev/null +++ b/test-server/test-server-status.c @@ -0,0 +1,164 @@ +/* + * libwebsockets-test-server - libwebsockets test implementation + * + * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com> + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + * + * The person who associated a work with this deed has dedicated + * the work to the public domain by waiving all of his or her rights + * to the work worldwide under copyright law, including all related + * and neighboring rights, to the extent allowed by law. You can copy, + * modify, distribute and perform the work, even for commercial purposes, + * all without asking permission. + * + * The test apps are intended to be adapted for use in your code, which + * may be proprietary. So unlike the library itself, they are licensed + * Public Domain. + */ +#include "test-server.h" +#include <time.h> + +static unsigned char server_info[1024]; +static int server_info_len; +static int current; +static char cache[16384]; +static int cache_len; +static struct per_session_data__lws_status *list; +static int live_wsi; + + +static void +update_status(struct lws *wsi, struct per_session_data__lws_status *pss) +{ + struct per_session_data__lws_status **pp = &list; + int subsequent = 0; + char *p = cache + LWS_PRE, *start = p; + char date[128]; + time_t t; + struct tm *ptm; +#ifndef WIN32 + struct tm tm; +#endif + + p += snprintf(p, 512, " { %s, \"wsi\":\"%d\", \"conns\":[", + server_info, live_wsi); + + /* render the list */ + while (*pp) { + t = (*pp)->tv_established.tv_sec; +#ifdef WIN32 + ptm = localtime(&t); + if (!ptm) +#else + ptm = &tm; + if (!localtime_r(&t, &tm)) +#endif + strcpy(date, "unknown"); + else + strftime(date, sizeof(date), "%F %H:%M %Z", ptm); + if ((p - start) > (sizeof(cache) - 512)) + break; + if (subsequent) + *p++ = ','; + subsequent = 1; + p += snprintf(p, sizeof(cache) - (p - start) - 1, + "{\"peer\":\"%s\",\"time\":\"%s\"," + "\"ua\":\"%s\"}", + (*pp)->ip, date, (*pp)->user_agent); + pp = &((*pp)->list); + } + + p += sprintf(p, "]}"); + cache_len = p - start; + lwsl_err("cache_len %d\n", cache_len); + *p = '\0'; + + /* since we changed the list, increment the 'version' */ + current++; + /* update everyone */ + lws_callback_on_writable_all_protocol(lws_get_context(wsi), + lws_get_protocol(wsi)); +} + + +/* lws-status protocol */ + +int +callback_lws_status(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + struct per_session_data__lws_status *pss = + (struct per_session_data__lws_status *)user, + **pp; + char name[128], rip[128]; + int m; + + switch (reason) { + + case LWS_CALLBACK_PROTOCOL_INIT: + /* + * Prepare the static server info + */ + server_info_len = sprintf((char *)server_info, + "\"version\":\"%s\"," + " \"hostname\":\"%s\"", + lws_get_library_version(), + lws_canonical_hostname( + lws_get_context(wsi))); + break; + + case LWS_CALLBACK_ESTABLISHED: + /* + * we keep a linked list of live pss, so we can walk it + */ + pss->last = 0; + pss->list = list; + list = pss; + live_wsi++; + lws_get_peer_addresses(wsi, lws_get_socket_fd(wsi), name, + sizeof(name), rip, sizeof(rip)); + sprintf(pss->ip, "%s (%s)", name, rip); + gettimeofday(&pss->tv_established, NULL); + strcpy(pss->user_agent, "unknown"); + lws_hdr_copy(wsi, pss->user_agent, sizeof(pss->user_agent), + WSI_TOKEN_HTTP_USER_AGENT); + update_status(wsi, pss); + break; + + case LWS_CALLBACK_SERVER_WRITEABLE: + m = lws_write(wsi, (unsigned char *)cache + LWS_PRE, cache_len, + LWS_WRITE_TEXT); + if (m < server_info_len) { + lwsl_err("ERROR %d writing to di socket\n", m); + return -1; + } + break; + + case LWS_CALLBACK_CLOSED: + /* + * remove ourselves from live pss list + */ + lwsl_err("CLOSING pss %p ********\n", pss); + + pp = &list; + while (*pp) { + if (*pp == pss) { + *pp = pss->list; + pss->list = NULL; + live_wsi--; + break; + } + pp = &((*pp)->list); + } + + update_status(wsi, pss); + break; + + default: + break; + } + + return 0; +} diff --git a/test-server/test-server.c b/test-server/test-server.c index 6062383e7acdf4ab5e287130f2706cabb8fe496c..09f7046472fc8ce8dfa6936cab054f652d1f5c23 100644 --- a/test-server/test-server.c +++ b/test-server/test-server.c @@ -68,6 +68,7 @@ enum demo_protocols { PROTOCOL_DUMB_INCREMENT, PROTOCOL_LWS_MIRROR, PROTOCOL_LWS_ECHOGEN, + PROTOCOL_LWS_STATUS, /* always last */ DEMO_PROTOCOL_COUNT @@ -102,6 +103,12 @@ static struct lws_protocols protocols[] = { sizeof(struct per_session_data__echogen), 128, }, + { + "lws-status", + callback_lws_status, + sizeof(struct per_session_data__lws_status), + 128, + }, { NULL, NULL, 0, 0 } /* terminator */ }; diff --git a/test-server/test-server.h b/test-server/test-server.h index 00556514378db8fe45c228c78003ea512eeb79a3..cb69d500e4b4c2c4e4bcf007c83c70687799d33b 100644 --- a/test-server/test-server.h +++ b/test-server/test-server.h @@ -94,6 +94,16 @@ struct per_session_data__echogen { int wr; }; +struct per_session_data__lws_status { + struct per_session_data__lws_status *list; + struct timeval tv_established; + int last; + char ip[270]; + char user_agent[512]; + const char *pos; + int len; +}; + extern int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len); @@ -106,6 +116,10 @@ callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason, extern int callback_lws_echogen(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len); +extern int +callback_lws_status(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len); + extern void dump_handshake_info(struct lws *wsi); diff --git a/test-server/test.html b/test-server/test.html index afd5bc8497bd545d8e3457845b9aa9b17becd3b7..eff2ba19d15a7c9075b1600e3ca6a1b89779607c 100644 --- a/test-server/test.html +++ b/test-server/test.html @@ -4,7 +4,7 @@ <meta charset=utf-8 http-equiv="Content-Language" content="en"/> <title>Minimal Websocket test app</title> <style type="text/css"> - div.title { font-size:18pt; font: Arial; font-weight:normal; text-align:center; color:#000000; } + span.title { font-size:18pt; font: Arial; font-weight:normal; text-align:center; color:#000000; } .browser { font-size:18pt; font: Arial; font-weight:normal; text-align:center; color:#ffff00; vertical-align:middle; text-align:center; background:#d0b070; padding:12px; -webkit-border-radius:10px; -moz-border-radius:10px; border-radius:10px;} .group2 { vertical-align:middle; text-align:center; @@ -20,6 +20,20 @@ -moz-border-radius:10px; border-radius:10px; color:#404000; } + td.wsstatus { vertical-align:middle; width:200px; height:50px; + text-align:center; + background:#f0f0c0; padding:6px; + -webkit-border-radius:8px; + -moz-border-radius:8px; + border-radius:8px; + color:#404000; } + td.l { vertical-align:middle; + text-align:center; + background:#d0d0b0; + padding:3px; + -webkit-border-radius:3px; + -moz-border-radius:3px; + border-radius:3px; } .content { vertical-align:top; text-align:center; background:#fffff0; padding:12px; -webkit-border-radius:10px; -moz-border-radius:10px; border-radius:10px; } .canvas { vertical-align:top; text-align:center; background:#efefd0; padding:12px; -webkit-border-radius:10px; -moz-border-radius:10px; border-radius:10px; } .tabs { @@ -94,19 +108,14 @@ have the test server send a big picture by http. <div class="content"> <div id="dumb" class="group2"> - <table> - <tr> - <td width=200px id=wsdi_statustd align=center class="explain"> - <div id=wsdi_status>Not initialized</div> - </td> - - <td align=center><div id=number> </div></td> - <td align=center> - <input type=button id=offset value="Reset counter" onclick="reset();" > - </td> - </tr> + <table> + <tr> + <td id=wsdi_statustd align=center class="wsstatus"> + <span id=wsdi_status>Websocket connection not initialized</span></td> + <td><span class="title">dumb increment-protocol</span></td> + </tr> <tr> - <td class="explain" colspan=3> + <td class="explain" colspan=2> The incrementing number is coming from the server at 20Hz and is individual for each connection to the server... try opening a second browser window. <br/><br/> @@ -114,6 +123,12 @@ The button sends a message over the websocket link to ask the server to zero just this connection's number. </td> </tr> + <tr> + <td align=center><div id=number style="font-size:120%;"> </div></td> + <td align=center> + <input type=button id=offset value="Reset counter" onclick="reset();" > + </td> + </tr> </table> </div> </div> @@ -127,8 +142,15 @@ to zero just this connection's number. <div id="mirror" class="group2"> <table> <tr> - <td colspan="3"> - <div class="title">libwebsockets "lws-mirror-protocol"</div> + <td colspan=1 id=wslm_statustd align=center class="wsstatus"> + <span id=wslm_status>Websocket connection not initialized</span> + </td> + <td> + <span class="title">lws-mirror-protocol</span> + </td> + </tr> + <tr> + <td colspan=2> <div class="explain"> Use the mouse to draw on the canvas below -- all other browser windows open on this page see your drawing in realtime and you can see any of theirs as @@ -144,20 +166,16 @@ run. </td> </tr> <tr> - <td>Drawing color: + <td colspan=2>Drawing color: <select id="color" onchange="update_color();"> <option value=#000000>Black</option> <option value=#0000ff>Blue</option> <option value=#20ff20>Green</option> <option value=#802020>Dark Red</option> </select> - </td> - <td colspan=2 id=wslm_statustd align=center class="explain"> - <div id=wslm_status>Not initialized</div> - </td> </tr> <tr> - <td colspan=3 width=500 height=320> + <td colspan=2 width=500 height=320> <div id="wslm_drawing" style="background:white"></div> </td> </tr> @@ -174,26 +192,61 @@ run. <div id="ot" class="group2"> <table> <tr> - <td colspan=3> -<div class="title">libwebsockets "open and close testing"</div> + <td> + </td></tr> - <tr> - <td align=center><input type=button id=ot_open_btn value="Open" onclick="ot_open();" ></td> - <td align=center><input type=button id=ot_close_btn disabled value="Close" onclick="ot_close();" ></td> - <td align=center><input type=button id=ot_req_close_btn disabled value="Request Server Close" onclick="ot_req_close();" ></td> + <tr><td id=ot_statustd align=center class="wsstatus"> + <span id=ot_status>Websocket connection not initialized</span> + </td> + <td colspan=2><span class="title">Open and close testing</span></td> </tr> - <tr><td id=ot_statustd align=center class="explain"> - <div id=ot_status>Not initialized</div> - </td> -<td class="explain" colspan=2> + <tr> +<td class="explain" colspan=3 style="padding:3"> To help with open and close testing, you can open and close a connection by hand using the buttons.<br> "<b>Close</b>" closes the connection from the browser with code 3000 and reason 'Bye!".<br> "<b>Request Server Close</b>" sends a message asking the server to initiate the close, which it does with code 1001 and reason "Seeya". -</td></tr></table> +</td></tr> + <tr> + <td align=center><input type=button id=ot_open_btn value="Open" onclick="ot_open();" ></td> + <td align=center><input type=button id=ot_close_btn disabled value="Close" onclick="ot_close();" ></td> + <td align=center><input type=button id=ot_req_close_btn disabled value="Request Server Close" onclick="ot_req_close();" ></td> + </tr> + +</table> +</div> + </div> + </div> + + <div class="tab"> + <input type="radio" id="tab-4" name="tab-group-1"> + <label for="tab-4">Server info</label> + + <div class="content"> +<div id="ot" class="group2"> + <table> + <tr> + <td id=s_statustd align=center class="wsstatus"> + <div id=s_status>Websocket connection not initialized</div> + </td> + <td colspan=1> +<span class="title">Server Info</span> + </td> + </tr><tr> +<td class="explain" colspan=2> +This information is sent by the server over a ws[s] link and updated live +whenever the information changes server-side. +</td></tr> + <tr> + <td align=center colspan=2><div id=servinfo></</div></td> + </tr> + <tr> + <td align=center colspan=2><div id=conninfo style="border : solid 2px #e0d040; padding : 4px; width : 500px; height : 350px; overflow : auto; "></</div></td> + </tr> +</table> </div> </div> </div> @@ -208,6 +261,19 @@ Join the mailing list: <a href="https://libwebsockets.org/mailman/listinfo/libwe <script> +/* + * We display untrusted stuff in html context... reject anything + * that has HTML stuff in it + */ + +function san(s) +{ + if (s.search("<") != -1) + return "invalid string"; + + return s; +} + /* BrowserDetect came from http://www.quirksmode.org/js/detect.html */ var BrowserDetect = { @@ -378,7 +444,9 @@ document.getElementById("number").textContent = get_appropriate_ws_url(); try { socket_di.onopen = function() { document.getElementById("wsdi_statustd").style.backgroundColor = "#40ff40"; - document.getElementById("wsdi_status").innerHTML = " <b>websocket connection opened</b><br>" + socket_di.extensions; + document.getElementById("wsdi_status").innerHTML = + " <b>websocket connection opened</b><br>" + + san(socket_di.extensions); } socket_di.onmessage =function got_packet(msg) { @@ -392,6 +460,55 @@ document.getElementById("number").textContent = get_appropriate_ws_url(); } catch(exception) { alert('<p>Error' + exception); } + + var socket_status, jso, s; + + if (typeof MozWebSocket != "undefined") { + socket_status = new MozWebSocket(get_appropriate_ws_url(), + "lws-status"); + } else { + socket_status = new WebSocket(get_appropriate_ws_url(), + "lws-status"); + } + + + try { + socket_status.onopen = function() { + document.getElementById("s_statustd").style.backgroundColor = "#40ff40"; + document.getElementById("s_status").innerHTML = + " <b>websocket connection opened</b><br>" + + san(socket_di.extensions); + } + + socket_status.onmessage =function got_packet(msg) { + jso = JSON.parse(msg.data); + + document.getElementById("servinfo").innerHTML = + "<table><tr><td class=l>Build info</td><td>"+ + san(jso.version) + "</td></tr>" + + "<tr><td class=l>Server info</td><td>" + + san(jso.hostname) + "</td></tr>" + + "</table>"; + s="<table>"; + var n; + for (n = 0; n < jso.conns.length; n++) + s = s + "<tr><td class=l>client " + (n + 1) + + "</td><td><b>" + san(jso.conns[n].peer) + + "</b><br>" + san(jso.conns[n].time) + + "<br>" + san(jso.conns[n].ua) + + "</td></tr>"; + s = s + "</table>"; + + document.getElementById("conninfo").innerHTML = s; + } + + socket_status.onclose = function(){ + document.getElementById("s_statustd").style.backgroundColor = "#ff4040"; + document.getElementById("s_status").textContent = " websocket connection CLOSED "; + } + } catch(exception) { + alert('<p>Error' + exception); + } function reset() { socket_di.send("reset\n"); @@ -410,7 +527,7 @@ function ot_open() { try { socket_ot.onopen = function() { document.getElementById("ot_statustd").style.backgroundColor = "#40ff40"; - document.getElementById("ot_status").innerHTML = " <b>websocket connection opened</b><br>" + socket_di.extensions; + document.getElementById("ot_status").innerHTML = " <b>websocket connection opened</b><br>" + san(socket_di.extensions); document.getElementById("ot_open_btn").disabled = true; document.getElementById("ot_close_btn").disabled = false; document.getElementById("ot_req_close_btn").disabled = false; @@ -460,7 +577,9 @@ function ot_req_close() { try { socket_lm.onopen = function() { document.getElementById("wslm_statustd").style.backgroundColor = "#40ff40"; - document.getElementById("wslm_status").innerHTML = " <b>websocket connection opened</b><br>" + socket_di.extensions; + document.getElementById("wslm_status").innerHTML = + " <b>websocket connection opened</b><br>" + + san(socket_di.extensions); } socket_lm.onmessage =function got_packet(msg) {