diff --git a/lib/core-net/close.c b/lib/core-net/close.c index cbc6bcac7..4d627aef4 100644 --- a/lib/core-net/close.c +++ b/lib/core-net/close.c @@ -306,7 +306,8 @@ __lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason, lws_cgi_remove_and_kill(wsi->parent); /* end the binding between us and master */ - wsi->parent->http.cgi->stdwsi[(int)wsi->cgi_channel] = + if (wsi->parent->http.cgi) + wsi->parent->http.cgi->stdwsi[(int)wsi->cgi_channel] = NULL; } wsi->socket_is_permanently_unusable = 1; diff --git a/lib/core-net/dummy-callback.c b/lib/core-net/dummy-callback.c index b583578e4..37d884795 100644 --- a/lib/core-net/dummy-callback.c +++ b/lib/core-net/dummy-callback.c @@ -661,7 +661,7 @@ lws_callback_http_dummy(struct lws *wsi, enum lws_callback_reasons reason, lwsl_debug("LWS_CALLBACK_CGI_TERMINATED: %d %" PRIu64 "\n", wsi->http.cgi->explicitly_chunked, (uint64_t)wsi->http.cgi->content_length); - if (!wsi->http.cgi->explicitly_chunked && + if (!(wsi->http.cgi->explicitly_chunked && wsi->http2_substream) && !wsi->http.cgi->content_length) { /* send terminating chunk */ lwsl_debug("LWS_CALLBACK_CGI_TERMINATED: ending\n"); @@ -670,6 +670,10 @@ lws_callback_http_dummy(struct lws *wsi, enum lws_callback_reasons reason, lws_set_timeout(wsi, PENDING_TIMEOUT_CGI, 3); break; } + if (wsi->http2_substream && !wsi->cgi_stdout_zero_length) + lws_write(wsi, (unsigned char *)buf + LWS_PRE, 0, + LWS_WRITE_HTTP_FINAL); + if (lws_http_transaction_completed(wsi)) return -1; return 0; diff --git a/lib/roles/cgi/cgi-server.c b/lib/roles/cgi/cgi-server.c index 0940d2f9c..eac732245 100644 --- a/lib/roles/cgi/cgi-server.c +++ b/lib/roles/cgi/cgi-server.c @@ -920,7 +920,7 @@ agin: /* payload processing */ m = !wsi->http.cgi->implied_chunked && !wsi->http2_substream && - !wsi->http.cgi->explicitly_chunked && + // !wsi->http.cgi->explicitly_chunked && !wsi->http.cgi->content_length; n = lws_get_socket_fd(wsi->http.cgi->stdwsi[LWS_STDOUT]); if (n < 0) @@ -972,21 +972,20 @@ agin: wsi->http.cgi->content_length_seen += n; } else { - if (m) { - uint8_t term[LWS_PRE + 6]; + if (!wsi->http2_substream && m) { + uint8_t term[LWS_PRE + 6]; - memcpy(term + LWS_PRE, (uint8_t *)"0\x0d\x0a\x0d\x0a", 5); + lwsl_notice("%s: sent trailer\n", __func__); + memcpy(term + LWS_PRE, (uint8_t *)"0\x0d\x0a\x0d\x0a", 5); - if (lws_write(wsi, term + LWS_PRE, 5, - LWS_WRITE_HTTP_FINAL) != 5) - return -1; - - wsi->http.cgi->cgi_transaction_over = 1; - - return 0; - } + if (lws_write(wsi, term + LWS_PRE, 5, + LWS_WRITE_HTTP_FINAL) != 5) + return -1; + wsi->http.cgi->cgi_transaction_over = 1; + return 0; + } if (wsi->cgi_stdout_zero_length) { lwsl_debug("%s: stdout is POLLHUP'd\n", __func__); @@ -1064,12 +1063,14 @@ handled: args.stdwsi = &wsi->http.cgi->stdwsi[0]; if (wsi->http.cgi->pid != -1) { + int m = wsi->http.cgi->being_closed; n = user_callback_handle_rxflow(wsi->protocol->callback, wsi, LWS_CALLBACK_CGI_TERMINATED, wsi->user_space, (void *)&args, wsi->http.cgi->pid); - wsi->http.cgi->pid = -1; - if (n && !wsi->http.cgi->being_closed) + if (wsi->http.cgi) + wsi->http.cgi->pid = -1; + if (n && !m) lws_close_free_wsi(wsi, 0, "lws_cgi_kill"); } diff --git a/lib/roles/http/private-lib-roles-http.h b/lib/roles/http/private-lib-roles-http.h index 5dfd3e842..1b97d1b1d 100644 --- a/lib/roles/http/private-lib-roles-http.h +++ b/lib/roles/http/private-lib-roles-http.h @@ -271,6 +271,7 @@ struct _lws_http_mode_related { unsigned int content_length_explicitly_zero:1; unsigned int did_stream_close:1; unsigned int multipart:1; + unsigned int cgi_transaction_complete:1; unsigned int multipart_issue_boundary:1; }; diff --git a/lib/roles/http/server/server.c b/lib/roles/http/server/server.c index b54fb2138..1cde7ac55 100644 --- a/lib/roles/http/server/server.c +++ b/lib/roles/http/server/server.c @@ -2153,10 +2153,13 @@ bail_nuke_ah: } #endif -LWS_VISIBLE int LWS_WARN_UNUSED_RESULT +int LWS_WARN_UNUSED_RESULT lws_http_transaction_completed(struct lws *wsi) { - int n = NO_PENDING_TIMEOUT; + int n; + + if (wsi->http.cgi_transaction_complete) + return 0; if (lws_has_buffered_out(wsi) #if defined(LWS_WITH_HTTP_STREAM_COMPRESSION) @@ -2208,7 +2211,11 @@ lws_http_transaction_completed(struct lws *wsi) #endif lws_access_log(wsi); - if (!wsi->hdr_parsing_completed) { + if (!wsi->hdr_parsing_completed +#if defined(LWS_WITH_CGI) + && !wsi->http.cgi +#endif + ) { char peer[64]; #if !defined(LWS_PLAT_OPTEE) @@ -2222,6 +2229,26 @@ lws_http_transaction_completed(struct lws *wsi) return 0; } +#if defined(LWS_WITH_CGI) + if (wsi->http.cgi) { + lwsl_debug("%s: cleaning cgi\n", __func__); + wsi->http.cgi_transaction_complete = 1; + lws_cgi_remove_and_kill(wsi); + for (n = 0; n < 3; n++) { + if (wsi->http.cgi->pipe_fds[n][!!(n == 0)] == 0) + lwsl_err("ZERO FD IN CGI CLOSE"); + + if (wsi->http.cgi->pipe_fds[n][!!(n == 0)] >= 0) { + close(wsi->http.cgi->pipe_fds[n][!!(n == 0)]); + wsi->http.cgi->pipe_fds[n][!!(n == 0)] = LWS_SOCK_INVALID; + } + } + + lws_free_set_NULL(wsi->http.cgi); + wsi->http.cgi_transaction_complete = 0; + } +#endif + /* if we can't go back to accept new headers, drop the connection */ if (wsi->http2_substream) return 1; @@ -2259,6 +2286,7 @@ lws_http_transaction_completed(struct lws *wsi) lws_vfs_file_close(&wsi->http.fop_fd); #endif + n = NO_PENDING_TIMEOUT; if (wsi->vhost->keepalive_timeout) n = PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE; lws_set_timeout(wsi, n, wsi->vhost->keepalive_timeout); diff --git a/minimal-examples/http-server/minimal-http-server-cgi/CMakeLists.txt b/minimal-examples/http-server/minimal-http-server-cgi/CMakeLists.txt new file mode 100644 index 000000000..690f1b556 --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-cgi/CMakeLists.txt @@ -0,0 +1,80 @@ +cmake_minimum_required(VERSION 2.8) +include(CheckCSourceCompiles) + +set(SAMP lws-minimal-http-server-cgi) +set(SRCS minimal-http-server.c) + +# If we are being built as part of lws, confirm current build config supports +# reqconfig, else skip building ourselves. +# +# If we are being built externally, confirm installed lws was configured to +# support reqconfig, else error out with a helpful message about the problem. +# +MACRO(require_lws_config reqconfig _val result) + + if (DEFINED ${reqconfig}) + if (${reqconfig}) + set (rq 1) + else() + set (rq 0) + endif() + else() + set(rq 0) + endif() + + if (${_val} EQUAL ${rq}) + set(SAME 1) + else() + set(SAME 0) + endif() + + if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME}) + if (${_val}) + message("${SAMP}: skipping as lws being built without ${reqconfig}") + else() + message("${SAMP}: skipping as lws built with ${reqconfig}") + endif() + set(${result} 0) + else() + if (LWS_WITH_MINIMAL_EXAMPLES) + set(MET ${SAME}) + else() + CHECK_C_SOURCE_COMPILES("#include \nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig}) + if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig}) + set(HAS_${reqconfig} 0) + else() + set(HAS_${reqconfig} 1) + endif() + if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val})) + set(MET 1) + else() + set(MET 0) + endif() + endif() + if (NOT MET) + if (${_val}) + message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}") + else() + message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project") + endif() + endif() + + endif() +ENDMACRO() + + +set(requirements 1) +require_lws_config(LWS_ROLE_H1 1 requirements) +require_lws_config(LWS_WITH_CGI 1 requirements) +require_lws_config(LWS_WITHOUT_SERVER 0 requirements) + +if (requirements) + add_executable(${SAMP} ${SRCS}) + + if (websockets_shared) + target_link_libraries(${SAMP} websockets_shared) + add_dependencies(${SAMP} websockets_shared) + else() + target_link_libraries(${SAMP} websockets) + endif() +endif() diff --git a/minimal-examples/http-server/minimal-http-server-cgi/README.md b/minimal-examples/http-server/minimal-http-server-cgi/README.md new file mode 100644 index 000000000..9f3143a7d --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-cgi/README.md @@ -0,0 +1,28 @@ +# lws minimal http server-cgi + +## build + +``` + $ cmake . && make +``` + +## usage + +This example runs a script ./my-cgi-script.sh when you vist / + +The script dumps some information from /proc on stdout, which +is proxied back to the browser, script output on stderr is +printed in the console. + +It's able to serve the script output over h1 using chunked encoding, +and over h2 having stripped the chunked encoding from the script +output. + +``` + $ ./lws-minimal-http-server-cgi +[2019/11/18 16:31:29:5481] U: LWS minimal http server | visit http://localhost:7681 +[2019/11/18 16:31:40:2176] N: CGI-stderr: lwstest script stderr: REQUEST_METHOD was GET +``` + +Visit http://localhost:7681 + diff --git a/minimal-examples/http-server/minimal-http-server-cgi/minimal-http-server.c b/minimal-examples/http-server/minimal-http-server-cgi/minimal-http-server.c new file mode 100644 index 000000000..c67e8ed22 --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-cgi/minimal-http-server.c @@ -0,0 +1,103 @@ +/* + * lws-minimal-http-server-cgi + * + * Written in 2010-2019 by Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + * + * This demonstrates the most minimal http server you can make with lws. + * + * To keep it simple, it serves stuff from the subdirectory + * "./mount-origin" of the directory it was started in. + * You can change that by changing mount.origin below. + */ + +#include +#include +#include + +static int interrupted; +static char cgi_script_fullpath[256]; + +static const struct lws_http_mount mount = { + /* .mount_next */ NULL, /* linked-list "next" */ + /* .mountpoint */ "/", /* mountpoint URL */ + /* .origin */ cgi_script_fullpath, /* cgi script */ + /* .def */ "/", /* default filename */ + /* .protocol */ NULL, + /* .cgienv */ NULL, + /* .extra_mimetypes */ NULL, + /* .interpret */ NULL, + /* .cgi_timeout */ 0, + /* .cache_max_age */ 0, + /* .auth_mask */ 0, + /* .cache_reusable */ 0, + /* .cache_revalidate */ 0, + /* .cache_intermediaries */ 0, + /* .origin_protocol */ LWSMPRO_CGI, /* files in a dir */ + /* .mountpoint_len */ 1, /* char count */ + /* .basic_auth_login_file */ NULL, +}; + +void sigint_handler(int sig) +{ + interrupted = 1; +} + +int main(int argc, const char **argv) +{ + struct lws_context_creation_info info; + struct lws_context *context; + const char *p; + int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE + /* for LLL_ verbosity above NOTICE to be built into lws, + * lws must have been configured and built with + * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ + /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ + /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ + /* | LLL_DEBUG */; + + signal(SIGINT, sigint_handler); + + if ((p = lws_cmdline_option(argc, argv, "-d"))) + logs = atoi(p); + + lws_set_log_level(logs, NULL); + lwsl_user("LWS minimal http server | visit http://localhost:7681\n"); + + { + char cwd[128]; + cwd[0] = '\0'; + getcwd(cwd, sizeof(cwd)); + + lws_snprintf(cgi_script_fullpath, sizeof(cgi_script_fullpath), + "%s/my-cgi-script.sh", cwd); + } + + memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + info.port = 7681; + info.mounts = &mount; + info.error_document_404 = "/404.html"; + info.options = + LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE; + + if (lws_cmdline_option(argc, argv, "-s")) { + info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + info.ssl_cert_filepath = "localhost-100y.cert"; + info.ssl_private_key_filepath = "localhost-100y.key"; + } + + context = lws_create_context(&info); + if (!context) { + lwsl_err("lws init failed\n"); + return 1; + } + + while (n >= 0 && !interrupted) + n = lws_service(context, 1000); + + lws_context_destroy(context); + + return 0; +} diff --git a/minimal-examples/http-server/minimal-http-server-cgi/my-cgi-script.sh b/minimal-examples/http-server/minimal-http-server-cgi/my-cgi-script.sh new file mode 100755 index 000000000..335787022 --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-cgi/my-cgi-script.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +echo -e -n "content-type: text/html\x0d\x0a" +echo -e -n "transfer-encoding: chunked\x0d\x0a" +echo -e -n "\x0d\x0a" + +echo "" +echo "

lwstest script stdout

" +>&2 echo -n "lwstest script stderr: REQUEST_METHOD was $REQUEST_METHOD" + +echo "

REQUEST_METHOD=$REQUEST_METHOD

" + +if [ "$REQUEST_METHOD" = "POST" ] ; then + >&2 echo "lwstest script stderr: doing read" + echo "CONTENT_LENGTH=$CONTENT_LENGTH" + read -n $CONTENT_LENGTH line + >&2 echo "lwstest script stderr: done read" + + echo "read=\"$line\"" +else + echo "" + echo "" + cat /proc/meminfo | while read line ; do + A=`echo "$line" | cut -d: -f1` + B=`echo "$line" | tr -s ' ' | cut -d' ' -f2-` + echo -e "" + echo -e "" + done + echo "
/proc/meminfo
$A$B
" +fi + +echo "
done" +echo "" + +exit 0 +