diff --git a/CMakeLists.txt b/CMakeLists.txt index 89372d9a5..b44449f13 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -250,7 +250,7 @@ set(LWS_OPENSSL_LIBRARIES CACHE PATH "Path to the OpenSSL library") set(LWS_OPENSSL_INCLUDE_DIRS CACHE PATH "Path to the OpenSSL include directory") set(LWS_WOLFSSL_LIBRARIES CACHE PATH "Path to the wolfSSL library") set(LWS_WOLFSSL_INCLUDE_DIRS CACHE PATH "Path to the wolfSSL include directory") -set( CACHE PATH "Path to the libev library") +set(LWS_LIBEV_LIBRARIES CACHE PATH "Path to the libev library") set(LWS_LIBEV_INCLUDE_DIRS CACHE PATH "Path to the libev include directory") set(LWS_LIBUV_LIBRARIES CACHE PATH "Path to the libuv library") set(LWS_LIBUV_INCLUDE_DIRS CACHE PATH "Path to the libuv include directory") @@ -610,22 +610,38 @@ set(HDR_PUBLIC ) set(SOURCES - lib/base64-decode.c + lib/misc/base64-decode.c lib/handshake.c lib/libwebsockets.c lib/service.c lib/pollfd.c lib/output.c - lib/parsers.c + lib/server/parsers.c lib/context.c lib/alloc.c - lib/header.c) + lib/header.c + lib/misc/lws-ring.c) + +if (LWS_WITH_CGI) + list(APPEND SOURCES + lib/server/cgi.c) +endif() + +if (LWS_WITH_ACCESS_LOG) + list(APPEND SOURCES + lib/server/access-log.c) +endif() + +if (LWS_WITH_PEER_LIMITS) + list(APPEND SOURCES + lib/server/peer-limits.c) +endif() if (NOT LWS_WITHOUT_CLIENT) list(APPEND SOURCES - lib/client.c - lib/client-handshake.c - lib/client-parser.c) + lib/client/client.c + lib/client/client-handshake.c + lib/client/client-parser.c) endif() if (LWS_WITH_MBEDTLS) @@ -673,51 +689,51 @@ endif() if (LWS_WITH_SSL) list(APPEND SOURCES lib/ssl.c - lib/lws-genhash.c) + lib/misc/lws-genhash.c) if (NOT LWS_WITHOUT_SERVER) list(APPEND SOURCES - lib/ssl-server.c) + lib/server/ssl-server.c) endif() if (NOT LWS_WITHOUT_CLIENT) list(APPEND SOURCES - lib/ssl-client.c) + lib/client/ssl-client.c) endif() endif() if (NOT LWS_WITHOUT_BUILTIN_SHA1) list(APPEND SOURCES - lib/sha-1.c) + lib/misc/sha-1.c) endif() if (LWS_WITH_HTTP2) list(APPEND SOURCES - lib/http2.c - lib/hpack.c - lib/ssl-http2.c) + lib/http2/http2.c + lib/http2/hpack.c + lib/http2/ssl-http2.c) endif() # select the active platform files if (WIN32) list(APPEND SOURCES - lib/lws-plat-win.c) + lib/plat/lws-plat-win.c) else() if (LWS_WITH_ESP8266) list(APPEND SOURCES - lib/lws-plat-esp8266.c) + lib/plat/lws-plat-esp8266.c) else() if (LWS_PLAT_OPTEE) list(APPEND SOURCES - lib/lws-plat-optee.c) + lib/plat/lws-plat-optee.c) else() if (LWS_WITH_ESP32) list(APPEND SOURCES - lib/lws-plat-esp32.c - lib/romfs.c) + lib/plat/lws-plat-esp32.c + lib/misc/romfs.c) else() list(APPEND SOURCES - lib/lws-plat-unix.c) + lib/plat/lws-plat-unix.c) endif() endif() endif() @@ -725,64 +741,65 @@ endif() if (NOT LWS_WITHOUT_SERVER) list(APPEND SOURCES - lib/server.c - lib/server-handshake.c) + lib/server/server.c + lib/server/lws-spa.c + lib/server/server-handshake.c) endif() if (NOT LWS_WITHOUT_EXTENSIONS) list(APPEND HDR_PRIVATE - lib/extension-permessage-deflate.h) + lib/ext/extension-permessage-deflate.h) list(APPEND SOURCES - lib/extension.c - lib/extension-permessage-deflate.c) + lib/ext/extension.c + lib/ext/extension-permessage-deflate.c) endif() if (LWS_WITH_HTTP_PROXY) list(APPEND SOURCES - lib/rewrite.c) + lib/server/rewrite.c) endif() if (LWS_WITH_LIBEV) list(APPEND SOURCES - lib/libev.c) + lib/event-libs/libev.c) endif() if (LWS_WITH_LIBUV) list(APPEND SOURCES - lib/libuv.c) + lib/event-libs/libuv.c) endif() if (LWS_WITH_LIBEVENT) list(APPEND SOURCES - lib/libevent.c) + lib/event-libs/libevent.c) endif() if (LWS_WITH_LEJP) list(APPEND SOURCES - lib/lejp.c) + lib/misc/lejp.c) list(APPEND HDR_PUBLIC - lib/lejp.h) + lib/misc/lejp.h) endif() if (LWS_WITH_LEJP_CONF) list(APPEND SOURCES - "lib/lejp-conf.c" + "lib/server/lejp-conf.c" ) endif() if (LWS_WITH_SMTP) list(APPEND SOURCES - lib/smtp.c) + lib/misc/smtp.c) endif() if (LWS_WITH_RANGES) list(APPEND SOURCES - lib/ranges.c) + lib/server/ranges.c) endif() if (LWS_WITH_ZIP_FOPS) if (LWS_WITH_ZLIB) list(APPEND SOURCES - lib/fops-zip.c) + lib/server/fops-zip.c) else() message(FATAL_ERROR "Pre-zipped file support (LWS_WITH_ZIP_FOPS) requires ZLIB (LWS_WITH_ZLIB)") endif() @@ -807,14 +824,14 @@ else() # Unix. if (NOT LWS_WITHOUT_DAEMONIZE) list(APPEND SOURCES - lib/daemonize.c) + lib/server/daemonize.c) endif() endif() if (UNIX) if (NOT LWS_HAVE_GETIFADDRS) - list(APPEND HDR_PRIVATE lib/getifaddrs.h) - list(APPEND SOURCES lib/getifaddrs.c) + list(APPEND HDR_PRIVATE lib/misc/getifaddrs.h) + list(APPEND SOURCES lib/misc/getifaddrs.c) endif() endif() diff --git a/READMEs/README.coding.md b/READMEs/README.coding.md index 7e934ba52..18bdaf82e 100644 --- a/READMEs/README.coding.md +++ b/READMEs/README.coding.md @@ -4,20 +4,81 @@ Notes about coding with lws @section era Old lws and lws v2.0 Originally lws only supported the "manual" method of handling everything in the -user callback found in test-server.c. +user callback found in test-server.c / test-server-http.c. -Since v2.0, the need for most or all of this manual boilerplate has been eliminated: -the protocols[0] http stuff is provided by a lib export `lws_callback_http_dummy()`. -You can serve parts of your filesystem at part of the URL space using mounts. +Since v2.0, the need for most or all of this manual boilerplate has been +eliminated: the protocols[0] http stuff is provided by a generic lib export +`lws_callback_http_dummy()`. You can serve parts of your filesystem at part of +the URL space using mounts, the dummy http callback will do the right thing. It's much preferred to use the "automated" v2.0 type scheme, because it's less code and it's easier to support. You can see an example of the new way in test-server-v2.0.c. -If you just need generic serving capability, consider not writing any server code -and instead use lwsws and writing your user code in a standalone plugin. The -server is configured for mounts etc using JSON, see README.lwsws.md. +If you just need generic serving capability, without the need to integrate lws +to some other app, consider not writing any server code at all, and instead use +the generic server `lwsws`, and writing your special user code in a standalone +"plugin". The server is configured for mounts etc using JSON, see +./READMEs/README.lwsws.md. + +Although the "plugins" are dynamically loaded if you use lwsws or lws built +with libuv, actually they may perfectly well be statically included if that +suits your situation better, eg, ESP32 test server, where the platform does +not support processes or dynamic loading, just #includes the plugins +one after the other and gets the same benefit from the same code. + +Isolating and collating the protocol code in one place also makes it very easy +to maintain and understand. + +So it if highly recommended you put your protocol-specific code into the +form of a "plugin" at the source level, even if you have no immediate plan to +use it dynamically-loaded. + +@section writeable Only send data when socket writeable + +You should only send data on a websocket connection from the user callback +`LWS_CALLBACK_SERVER_WRITEABLE` (or `LWS_CALLBACK_CLIENT_WRITEABLE` for +clients). + +If you want to send something, do NOT just send it but request a callback +when the socket is writeable using + + - `lws_callback_on_writable(context, wsi)` for a specific `wsi`, or + + - `lws_callback_on_writable_all_protocol(protocol)` for all connections +using that protocol to get a callback when next writeable. + +Usually you will get called back immediately next time around the service +loop, but if your peer is slow or temporarily inactive the callback will be +delayed accordingly. Generating what to write and sending it should be done +in the ...WRITEABLE callback. + +See the test server code for an example of how to do this. + +Otherwise evolved libs like libuv get this wrong, they will allow you to "send" +anything you want but it only uses up your local memory (and costs you +memcpys) until the socket can actually accept it. It is much better to regulate +your send action by the downstream peer readiness to take new data in the first +place, avoiding all the wasted buffering. + +Libwebsockets' concept is that the downstream peer is truly the boss, if he, +or our connection to him, cannot handle anything new, we should not generate +anything new for him. This is how unix shell piping works, you may have +`cat a.txt | grep xyz > remote", but actually that does not cat anything from +a.txt while remote cannot accept anything new. + +@section otherwr Do not rely on only your own WRITEABLE requests appearing + +Libwebsockets may generate additional `LWS_CALLBACK_CLIENT_WRITEABLE` events +if it met network conditions where it had to buffer your send data internally. + +So your code for `LWS_CALLBACK_CLIENT_WRITEABLE` needs to own the decision +about what to send, it can't assume that just because the writeable callback +came it really is time to send something. + +It's quite possible you get an 'extra' writeable callback at any time and +just need to `return 0` and wait for the expected callback later. @section dae Daemonization @@ -28,8 +89,7 @@ headless background process and exit the starting process. Notice stdout, stderr, stdin are all redirected to /dev/null to enforce your daemon is headless, so you'll need to sort out alternative logging, by, eg, -syslog. - +syslog via `lws_set_log_level(..., lwsl_emit_syslog)`. @section conns Maximum number of connections @@ -81,7 +141,9 @@ repeat that in other words: There is another network-programming truism that surprises some people which is if the sink for the data cannot accept more: -***YOU MUST PERFORM RX FLOW CONTROL*** +***YOU MUST PERFORM RX FLOW CONTROL*** to stop taking new input. TCP will make +this situation known to the upstream sender by making it impossible for him to +send anything more on the connection until we start accepting things again. See the mirror protocol implementations for example code. @@ -99,42 +161,6 @@ you might simultaneously create more than one context from different threads. SSL_library_init() is called from the context create api and it also is not reentrant. So at least create the contexts sequentially. - -@section writeable Only send data when socket writeable - -You should only send data on a websocket connection from the user callback -`LWS_CALLBACK_SERVER_WRITEABLE` (or `LWS_CALLBACK_CLIENT_WRITEABLE` for -clients). - -If you want to send something, do not just send it but request a callback -when the socket is writeable using - - - `lws_callback_on_writable(context, wsi)` for a specific `wsi`, or - - - `lws_callback_on_writable_all_protocol(protocol)` for all connections -using that protocol to get a callback when next writeable. - -Usually you will get called back immediately next time around the service -loop, but if your peer is slow or temporarily inactive the callback will be -delayed accordingly. Generating what to write and sending it should be done -in the ...WRITEABLE callback. - -See the test server code for an example of how to do this. - - -@section otherwr Do not rely on only your own WRITEABLE requests appearing - -Libwebsockets may generate additional `LWS_CALLBACK_CLIENT_WRITEABLE` events -if it met network conditions where it had to buffer your send data internally. - -So your code for `LWS_CALLBACK_CLIENT_WRITEABLE` needs to own the decision -about what to send, it can't assume that just because the writeable callback -came it really is time to send something. - -It's quite possible you get an 'extra' writeable callback at any time and -just need to `return 0` and wait for the expected callback later. - - @section closing Closing connections from the user side When you want to close a connection, you do it by returning `-1` from a diff --git a/lib/.gitignore b/lib/.gitignore deleted file mode 100644 index dbed3ffb7..000000000 --- a/lib/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -#Ignore build files -Makefile -*.o -*.lo -*.la -.libs -.deps - diff --git a/lib/client-handshake.c b/lib/client/client-handshake.c similarity index 100% rename from lib/client-handshake.c rename to lib/client/client-handshake.c diff --git a/lib/client-parser.c b/lib/client/client-parser.c similarity index 100% rename from lib/client-parser.c rename to lib/client/client-parser.c diff --git a/lib/client.c b/lib/client/client.c old mode 100755 new mode 100644 similarity index 100% rename from lib/client.c rename to lib/client/client.c diff --git a/lib/ssl-client.c b/lib/client/ssl-client.c similarity index 100% rename from lib/ssl-client.c rename to lib/client/ssl-client.c diff --git a/lib/context.c b/lib/context.c index ae96ddfea..d1344595c 100644 --- a/lib/context.c +++ b/lib/context.c @@ -84,7 +84,7 @@ const struct http2_settings lws_h2_defaults = { { const struct http2_settings lws_h2_stock_settings = { { 1, - /* H2SET_HEADER_TABLE_SIZE */ 512, + /* H2SET_HEADER_TABLE_SIZE */ 4096, /* *** This controls how many entries in the dynamic table *** * Allows the sender to inform the remote endpoint of the maximum * size of the header compression table used to decode header @@ -92,6 +92,8 @@ const struct http2_settings lws_h2_stock_settings = { { * less than this value by using signaling specific to the header * compression format inside a header block (see [COMPRESSION]). * The initial value is 4,096 octets. + * + * Can't pass h2spec with less than 4096 here... */ /* H2SET_ENABLE_PUSH */ 1, /* H2SET_MAX_CONCURRENT_STREAMS */ 24, diff --git a/lib/libev.c b/lib/event-libs/libev.c similarity index 100% rename from lib/libev.c rename to lib/event-libs/libev.c diff --git a/lib/libevent.c b/lib/event-libs/libevent.c similarity index 100% rename from lib/libevent.c rename to lib/event-libs/libevent.c diff --git a/lib/libuv.c b/lib/event-libs/libuv.c similarity index 100% rename from lib/libuv.c rename to lib/event-libs/libuv.c diff --git a/lib/extension-permessage-deflate.c b/lib/ext/extension-permessage-deflate.c similarity index 100% rename from lib/extension-permessage-deflate.c rename to lib/ext/extension-permessage-deflate.c diff --git a/lib/extension-permessage-deflate.h b/lib/ext/extension-permessage-deflate.h similarity index 100% rename from lib/extension-permessage-deflate.h rename to lib/ext/extension-permessage-deflate.h diff --git a/lib/extension.c b/lib/ext/extension.c similarity index 100% rename from lib/extension.c rename to lib/ext/extension.c diff --git a/lib/hpack.c b/lib/http2/hpack.c similarity index 96% rename from lib/hpack.c rename to lib/http2/hpack.c index f7d916f93..eb1e97cf2 100644 --- a/lib/hpack.c +++ b/lib/http2/hpack.c @@ -384,7 +384,8 @@ lws_token_from_index(struct lws *wsi, int index, const char **arg, int *len, if (index < 0) index += dyn->num_entries; - lwsl_header("%s: dyn index %d, tok %d\n", __func__, index, dyn->entries[index].lws_hdr_idx); + lwsl_header("%s: dyn index %d, tok %d\n", __func__, index, + dyn->entries[index].lws_hdr_idx); if (arg && len) { *arg = dyn->entries[index].value; @@ -410,9 +411,11 @@ lws_h2_dynamic_table_dump(struct lws *wsi) return 1; dyn = &nwsi->u.h2.h2n->hpack_dyn_table; - lwsl_header("Dump dyn table for nwsi %p (%d / %d members, pos = %d, start index %d, virt used %d / %d)\n", nwsi, - dyn->used_entries, dyn->num_entries, dyn->pos, (uint32_t)ARRAY_SIZE(static_token), - dyn->virtual_payload_usage, dyn->virtual_payload_max); + lwsl_header("Dump dyn table for nwsi %p (%d / %d members, pos = %d, " + "start index %d, virt used %d / %d)\n", nwsi, + dyn->used_entries, dyn->num_entries, dyn->pos, + (uint32_t)ARRAY_SIZE(static_token), + dyn->virtual_payload_usage, dyn->virtual_payload_max); for (n = 0; n < dyn->used_entries; n++) { m = (dyn->pos - 1 - n) % dyn->num_entries; @@ -424,8 +427,9 @@ lws_h2_dynamic_table_dump(struct lws *wsi) else p = "(ignored)"; lwsl_header(" %3d: tok %s: (len %d) val '%s'\n", - (int)(n + ARRAY_SIZE(static_token)), p, dyn->entries[m].hdr_len, - dyn->entries[m].value ? dyn->entries[m].value : "null"); + (int)(n + ARRAY_SIZE(static_token)), p, + dyn->entries[m].hdr_len, dyn->entries[m].value ? + dyn->entries[m].value : "null"); } #endif return 0; @@ -509,6 +513,8 @@ lws_dynamic_token_insert(struct lws *wsi, int hdr_len, dyn->entries[new_index].value_len = 0; if (lws_hdr_index != LWS_HPACK_IGNORE_ENTRY) { + if (dyn->entries[new_index].value) + lws_free_set_NULL(dyn->entries[new_index].value); dyn->entries[new_index].value = lws_malloc(len + 1, "hpack dyn"); if (!dyn->entries[new_index].value) return 1; @@ -565,7 +571,9 @@ lws_hpack_dynamic_size(struct lws *wsi, int size) goto bail; dyn = &nwsi->u.h2.h2n->hpack_dyn_table; - lwsl_info("%s: from %d to %d\n", __func__, (int)dyn->num_entries, size); + lwsl_info("%s: from %d to %d, lim %d\n", __func__, + (int)dyn->num_entries, size, + nwsi->u.h2.h2n->set.s[H2SET_HEADER_TABLE_SIZE]); if (size > nwsi->u.h2.h2n->set.s[H2SET_HEADER_TABLE_SIZE]) { lws_h2_goaway(nwsi, H2_ERR_COMPRESSION_ERROR, @@ -615,7 +623,10 @@ lws_hpack_dynamic_size(struct lws *wsi, int size) dyn->entries = dte; dyn->num_entries = size; dyn->used_entries = min; - dyn->pos = min % size; + if (size) + dyn->pos = min % size; + else + dyn->pos = 0; lws_h2_dynamic_table_dump(wsi); @@ -732,7 +743,6 @@ int lws_hpack_interpret(struct lws *wsi, unsigned char c) h2n->ext_count = 0; h2n->hpack_hdr_len = 0; h2n->unknown_header = 0; - h2n->seen_nonpseudoheader = 0; if (c & 0x80) { /* 1.... indexed header field only */ /* just a possibly-extended integer */ @@ -930,7 +940,8 @@ pre_data: } else h2n->hdr_idx = 1; } else { - n = lws_token_from_index(wsi, h2n->hdr_idx, NULL, NULL, NULL); + n = lws_token_from_index(wsi, h2n->hdr_idx, NULL, + NULL, NULL); lwsl_header(" lws_tok_from_idx(%d) says %d\n", h2n->hdr_idx, n); } @@ -1010,9 +1021,11 @@ pre_data: if (h2n->hdr_idx && h2n->hdr_idx != LWS_HPACK_IGNORE_ENTRY) { - if (ah->hdr_token_idx == WSI_TOKEN_HTTP_COLON_PATH) { + if (ah->hdr_token_idx == + WSI_TOKEN_HTTP_COLON_PATH) { - switch (lws_parse_urldecode(wsi, &c1)) { + switch (lws_parse_urldecode( + wsi, &c1)) { case LPUR_CONTINUE: break; case LPUR_SWALLOW: @@ -1020,8 +1033,8 @@ pre_data: case LPUR_EXCESSIVE: case LPUR_FORBID: lws_h2_goaway(nwsi, - H2_ERR_PROTOCOL_ERROR, - "Evil URI"); + H2_ERR_PROTOCOL_ERROR, + "Evil URI"); return 1; default: @@ -1030,11 +1043,11 @@ pre_data: } if (lws_frag_append(wsi, c1)) { lwsl_notice("%s: frag app fail\n", - __func__); + __func__); return 1; } - } else - lwsl_header("ignoring %c\n", c1); + } //else + //lwsl_header("ignoring %c\n", c1); } else { /* * Convert name using existing parser, @@ -1140,7 +1153,8 @@ swallow: wsi->u.hdr.parser_state == WSI_TOKEN_NAME_PART || wsi->u.hdr.parser_state == WSI_TOKEN_SKIPPING) { if (h2n->first_hdr_char == ':') { - lwsl_info("HPKT_LITERAL_HDR_VALUE_INCR: end parser state %d unk hdr %d\n", + lwsl_info("HPKT_LITERAL_HDR_VALUE_INCR:" + " end state %d unk hdr %d\n", wsi->u.hdr.parser_state, h2n->unknown_header); /* unknown pseudoheaders are illegal */ @@ -1180,7 +1194,8 @@ add_it: if (m == 255) m = -1; } else - m = lws_token_from_index(wsi, h2n->hdr_idx, NULL, NULL, NULL); + m = lws_token_from_index(wsi, h2n->hdr_idx, NULL, NULL, + NULL); if (m != -1 && m != LWS_HPACK_IGNORE_ENTRY) lws_dump_header(wsi, m); diff --git a/lib/http2.c b/lib/http2/http2.c similarity index 97% rename from lib/http2.c rename to lib/http2/http2.c index 560f414ba..7ba106022 100644 --- a/lib/http2.c +++ b/lib/http2/http2.c @@ -189,7 +189,8 @@ lws_wsi_server_new(struct lws_vhost *vh, struct lws *parent_wsi, wsi->vhost->conn_stats.h2_subs++; - lwsl_info("%s: %p new ch %p, sid %d, usersp=%p, tx cr %d, peer_credit %d (nwsi tx_cr %d)\n", + lwsl_info("%s: %p new ch %p, sid %d, usersp=%p, tx cr %d, " + "peer_credit %d (nwsi tx_cr %d)\n", __func__, parent_wsi, wsi, sid, wsi->user_space, wsi->u.h2.tx_cr, wsi->u.h2.peer_tx_cr_est, nwsi->u.h2.tx_cr); @@ -274,6 +275,7 @@ lws_h2_goaway(struct lws *wsi, uint32_t err, const char *reason) lwsl_info("%s: %p: ERR 0x%x, '%s'\n", __func__, wsi, err, reason); pps->u.ga.err = err; + pps->u.ga.highest_sid = h2n->highest_sid; strncpy(pps->u.ga.str, reason, sizeof(pps->u.ga.str) - 1); pps->u.ga.str[sizeof(pps->u.ga.str) - 1] = '\0'; lws_pps_schedule(wsi, pps); @@ -334,7 +336,7 @@ lws_h2_settings(struct lws *wsi, struct http2_settings *settings, case H2SET_ENABLE_PUSH: if (b > 1) { lws_h2_goaway(nwsi, H2_ERR_PROTOCOL_ERROR, - "ENABLE_PUSH invalid arg"); + "ENABLE_PUSH invalid arg"); return 1; } break; @@ -343,7 +345,7 @@ lws_h2_settings(struct lws *wsi, struct http2_settings *settings, case H2SET_INITIAL_WINDOW_SIZE: if (b > 0x7fffffff) { lws_h2_goaway(nwsi, H2_ERR_FLOW_CONTROL_ERROR, - "Inital Window beyond max"); + "Inital Window beyond max"); return 1; } /* @@ -621,9 +623,9 @@ int lws_h2_do_pps_send(struct lws *wsi) *p++ = pps->u.ga.err >> 16; *p++ = pps->u.ga.err >> 8; *p++ = pps->u.ga.err; - q = (unsigned char *)h2n->goaway_str; + q = (unsigned char *)pps->u.ga.str; n = 0; - while (*q && n++ < sizeof(h2n->goaway_str)) + while (*q && n++ < sizeof(pps->u.ga.str)) *p++ = *q++; h2n->we_told_goaway = 1; n = lws_h2_frame_write(wsi, LWS_H2_FRAME_TYPE_GOAWAY, 0, @@ -653,13 +655,16 @@ int lws_h2_do_pps_send(struct lws *wsi) break; case LWS_H2_PPS_UPDATE_WINDOW: - lwsl_notice("LWS_H2_PPS_UPDATE_WINDOW: sid %d: add %d\n", pps->u.update_window.sid, pps->u.update_window.credit); + lwsl_notice("LWS_H2_PPS_UPDATE_WINDOW: sid %d: add %d\n", + pps->u.update_window.sid, + pps->u.update_window.credit); *p++ = pps->u.update_window.credit >> 24; *p++ = pps->u.update_window.credit >> 16; *p++ = pps->u.update_window.credit >> 8; *p++ = pps->u.update_window.credit; n = lws_h2_frame_write(wsi, LWS_H2_FRAME_TYPE_WINDOW_UPDATE, - 0, pps->u.update_window.sid, 4, &set[LWS_PRE]); + 0, pps->u.update_window.sid, 4, + &set[LWS_PRE]); if (n != 4) { lwsl_info("send %d %d\n", n, m); goto bail; @@ -802,7 +807,8 @@ lws_h2_parse_frame_header(struct lws *wsi) break; h2n->swsi->u.h2.peer_tx_cr_est -= h2n->length; - lwsl_debug(" peer_tx_cr_est %d\n", h2n->swsi->u.h2.peer_tx_cr_est); + lwsl_debug(" peer_tx_cr_est %d\n", + h2n->swsi->u.h2.peer_tx_cr_est); if (h2n->swsi->u.h2.peer_tx_cr_est < 32768) { h2n->swsi->u.h2.peer_tx_cr_est += 65536; pps = lws_h2_new_pps(LWS_H2_PPS_UPDATE_WINDOW); @@ -819,9 +825,9 @@ lws_h2_parse_frame_header(struct lws *wsi) lws_pps_schedule(wsi, pps); } - if (( + if ( h2n->swsi->u.h2.h2_state == LWS_H2_STATE_HALF_CLOSED_REMOTE || - h2n->swsi->u.h2.h2_state == LWS_H2_STATE_CLOSED)) { + h2n->swsi->u.h2.h2_state == LWS_H2_STATE_CLOSED) { lws_h2_goaway(wsi, H2_ERR_STREAM_CLOSED, "conn closed"); break; } @@ -950,7 +956,8 @@ lws_h2_parse_frame_header(struct lws *wsi) return 1; } - h2n->swsi = lws_wsi_server_new(wsi->vhost, wsi, h2n->sid); + h2n->swsi = lws_wsi_server_new(wsi->vhost, wsi, + h2n->sid); if (!h2n->swsi) { lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR, "OOM"); @@ -986,7 +993,8 @@ lws_h2_parse_frame_header(struct lws *wsi) /* END_STREAM means after servicing this, close the stream */ - h2n->swsi->u.h2.END_STREAM = !!(h2n->flags & LWS_H2_FLAG_END_STREAM); + h2n->swsi->u.h2.END_STREAM = + !!(h2n->flags & LWS_H2_FLAG_END_STREAM); lwsl_info("%s: hdr END_STREAM = %d\n",__func__, h2n->swsi->u.h2.END_STREAM); @@ -1187,7 +1195,9 @@ lws_h2_parse_end_of_frame(struct lws *wsi) lwsl_info(" action start...\n"); n = lws_http_action(h2n->swsi); - lwsl_info(" action result %d (wsi->u.http.rx_content_remain %lld)\n", n, h2n->swsi->u.http.rx_content_remain); + lwsl_info(" action result %d " + "(wsi->u.http.rx_content_remain %lld)\n", + n, h2n->swsi->u.http.rx_content_remain); /* * Commonly we only managed to start a larger transfer that will @@ -1206,7 +1216,8 @@ lws_h2_parse_end_of_frame(struct lws *wsi) break; if (lws_hdr_total_length(h2n->swsi, WSI_TOKEN_HTTP_CONTENT_LENGTH) && - h2n->swsi->u.h2.END_STREAM && h2n->swsi->u.http.rx_content_length && + h2n->swsi->u.h2.END_STREAM && + h2n->swsi->u.http.rx_content_length && h2n->swsi->u.http.rx_content_remain) { lws_h2_rst_stream(h2n->swsi, H2_ERR_PROTOCOL_ERROR, "Not enough rx content"); @@ -1291,7 +1302,8 @@ lws_h2_parse_end_of_frame(struct lws *wsi) case LWS_H2_FRAME_TYPE_GOAWAY: lwsl_info("GOAWAY: last sid %d, error 0x%08X, string '%s'\n", - h2n->goaway_last_sid, h2n->goaway_err, h2n->goaway_str); + h2n->goaway_last_sid, h2n->goaway_err, + h2n->goaway_str); wsi->u.h2.GOING_AWAY = 1; return 1; @@ -1446,17 +1458,11 @@ lws_h2_parser(struct lws *wsi, unsigned char c) } h2n->swsi->state = LWSS_HTTP_BODY; - h2n->inside++; - /* because the HTTP_BODY stuff will handle it */ - //h2n->swsi->u.http.rx_content_remain--; - //lwsl_info("remain %lld, %d / %d", - // (long long)h2n->swsi->u.http.rx_content_remain, - // h2n->inside, h2n->length); if (lws_hdr_total_length(h2n->swsi, WSI_TOKEN_HTTP_CONTENT_LENGTH) && h2n->swsi->u.http.rx_content_length && - h2n->swsi->u.http.rx_content_remain == 1 && /* last byte */ + h2n->swsi->u.http.rx_content_remain == 1 && /* last */ h2n->inside < h2n->length) { /* unread data in frame */ lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR, "More rx than content_length told"); diff --git a/lib/huftable.h b/lib/http2/huftable.h similarity index 100% rename from lib/huftable.h rename to lib/http2/huftable.h diff --git a/lib/minihuf.c b/lib/http2/minihuf.c similarity index 100% rename from lib/minihuf.c rename to lib/http2/minihuf.c diff --git a/lib/ssl-http2.c b/lib/http2/ssl-http2.c similarity index 96% rename from lib/ssl-http2.c rename to lib/http2/ssl-http2.c index 4931d90a4..f01734354 100644 --- a/lib/ssl-http2.c +++ b/lib/http2/ssl-http2.c @@ -52,7 +52,8 @@ #if !defined(LWS_NO_SERVER) #if defined(LWS_OPENSSL_SUPPORT) -#if defined(LWS_WITH_MBEDTLS) || (defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x10002000L) +#if defined(LWS_WITH_MBEDTLS) || (defined(OPENSSL_VERSION_NUMBER) && \ + OPENSSL_VERSION_NUMBER >= 0x10002000L) struct alpn_ctx { unsigned char *data; @@ -79,7 +80,8 @@ alpn_cb(SSL *s, const unsigned char **out, unsigned char *outlen, LWS_VISIBLE void lws_context_init_http2_ssl(struct lws_vhost *vhost) { -#if defined(LWS_WITH_MBEDTLS) || (defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x10002000L) +#if defined(LWS_WITH_MBEDTLS) || (defined(OPENSSL_VERSION_NUMBER) && \ + OPENSSL_VERSION_NUMBER >= 0x10002000L) static struct alpn_ctx protos = { (unsigned char *)"\x02h2" "\x08http/1.1", 6 + 9 }; @@ -94,7 +96,8 @@ lws_context_init_http2_ssl(struct lws_vhost *vhost) int lws_h2_configure_if_upgraded(struct lws *wsi) { -#if defined(LWS_WITH_MBEDTLS) || (defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x10002000L) +#if defined(LWS_WITH_MBEDTLS) || (defined(OPENSSL_VERSION_NUMBER) && \ + OPENSSL_VERSION_NUMBER >= 0x10002000L) struct allocated_headers *ah; const unsigned char *name = NULL; char cstr[10]; diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c old mode 100755 new mode 100644 index 4fe4b2efd..669f4edf8 --- a/lib/libwebsockets.c +++ b/lib/libwebsockets.c @@ -25,11 +25,6 @@ #include #endif -#if defined(WIN32) || defined(_WIN32) -#else -#include -#endif - #ifdef LWS_WITH_IPV6 #if defined(WIN32) || defined(_WIN32) #include @@ -277,33 +272,6 @@ lws_bind_protocol(struct lws *wsi, const struct lws_protocols *p) return 0; } -#ifdef LWS_WITH_CGI -static void -lws_cgi_remove_and_kill(struct lws *wsi) -{ - struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; - struct lws_cgi **pcgi = &pt->cgi_list; - - /* remove us from the cgi list */ - lwsl_debug("%s: remove cgi %p from list\n", __func__, wsi->cgi); - while (*pcgi) { - if (*pcgi == wsi->cgi) { - /* drop us from the pt cgi list */ - *pcgi = (*pcgi)->cgi_list; - break; - } - pcgi = &(*pcgi)->cgi_list; - } - if (wsi->cgi->headers_buf) { - lwsl_debug("close: freed cgi headers\n"); - lws_free_set_NULL(wsi->cgi->headers_buf); - } - /* we have a cgi going, we must kill it */ - wsi->cgi->being_closed = 1; - lws_cgi_kill(wsi); -} -#endif - void lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason) { @@ -1828,6 +1796,56 @@ LWS_VISIBLE int lwsl_visible(int level) return log_level & level; } +LWS_VISIBLE void +lwsl_hexdump_level(int hexdump_level, const void *vbuf, size_t len) +{ + unsigned char *buf = (unsigned char *)vbuf; + unsigned int n, m, start; + char line[80]; + char *p; + + if (!lwsl_visible(hexdump_level)) + return; + + _lws_log(hexdump_level, "\n"); + + for (n = 0; n < len;) { + start = n; + p = line; + + p += sprintf(p, "%04X: ", start); + + for (m = 0; m < 16 && n < len; m++) + p += sprintf(p, "%02X ", buf[n++]); + while (m++ < 16) + p += sprintf(p, " "); + + p += sprintf(p, " "); + + for (m = 0; m < 16 && (start + m) < len; m++) { + if (buf[start + m] >= ' ' && buf[start + m] < 127) + *p++ = buf[start + m]; + else + *p++ = '.'; + } + while (m++ < 16) + *p++ = ' '; + + *p++ = '\n'; + *p = '\0'; + _lws_log(hexdump_level, "%s", line); + (void)line; + } + + _lws_log(hexdump_level, "\n"); +} + +LWS_VISIBLE void +lwsl_hexdump(const void *vbuf, size_t len) +{ + lwsl_hexdump_level(LLL_DEBUG, vbuf, len); +} + LWS_VISIBLE int lws_is_ssl(struct lws *wsi) { @@ -2560,1020 +2578,7 @@ lws_is_cgi(struct lws *wsi) { #endif } -#ifdef LWS_WITH_CGI -static int -urlencode(const char *in, int inlen, char *out, int outlen) -{ - char *start = out, *end = out + outlen; - - while (inlen-- && out < end - 4) { - if ((*in >= 'A' && *in <= 'Z') || - (*in >= 'a' && *in <= 'z') || - (*in >= '0' && *in <= '9') || - *in == '-' || - *in == '_' || - *in == '.' || - *in == '~') { - *out++ = *in++; - continue; - } - if (*in == ' ') { - *out++ = '+'; - in++; - continue; - } - *out++ = '%'; - *out++ = hex[(*in) >> 4]; - *out++ = hex[(*in++) & 15]; - } - *out = '\0'; - - if (out >= end - 4) - return -1; - - return out - start; -} - -static struct lws * -lws_create_basic_wsi(struct lws_context *context, int tsi) -{ - struct lws *new_wsi; - - if (!context->vhost_list) - return NULL; - - if ((unsigned int)context->pt[tsi].fds_count == - context->fd_limit_per_thread - 1) { - lwsl_err("no space for new conn\n"); - return NULL; - } - - new_wsi = lws_zalloc(sizeof(struct lws), "new wsi"); - if (new_wsi == NULL) { - lwsl_err("Out of memory for new connection\n"); - return NULL; - } - - new_wsi->tsi = tsi; - new_wsi->context = context; - new_wsi->pending_timeout = NO_PENDING_TIMEOUT; - new_wsi->rxflow_change_to = LWS_RXFLOW_ALLOW; - - /* initialize the instance struct */ - - new_wsi->state = LWSS_CGI; - new_wsi->mode = LWSCM_CGI; - new_wsi->hdr_parsing_completed = 0; - new_wsi->position_in_fds_table = -1; - - /* - * these can only be set once the protocol is known - * we set an unestablished connection's protocol pointer - * to the start of the defauly vhost supported list, so it can look - * for matching ones during the handshake - */ - new_wsi->protocol = context->vhost_list->protocols; - new_wsi->user_space = NULL; - new_wsi->ietf_spec_revision = 0; - new_wsi->desc.sockfd = LWS_SOCK_INVALID; - context->count_wsi_allocated++; - - return new_wsi; -} - -LWS_VISIBLE LWS_EXTERN int -lws_cgi(struct lws *wsi, const char * const *exec_array, int script_uri_path_len, - int timeout_secs, const struct lws_protocol_vhost_options *mp_cgienv) -{ - struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; - char *env_array[30], cgi_path[400], e[1024], *p = e, - *end = p + sizeof(e) - 1, tok[256], *t; - struct lws_cgi *cgi; - int n, m, i, uritok = -1; - - /* - * give the master wsi a cgi struct - */ - - wsi->cgi = lws_zalloc(sizeof(*wsi->cgi), "new cgi"); - if (!wsi->cgi) { - lwsl_err("%s: OOM\n", __func__); - return -1; - } - - wsi->cgi->response_code = HTTP_STATUS_OK; - - cgi = wsi->cgi; - cgi->wsi = wsi; /* set cgi's owning wsi */ - - /* create pipes for [stdin|stdout] and [stderr] */ - - for (n = 0; n < 3; n++) - if (pipe(cgi->pipe_fds[n]) == -1) - goto bail1; - - /* create cgi wsis for each stdin/out/err fd */ - - for (n = 0; n < 3; n++) { - cgi->stdwsi[n] = lws_create_basic_wsi(wsi->context, wsi->tsi); - if (!cgi->stdwsi[n]) - goto bail2; - cgi->stdwsi[n]->cgi_channel = n; - cgi->stdwsi[n]->vhost = wsi->vhost; - - lwsl_debug("%s: cgi %p: pipe fd %d -> fd %d / %d\n", __func__, - cgi->stdwsi[n], n, cgi->pipe_fds[n][!!(n == 0)], - cgi->pipe_fds[n][!(n == 0)]); - - /* read side is 0, stdin we want the write side, others read */ - cgi->stdwsi[n]->desc.sockfd = cgi->pipe_fds[n][!!(n == 0)]; - if (fcntl(cgi->pipe_fds[n][!!(n == 0)], F_SETFL, O_NONBLOCK) < 0) { - lwsl_err("%s: setting NONBLOCK failed\n", __func__); - goto bail2; - } - } - - for (n = 0; n < 3; n++) { - lws_libuv_accept(cgi->stdwsi[n], cgi->stdwsi[n]->desc); - if (insert_wsi_socket_into_fds(wsi->context, cgi->stdwsi[n])) - goto bail3; - cgi->stdwsi[n]->parent = wsi; - cgi->stdwsi[n]->sibling_list = wsi->child_list; - wsi->child_list = cgi->stdwsi[n]; - } - - lws_change_pollfd(cgi->stdwsi[LWS_STDIN], LWS_POLLIN, LWS_POLLOUT); - lws_change_pollfd(cgi->stdwsi[LWS_STDOUT], LWS_POLLOUT, LWS_POLLIN); - lws_change_pollfd(cgi->stdwsi[LWS_STDERR], LWS_POLLOUT, LWS_POLLIN); - - lwsl_debug("%s: fds in %d, out %d, err %d\n", __func__, - cgi->stdwsi[LWS_STDIN]->desc.sockfd, - cgi->stdwsi[LWS_STDOUT]->desc.sockfd, - cgi->stdwsi[LWS_STDERR]->desc.sockfd); - - if (timeout_secs) - lws_set_timeout(wsi, PENDING_TIMEOUT_CGI, timeout_secs); - - /* the cgi stdout is always sending us http1.x header data first */ - wsi->hdr_state = LCHS_HEADER; - - /* add us to the pt list of active cgis */ - lwsl_debug("%s: adding cgi %p to list\n", __func__, wsi->cgi); - cgi->cgi_list = pt->cgi_list; - pt->cgi_list = cgi; - - /* prepare his CGI env */ - - n = 0; - - if (lws_is_ssl(wsi)) - env_array[n++] = "HTTPS=ON"; - if (wsi->u.hdr.ah) { - static const unsigned char meths[] = { - WSI_TOKEN_GET_URI, - WSI_TOKEN_POST_URI, - WSI_TOKEN_OPTIONS_URI, - WSI_TOKEN_PUT_URI, - WSI_TOKEN_PATCH_URI, - WSI_TOKEN_DELETE_URI, - WSI_TOKEN_CONNECT, - WSI_TOKEN_HEAD_URI, - #ifdef LWS_WITH_HTTP2 - WSI_TOKEN_HTTP_COLON_PATH, - #endif - }; - static const char * const meth_names[] = { - "GET", "POST", "OPTIONS", "PUT", "PATCH", "DELETE", - "CONNECT", ":path" - }; - - if (script_uri_path_len >= 0) - for (m = 0; m < ARRAY_SIZE(meths); m++) - if (lws_hdr_total_length(wsi, meths[m]) >= - script_uri_path_len) { - uritok = meths[m]; - break; - } - - if (script_uri_path_len < 0 && uritok < 0) - goto bail3; -// if (script_uri_path_len < 0) -// uritok = 0; - - if (uritok >= 0) { - lws_snprintf(cgi_path, sizeof(cgi_path) - 1, "REQUEST_URI=%s", - lws_hdr_simple_ptr(wsi, uritok)); - cgi_path[sizeof(cgi_path) - 1] = '\0'; - env_array[n++] = cgi_path; - } - - if (m >= 0) { - env_array[n++] = p; - if (m < 8) - p += lws_snprintf(p, end - p, "REQUEST_METHOD=%s", - meth_names[m]); - else - p += lws_snprintf(p, end - p, "REQUEST_METHOD=%s", - lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_METHOD)); - p++; - } - - env_array[n++] = p; - p += lws_snprintf(p, end - p, "QUERY_STRING="); - /* dump the individual URI Arg parameters */ - m = 0; - while (script_uri_path_len >= 0) { - i = lws_hdr_copy_fragment(wsi, tok, sizeof(tok), - WSI_TOKEN_HTTP_URI_ARGS, m); - if (i < 0) - break; - t = tok; - while (*t && *t != '=' && p < end - 4) - *p++ = *t++; - if (*t == '=') - *p++ = *t++; - i = urlencode(t, i- (t - tok), p, end - p); - if (i > 0) { - p += i; - *p++ = '&'; - } - m++; - } - if (m) - p--; - *p++ = '\0'; - - if (script_uri_path_len >= 0) { - env_array[n++] = p; - p += lws_snprintf(p, end - p, "PATH_INFO=%s", - lws_hdr_simple_ptr(wsi, uritok) + - script_uri_path_len); - p++; - } - } - if (script_uri_path_len >= 0 && - lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_REFERER)) { - env_array[n++] = p; - p += lws_snprintf(p, end - p, "HTTP_REFERER=%s", - lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_REFERER)); - p++; - } - if (script_uri_path_len >= 0 && - lws_hdr_total_length(wsi, WSI_TOKEN_HOST)) { - env_array[n++] = p; - p += lws_snprintf(p, end - p, "HTTP_HOST=%s", - lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST)); - p++; - } - if (script_uri_path_len >= 0 && - lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COOKIE)) { - env_array[n++] = p; - p += lws_snprintf(p, end - p, "HTTP_COOKIE=%s", - lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COOKIE)); - p++; - } - if (script_uri_path_len >= 0 && - lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_USER_AGENT)) { - env_array[n++] = p; - p += lws_snprintf(p, end - p, "USER_AGENT=%s", - lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_USER_AGENT)); - p++; - } - if (script_uri_path_len >= 0 && - uritok == WSI_TOKEN_POST_URI) { - if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE)) { - env_array[n++] = p; - p += lws_snprintf(p, end - p, "CONTENT_TYPE=%s", - lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE)); - p++; - } - if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) { - env_array[n++] = p; - p += lws_snprintf(p, end - p, "CONTENT_LENGTH=%s", - lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)); - p++; - } - } - env_array[n++] = "PATH=/bin:/usr/bin:/usr/local/bin:/var/www/cgi-bin"; - - env_array[n++] = p; - p += lws_snprintf(p, end - p, "SCRIPT_PATH=%s", exec_array[0]) + 1; - - while (mp_cgienv) { - env_array[n++] = p; - p += lws_snprintf(p, end - p, "%s=%s", mp_cgienv->name, - mp_cgienv->value); - lwsl_debug(" Applying mount-specific cgi env '%s'\n", - env_array[n - 1]); - p++; - mp_cgienv = mp_cgienv->next; - } - - env_array[n++] = "SERVER_SOFTWARE=libwebsockets"; - env_array[n] = NULL; - -#if 0 - for (m = 0; m < n; m++) - lwsl_err(" %s\n", env_array[m]); -#endif - - /* - * Actually having made the env, as a cgi we don't need the ah - * any more - */ - if (script_uri_path_len >= 0 && - lws_header_table_is_in_detachable_state(wsi)) - lws_header_table_detach(wsi, 0); - - /* we are ready with the redirection pipes... run the thing */ -#if !defined(LWS_HAVE_VFORK) || !defined(LWS_HAVE_EXECVPE) - cgi->pid = fork(); -#else - cgi->pid = vfork(); -#endif - if (cgi->pid < 0) { - lwsl_err("fork failed, errno %d", errno); - goto bail3; - } - -#if defined(__linux__) - prctl(PR_SET_PDEATHSIG, SIGTERM); -#endif - if (script_uri_path_len >= 0) - /* stops non-daemonized main processess getting SIGINT - * from TTY */ - setpgrp(); - - if (cgi->pid) { - /* we are the parent process */ - wsi->context->count_cgi_spawned++; - lwsl_debug("%s: cgi %p spawned PID %d\n", __func__, cgi, cgi->pid); - - for (n = 0; n < 3; n++) - close(cgi->pipe_fds[n][!(n == 0)]); - - /* inform cgi owner of the child PID */ - n = user_callback_handle_rxflow(wsi->protocol->callback, wsi, - LWS_CALLBACK_CGI_PROCESS_ATTACH, - wsi->user_space, NULL, cgi->pid); - (void)n; - - return 0; - } - - /* somewhere we can at least read things and enter it */ - if (chdir("/tmp")) - lwsl_notice("%s: Failed to chdir\n", __func__); - - /* We are the forked process, redirect and kill inherited things. - * - * Because of vfork(), we cannot do anything that changes pages in - * the parent environment. Stuff that changes kernel state for the - * process is OK. Stuff that happens after the execvpe() is OK. - */ - - for (n = 0; n < 3; n++) { - if (dup2(cgi->pipe_fds[n][!(n == 0)], n) < 0) { - lwsl_err("%s: stdin dup2 failed\n", __func__); - goto bail3; - } - close(cgi->pipe_fds[n][!(n == 0)]); - } - -#if !defined(LWS_HAVE_VFORK) || !defined(LWS_HAVE_EXECVPE) - for (m = 0; m < n; m++) { - p = strchr(env_array[m], '='); - *p++ = '\0'; - setenv(env_array[m], p, 1); - } - execvp(exec_array[0], (char * const *)&exec_array[0]); -#else - execvpe(exec_array[0], (char * const *)&exec_array[0], &env_array[0]); -#endif - - exit(1); - -bail3: - /* drop us from the pt cgi list */ - pt->cgi_list = cgi->cgi_list; - - while (--n >= 0) - remove_wsi_socket_from_fds(wsi->cgi->stdwsi[n]); -bail2: - for (n = 0; n < 3; n++) - if (wsi->cgi->stdwsi[n]) - lws_free_wsi(cgi->stdwsi[n]); - -bail1: - for (n = 0; n < 3; n++) { - if (cgi->pipe_fds[n][0]) - close(cgi->pipe_fds[n][0]); - if (cgi->pipe_fds[n][1]) - close(cgi->pipe_fds[n][1]); - } - - lws_free_set_NULL(wsi->cgi); - - lwsl_err("%s: failed\n", __func__); - - return -1; -} - -/* we have to parse out these headers in the CGI output */ - -static const char * const significant_hdr[SIGNIFICANT_HDR_COUNT] = { - "content-length: ", - "location: ", - "status: ", - "transfer-encoding: chunked", -}; - -enum header_recode { - HR_NAME, - HR_WHITESPACE, - HR_ARG, - HR_CRLF, -}; - -LWS_VISIBLE LWS_EXTERN int -lws_cgi_write_split_stdout_headers(struct lws *wsi) -{ - int n, m, cmd; - unsigned char buf[LWS_PRE + 1024], *start = &buf[LWS_PRE], *p = start, - *end = &buf[sizeof(buf) - 1 - LWS_PRE], *name, - *value = NULL; - char c, hrs; - - if (!wsi->cgi) - return -1; - - while (wsi->hdr_state != LHCS_PAYLOAD) { - /* - * We have to separate header / finalize and payload chunks, - * since they need to be handled separately - */ - switch (wsi->hdr_state) { - case LHCS_RESPONSE: - lwsl_debug("LHCS_RESPONSE: issuing response %d\n", - wsi->cgi->response_code); - if (lws_add_http_header_status(wsi, wsi->cgi->response_code, - &p, end)) - return 1; - if (!wsi->cgi->explicitly_chunked && - !wsi->cgi->content_length && - lws_add_http_header_by_token(wsi, - WSI_TOKEN_HTTP_TRANSFER_ENCODING, - (unsigned char *)"chunked", 7, &p, end)) - return 1; - if (!(wsi->http2_substream)) - if (lws_add_http_header_by_token(wsi, WSI_TOKEN_CONNECTION, - (unsigned char *)"close", 5, &p, end)) - return 1; - n = lws_write(wsi, start, p - start, - LWS_WRITE_HTTP_HEADERS | LWS_WRITE_NO_FIN); - - /* - * so we have a bunch of http/1 style ascii headers - * starting from wsi->cgi->headers_buf through - * wsi->cgi->headers_pos. These are OK for http/1 - * connections, but they're no good for http/2 conns. - * - * Let's redo them at headers_pos forward using the - * correct coding for http/1 or http/2 - */ - if (!wsi->http2_substream) - goto post_hpack_recode; - - p = wsi->cgi->headers_start; - wsi->cgi->headers_start = wsi->cgi->headers_pos; - wsi->cgi->headers_dumped = wsi->cgi->headers_start; - hrs = HR_NAME; - name = buf; - - while (p < wsi->cgi->headers_start) { - //lwsl_notice("%c (%d)\n", *p, (int)(wsi->cgi->headers_start - p)); - switch (hrs) { - case HR_NAME: - /* - * in http/2 upper-case header names - * are illegal. So convert to lower- - * case. - */ - if (name - buf > 64) - return -1; - if (*p != ':') { - if (*p >= 'A' && *p <= 'Z') - *name++ = (*p++) + ('a' - 'A'); - else - *name++ = *p++; - } else { - p++; - *name++ = '\0'; - value = name; - hrs = HR_WHITESPACE; - } - break; - case HR_WHITESPACE: - if (*p == ' ') { - p++; - break; - } - hrs = HR_ARG; - /* fallthru */ - case HR_ARG: - if (name > end - 64) - return -1; - - if (*p != '\x0a' && *p != '\x0d') { - *name++ = *p++; - break; - } - hrs = HR_CRLF; - /* fallthru */ - case HR_CRLF: - if ((*p != '\x0a' && *p != '\x0d') || - p + 1 == wsi->cgi->headers_start) { - *name = '\0'; - if ((strcmp((const char *)buf, "transfer-encoding") - )) { - lwsl_debug("adding header %s: %s\n", buf, value); - if (lws_add_http_header_by_name(wsi, buf, - (unsigned char *)value, name - value, - (unsigned char **)&wsi->cgi->headers_pos, - (unsigned char *)wsi->cgi->headers_end)) - return 1; - hrs = HR_NAME; - name = buf; - break; - } - } - p++; - break; - } - } -post_hpack_recode: - /* finalize cached headers before dumping them */ - if (lws_finalize_http_header(wsi, - (unsigned char **)&wsi->cgi->headers_pos, - (unsigned char *)wsi->cgi->headers_end)) { - - lwsl_notice("finalize failed\n"); - return -1; - } - - wsi->hdr_state = LHCS_DUMP_HEADERS; - wsi->reason_bf |= LWS_CB_REASON_AUX_BF__CGI_HEADERS; - lws_callback_on_writable(wsi); - /* back to the loop for writeability again */ - return 0; - - case LHCS_DUMP_HEADERS: - - n = wsi->cgi->headers_pos - wsi->cgi->headers_dumped; - if (n > 512) - n = 512; - - lwsl_debug("LHCS_DUMP_HEADERS: %d\n", n); - - cmd = LWS_WRITE_HTTP_HEADERS_CONTINUATION; - if (wsi->cgi->headers_dumped + n != wsi->cgi->headers_pos) { - lwsl_notice("adding no fin flag\n"); - cmd |= LWS_WRITE_NO_FIN; - } - - m = lws_write(wsi, (unsigned char *)wsi->cgi->headers_dumped, - n, cmd); - if (m < 0) { - lwsl_debug("%s: write says %d\n", __func__, m); - return -1; - } - wsi->cgi->headers_dumped += n; - if (wsi->cgi->headers_dumped == wsi->cgi->headers_pos) { - wsi->hdr_state = LHCS_PAYLOAD; - lws_free_set_NULL(wsi->cgi->headers_buf); - lwsl_debug("freed cgi headers\n"); - } else { - wsi->reason_bf |= LWS_CB_REASON_AUX_BF__CGI_HEADERS; - lws_callback_on_writable(wsi); - } - - /* writeability becomes uncertain now we wrote - * something, we must return to the event loop - */ - return 0; - } - - if (!wsi->cgi->headers_buf) { - /* if we don't already have a headers buf, cook one up */ - n = 2048; - if (wsi->http2_substream) - n = 4096; - wsi->cgi->headers_buf = lws_malloc(n + LWS_PRE, - "cgi hdr buf"); - if (!wsi->cgi->headers_buf) { - lwsl_err("OOM\n"); - return -1; - } - - lwsl_debug("allocated cgi hdrs\n"); - wsi->cgi->headers_start = wsi->cgi->headers_buf + LWS_PRE; - wsi->cgi->headers_pos = wsi->cgi->headers_start; - wsi->cgi->headers_dumped = wsi->cgi->headers_pos; - wsi->cgi->headers_end = wsi->cgi->headers_buf + n - 1; - - for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++) { - wsi->cgi->match[n] = 0; - wsi->cgi->lp = 0; - } - } - - n = lws_get_socket_fd(wsi->cgi->stdwsi[LWS_STDOUT]); - if (n < 0) - return -1; - n = read(n, &c, 1); - if (n < 0) { - if (errno != EAGAIN) { - lwsl_debug("%s: read says %d\n", __func__, n); - return -1; - } - else - n = 0; - - if (wsi->cgi->headers_pos >= wsi->cgi->headers_end - 4) { - lwsl_notice("CGI headers larger than buffer size\n"); - - return -1; - } - } - if (n) { - lwsl_debug("-- 0x%02X %c %d %d\n", (unsigned char)c, c, - wsi->cgi->match[1], wsi->hdr_state); - if (!c) - return -1; - switch (wsi->hdr_state) { - case LCHS_HEADER: - hdr: - for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++) { - /* significant headers with numeric decimal payloads */ - if (!significant_hdr[n][wsi->cgi->match[n]] && - (c >= '0' && c <= '9') && - wsi->cgi->lp < sizeof(wsi->cgi->l) - 1) { - wsi->cgi->l[wsi->cgi->lp++] = c; - wsi->cgi->l[wsi->cgi->lp] = '\0'; - switch (n) { - case SIGNIFICANT_HDR_CONTENT_LENGTH: - wsi->cgi->content_length = atoll(wsi->cgi->l); - break; - case SIGNIFICANT_HDR_STATUS: - wsi->cgi->response_code = atol(wsi->cgi->l); - lwsl_debug("Status set to %d\n", wsi->cgi->response_code); - break; - default: - break; - } - } - /* hits up to the NUL are sticky until next hdr */ - if (significant_hdr[n][wsi->cgi->match[n]]) { - if (tolower(c) == significant_hdr[n][wsi->cgi->match[n]]) - wsi->cgi->match[n]++; - else - wsi->cgi->match[n] = 0; - } - } - - /* some cgi only send us \x0a for EOL */ - if (c == '\x0a') { - wsi->hdr_state = LCHS_SINGLE_0A; - *wsi->cgi->headers_pos++ = '\x0d'; - } - *wsi->cgi->headers_pos++ = c; - if (c == '\x0d') - wsi->hdr_state = LCHS_LF1; - - if (wsi->hdr_state != LCHS_HEADER && - !significant_hdr[SIGNIFICANT_HDR_TRANSFER_ENCODING][wsi->cgi->match[SIGNIFICANT_HDR_TRANSFER_ENCODING]]) { - lwsl_debug("cgi produced chunked\n"); - wsi->cgi->explicitly_chunked = 1; - } - - /* presence of Location: mandates 302 retcode */ - if (wsi->hdr_state != LCHS_HEADER && - !significant_hdr[SIGNIFICANT_HDR_LOCATION][wsi->cgi->match[SIGNIFICANT_HDR_LOCATION]]) { - lwsl_debug("CGI: Location hdr seen\n"); - wsi->cgi->response_code = 302; - } - - break; - case LCHS_LF1: - *wsi->cgi->headers_pos++ = c; - if (c == '\x0a') { - wsi->hdr_state = LCHS_CR2; - break; - } - /* we got \r[^\n]... it's unreasonable */ - lwsl_debug("%s: funny CRLF 0x%02X\n", __func__, (unsigned char)c); - return -1; - - case LCHS_CR2: - if (c == '\x0d') { - /* drop the \x0d */ - wsi->hdr_state = LCHS_LF2; - break; - } - wsi->hdr_state = LCHS_HEADER; - for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++) - wsi->cgi->match[n] = 0; - wsi->cgi->lp = 0; - goto hdr; - - case LCHS_LF2: - case LCHS_SINGLE_0A: - m = wsi->hdr_state; - if (c == '\x0a') { - lwsl_debug("Content-Length: %lld\n", (unsigned long long)wsi->cgi->content_length); - wsi->hdr_state = LHCS_RESPONSE; - /* drop the \0xa ... finalize will add it if needed */ - break; - } - if (m == LCHS_LF2) - /* we got \r\n\r[^\n]... it's unreasonable */ - return -1; - /* we got \x0anext header, it's reasonable */ - *wsi->cgi->headers_pos++ = c; - wsi->hdr_state = LCHS_HEADER; - for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++) - wsi->cgi->match[n] = 0; - wsi->cgi->lp = 0; - break; - case LHCS_PAYLOAD: - break; - } - } - - /* ran out of input, ended the headers, or filled up the headers buf */ - if (!n || wsi->hdr_state == LHCS_PAYLOAD) - return 0; - } - - /* payload processing */ - - m = !wsi->cgi->explicitly_chunked && !wsi->cgi->content_length; - n = lws_get_socket_fd(wsi->cgi->stdwsi[LWS_STDOUT]); - if (n < 0) - return -1; - n = read(n, start, sizeof(buf) - LWS_PRE - (m ? LWS_HTTP_CHUNK_HDR_SIZE : 0)); - - if (n < 0 && errno != EAGAIN) { - lwsl_debug("%s: stdout read says %d\n", __func__, n); - return -1; - } - if (n > 0) { - if (!wsi->http2_substream && m) { - char chdr[LWS_HTTP_CHUNK_HDR_SIZE]; - m = lws_snprintf(chdr, LWS_HTTP_CHUNK_HDR_SIZE - 3, - "%X\x0d\x0a", n); - memmove(start + m, start, n); - memcpy(start, chdr, m); - memcpy(start + m + n, "\x0d\x0a", 2); - n += m + 2; - } - cmd = LWS_WRITE_HTTP; - if (wsi->cgi->content_length_seen + n == wsi->cgi->content_length) - cmd = LWS_WRITE_HTTP_FINAL; - m = lws_write(wsi, (unsigned char *)start, n, cmd); - //lwsl_notice("write %d\n", m); - if (m < 0) { - lwsl_debug("%s: stdout write says %d\n", __func__, m); - return -1; - } - wsi->cgi->content_length_seen += n; - } else { - if (wsi->cgi_stdout_zero_length) { - lwsl_debug("%s: stdout is POLLHUP'd\n", __func__); - if (wsi->http2_substream) - m = lws_write(wsi, (unsigned char *)start, 0, - LWS_WRITE_HTTP_FINAL); - return 1; - } - wsi->cgi_stdout_zero_length = 1; - } - return 0; -} - -LWS_VISIBLE LWS_EXTERN int -lws_cgi_kill(struct lws *wsi) -{ - struct lws_cgi_args args; - int status, n; - - lwsl_debug("%s: %p\n", __func__, wsi); - - if (!wsi->cgi) - return 0; - - if (wsi->cgi->pid > 0) { - n = waitpid(wsi->cgi->pid, &status, WNOHANG); - if (n > 0) { - lwsl_debug("%s: PID %d reaped\n", __func__, - wsi->cgi->pid); - goto handled; - } - /* kill the process group */ - n = kill(-wsi->cgi->pid, SIGTERM); - lwsl_debug("%s: SIGTERM child PID %d says %d (errno %d)\n", - __func__, wsi->cgi->pid, n, errno); - if (n < 0) { - /* - * hum seen errno=3 when process is listed in ps, - * it seems we don't always retain process grouping - * - * Direct these fallback attempt to the exact child - */ - n = kill(wsi->cgi->pid, SIGTERM); - if (n < 0) { - n = kill(wsi->cgi->pid, SIGPIPE); - if (n < 0) { - n = kill(wsi->cgi->pid, SIGKILL); - if (n < 0) - lwsl_err("%s: SIGKILL PID %d failed errno %d (maybe zombie)\n", - __func__, wsi->cgi->pid, errno); - } - } - } - /* He could be unkillable because he's a zombie */ - n = 1; - while (n > 0) { - n = waitpid(-wsi->cgi->pid, &status, WNOHANG); - if (n > 0) - lwsl_debug("%s: reaped PID %d\n", __func__, n); - if (n <= 0) { - n = waitpid(wsi->cgi->pid, &status, WNOHANG); - if (n > 0) - lwsl_debug("%s: reaped PID %d\n", __func__, n); - } - } - } - -handled: - args.stdwsi = &wsi->cgi->stdwsi[0]; - - if (wsi->cgi->pid != -1) { - n = user_callback_handle_rxflow(wsi->protocol->callback, wsi, - LWS_CALLBACK_CGI_TERMINATED, - wsi->user_space, - (void *)&args, wsi->cgi->pid); - wsi->cgi->pid = -1; - if (n && !wsi->cgi->being_closed) - lws_close_free_wsi(wsi, 0); - } - - return 0; -} - -LWS_EXTERN int -lws_cgi_kill_terminated(struct lws_context_per_thread *pt) -{ - struct lws_cgi **pcgi, *cgi = NULL; - int status, n = 1; - - while (n > 0) { - /* find finished guys but don't reap yet */ - n = waitpid(-1, &status, WNOHANG); - if (n <= 0) - continue; - lwsl_debug("%s: observed PID %d terminated\n", __func__, n); - - pcgi = &pt->cgi_list; - - /* check all the subprocesses on the cgi list */ - while (*pcgi) { - /* get the next one first as list may change */ - cgi = *pcgi; - pcgi = &(*pcgi)->cgi_list; - - if (cgi->pid <= 0) - continue; - - /* finish sending cached headers */ - if (cgi->headers_buf) - continue; - - /* wait for stdout to be drained */ - if (cgi->content_length > cgi->content_length_seen) - continue; - - if (cgi->content_length) { - lwsl_debug("%s: wsi %p: expected content length seen: %lld\n", - __func__, cgi->wsi, (unsigned long long)cgi->content_length_seen); - } - - /* reap it */ - waitpid(n, &status, WNOHANG); - /* - * he's already terminated so no need for kill() - * but we should do the terminated cgi callback - * and close him if he's not already closing - */ - if (n == cgi->pid) { - lwsl_debug("%s: found PID %d on cgi list\n", - __func__, n); - - if (!cgi->content_length) { - /* - * well, if he sends chunked... - * give him 2s after the - * cgi terminated to send buffered - */ - cgi->chunked_grace++; - continue; - } - - /* defeat kill() */ - cgi->pid = 0; - lws_cgi_kill(cgi->wsi); - - break; - } - cgi = NULL; - } - /* if not found on the cgi list, as he's one of ours, reap */ - if (!cgi) { - lwsl_debug("%s: reading PID %d although no cgi match\n", - __func__, n); - waitpid(n, &status, WNOHANG); - } - } - - pcgi = &pt->cgi_list; - - /* check all the subprocesses on the cgi list */ - while (*pcgi) { - /* get the next one first as list may change */ - cgi = *pcgi; - pcgi = &(*pcgi)->cgi_list; - - if (cgi->pid <= 0) - continue; - - /* we deferred killing him after reaping his PID */ - if (cgi->chunked_grace) { - cgi->chunked_grace++; - if (cgi->chunked_grace < 2) - continue; - goto finish_him; - } - - /* finish sending cached headers */ - if (cgi->headers_buf) - continue; - - /* wait for stdout to be drained */ - if (cgi->content_length > cgi->content_length_seen) - continue; - - if (cgi->content_length) - lwsl_debug("%s: wsi %p: expected content length seen: %lld\n", - __func__, cgi->wsi, - (unsigned long long)cgi->content_length_seen); - - /* reap it */ - if (waitpid(cgi->pid, &status, WNOHANG) > 0) { - - if (!cgi->content_length) { - /* - * well, if he sends chunked... - * give him 2s after the - * cgi terminated to send buffered - */ - cgi->chunked_grace++; - continue; - } -finish_him: - lwsl_debug("%s: found PID %d on cgi list\n", - __func__, cgi->pid); - - /* defeat kill() */ - cgi->pid = 0; - lws_cgi_kill(cgi->wsi); - - break; - } - } - - return 0; -} - -LWS_VISIBLE LWS_EXTERN struct lws * -lws_cgi_get_stdwsi(struct lws *wsi, enum lws_enum_stdinouterr ch) -{ - if (!wsi->cgi) - return NULL; - - return wsi->cgi->stdwsi[ch]; -} - -#endif #ifdef LWS_NO_EXTENSIONS LWS_EXTERN int @@ -3584,61 +2589,6 @@ lws_set_extension_option(struct lws *wsi, const char *ext_name, } #endif -#ifdef LWS_WITH_ACCESS_LOG -int -lws_access_log(struct lws *wsi) -{ - char *p = wsi->access_log.user_agent, ass[512], - *p1 = wsi->access_log.referrer; - int l; - - if (!wsi->access_log_pending) - return 0; - - if (!wsi->access_log.header_log) - return 0; - - if (!p) - p = ""; - - if (!p1) - p1 = ""; - - /* - * We do this in two parts to restrict an oversize referrer such that - * we will always have space left to append an empty useragent, while - * maintaining the structure of the log text - */ - l = lws_snprintf(ass, sizeof(ass) - 7, "%s %d %lu \"%s", - wsi->access_log.header_log, - wsi->access_log.response, wsi->access_log.sent, p1); - if (strlen(p) > sizeof(ass) - 6 - l) - p[sizeof(ass) - 6 - l] = '\0'; - l += lws_snprintf(ass + l, sizeof(ass) - 1 - l, "\" \"%s\"\n", p); - - if (wsi->vhost->log_fd != (int)LWS_INVALID_FILE) { - if (write(wsi->vhost->log_fd, ass, l) != l) - lwsl_err("Failed to write log\n"); - } else - lwsl_err("%s", ass); - - if (wsi->access_log.header_log) { - lws_free(wsi->access_log.header_log); - wsi->access_log.header_log = NULL; - } - if (wsi->access_log.user_agent) { - lws_free(wsi->access_log.user_agent); - wsi->access_log.user_agent = NULL; - } - if (wsi->access_log.referrer) { - lws_free(wsi->access_log.referrer); - wsi->access_log.referrer = NULL; - } - wsi->access_log_pending = 0; - - return 0; -} -#endif void lws_sum_stats(const struct lws_context *ctx, struct lws_conn_stats *cs) @@ -4091,253 +3041,3 @@ lws_stats_atomic_max(struct lws_context * context, #endif -LWS_VISIBLE LWS_EXTERN struct lws_ring * -lws_ring_create(size_t element_len, size_t count, void (*destroy_element)(void *)) -{ - struct lws_ring *ring = lws_malloc(sizeof(*ring), "ring create"); - - if (!ring) - return NULL; - - ring->buflen = count * element_len; - ring->element_len = element_len; - ring->head = 0; - ring->oldest_tail = 0; - ring->destroy_element = destroy_element; - - ring->buf = lws_malloc(ring->buflen, "ring buf"); - if (!ring->buf) { - lws_free(ring); - - return NULL; - } - - return ring; -} - -LWS_VISIBLE LWS_EXTERN void -lws_ring_destroy(struct lws_ring *ring) -{ - if (ring->destroy_element) - while (ring->oldest_tail != ring->head) { - ring->destroy_element((uint8_t *)ring->buf + ring->oldest_tail); - ring->oldest_tail = (ring->oldest_tail + ring->element_len) % - ring->buflen; - } - if (ring->buf) - lws_free_set_NULL(ring->buf); - - lws_free(ring); -} - -LWS_VISIBLE LWS_EXTERN size_t -lws_ring_get_count_free_elements(struct lws_ring *ring) -{ - int f; - - /* - * possible ringbuf patterns - * - * h == t - * |--------t***h---| - * |**h-----------t*| - * |t**************h| - * |*****ht*********| - */ - if (ring->head == ring->oldest_tail) - f = ring->buflen - ring->element_len; - else - if (ring->head < ring->oldest_tail) - f = (ring->oldest_tail - ring->head) - ring->element_len; - else - f = (ring->buflen - ring->head) + ring->oldest_tail - ring->element_len; - - if (f < 2) - return 0; - - return f / ring->element_len; -} - -LWS_VISIBLE LWS_EXTERN size_t -lws_ring_get_count_waiting_elements(struct lws_ring *ring, uint32_t *tail) -{ int f; - - if (!tail) - tail = &ring->oldest_tail; - /* - * possible ringbuf patterns - * - * h == t - * |--------t***h---| - * |**h-----------t*| - * |t**************h| - * |*****ht*********| - */ - if (ring->head == *tail) - f = 0; - else - if (ring->head > *tail) - f = (ring->head - *tail); - else - f = (ring->buflen - *tail) + ring->head; - - return f / ring->element_len; -} - -LWS_VISIBLE LWS_EXTERN int -lws_ring_next_linear_insert_range(struct lws_ring *ring, void **start, size_t *bytes) -{ - int n; - - /* n is how many bytes the whole fifo can take */ - n = lws_ring_get_count_free_elements(ring) * ring->element_len; - - if (!n) - return 1; - - if (ring->head + n > ring->buflen) { - *start = (void *)(((uint8_t *)ring->buf) + ring->head); - *bytes = ring->buflen - ring->head; - - return 0; - } - - *start = (void *)(((uint8_t *)ring->buf) + ring->head); - *bytes = n; - - return 0; -} - -LWS_VISIBLE LWS_EXTERN void -lws_ring_bump_head(struct lws_ring *ring, size_t bytes) -{ - ring->head = (ring->head + bytes) % ring->buflen; -} - -LWS_VISIBLE LWS_EXTERN size_t -lws_ring_insert(struct lws_ring *ring, const void *src, size_t max_count) -{ - const uint8_t *osrc = src; - int m, n; - - /* n is how many bytes the whole fifo can take */ - n = lws_ring_get_count_free_elements(ring) * ring->element_len; - - /* restrict n to how much we want to insert */ - if ((size_t)n > max_count * ring->element_len) - n = max_count * ring->element_len; - - /* - * n is legal to insert, but as an optimization we can cut the - * insert into one or two memcpys, depending on if it wraps - */ - if (ring->head + n > ring->buflen) { - - /* - * He does wrap. The first memcpy should take us up to - * the end of the buffer - */ - - m = ring->buflen - ring->head; - memcpy(((uint8_t *)ring->buf) + ring->head, src, m); - /* we know it will wrap exactly back to zero */ - ring->head = 0; - - /* adapt the second memcpy for what we already did */ - - src = ((uint8_t *)src) + m; - n -= m; - } - - memcpy(((uint8_t *)ring->buf) + ring->head, src, n); - ring->head = (ring->head + n) % ring->buflen; - - return (((uint8_t *)src + n) - osrc) / ring->element_len; -} - -LWS_VISIBLE LWS_EXTERN size_t -lws_ring_consume(struct lws_ring *ring, uint32_t *tail, void *dest, - size_t max_count) -{ - uint8_t *odest = dest; - void *orig_tail = tail; - uint32_t fake_tail; - int m, n; - - if (!tail) { - fake_tail = ring->oldest_tail; - tail = &fake_tail; - } - - /* n is how many bytes the whole fifo has for us */ - n = lws_ring_get_count_waiting_elements(ring, tail) * ring->element_len; - - /* restrict n to how much we want to insert */ - if ((size_t)n > max_count * ring->element_len) - n = max_count * ring->element_len; - - if (!dest) { - *tail = ((*tail) + n) % ring->buflen; - if (!orig_tail) /* single tail */ - lws_ring_update_oldest_tail(ring, *tail); - - return n / ring->element_len; - } - if (*tail + n > ring->buflen) { - - /* - * He does wrap. The first memcpy should take us up to - * the end of the buffer - */ - - m = ring->buflen - *tail; - memcpy(dest, ((uint8_t *)ring->buf) + *tail, m); - /* we know it will wrap exactly back to zero */ - *tail = 0; - - /* adapt the second memcpy for what we already did */ - - dest = ((uint8_t *)dest) + m; - n -= m; - } - - memcpy(dest, ((uint8_t *)ring->buf) + *tail, n); - - *tail = ((*tail) + n) % ring->buflen; - if (!orig_tail) /* single tail */ - lws_ring_update_oldest_tail(ring, *tail); - - return (((uint8_t *)dest + n) - odest) / ring->element_len; -} - -LWS_VISIBLE LWS_EXTERN const void * -lws_ring_get_element(struct lws_ring *ring, uint32_t *tail) -{ - if (!tail) - tail = &ring->oldest_tail; - - if (*tail == ring->head) - return NULL; - - return ((uint8_t *)ring->buf) + *tail; -} - -LWS_VISIBLE LWS_EXTERN void -lws_ring_update_oldest_tail(struct lws_ring *ring, uint32_t tail) -{ - if (!ring->destroy_element) { - ring->oldest_tail = tail; - return; - } - - while (ring->oldest_tail != tail) { - ring->destroy_element((uint8_t *)ring->buf + ring->oldest_tail); - ring->oldest_tail = (ring->oldest_tail + ring->element_len) % ring->buflen; - } -} - -LWS_VISIBLE LWS_EXTERN uint32_t -lws_ring_get_oldest_tail(struct lws_ring *ring) -{ - return ring->oldest_tail; -} diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index 5d358fd00..ef996c5d7 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -313,14 +313,35 @@ lwsl_timestamp(int level, char *p, int len); #endif +/** + * lwsl_hexdump() - helper to hexdump a buffer + * + * \param level: one of LLL_ constants + * \param buf: buffer start to dump + * \param len: length of buffer to dump + * + * If \p level is visible, does a nice hexdump -C style dump of \p buf for + * \p len bytes. This can be extremely convenient while debugging. + */ +LWS_VISIBLE LWS_EXTERN void +lwsl_hexdump_level(int level, const void *vbuf, size_t len); + /** * lwsl_hexdump() - helper to hexdump a buffer (DEBUG builds only) * * \param buf: buffer start to dump * \param len: length of buffer to dump + * + * Calls through to lwsl_hexdump_level(LLL_DEBUG, ... for compatability. + * It's better to use lwsl_hexdump_level(level, ... directly so you can control + * the visibility. */ -LWS_VISIBLE LWS_EXTERN void lwsl_hexdump(const void *buf, size_t len); +LWS_VISIBLE LWS_EXTERN void +lwsl_hexdump(const void *buf, size_t len); +/** + * lws_is_be() - returns nonzero if the platform is Big Endian + */ static LWS_INLINE int lws_is_be(void) { const int probe = ~0xff; @@ -335,7 +356,8 @@ static LWS_INLINE int lws_is_be(void) { * the default stderr one. * * log level defaults to "err", "warn" and "notice" contexts enabled and - * emission on stderr. + * emission on stderr. If stderr is a tty (according to isatty()) then + * the output is coloured according to the log level using ANSI escapes. */ LWS_VISIBLE LWS_EXTERN void lws_set_log_level(int level, diff --git a/lib/base64-decode.c b/lib/misc/base64-decode.c similarity index 100% rename from lib/base64-decode.c rename to lib/misc/base64-decode.c diff --git a/lib/getifaddrs.c b/lib/misc/getifaddrs.c similarity index 100% rename from lib/getifaddrs.c rename to lib/misc/getifaddrs.c diff --git a/lib/getifaddrs.h b/lib/misc/getifaddrs.h similarity index 100% rename from lib/getifaddrs.h rename to lib/misc/getifaddrs.h diff --git a/lib/lejp.c b/lib/misc/lejp.c similarity index 100% rename from lib/lejp.c rename to lib/misc/lejp.c diff --git a/lib/lejp.h b/lib/misc/lejp.h similarity index 100% rename from lib/lejp.h rename to lib/misc/lejp.h diff --git a/lib/lws-genhash.c b/lib/misc/lws-genhash.c similarity index 100% rename from lib/lws-genhash.c rename to lib/misc/lws-genhash.c diff --git a/lib/misc/lws-ring.c b/lib/misc/lws-ring.c new file mode 100644 index 000000000..c2f86ee5c --- /dev/null +++ b/lib/misc/lws-ring.c @@ -0,0 +1,280 @@ +/* + * libwebsockets - lws-ring multi-tail abstract ringbuffer api + * + * Copyright (C) 2017 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "private-libwebsockets.h" + +LWS_VISIBLE LWS_EXTERN struct lws_ring * +lws_ring_create(size_t element_len, size_t count, + void (*destroy_element)(void *)) +{ + struct lws_ring *ring = lws_malloc(sizeof(*ring), "ring create"); + + if (!ring) + return NULL; + + ring->buflen = count * element_len; + ring->element_len = element_len; + ring->head = 0; + ring->oldest_tail = 0; + ring->destroy_element = destroy_element; + + ring->buf = lws_malloc(ring->buflen, "ring buf"); + if (!ring->buf) { + lws_free(ring); + + return NULL; + } + + return ring; +} + +LWS_VISIBLE LWS_EXTERN void +lws_ring_destroy(struct lws_ring *ring) +{ + if (ring->destroy_element) + while (ring->oldest_tail != ring->head) { + ring->destroy_element((uint8_t *)ring->buf + + ring->oldest_tail); + ring->oldest_tail = + (ring->oldest_tail + ring->element_len) % + ring->buflen; + } + if (ring->buf) + lws_free_set_NULL(ring->buf); + + lws_free(ring); +} + +LWS_VISIBLE LWS_EXTERN size_t +lws_ring_get_count_free_elements(struct lws_ring *ring) +{ + int f; + + /* + * possible ringbuf patterns + * + * h == t + * |--------t***h---| + * |**h-----------t*| + * |t**************h| + * |*****ht*********| + */ + if (ring->head == ring->oldest_tail) + f = ring->buflen - ring->element_len; + else + if (ring->head < ring->oldest_tail) + f = (ring->oldest_tail - ring->head) - + ring->element_len; + else + f = (ring->buflen - ring->head) + ring->oldest_tail - + ring->element_len; + + if (f < 2) + return 0; + + return f / ring->element_len; +} + +LWS_VISIBLE LWS_EXTERN size_t +lws_ring_get_count_waiting_elements(struct lws_ring *ring, uint32_t *tail) +{ int f; + + if (!tail) + tail = &ring->oldest_tail; + /* + * possible ringbuf patterns + * + * h == t + * |--------t***h---| + * |**h-----------t*| + * |t**************h| + * |*****ht*********| + */ + if (ring->head == *tail) + f = 0; + else + if (ring->head > *tail) + f = (ring->head - *tail); + else + f = (ring->buflen - *tail) + ring->head; + + return f / ring->element_len; +} + +LWS_VISIBLE LWS_EXTERN int +lws_ring_next_linear_insert_range(struct lws_ring *ring, void **start, + size_t *bytes) +{ + int n; + + /* n is how many bytes the whole fifo can take */ + n = lws_ring_get_count_free_elements(ring) * ring->element_len; + + if (!n) + return 1; + + if (ring->head + n > ring->buflen) { + *start = (void *)(((uint8_t *)ring->buf) + ring->head); + *bytes = ring->buflen - ring->head; + + return 0; + } + + *start = (void *)(((uint8_t *)ring->buf) + ring->head); + *bytes = n; + + return 0; +} + +LWS_VISIBLE LWS_EXTERN void +lws_ring_bump_head(struct lws_ring *ring, size_t bytes) +{ + ring->head = (ring->head + bytes) % ring->buflen; +} + +LWS_VISIBLE LWS_EXTERN size_t +lws_ring_insert(struct lws_ring *ring, const void *src, size_t max_count) +{ + const uint8_t *osrc = src; + int m, n; + + /* n is how many bytes the whole fifo can take */ + n = lws_ring_get_count_free_elements(ring) * ring->element_len; + + /* restrict n to how much we want to insert */ + if ((size_t)n > max_count * ring->element_len) + n = max_count * ring->element_len; + + /* + * n is legal to insert, but as an optimization we can cut the + * insert into one or two memcpys, depending on if it wraps + */ + if (ring->head + n > ring->buflen) { + + /* + * He does wrap. The first memcpy should take us up to + * the end of the buffer + */ + + m = ring->buflen - ring->head; + memcpy(((uint8_t *)ring->buf) + ring->head, src, m); + /* we know it will wrap exactly back to zero */ + ring->head = 0; + + /* adapt the second memcpy for what we already did */ + + src = ((uint8_t *)src) + m; + n -= m; + } + + memcpy(((uint8_t *)ring->buf) + ring->head, src, n); + ring->head = (ring->head + n) % ring->buflen; + + return (((uint8_t *)src + n) - osrc) / ring->element_len; +} + +LWS_VISIBLE LWS_EXTERN size_t +lws_ring_consume(struct lws_ring *ring, uint32_t *tail, void *dest, + size_t max_count) +{ + uint8_t *odest = dest; + void *orig_tail = tail; + uint32_t fake_tail; + int m, n; + + if (!tail) { + fake_tail = ring->oldest_tail; + tail = &fake_tail; + } + + /* n is how many bytes the whole fifo has for us */ + n = lws_ring_get_count_waiting_elements(ring, tail) * ring->element_len; + + /* restrict n to how much we want to insert */ + if ((size_t)n > max_count * ring->element_len) + n = max_count * ring->element_len; + + if (!dest) { + *tail = ((*tail) + n) % ring->buflen; + if (!orig_tail) /* single tail */ + lws_ring_update_oldest_tail(ring, *tail); + + return n / ring->element_len; + } + if (*tail + n > ring->buflen) { + + /* + * He does wrap. The first memcpy should take us up to + * the end of the buffer + */ + + m = ring->buflen - *tail; + memcpy(dest, ((uint8_t *)ring->buf) + *tail, m); + /* we know it will wrap exactly back to zero */ + *tail = 0; + + /* adapt the second memcpy for what we already did */ + + dest = ((uint8_t *)dest) + m; + n -= m; + } + + memcpy(dest, ((uint8_t *)ring->buf) + *tail, n); + + *tail = ((*tail) + n) % ring->buflen; + if (!orig_tail) /* single tail */ + lws_ring_update_oldest_tail(ring, *tail); + + return (((uint8_t *)dest + n) - odest) / ring->element_len; +} + +LWS_VISIBLE LWS_EXTERN const void * +lws_ring_get_element(struct lws_ring *ring, uint32_t *tail) +{ + if (!tail) + tail = &ring->oldest_tail; + + if (*tail == ring->head) + return NULL; + + return ((uint8_t *)ring->buf) + *tail; +} + +LWS_VISIBLE LWS_EXTERN void +lws_ring_update_oldest_tail(struct lws_ring *ring, uint32_t tail) +{ + if (!ring->destroy_element) { + ring->oldest_tail = tail; + return; + } + + while (ring->oldest_tail != tail) { + ring->destroy_element((uint8_t *)ring->buf + ring->oldest_tail); + ring->oldest_tail = (ring->oldest_tail + ring->element_len) % + ring->buflen; + } +} + +LWS_VISIBLE LWS_EXTERN uint32_t +lws_ring_get_oldest_tail(struct lws_ring *ring) +{ + return ring->oldest_tail; +} diff --git a/lib/romfs.c b/lib/misc/romfs.c similarity index 100% rename from lib/romfs.c rename to lib/misc/romfs.c diff --git a/lib/romfs.h b/lib/misc/romfs.h similarity index 100% rename from lib/romfs.h rename to lib/misc/romfs.h diff --git a/lib/sha-1.c b/lib/misc/sha-1.c similarity index 100% rename from lib/sha-1.c rename to lib/misc/sha-1.c diff --git a/lib/smtp.c b/lib/misc/smtp.c similarity index 96% rename from lib/smtp.c rename to lib/misc/smtp.c index 9eaa05902..dc76360c6 100644 --- a/lib/smtp.c +++ b/lib/misc/smtp.c @@ -1,7 +1,7 @@ /* * SMTP support for libwebsockets * - * Copyright (C) 2016 Andy Green + * Copyright (C) 2016-2017 Andy Green * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -93,7 +93,8 @@ lwsgs_email_read(struct uv_stream_s *s, ssize_t nread, const uv_buf_t *buf) email->estate = LGSSMTP_SENT_HELO; break; case LGSSMTP_SENT_HELO: - n = sprintf(email->content, "MAIL FROM: <%s>\n", email->email_from); + n = sprintf(email->content, "MAIL FROM: <%s>\n", + email->email_from); email->estate = LGSSMTP_SENT_FROM; break; case LGSSMTP_SENT_FROM: @@ -105,7 +106,8 @@ lwsgs_email_read(struct uv_stream_s *s, ssize_t nread, const uv_buf_t *buf) email->estate = LGSSMTP_SENT_DATA; break; case LGSSMTP_SENT_DATA: - if (email->on_get_body(email, email->content, email->max_content_size)) + if (email->on_get_body(email, email->content, + email->max_content_size)) return; n = strlen(email->content); email->estate = LGSSMTP_SENT_BODY; @@ -238,4 +240,3 @@ lws_email_destroy(struct lws_email *email) uv_timer_stop(&email->timeout_email); uv_close((uv_handle_t *)&email->timeout_email, NULL); } - diff --git a/lib/output.c b/lib/output.c index 0c5f41c87..ed4752490 100644 --- a/lib/output.c +++ b/lib/output.c @@ -46,49 +46,9 @@ lws_0405_frame_mask_generate(struct lws *wsi) return 0; } -LWS_VISIBLE void lwsl_hexdump(const void *vbuf, size_t len) -{ - unsigned char *buf = (unsigned char *)vbuf; - unsigned int n, m, start; - char line[80]; - char *p; - - lwsl_debug("\n"); - - for (n = 0; n < len;) { - start = n; - p = line; - - p += sprintf(p, "%04X: ", start); - - for (m = 0; m < 16 && n < len; m++) - p += sprintf(p, "%02X ", buf[n++]); - while (m++ < 16) - p += sprintf(p, " "); - - p += sprintf(p, " "); - - for (m = 0; m < 16 && (start + m) < len; m++) { - if (buf[start + m] >= ' ' && buf[start + m] < 127) - *p++ = buf[start + m]; - else - *p++ = '.'; - } - while (m++ < 16) - *p++ = ' '; - - *p++ = '\n'; - *p = '\0'; - lwsl_debug("%s", line); - (void)line; - } - lwsl_debug("\n"); -} - /* * notice this returns number of bytes consumed, or -1 */ - int lws_issue_raw(struct lws *wsi, unsigned char *buf, size_t len) { struct lws_context *context = lws_get_context(wsi); diff --git a/lib/lws-plat-esp32.c b/lib/plat/lws-plat-esp32.c similarity index 100% rename from lib/lws-plat-esp32.c rename to lib/plat/lws-plat-esp32.c diff --git a/lib/lws-plat-esp8266.c b/lib/plat/lws-plat-esp8266.c similarity index 100% rename from lib/lws-plat-esp8266.c rename to lib/plat/lws-plat-esp8266.c diff --git a/lib/lws-plat-optee.c b/lib/plat/lws-plat-optee.c similarity index 100% rename from lib/lws-plat-optee.c rename to lib/plat/lws-plat-optee.c diff --git a/lib/lws-plat-unix.c b/lib/plat/lws-plat-unix.c similarity index 100% rename from lib/lws-plat-unix.c rename to lib/plat/lws-plat-unix.c diff --git a/lib/lws-plat-win.c b/lib/plat/lws-plat-win.c similarity index 100% rename from lib/lws-plat-win.c rename to lib/plat/lws-plat-win.c diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index 16ddcba19..80e96a5ba 100644 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -1094,17 +1094,7 @@ struct lws_context { int uid, gid; int fd_random; -#ifdef LWS_OPENSSL_SUPPORT -#define lws_ssl_anybody_has_buffered_read(w) \ - (w->vhost->use_ssl && \ - w->context->pt[(int)w->tsi].pending_read_list) -#define lws_ssl_anybody_has_buffered_read_tsi(c, t) \ - (/*c->use_ssl && */ \ - c->pt[(int)t].pending_read_list) -#else -#define lws_ssl_anybody_has_buffered_read(ctx) (0) -#define lws_ssl_anybody_has_buffered_read_tsi(ctx, t) (0) -#endif + int count_wsi_allocated; int count_cgi_spawned; unsigned int options; @@ -2023,6 +2013,9 @@ lws_get_addr_scope(const char *ipaddr); LWS_EXTERN void lws_close_free_wsi(struct lws *wsi, enum lws_close_status); +LWS_EXTERN void +lws_free_wsi(struct lws *wsi); + LWS_EXTERN int remove_wsi_socket_from_fds(struct lws *wsi); LWS_EXTERN int @@ -2297,6 +2290,7 @@ enum lws_ssl_capable_status { #define lws_ssl_SSL_CTX_destroy(_a) #define lws_ssl_remove_wsi_from_buffered_list(_a) #define lws_context_init_ssl_library(_a) +#define lws_ssl_anybody_has_buffered_read_tsi(_a, _b) (0) #else #define LWS_SSL_ENABLED(context) (context->use_ssl) LWS_EXTERN int openssl_websocket_private_data_index; @@ -2326,6 +2320,8 @@ LWS_EXTERN int lws_ssl_client_connect2(struct lws *wsi); LWS_EXTERN void lws_ssl_elaborate_error(void); +LWS_EXTERN int +lws_ssl_anybody_has_buffered_read_tsi(struct lws_context *context, int tsi); #ifndef LWS_NO_SERVER LWS_EXTERN int lws_context_init_server_ssl(struct lws_context_creation_info *info, @@ -2474,6 +2470,8 @@ lws_handshake_server(struct lws *wsi, unsigned char **buf, size_t len); #ifdef LWS_WITH_ACCESS_LOG LWS_EXTERN int lws_access_log(struct lws *wsi); +LWS_EXTERN void +lws_prepare_access_log_info(struct lws *wsi, char *uri_ptr, int meth); #else #define lws_access_log(_a) #endif @@ -2481,6 +2479,9 @@ lws_access_log(struct lws *wsi); LWS_EXTERN int lws_cgi_kill_terminated(struct lws_context_per_thread *pt); +LWS_EXTERN void +lws_cgi_remove_and_kill(struct lws *wsi); + int lws_protocol_init(struct lws_context *context); @@ -2595,6 +2596,11 @@ void lws_peer_track_ah_detach(struct lws_context *context, struct lws_peer *peer); void lws_peer_cull_peer_wait_list(struct lws_context *context); +struct lws_peer * +lws_get_or_create_peer(struct lws_vhost *vhost, lws_sockfd_type sockfd); +void +lws_peer_add_wsi(struct lws_context *context, struct lws_peer *peer, + struct lws *wsi); #endif #ifdef __cplusplus diff --git a/lib/server/access-log.c b/lib/server/access-log.c new file mode 100644 index 000000000..3cc15e455 --- /dev/null +++ b/lib/server/access-log.c @@ -0,0 +1,172 @@ +/* + * libwebsockets - server access log handling + * + * Copyright (C) 2010-2017 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "private-libwebsockets.h" + +/* + * Produce Apache-compatible log string for wsi, like this: + * + * 2.31.234.19 - - [27/Mar/2016:03:22:44 +0800] + * "GET /aep-screen.png HTTP/1.1" + * 200 152987 "https://libwebsockets.org/index.html" + * "Mozilla/5.0 (Macint... Chrome/49.0.2623.87 Safari/537.36" + * + */ + +extern const char * const method_names[]; + +static const char * const hver[] = { + "HTTP/1.0", "HTTP/1.1", "HTTP/2" +}; + +void +lws_prepare_access_log_info(struct lws *wsi, char *uri_ptr, int meth) +{ +#ifdef LWS_WITH_IPV6 + char ads[INET6_ADDRSTRLEN]; +#else + char ads[INET_ADDRSTRLEN]; +#endif + char da[64]; + const char *pa, *me; + struct tm *tmp; + time_t t = time(NULL); + int l = 256, m; + + if (wsi->access_log_pending) + lws_access_log(wsi); + + wsi->access_log.header_log = lws_malloc(l, "access log"); + if (wsi->access_log.header_log) { + + tmp = localtime(&t); + if (tmp) + strftime(da, sizeof(da), "%d/%b/%Y:%H:%M:%S %z", tmp); + else + strcpy(da, "01/Jan/1970:00:00:00 +0000"); + + pa = lws_get_peer_simple(wsi, ads, sizeof(ads)); + if (!pa) + pa = "(unknown)"; + + if (wsi->http2_substream) + me = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_METHOD); + else + me = method_names[meth]; + if (!me) + me = "(null)"; + + lws_snprintf(wsi->access_log.header_log, l, + "%s - - [%s] \"%s %s %s\"", + pa, da, me, uri_ptr, + hver[wsi->u.http.request_version]); + + l = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_USER_AGENT); + if (l) { + wsi->access_log.user_agent = lws_malloc(l + 2, "access log"); + if (!wsi->access_log.user_agent) { + lwsl_err("OOM getting user agent\n"); + lws_free_set_NULL(wsi->access_log.header_log); + return; + } + + lws_hdr_copy(wsi, wsi->access_log.user_agent, + l + 1, WSI_TOKEN_HTTP_USER_AGENT); + + for (m = 0; m < l; m++) + if (wsi->access_log.user_agent[m] == '\"') + wsi->access_log.user_agent[m] = '\''; + } + l = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_REFERER); + if (l) { + wsi->access_log.referrer = lws_malloc(l + 2, "referrer"); + if (!wsi->access_log.referrer) { + lwsl_err("OOM getting user agent\n"); + lws_free_set_NULL(wsi->access_log.user_agent); + lws_free_set_NULL(wsi->access_log.header_log); + return; + } + lws_hdr_copy(wsi, wsi->access_log.referrer, + l + 1, WSI_TOKEN_HTTP_REFERER); + + for (m = 0; m < l; m++) + if (wsi->access_log.referrer[m] == '\"') + wsi->access_log.referrer[m] = '\''; + } + wsi->access_log_pending = 1; + } +} + + +int +lws_access_log(struct lws *wsi) +{ + char *p = wsi->access_log.user_agent, ass[512], + *p1 = wsi->access_log.referrer; + int l; + + if (!wsi->access_log_pending) + return 0; + + if (!wsi->access_log.header_log) + return 0; + + if (!p) + p = ""; + + if (!p1) + p1 = ""; + + /* + * We do this in two parts to restrict an oversize referrer such that + * we will always have space left to append an empty useragent, while + * maintaining the structure of the log text + */ + l = lws_snprintf(ass, sizeof(ass) - 7, "%s %d %lu \"%s", + wsi->access_log.header_log, + wsi->access_log.response, wsi->access_log.sent, p1); + if (strlen(p) > sizeof(ass) - 6 - l) + p[sizeof(ass) - 6 - l] = '\0'; + l += lws_snprintf(ass + l, sizeof(ass) - 1 - l, "\" \"%s\"\n", p); + + if (wsi->vhost->log_fd != (int)LWS_INVALID_FILE) { + if (write(wsi->vhost->log_fd, ass, l) != l) + lwsl_err("Failed to write log\n"); + } else + lwsl_err("%s", ass); + + if (wsi->access_log.header_log) { + lws_free(wsi->access_log.header_log); + wsi->access_log.header_log = NULL; + } + if (wsi->access_log.user_agent) { + lws_free(wsi->access_log.user_agent); + wsi->access_log.user_agent = NULL; + } + if (wsi->access_log.referrer) { + lws_free(wsi->access_log.referrer); + wsi->access_log.referrer = NULL; + } + wsi->access_log_pending = 0; + + return 0; +} + diff --git a/lib/server/cgi.c b/lib/server/cgi.c new file mode 100644 index 000000000..1b9dfe45a --- /dev/null +++ b/lib/server/cgi.c @@ -0,0 +1,1103 @@ +/* + * libwebsockets - CGI management + * + * Copyright (C) 2010-2017 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "private-libwebsockets.h" + +#if defined(WIN32) || defined(_WIN32) +#else +#include +#endif + +static const char *hex = "0123456789ABCDEF"; + +static int +urlencode(const char *in, int inlen, char *out, int outlen) +{ + char *start = out, *end = out + outlen; + + while (inlen-- && out < end - 4) { + if ((*in >= 'A' && *in <= 'Z') || + (*in >= 'a' && *in <= 'z') || + (*in >= '0' && *in <= '9') || + *in == '-' || + *in == '_' || + *in == '.' || + *in == '~') { + *out++ = *in++; + continue; + } + if (*in == ' ') { + *out++ = '+'; + in++; + continue; + } + *out++ = '%'; + *out++ = hex[(*in) >> 4]; + *out++ = hex[(*in++) & 15]; + } + *out = '\0'; + + if (out >= end - 4) + return -1; + + return out - start; +} + +static struct lws * +lws_create_basic_wsi(struct lws_context *context, int tsi) +{ + struct lws *new_wsi; + + if (!context->vhost_list) + return NULL; + + if ((unsigned int)context->pt[tsi].fds_count == + context->fd_limit_per_thread - 1) { + lwsl_err("no space for new conn\n"); + return NULL; + } + + new_wsi = lws_zalloc(sizeof(struct lws), "new wsi"); + if (new_wsi == NULL) { + lwsl_err("Out of memory for new connection\n"); + return NULL; + } + + new_wsi->tsi = tsi; + new_wsi->context = context; + new_wsi->pending_timeout = NO_PENDING_TIMEOUT; + new_wsi->rxflow_change_to = LWS_RXFLOW_ALLOW; + + /* initialize the instance struct */ + + new_wsi->state = LWSS_CGI; + new_wsi->mode = LWSCM_CGI; + new_wsi->hdr_parsing_completed = 0; + new_wsi->position_in_fds_table = -1; + + /* + * these can only be set once the protocol is known + * we set an unestablished connection's protocol pointer + * to the start of the defauly vhost supported list, so it can look + * for matching ones during the handshake + */ + new_wsi->protocol = context->vhost_list->protocols; + new_wsi->user_space = NULL; + new_wsi->ietf_spec_revision = 0; + new_wsi->desc.sockfd = LWS_SOCK_INVALID; + context->count_wsi_allocated++; + + return new_wsi; +} + +LWS_VISIBLE LWS_EXTERN int +lws_cgi(struct lws *wsi, const char * const *exec_array, int script_uri_path_len, + int timeout_secs, const struct lws_protocol_vhost_options *mp_cgienv) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + char *env_array[30], cgi_path[400], e[1024], *p = e, + *end = p + sizeof(e) - 1, tok[256], *t; + struct lws_cgi *cgi; + int n, m, i, uritok = -1; + + /* + * give the master wsi a cgi struct + */ + + wsi->cgi = lws_zalloc(sizeof(*wsi->cgi), "new cgi"); + if (!wsi->cgi) { + lwsl_err("%s: OOM\n", __func__); + return -1; + } + + wsi->cgi->response_code = HTTP_STATUS_OK; + + cgi = wsi->cgi; + cgi->wsi = wsi; /* set cgi's owning wsi */ + + /* create pipes for [stdin|stdout] and [stderr] */ + + for (n = 0; n < 3; n++) + if (pipe(cgi->pipe_fds[n]) == -1) + goto bail1; + + /* create cgi wsis for each stdin/out/err fd */ + + for (n = 0; n < 3; n++) { + cgi->stdwsi[n] = lws_create_basic_wsi(wsi->context, wsi->tsi); + if (!cgi->stdwsi[n]) + goto bail2; + cgi->stdwsi[n]->cgi_channel = n; + cgi->stdwsi[n]->vhost = wsi->vhost; + + lwsl_debug("%s: cgi %p: pipe fd %d -> fd %d / %d\n", __func__, + cgi->stdwsi[n], n, cgi->pipe_fds[n][!!(n == 0)], + cgi->pipe_fds[n][!(n == 0)]); + + /* read side is 0, stdin we want the write side, others read */ + cgi->stdwsi[n]->desc.sockfd = cgi->pipe_fds[n][!!(n == 0)]; + if (fcntl(cgi->pipe_fds[n][!!(n == 0)], F_SETFL, + O_NONBLOCK) < 0) { + lwsl_err("%s: setting NONBLOCK failed\n", __func__); + goto bail2; + } + } + + for (n = 0; n < 3; n++) { + lws_libuv_accept(cgi->stdwsi[n], cgi->stdwsi[n]->desc); + if (insert_wsi_socket_into_fds(wsi->context, cgi->stdwsi[n])) + goto bail3; + cgi->stdwsi[n]->parent = wsi; + cgi->stdwsi[n]->sibling_list = wsi->child_list; + wsi->child_list = cgi->stdwsi[n]; + } + + lws_change_pollfd(cgi->stdwsi[LWS_STDIN], LWS_POLLIN, LWS_POLLOUT); + lws_change_pollfd(cgi->stdwsi[LWS_STDOUT], LWS_POLLOUT, LWS_POLLIN); + lws_change_pollfd(cgi->stdwsi[LWS_STDERR], LWS_POLLOUT, LWS_POLLIN); + + lwsl_debug("%s: fds in %d, out %d, err %d\n", __func__, + cgi->stdwsi[LWS_STDIN]->desc.sockfd, + cgi->stdwsi[LWS_STDOUT]->desc.sockfd, + cgi->stdwsi[LWS_STDERR]->desc.sockfd); + + if (timeout_secs) + lws_set_timeout(wsi, PENDING_TIMEOUT_CGI, timeout_secs); + + /* the cgi stdout is always sending us http1.x header data first */ + wsi->hdr_state = LCHS_HEADER; + + /* add us to the pt list of active cgis */ + lwsl_debug("%s: adding cgi %p to list\n", __func__, wsi->cgi); + cgi->cgi_list = pt->cgi_list; + pt->cgi_list = cgi; + + /* prepare his CGI env */ + + n = 0; + + if (lws_is_ssl(wsi)) + env_array[n++] = "HTTPS=ON"; + if (wsi->u.hdr.ah) { + static const unsigned char meths[] = { + WSI_TOKEN_GET_URI, + WSI_TOKEN_POST_URI, + WSI_TOKEN_OPTIONS_URI, + WSI_TOKEN_PUT_URI, + WSI_TOKEN_PATCH_URI, + WSI_TOKEN_DELETE_URI, + WSI_TOKEN_CONNECT, + WSI_TOKEN_HEAD_URI, + #ifdef LWS_WITH_HTTP2 + WSI_TOKEN_HTTP_COLON_PATH, + #endif + }; + static const char * const meth_names[] = { + "GET", "POST", "OPTIONS", "PUT", "PATCH", "DELETE", + "CONNECT", ":path" + }; + + if (script_uri_path_len >= 0) + for (m = 0; m < ARRAY_SIZE(meths); m++) + if (lws_hdr_total_length(wsi, meths[m]) >= + script_uri_path_len) { + uritok = meths[m]; + break; + } + + if (script_uri_path_len < 0 && uritok < 0) + goto bail3; +// if (script_uri_path_len < 0) +// uritok = 0; + + if (uritok >= 0) { + lws_snprintf(cgi_path, sizeof(cgi_path) - 1, + "REQUEST_URI=%s", + lws_hdr_simple_ptr(wsi, uritok)); + cgi_path[sizeof(cgi_path) - 1] = '\0'; + env_array[n++] = cgi_path; + } + + if (m >= 0) { + env_array[n++] = p; + if (m < 8) + p += lws_snprintf(p, end - p, + "REQUEST_METHOD=%s", + meth_names[m]); + else + p += lws_snprintf(p, end - p, + "REQUEST_METHOD=%s", + lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_METHOD)); + p++; + } + + env_array[n++] = p; + p += lws_snprintf(p, end - p, "QUERY_STRING="); + /* dump the individual URI Arg parameters */ + m = 0; + while (script_uri_path_len >= 0) { + i = lws_hdr_copy_fragment(wsi, tok, sizeof(tok), + WSI_TOKEN_HTTP_URI_ARGS, m); + if (i < 0) + break; + t = tok; + while (*t && *t != '=' && p < end - 4) + *p++ = *t++; + if (*t == '=') + *p++ = *t++; + i = urlencode(t, i- (t - tok), p, end - p); + if (i > 0) { + p += i; + *p++ = '&'; + } + m++; + } + if (m) + p--; + *p++ = '\0'; + + if (script_uri_path_len >= 0) { + env_array[n++] = p; + p += lws_snprintf(p, end - p, "PATH_INFO=%s", + lws_hdr_simple_ptr(wsi, uritok) + + script_uri_path_len); + p++; + } + } + if (script_uri_path_len >= 0 && + lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_REFERER)) { + env_array[n++] = p; + p += lws_snprintf(p, end - p, "HTTP_REFERER=%s", + lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_REFERER)); + p++; + } + if (script_uri_path_len >= 0 && + lws_hdr_total_length(wsi, WSI_TOKEN_HOST)) { + env_array[n++] = p; + p += lws_snprintf(p, end - p, "HTTP_HOST=%s", + lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST)); + p++; + } + if (script_uri_path_len >= 0 && + lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COOKIE)) { + env_array[n++] = p; + p += lws_snprintf(p, end - p, "HTTP_COOKIE=%s", + lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COOKIE)); + p++; + } + if (script_uri_path_len >= 0 && + lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_USER_AGENT)) { + env_array[n++] = p; + p += lws_snprintf(p, end - p, "USER_AGENT=%s", + lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_USER_AGENT)); + p++; + } + if (script_uri_path_len >= 0 && + uritok == WSI_TOKEN_POST_URI) { + if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE)) { + env_array[n++] = p; + p += lws_snprintf(p, end - p, "CONTENT_TYPE=%s", + lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE)); + p++; + } + if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) { + env_array[n++] = p; + p += lws_snprintf(p, end - p, "CONTENT_LENGTH=%s", + lws_hdr_simple_ptr(wsi, + WSI_TOKEN_HTTP_CONTENT_LENGTH)); + p++; + } + } + env_array[n++] = "PATH=/bin:/usr/bin:/usr/local/bin:/var/www/cgi-bin"; + + env_array[n++] = p; + p += lws_snprintf(p, end - p, "SCRIPT_PATH=%s", exec_array[0]) + 1; + + while (mp_cgienv) { + env_array[n++] = p; + p += lws_snprintf(p, end - p, "%s=%s", mp_cgienv->name, + mp_cgienv->value); + lwsl_debug(" Applying mount-specific cgi env '%s'\n", + env_array[n - 1]); + p++; + mp_cgienv = mp_cgienv->next; + } + + env_array[n++] = "SERVER_SOFTWARE=libwebsockets"; + env_array[n] = NULL; + +#if 0 + for (m = 0; m < n; m++) + lwsl_err(" %s\n", env_array[m]); +#endif + + /* + * Actually having made the env, as a cgi we don't need the ah + * any more + */ + if (script_uri_path_len >= 0 && + lws_header_table_is_in_detachable_state(wsi)) + lws_header_table_detach(wsi, 0); + + /* we are ready with the redirection pipes... run the thing */ +#if !defined(LWS_HAVE_VFORK) || !defined(LWS_HAVE_EXECVPE) + cgi->pid = fork(); +#else + cgi->pid = vfork(); +#endif + if (cgi->pid < 0) { + lwsl_err("fork failed, errno %d", errno); + goto bail3; + } + +#if defined(__linux__) + prctl(PR_SET_PDEATHSIG, SIGTERM); +#endif + if (script_uri_path_len >= 0) + /* stops non-daemonized main processess getting SIGINT + * from TTY */ + setpgrp(); + + if (cgi->pid) { + /* we are the parent process */ + wsi->context->count_cgi_spawned++; + lwsl_debug("%s: cgi %p spawned PID %d\n", __func__, + cgi, cgi->pid); + + for (n = 0; n < 3; n++) + close(cgi->pipe_fds[n][!(n == 0)]); + + /* inform cgi owner of the child PID */ + n = user_callback_handle_rxflow(wsi->protocol->callback, wsi, + LWS_CALLBACK_CGI_PROCESS_ATTACH, + wsi->user_space, NULL, cgi->pid); + (void)n; + + return 0; + } + + /* somewhere we can at least read things and enter it */ + if (chdir("/tmp")) + lwsl_notice("%s: Failed to chdir\n", __func__); + + /* We are the forked process, redirect and kill inherited things. + * + * Because of vfork(), we cannot do anything that changes pages in + * the parent environment. Stuff that changes kernel state for the + * process is OK. Stuff that happens after the execvpe() is OK. + */ + + for (n = 0; n < 3; n++) { + if (dup2(cgi->pipe_fds[n][!(n == 0)], n) < 0) { + lwsl_err("%s: stdin dup2 failed\n", __func__); + goto bail3; + } + close(cgi->pipe_fds[n][!(n == 0)]); + } + +#if !defined(LWS_HAVE_VFORK) || !defined(LWS_HAVE_EXECVPE) + for (m = 0; m < n; m++) { + p = strchr(env_array[m], '='); + *p++ = '\0'; + setenv(env_array[m], p, 1); + } + execvp(exec_array[0], (char * const *)&exec_array[0]); +#else + execvpe(exec_array[0], (char * const *)&exec_array[0], &env_array[0]); +#endif + + exit(1); + +bail3: + /* drop us from the pt cgi list */ + pt->cgi_list = cgi->cgi_list; + + while (--n >= 0) + remove_wsi_socket_from_fds(wsi->cgi->stdwsi[n]); +bail2: + for (n = 0; n < 3; n++) + if (wsi->cgi->stdwsi[n]) + lws_free_wsi(cgi->stdwsi[n]); + +bail1: + for (n = 0; n < 3; n++) { + if (cgi->pipe_fds[n][0]) + close(cgi->pipe_fds[n][0]); + if (cgi->pipe_fds[n][1]) + close(cgi->pipe_fds[n][1]); + } + + lws_free_set_NULL(wsi->cgi); + + lwsl_err("%s: failed\n", __func__); + + return -1; +} + +/* we have to parse out these headers in the CGI output */ + +static const char * const significant_hdr[SIGNIFICANT_HDR_COUNT] = { + "content-length: ", + "location: ", + "status: ", + "transfer-encoding: chunked", +}; + +enum header_recode { + HR_NAME, + HR_WHITESPACE, + HR_ARG, + HR_CRLF, +}; + +LWS_VISIBLE LWS_EXTERN int +lws_cgi_write_split_stdout_headers(struct lws *wsi) +{ + int n, m, cmd; + unsigned char buf[LWS_PRE + 1024], *start = &buf[LWS_PRE], *p = start, + *end = &buf[sizeof(buf) - 1 - LWS_PRE], *name, + *value = NULL; + char c, hrs; + + if (!wsi->cgi) + return -1; + + while (wsi->hdr_state != LHCS_PAYLOAD) { + /* + * We have to separate header / finalize and payload chunks, + * since they need to be handled separately + */ + switch (wsi->hdr_state) { + case LHCS_RESPONSE: + lwsl_debug("LHCS_RESPONSE: issuing response %d\n", + wsi->cgi->response_code); + if (lws_add_http_header_status(wsi, + wsi->cgi->response_code, + &p, end)) + return 1; + if (!wsi->cgi->explicitly_chunked && + !wsi->cgi->content_length && + lws_add_http_header_by_token(wsi, + WSI_TOKEN_HTTP_TRANSFER_ENCODING, + (unsigned char *)"chunked", 7, &p, end)) + return 1; + if (!(wsi->http2_substream)) + if (lws_add_http_header_by_token(wsi, + WSI_TOKEN_CONNECTION, + (unsigned char *)"close", 5, + &p, end)) + return 1; + n = lws_write(wsi, start, p - start, + LWS_WRITE_HTTP_HEADERS | LWS_WRITE_NO_FIN); + + /* + * so we have a bunch of http/1 style ascii headers + * starting from wsi->cgi->headers_buf through + * wsi->cgi->headers_pos. These are OK for http/1 + * connections, but they're no good for http/2 conns. + * + * Let's redo them at headers_pos forward using the + * correct coding for http/1 or http/2 + */ + if (!wsi->http2_substream) + goto post_hpack_recode; + + p = wsi->cgi->headers_start; + wsi->cgi->headers_start = wsi->cgi->headers_pos; + wsi->cgi->headers_dumped = wsi->cgi->headers_start; + hrs = HR_NAME; + name = buf; + + while (p < wsi->cgi->headers_start) { + switch (hrs) { + case HR_NAME: + /* + * in http/2 upper-case header names + * are illegal. So convert to lower- + * case. + */ + if (name - buf > 64) + return -1; + if (*p != ':') { + if (*p >= 'A' && *p <= 'Z') + *name++ = (*p++) + + ('a' - 'A'); + else + *name++ = *p++; + } else { + p++; + *name++ = '\0'; + value = name; + hrs = HR_WHITESPACE; + } + break; + case HR_WHITESPACE: + if (*p == ' ') { + p++; + break; + } + hrs = HR_ARG; + /* fallthru */ + case HR_ARG: + if (name > end - 64) + return -1; + + if (*p != '\x0a' && *p != '\x0d') { + *name++ = *p++; + break; + } + hrs = HR_CRLF; + /* fallthru */ + case HR_CRLF: + if ((*p != '\x0a' && *p != '\x0d') || + p + 1 == wsi->cgi->headers_start) { + *name = '\0'; + if ((strcmp((const char *)buf, + "transfer-encoding") + )) { + lwsl_debug("+ %s: %s\n", + buf, value); + if ( + lws_add_http_header_by_name(wsi, buf, + (unsigned char *)value, name - value, + (unsigned char **)&wsi->cgi->headers_pos, + (unsigned char *)wsi->cgi->headers_end)) + return 1; + hrs = HR_NAME; + name = buf; + break; + } + } + p++; + break; + } + } +post_hpack_recode: + /* finalize cached headers before dumping them */ + if (lws_finalize_http_header(wsi, + (unsigned char **)&wsi->cgi->headers_pos, + (unsigned char *)wsi->cgi->headers_end)) { + + lwsl_notice("finalize failed\n"); + return -1; + } + + wsi->hdr_state = LHCS_DUMP_HEADERS; + wsi->reason_bf |= LWS_CB_REASON_AUX_BF__CGI_HEADERS; + lws_callback_on_writable(wsi); + /* back to the loop for writeability again */ + return 0; + + case LHCS_DUMP_HEADERS: + + n = wsi->cgi->headers_pos - wsi->cgi->headers_dumped; + if (n > 512) + n = 512; + + lwsl_debug("LHCS_DUMP_HEADERS: %d\n", n); + + cmd = LWS_WRITE_HTTP_HEADERS_CONTINUATION; + if (wsi->cgi->headers_dumped + n != + wsi->cgi->headers_pos) { + lwsl_notice("adding no fin flag\n"); + cmd |= LWS_WRITE_NO_FIN; + } + + m = lws_write(wsi, + (unsigned char *)wsi->cgi->headers_dumped, + n, cmd); + if (m < 0) { + lwsl_debug("%s: write says %d\n", __func__, m); + return -1; + } + wsi->cgi->headers_dumped += n; + if (wsi->cgi->headers_dumped == wsi->cgi->headers_pos) { + wsi->hdr_state = LHCS_PAYLOAD; + lws_free_set_NULL(wsi->cgi->headers_buf); + lwsl_debug("freed cgi headers\n"); + } else { + wsi->reason_bf |= LWS_CB_REASON_AUX_BF__CGI_HEADERS; + lws_callback_on_writable(wsi); + } + + /* writeability becomes uncertain now we wrote + * something, we must return to the event loop + */ + return 0; + } + + if (!wsi->cgi->headers_buf) { + /* if we don't already have a headers buf, cook one up */ + n = 2048; + if (wsi->http2_substream) + n = 4096; + wsi->cgi->headers_buf = lws_malloc(n + LWS_PRE, + "cgi hdr buf"); + if (!wsi->cgi->headers_buf) { + lwsl_err("OOM\n"); + return -1; + } + + lwsl_debug("allocated cgi hdrs\n"); + wsi->cgi->headers_start = wsi->cgi->headers_buf + LWS_PRE; + wsi->cgi->headers_pos = wsi->cgi->headers_start; + wsi->cgi->headers_dumped = wsi->cgi->headers_pos; + wsi->cgi->headers_end = wsi->cgi->headers_buf + n - 1; + + for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++) { + wsi->cgi->match[n] = 0; + wsi->cgi->lp = 0; + } + } + + n = lws_get_socket_fd(wsi->cgi->stdwsi[LWS_STDOUT]); + if (n < 0) + return -1; + n = read(n, &c, 1); + if (n < 0) { + if (errno != EAGAIN) { + lwsl_debug("%s: read says %d\n", __func__, n); + return -1; + } + else + n = 0; + + if (wsi->cgi->headers_pos >= wsi->cgi->headers_end - 4) { + lwsl_notice("CGI hdrs > buf size\n"); + + return -1; + } + } + if (!n) + goto agin; + + lwsl_debug("-- 0x%02X %c %d %d\n", (unsigned char)c, c, + wsi->cgi->match[1], wsi->hdr_state); + if (!c) + return -1; + switch (wsi->hdr_state) { + case LCHS_HEADER: + hdr: + for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++) { + /* + * significant headers with + * numeric decimal payloads + */ + if (!significant_hdr[n][wsi->cgi->match[n]] && + (c >= '0' && c <= '9') && + wsi->cgi->lp < sizeof(wsi->cgi->l) - 1) { + wsi->cgi->l[wsi->cgi->lp++] = c; + wsi->cgi->l[wsi->cgi->lp] = '\0'; + switch (n) { + case SIGNIFICANT_HDR_CONTENT_LENGTH: + wsi->cgi->content_length = + atoll(wsi->cgi->l); + break; + case SIGNIFICANT_HDR_STATUS: + wsi->cgi->response_code = + atol(wsi->cgi->l); + lwsl_debug("Status set to %d\n", + wsi->cgi->response_code); + break; + default: + break; + } + } + /* hits up to the NUL are sticky until next hdr */ + if (significant_hdr[n][wsi->cgi->match[n]]) { + if (tolower(c) == + significant_hdr[n][wsi->cgi->match[n]]) + wsi->cgi->match[n]++; + else + wsi->cgi->match[n] = 0; + } + } + + /* some cgi only send us \x0a for EOL */ + if (c == '\x0a') { + wsi->hdr_state = LCHS_SINGLE_0A; + *wsi->cgi->headers_pos++ = '\x0d'; + } + *wsi->cgi->headers_pos++ = c; + if (c == '\x0d') + wsi->hdr_state = LCHS_LF1; + + if (wsi->hdr_state != LCHS_HEADER && + !significant_hdr[SIGNIFICANT_HDR_TRANSFER_ENCODING] + [wsi->cgi->match[ + SIGNIFICANT_HDR_TRANSFER_ENCODING]]) { + lwsl_debug("cgi produced chunked\n"); + wsi->cgi->explicitly_chunked = 1; + } + + /* presence of Location: mandates 302 retcode */ + if (wsi->hdr_state != LCHS_HEADER && + !significant_hdr[SIGNIFICANT_HDR_LOCATION][ + wsi->cgi->match[SIGNIFICANT_HDR_LOCATION]]) { + lwsl_debug("CGI: Location hdr seen\n"); + wsi->cgi->response_code = 302; + } + + break; + case LCHS_LF1: + *wsi->cgi->headers_pos++ = c; + if (c == '\x0a') { + wsi->hdr_state = LCHS_CR2; + break; + } + /* we got \r[^\n]... it's unreasonable */ + lwsl_debug("%s: funny CRLF 0x%02X\n", __func__, + (unsigned char)c); + return -1; + + case LCHS_CR2: + if (c == '\x0d') { + /* drop the \x0d */ + wsi->hdr_state = LCHS_LF2; + break; + } + wsi->hdr_state = LCHS_HEADER; + for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++) + wsi->cgi->match[n] = 0; + wsi->cgi->lp = 0; + goto hdr; + + case LCHS_LF2: + case LCHS_SINGLE_0A: + m = wsi->hdr_state; + if (c == '\x0a') { + lwsl_debug("Content-Length: %lld\n", + (unsigned long long) + wsi->cgi->content_length); + wsi->hdr_state = LHCS_RESPONSE; + /* + * drop the \0xa ... finalize + * will add it if needed (HTTP/1) + */ + break; + } + if (m == LCHS_LF2) + /* we got \r\n\r[^\n]... unreasonable */ + return -1; + /* we got \x0anext header, it's reasonable */ + *wsi->cgi->headers_pos++ = c; + wsi->hdr_state = LCHS_HEADER; + for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++) + wsi->cgi->match[n] = 0; + wsi->cgi->lp = 0; + break; + case LHCS_PAYLOAD: + break; + } + +agin: + /* ran out of input, ended the hdrs, or filled up the hdrs buf */ + if (!n || wsi->hdr_state == LHCS_PAYLOAD) + return 0; + } + + /* payload processing */ + + m = !wsi->cgi->explicitly_chunked && !wsi->cgi->content_length; + n = lws_get_socket_fd(wsi->cgi->stdwsi[LWS_STDOUT]); + if (n < 0) + return -1; + n = read(n, start, sizeof(buf) - LWS_PRE - + (m ? LWS_HTTP_CHUNK_HDR_SIZE : 0)); + + if (n < 0 && errno != EAGAIN) { + lwsl_debug("%s: stdout read says %d\n", __func__, n); + return -1; + } + if (n > 0) { + if (!wsi->http2_substream && m) { + char chdr[LWS_HTTP_CHUNK_HDR_SIZE]; + m = lws_snprintf(chdr, LWS_HTTP_CHUNK_HDR_SIZE - 3, + "%X\x0d\x0a", n); + memmove(start + m, start, n); + memcpy(start, chdr, m); + memcpy(start + m + n, "\x0d\x0a", 2); + n += m + 2; + } + cmd = LWS_WRITE_HTTP; + if (wsi->cgi->content_length_seen + n == wsi->cgi->content_length) + cmd = LWS_WRITE_HTTP_FINAL; + m = lws_write(wsi, (unsigned char *)start, n, cmd); + //lwsl_notice("write %d\n", m); + if (m < 0) { + lwsl_debug("%s: stdout write says %d\n", __func__, m); + return -1; + } + wsi->cgi->content_length_seen += n; + } else { + if (wsi->cgi_stdout_zero_length) { + lwsl_debug("%s: stdout is POLLHUP'd\n", __func__); + if (wsi->http2_substream) + m = lws_write(wsi, (unsigned char *)start, 0, + LWS_WRITE_HTTP_FINAL); + return 1; + } + wsi->cgi_stdout_zero_length = 1; + } + return 0; +} + +LWS_VISIBLE LWS_EXTERN int +lws_cgi_kill(struct lws *wsi) +{ + struct lws_cgi_args args; + int status, n; + + lwsl_debug("%s: %p\n", __func__, wsi); + + if (!wsi->cgi) + return 0; + + if (wsi->cgi->pid > 0) { + n = waitpid(wsi->cgi->pid, &status, WNOHANG); + if (n > 0) { + lwsl_debug("%s: PID %d reaped\n", __func__, + wsi->cgi->pid); + goto handled; + } + /* kill the process group */ + n = kill(-wsi->cgi->pid, SIGTERM); + lwsl_debug("%s: SIGTERM child PID %d says %d (errno %d)\n", + __func__, wsi->cgi->pid, n, errno); + if (n < 0) { + /* + * hum seen errno=3 when process is listed in ps, + * it seems we don't always retain process grouping + * + * Direct these fallback attempt to the exact child + */ + n = kill(wsi->cgi->pid, SIGTERM); + if (n < 0) { + n = kill(wsi->cgi->pid, SIGPIPE); + if (n < 0) { + n = kill(wsi->cgi->pid, SIGKILL); + if (n < 0) + lwsl_err("%s: SIGKILL PID %d " + "failed errno %d " + "(maybe zombie)\n", + __func__, + wsi->cgi->pid, errno); + } + } + } + /* He could be unkillable because he's a zombie */ + n = 1; + while (n > 0) { + n = waitpid(-wsi->cgi->pid, &status, WNOHANG); + if (n > 0) + lwsl_debug("%s: reaped PID %d\n", __func__, n); + if (n <= 0) { + n = waitpid(wsi->cgi->pid, &status, WNOHANG); + if (n > 0) + lwsl_debug("%s: reaped PID %d\n", + __func__, n); + } + } + } + +handled: + args.stdwsi = &wsi->cgi->stdwsi[0]; + + if (wsi->cgi->pid != -1) { + n = user_callback_handle_rxflow(wsi->protocol->callback, wsi, + LWS_CALLBACK_CGI_TERMINATED, + wsi->user_space, + (void *)&args, wsi->cgi->pid); + wsi->cgi->pid = -1; + if (n && !wsi->cgi->being_closed) + lws_close_free_wsi(wsi, 0); + } + + return 0; +} + +LWS_EXTERN int +lws_cgi_kill_terminated(struct lws_context_per_thread *pt) +{ + struct lws_cgi **pcgi, *cgi = NULL; + int status, n = 1; + + while (n > 0) { + /* find finished guys but don't reap yet */ + n = waitpid(-1, &status, WNOHANG); + if (n <= 0) + continue; + lwsl_debug("%s: observed PID %d terminated\n", __func__, n); + + pcgi = &pt->cgi_list; + + /* check all the subprocesses on the cgi list */ + while (*pcgi) { + /* get the next one first as list may change */ + cgi = *pcgi; + pcgi = &(*pcgi)->cgi_list; + + if (cgi->pid <= 0) + continue; + + /* finish sending cached headers */ + if (cgi->headers_buf) + continue; + + /* wait for stdout to be drained */ + if (cgi->content_length > cgi->content_length_seen) + continue; + + if (cgi->content_length) { + lwsl_debug("%s: wsi %p: expected content length seen: %lld\n", + __func__, cgi->wsi, + (unsigned long long)cgi->content_length_seen); + } + + /* reap it */ + waitpid(n, &status, WNOHANG); + /* + * he's already terminated so no need for kill() + * but we should do the terminated cgi callback + * and close him if he's not already closing + */ + if (n == cgi->pid) { + lwsl_debug("%s: found PID %d on cgi list\n", + __func__, n); + + if (!cgi->content_length) { + /* + * well, if he sends chunked... + * give him 2s after the + * cgi terminated to send buffered + */ + cgi->chunked_grace++; + continue; + } + + /* defeat kill() */ + cgi->pid = 0; + lws_cgi_kill(cgi->wsi); + + break; + } + cgi = NULL; + } + /* if not found on the cgi list, as he's one of ours, reap */ + if (!cgi) { + lwsl_debug("%s: reading PID %d although no cgi match\n", + __func__, n); + waitpid(n, &status, WNOHANG); + } + } + + pcgi = &pt->cgi_list; + + /* check all the subprocesses on the cgi list */ + while (*pcgi) { + /* get the next one first as list may change */ + cgi = *pcgi; + pcgi = &(*pcgi)->cgi_list; + + if (cgi->pid <= 0) + continue; + + /* we deferred killing him after reaping his PID */ + if (cgi->chunked_grace) { + cgi->chunked_grace++; + if (cgi->chunked_grace < 2) + continue; + goto finish_him; + } + + /* finish sending cached headers */ + if (cgi->headers_buf) + continue; + + /* wait for stdout to be drained */ + if (cgi->content_length > cgi->content_length_seen) + continue; + + if (cgi->content_length) + lwsl_debug("%s: wsi %p: expected content length seen: %lld\n", + __func__, cgi->wsi, + (unsigned long long)cgi->content_length_seen); + + /* reap it */ + if (waitpid(cgi->pid, &status, WNOHANG) > 0) { + + if (!cgi->content_length) { + /* + * well, if he sends chunked... + * give him 2s after the + * cgi terminated to send buffered + */ + cgi->chunked_grace++; + continue; + } +finish_him: + lwsl_debug("%s: found PID %d on cgi list\n", + __func__, cgi->pid); + + /* defeat kill() */ + cgi->pid = 0; + lws_cgi_kill(cgi->wsi); + + break; + } + } + + return 0; +} + +LWS_VISIBLE LWS_EXTERN struct lws * +lws_cgi_get_stdwsi(struct lws *wsi, enum lws_enum_stdinouterr ch) +{ + if (!wsi->cgi) + return NULL; + + return wsi->cgi->stdwsi[ch]; +} + +void +lws_cgi_remove_and_kill(struct lws *wsi) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + struct lws_cgi **pcgi = &pt->cgi_list; + + /* remove us from the cgi list */ + lwsl_debug("%s: remove cgi %p from list\n", __func__, wsi->cgi); + while (*pcgi) { + if (*pcgi == wsi->cgi) { + /* drop us from the pt cgi list */ + *pcgi = (*pcgi)->cgi_list; + break; + } + pcgi = &(*pcgi)->cgi_list; + } + if (wsi->cgi->headers_buf) { + lwsl_debug("close: freed cgi headers\n"); + lws_free_set_NULL(wsi->cgi->headers_buf); + } + /* we have a cgi going, we must kill it */ + wsi->cgi->being_closed = 1; + lws_cgi_kill(wsi); +} diff --git a/lib/daemonize.c b/lib/server/daemonize.c similarity index 100% rename from lib/daemonize.c rename to lib/server/daemonize.c diff --git a/lib/fops-zip.c b/lib/server/fops-zip.c similarity index 100% rename from lib/fops-zip.c rename to lib/server/fops-zip.c diff --git a/lib/lejp-conf.c b/lib/server/lejp-conf.c similarity index 99% rename from lib/lejp-conf.c rename to lib/server/lejp-conf.c index e84f4b24c..a60573718 100644 --- a/lib/lejp-conf.c +++ b/lib/server/lejp-conf.c @@ -20,7 +20,7 @@ */ #include "private-libwebsockets.h" -#include "lejp.h" +#include "../misc/lejp.h" #ifndef _WIN32 /* this is needed for Travis CI */ diff --git a/lib/server/lws-spa.c b/lib/server/lws-spa.c new file mode 100644 index 000000000..73117c504 --- /dev/null +++ b/lib/server/lws-spa.c @@ -0,0 +1,586 @@ +/* + * libwebsockets - Stateful urldecode for POST + * + * Copyright (C) 2010-2017 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "private-libwebsockets.h" + +#define LWS_MAX_ELEM_NAME 32 + +enum urldecode_stateful { + US_NAME, + US_IDLE, + US_PC1, + US_PC2, + + MT_LOOK_BOUND_IN, + MT_HNAME, + MT_DISP, + MT_TYPE, + MT_IGNORE1, + MT_IGNORE2, +}; + +static const char * const mp_hdr[] = { + "content-disposition: ", + "content-type: ", + "\x0d\x0a" +}; + +typedef int (*lws_urldecode_stateful_cb)(void *data, + const char *name, char **buf, int len, int final); + +struct lws_urldecode_stateful { + char *out; + void *data; + char name[LWS_MAX_ELEM_NAME]; + char temp[LWS_MAX_ELEM_NAME]; + char content_type[32]; + char content_disp[32]; + char content_disp_filename[256]; + char mime_boundary[128]; + int out_len; + int pos; + int hdr_idx; + int mp; + int sum; + + unsigned int multipart_form_data:1; + unsigned int inside_quote:1; + unsigned int subname:1; + unsigned int boundary_real_crlf:1; + + enum urldecode_stateful state; + + lws_urldecode_stateful_cb output; +}; + +static struct lws_urldecode_stateful * +lws_urldecode_s_create(struct lws *wsi, char *out, int out_len, void *data, + lws_urldecode_stateful_cb output) +{ + struct lws_urldecode_stateful *s = lws_zalloc(sizeof(*s), + "stateful urldecode"); + char buf[200], *p; + int m = 0; + + if (!s) + return NULL; + + s->out = out; + s->out_len = out_len; + s->output = output; + s->pos = 0; + s->sum = 0; + s->mp = 0; + s->state = US_NAME; + s->name[0] = '\0'; + s->data = data; + + if (lws_hdr_copy(wsi, buf, sizeof(buf), + WSI_TOKEN_HTTP_CONTENT_TYPE) > 0) { + /* multipart/form-data; boundary=----WebKitFormBoundarycc7YgAPEIHvgE9Bf */ + + if (!strncmp(buf, "multipart/form-data", 19)) { + s->multipart_form_data = 1; + s->state = MT_LOOK_BOUND_IN; + s->mp = 2; + p = strstr(buf, "boundary="); + if (p) { + p += 9; + s->mime_boundary[m++] = '\x0d'; + s->mime_boundary[m++] = '\x0a'; + s->mime_boundary[m++] = '-'; + s->mime_boundary[m++] = '-'; + while (m < sizeof(s->mime_boundary) - 1 && + *p && *p != ' ') + s->mime_boundary[m++] = *p++; + + s->mime_boundary[m] = '\0'; + + lwsl_notice("boundary '%s'\n", s->mime_boundary); + } + } + } + + return s; +} + +static int +lws_urldecode_s_process(struct lws_urldecode_stateful *s, const char *in, + int len) +{ + int n, m, hit = 0; + char c, was_end = 0; + + while (len--) { + if (s->pos == s->out_len - s->mp - 1) { + if (s->output(s->data, s->name, &s->out, s->pos, 0)) + return -1; + + was_end = s->pos; + s->pos = 0; + } + switch (s->state) { + + /* states for url arg style */ + + case US_NAME: + s->inside_quote = 0; + if (*in == '=') { + s->name[s->pos] = '\0'; + s->pos = 0; + s->state = US_IDLE; + in++; + continue; + } + if (*in == '&') { + s->name[s->pos] = '\0'; + if (s->output(s->data, s->name, &s->out, + s->pos, 1)) + return -1; + s->pos = 0; + s->state = US_IDLE; + in++; + continue; + } + if (s->pos >= sizeof(s->name) - 1) { + lwsl_notice("Name too long\n"); + return -1; + } + s->name[s->pos++] = *in++; + break; + case US_IDLE: + if (*in == '%') { + s->state++; + in++; + continue; + } + if (*in == '&') { + s->out[s->pos] = '\0'; + if (s->output(s->data, s->name, &s->out, + s->pos, 1)) + return -1; + s->pos = 0; + s->state = US_NAME; + in++; + continue; + } + if (*in == '+') { + in++; + s->out[s->pos++] = ' '; + continue; + } + s->out[s->pos++] = *in++; + break; + case US_PC1: + n = char_to_hex(*in); + if (n < 0) + return -1; + + in++; + s->sum = n << 4; + s->state++; + break; + + case US_PC2: + n = char_to_hex(*in); + if (n < 0) + return -1; + + in++; + s->out[s->pos++] = s->sum | n; + s->state = US_IDLE; + break; + + + /* states for multipart / mime style */ + + case MT_LOOK_BOUND_IN: +retry_as_first: + if (*in == s->mime_boundary[s->mp] && + s->mime_boundary[s->mp]) { + in++; + s->mp++; + if (!s->mime_boundary[s->mp]) { + s->mp = 0; + s->state = MT_IGNORE1; + + if (s->pos || was_end) + if (s->output(s->data, s->name, + &s->out, s->pos, 1)) + return -1; + + s->pos = 0; + + s->content_disp[0] = '\0'; + s->name[0] = '\0'; + s->content_disp_filename[0] = '\0'; + s->boundary_real_crlf = 1; + } + continue; + } + if (s->mp) { + n = 0; + if (!s->boundary_real_crlf) + n = 2; + + memcpy(s->out + s->pos, s->mime_boundary + n, + s->mp - n); + s->pos += s->mp; + s->mp = 0; + goto retry_as_first; + } + + s->out[s->pos++] = *in; + in++; + s->mp = 0; + break; + + case MT_HNAME: + m = 0; + c =*in; + if (c >= 'A' && c <= 'Z') + c += 'a' - 'A'; + for (n = 0; n < ARRAY_SIZE(mp_hdr); n++) + if (c == mp_hdr[n][s->mp]) { + m++; + hit = n; + } + in++; + if (!m) { + s->mp = 0; + continue; + } + + s->mp++; + if (m != 1) + continue; + + if (mp_hdr[hit][s->mp]) + continue; + + s->mp = 0; + s->temp[0] = '\0'; + s->subname = 0; + + if (hit == 2) + s->state = MT_LOOK_BOUND_IN; + else + s->state += hit + 1; + break; + + case MT_DISP: + /* form-data; name="file"; filename="t.txt" */ + + if (*in == '\x0d') { + if (s->content_disp_filename[0]) + if (s->output(s->data, s->name, + &s->out, s->pos, + LWS_UFS_OPEN)) + return -1; + s->state = MT_IGNORE2; + goto done; + } + if (*in == ';') { + s->subname = 1; + s->temp[0] = '\0'; + s->mp = 0; + goto done; + } + + if (*in == '\"') { + s->inside_quote ^= 1; + goto done; + } + + if (s->subname) { + if (*in == '=') { + s->temp[s->mp] = '\0'; + s->subname = 0; + s->mp = 0; + goto done; + } + if (s->mp < sizeof(s->temp) - 1 && + (*in != ' ' || s->inside_quote)) + s->temp[s->mp++] = *in; + goto done; + } + + if (!s->temp[0]) { + if (s->mp < sizeof(s->content_disp) - 1) + s->content_disp[s->mp++] = *in; + s->content_disp[s->mp] = '\0'; + goto done; + } + + if (!strcmp(s->temp, "name")) { + if (s->mp < sizeof(s->name) - 1) + s->name[s->mp++] = *in; + s->name[s->mp] = '\0'; + goto done; + } + + if (!strcmp(s->temp, "filename")) { + if (s->mp < sizeof(s->content_disp_filename) - 1) + s->content_disp_filename[s->mp++] = *in; + s->content_disp_filename[s->mp] = '\0'; + goto done; + } +done: + in++; + break; + + case MT_TYPE: + if (*in == '\x0d') + s->state = MT_IGNORE2; + else { + if (s->mp < sizeof(s->content_type) - 1) + s->content_type[s->mp++] = *in; + s->content_type[s->mp] = '\0'; + } + in++; + break; + + case MT_IGNORE1: + if (*in == '\x0d') + s->state = MT_IGNORE2; + in++; + break; + + case MT_IGNORE2: + s->mp = 0; + if (*in == '\x0a') + s->state = MT_HNAME; + in++; + break; + } + } + + return 0; +} + +static int +lws_urldecode_s_destroy(struct lws_urldecode_stateful *s) +{ + int ret = 0; + + if (s->state != US_IDLE) + ret = -1; + + if (!ret) + if (s->output(s->data, s->name, &s->out, s->pos, 1)) + ret = -1; + + lws_free(s); + + return ret; +} + +struct lws_spa { + struct lws_urldecode_stateful *s; + lws_spa_fileupload_cb opt_cb; + const char * const *param_names; + int count_params; + char **params; + int *param_length; + void *opt_data; + + char *storage; + char *end; + int max_storage; + + char finalized; +}; + +static int +lws_urldecode_spa_lookup(struct lws_spa *spa, + const char *name) +{ + int n; + + for (n = 0; n < spa->count_params; n++) + if (!strcmp(spa->param_names[n], name)) + return n; + + return -1; +} + +static int +lws_urldecode_spa_cb(void *data, const char *name, char **buf, int len, + int final) +{ + struct lws_spa *spa = + (struct lws_spa *)data; + int n; + + if (spa->s->content_disp_filename[0]) { + if (spa->opt_cb) { + n = spa->opt_cb(spa->opt_data, name, + spa->s->content_disp_filename, + *buf, len, final); + + if (n < 0) + return -1; + } + return 0; + } + n = lws_urldecode_spa_lookup(spa, name); + + if (n == -1 || !len) /* unrecognized */ + return 0; + + if (!spa->params[n]) + spa->params[n] = *buf; + + if ((*buf) + len >= spa->end) { + lwsl_notice("%s: exceeded storage\n", __func__); + return -1; + } + + spa->param_length[n] += len; + + /* move it on inside storage */ + (*buf) += len; + *((*buf)++) = '\0'; + + spa->s->out_len -= len + 1; + + return 0; +} + +LWS_VISIBLE LWS_EXTERN struct lws_spa * +lws_spa_create(struct lws *wsi, const char * const *param_names, + int count_params, int max_storage, + lws_spa_fileupload_cb opt_cb, void *opt_data) +{ + struct lws_spa *spa = lws_zalloc(sizeof(*spa), "spa"); + + if (!spa) + return NULL; + + spa->param_names = param_names; + spa->count_params = count_params; + spa->max_storage = max_storage; + spa->opt_cb = opt_cb; + spa->opt_data = opt_data; + + spa->storage = lws_malloc(max_storage, "spa"); + if (!spa->storage) + goto bail2; + spa->end = spa->storage + max_storage - 1; + + spa->params = lws_zalloc(sizeof(char *) * count_params, "spa params"); + if (!spa->params) + goto bail3; + + spa->s = lws_urldecode_s_create(wsi, spa->storage, max_storage, spa, + lws_urldecode_spa_cb); + if (!spa->s) + goto bail4; + + spa->param_length = lws_zalloc(sizeof(int) * count_params, + "spa param len"); + if (!spa->param_length) + goto bail5; + + lwsl_info("%s: Created SPA %p\n", __func__, spa); + + return spa; + +bail5: + lws_urldecode_s_destroy(spa->s); +bail4: + lws_free(spa->params); +bail3: + lws_free(spa->storage); +bail2: + lws_free(spa); + + return NULL; +} + +LWS_VISIBLE LWS_EXTERN int +lws_spa_process(struct lws_spa *ludspa, const char *in, int len) +{ + if (!ludspa) { + lwsl_err("%s: NULL spa\n", __func__); + return -1; + } + /* we reject any junk after the last part arrived and we finalized */ + if (ludspa->finalized) + return 0; + + return lws_urldecode_s_process(ludspa->s, in, len); +} + +LWS_VISIBLE LWS_EXTERN int +lws_spa_get_length(struct lws_spa *ludspa, int n) +{ + if (n >= ludspa->count_params) + return 0; + + return ludspa->param_length[n]; +} + +LWS_VISIBLE LWS_EXTERN const char * +lws_spa_get_string(struct lws_spa *ludspa, int n) +{ + if (n >= ludspa->count_params) + return NULL; + + return ludspa->params[n]; +} + +LWS_VISIBLE LWS_EXTERN int +lws_spa_finalize(struct lws_spa *spa) +{ + if (spa->s) { + lws_urldecode_s_destroy(spa->s); + spa->s = NULL; + } + + spa->finalized = 1; + + return 0; +} + +LWS_VISIBLE LWS_EXTERN int +lws_spa_destroy(struct lws_spa *spa) +{ + int n = 0; + + lwsl_notice("%s: destroy spa %p\n", __func__, spa); + + if (spa->s) + lws_urldecode_s_destroy(spa->s); + + lwsl_debug("%s %p %p %p %p\n", __func__, + spa->param_length, + spa->params, + spa->storage, + spa); + + lws_free(spa->param_length); + lws_free(spa->params); + lws_free(spa->storage); + lws_free(spa); + + return n; +} diff --git a/lib/parsers.c b/lib/server/parsers.c similarity index 100% rename from lib/parsers.c rename to lib/server/parsers.c diff --git a/lib/server/peer-limits.c b/lib/server/peer-limits.c new file mode 100644 index 000000000..daaae9c96 --- /dev/null +++ b/lib/server/peer-limits.c @@ -0,0 +1,252 @@ +/* + * libwebsockets - peer limits tracking + * + * Copyright (C) 2010-2017 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "private-libwebsockets.h" + +/* requires context->lock */ +static void +__lws_peer_remove_from_peer_wait_list(struct lws_context *context, + struct lws_peer *peer) +{ + struct lws_peer *df; + + lws_start_foreach_llp(struct lws_peer **, p, context->peer_wait_list) { + if (*p == peer) { + df = *p; + + *p = df->peer_wait_list; + df->peer_wait_list = NULL; + + return; + } + } lws_end_foreach_llp(p, peer_wait_list); +} + +/* requires context->lock */ +static void +__lws_peer_add_to_peer_wait_list(struct lws_context *context, + struct lws_peer *peer) +{ + __lws_peer_remove_from_peer_wait_list(context, peer); + + peer->peer_wait_list = context->peer_wait_list; + context->peer_wait_list = peer; +} + + +struct lws_peer * +lws_get_or_create_peer(struct lws_vhost *vhost, lws_sockfd_type sockfd) +{ + struct lws_context *context = vhost->context; + socklen_t rlen = 0; + void *q; + uint8_t *q8; + struct lws_peer *peer; + uint32_t hash = 0; + int n, af = AF_INET; + struct sockaddr_storage addr; + +#ifdef LWS_WITH_IPV6 + if (LWS_IPV6_ENABLED(vhost)) { + af = AF_INET6; + } +#endif + rlen = sizeof(addr); + if (getpeername(sockfd, (struct sockaddr*)&addr, &rlen)) + return NULL; + + if (af == AF_INET) { + struct sockaddr_in *s = (struct sockaddr_in *)&addr; + q = &s->sin_addr; + rlen = sizeof(s->sin_addr); + } else +#ifdef LWS_WITH_IPV6 + { + struct sockaddr_in6 *s = (struct sockaddr_in6 *)&addr; + q = &s->sin6_addr; + rlen = sizeof(s->sin6_addr); + } +#else + return NULL; +#endif + + q8 = q; + for (n = 0; n < rlen; n++) + hash = (((hash << 4) | (hash >> 28)) * n) ^ q8[n]; + + hash = hash % context->pl_hash_elements; + + lws_context_lock(context); /* <====================================== */ + + lws_start_foreach_ll(struct lws_peer *, peerx, + context->pl_hash_table[hash]) { + if (peerx->af == af && !memcmp(q, peerx->addr, rlen)) { + lws_context_unlock(context); /* === */ + return peerx; + } + } lws_end_foreach_ll(peerx, next); + + lwsl_info("%s: creating new peer\n", __func__); + + peer = lws_zalloc(sizeof(*peer), "peer"); + if (!peer) { + lws_context_unlock(context); /* === */ + return NULL; + } + + context->count_peers++; + peer->next = context->pl_hash_table[hash]; + peer->hash = hash; + peer->af = af; + context->pl_hash_table[hash] = peer; + memcpy(peer->addr, q, rlen); + time(&peer->time_created); + /* + * On creation, the peer has no wsi attached, so is created on the + * wait list. When a wsi is added it is removed from the wait list. + */ + time(&peer->time_closed_all); + __lws_peer_add_to_peer_wait_list(context, peer); + + lws_context_unlock(context); /* ====================================> */ + + return peer; +} + +/* requires context->lock */ +static int +__lws_peer_destroy(struct lws_context *context, struct lws_peer *peer) +{ + lws_start_foreach_llp(struct lws_peer **, p, + context->pl_hash_table[peer->hash]) { + if (*p == peer) { + struct lws_peer *df = *p; + *p = df->next; + lws_free(df); + context->count_peers--; + + return 0; + } + } lws_end_foreach_llp(p, next); + + return 1; +} + +void +lws_peer_cull_peer_wait_list(struct lws_context *context) +{ + struct lws_peer *df; + time_t t; + + time(&t); + + if (context->next_cull && t < context->next_cull) + return; + + lws_context_lock(context); /* <====================================== */ + + context->next_cull = t + 5; + + lws_start_foreach_llp(struct lws_peer **, p, context->peer_wait_list) { + if (t - (*p)->time_closed_all > 10) { + df = *p; + + /* remove us from the peer wait list */ + *p = df->peer_wait_list; + df->peer_wait_list = NULL; + + __lws_peer_destroy(context, df); + continue; /* we already point to next, if any */ + } + } lws_end_foreach_llp(p, peer_wait_list); + + lws_context_unlock(context); /* ====================================> */ +} + +void +lws_peer_add_wsi(struct lws_context *context, struct lws_peer *peer, + struct lws *wsi) +{ + if (!peer) + return; + + lws_context_lock(context); /* <====================================== */ + + peer->count_wsi++; + wsi->peer = peer; + __lws_peer_remove_from_peer_wait_list(context, peer); + + lws_context_unlock(context); /* ====================================> */ +} + +void +lws_peer_track_wsi_close(struct lws_context *context, struct lws_peer *peer) +{ + if (!peer) + return; + + lws_context_lock(context); /* <====================================== */ + + assert(peer->count_wsi); + peer->count_wsi--; + + if (!peer->count_wsi && !peer->count_ah) { + /* + * in order that we can accumulate peer activity correctly + * allowing for periods when the peer has no connections, + * we don't synchronously destroy the peer when his last + * wsi closes. Instead we mark the time his last wsi + * closed and add him to a peer_wait_list to be reaped + * later if no further activity is coming. + */ + time(&peer->time_closed_all); + __lws_peer_add_to_peer_wait_list(context, peer); + } + + lws_context_unlock(context); /* ====================================> */ +} + +int +lws_peer_confirm_ah_attach_ok(struct lws_context *context, struct lws_peer *peer) +{ + if (!peer) + return 0; + + if (context->ip_limit_ah && peer->count_ah >= context->ip_limit_ah) { + lwsl_info("peer reached ah limit %d, deferring\n", + context->ip_limit_ah); + + return 1; + } + + return 0; +} + +void +lws_peer_track_ah_detach(struct lws_context *context, struct lws_peer *peer) +{ + if (!peer) + return; + + assert(peer->count_ah); + peer->count_ah--; +} + diff --git a/lib/ranges.c b/lib/server/ranges.c similarity index 100% rename from lib/ranges.c rename to lib/server/ranges.c diff --git a/lib/rewrite.c b/lib/server/rewrite.c similarity index 100% rename from lib/rewrite.c rename to lib/server/rewrite.c diff --git a/lib/server-handshake.c b/lib/server/server-handshake.c similarity index 100% rename from lib/server-handshake.c rename to lib/server/server-handshake.c diff --git a/lib/server.c b/lib/server/server.c similarity index 76% rename from lib/server.c rename to lib/server/server.c index 3ddbb67f8..27237a9e9 100644 --- a/lib/server.c +++ b/lib/server/server.c @@ -19,10 +19,9 @@ * MA 02110-1301 USA */ - #include "private-libwebsockets.h" -static const char * const method_names[] = { +const char * const method_names[] = { "GET", "POST", "OPTIONS", "PUT", "PATCH", "DELETE", "CONNECT", "HEAD", #ifdef LWS_WITH_HTTP2 ":path", @@ -107,11 +106,13 @@ lws_context_init_server(struct lws_context_creation_info *info, #if (defined(WIN32) || defined(_WIN32)) && defined(SO_EXCLUSIVEADDRUSE) /* * only accept that we are the only listener on the port - * https://msdn.microsoft.com/zh-tw/library/windows/desktop/ms740621(v=vs.85).aspx + * https://msdn.microsoft.com/zh-tw/library/ + * windows/desktop/ms740621(v=vs.85).aspx * * for lws, to match Linux, we default to exclusive listen */ - if (!lws_check_opt(vhost->options, LWS_SERVER_OPTION_ALLOW_LISTEN_SHARE)) { + if (!lws_check_opt(vhost->options, + LWS_SERVER_OPTION_ALLOW_LISTEN_SHARE)) { if (setsockopt(sockfd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (const void *)&opt, sizeof(opt)) < 0) { lwsl_err("reuseaddr failed\n"); @@ -134,9 +135,10 @@ lws_context_init_server(struct lws_context_creation_info *info, #if defined(LWS_WITH_IPV6) && defined(IPV6_V6ONLY) if (LWS_IPV6_ENABLED(vhost)) { if (vhost->options & LWS_SERVER_OPTION_IPV6_V6ONLY_MODIFY) { - int value = (vhost->options & LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE) ? 1 : 0; + int value = (vhost->options & + LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE) ? 1 : 0; if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, - (const void*)&value, sizeof(value)) < 0) { + (const void*)&value, sizeof(value)) < 0) { compatible_close(sockfd); return 1; } @@ -551,10 +553,13 @@ lws_http_serve(struct lws *wsi, char *uri, const char *origin, if (n > (int)strlen(pvo->name) && !strcmp(&path[n - strlen(pvo->name)], pvo->name)) { wsi->sending_chunked = 1; - wsi->protocol_interpret_idx = (char)(lws_intptr_t)pvo->value; + wsi->protocol_interpret_idx = + (char)(lws_intptr_t)pvo->value; lwsl_info("want %s interpreted by %s\n", path, - wsi->vhost->protocols[(int)(lws_intptr_t)(pvo->value)].name); - wsi->protocol = &wsi->vhost->protocols[(int)(lws_intptr_t)(pvo->value)]; + wsi->vhost->protocols[ + (int)(lws_intptr_t)(pvo->value)].name); + wsi->protocol = &wsi->vhost->protocols[ + (int)(lws_intptr_t)(pvo->value)]; if (lws_ensure_user_space(wsi)) return -1; break; @@ -564,7 +569,7 @@ lws_http_serve(struct lws *wsi, char *uri, const char *origin, if (m->protocol) { const struct lws_protocols *pp = lws_vhost_name_to_protocol( - wsi->vhost, m->protocol); + wsi->vhost, m->protocol); if (lws_bind_protocol(wsi, pp)) return 1; @@ -605,7 +610,8 @@ lws_find_mount(struct lws *wsi, const char *uri_ptr, int uri_len) ((hm->origin_protocol == LWSMPRO_CGI || lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI) || (wsi->http2_substream && - lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COLON_PATH)) || + lws_hdr_total_length(wsi, + WSI_TOKEN_HTTP_COLON_PATH)) || hm->protocol) && hm->mountpoint_len > best)) { best = hm->mountpoint_len; @@ -687,7 +693,8 @@ lws_unauthorised_basic_auth(struct lws *wsi) if (lws_finalize_http_header(wsi, &p, end)) return -1; - n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS | LWS_WRITE_H2_STREAM_END); + n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS | + LWS_WRITE_H2_STREAM_END); if (n < 0) return -1; @@ -725,97 +732,6 @@ int lws_clean_url(char *p) return 0; } -/* - * Produce Apache-compatible log string for wsi, like this: - * - * 2.31.234.19 - - [27/Mar/2016:03:22:44 +0800] - * "GET /aep-screen.png HTTP/1.1" - * 200 152987 "https://libwebsockets.org/index.html" - * "Mozilla/5.0 (Macint... Chrome/49.0.2623.87 Safari/537.36" - * - */ -#ifdef LWS_WITH_ACCESS_LOG -static const char * const hver[] = { - "http/1.0", "http/1.1", "http/2" -}; -static void -lws_prepare_access_log_info(struct lws *wsi, char *uri_ptr, int meth) -{ -#ifdef LWS_WITH_IPV6 - char ads[INET6_ADDRSTRLEN]; -#else - char ads[INET_ADDRSTRLEN]; -#endif - char da[64]; - const char *pa, *me; - struct tm *tmp; - time_t t = time(NULL); - int l = 256, m; - - if (wsi->access_log_pending) - lws_access_log(wsi); - - wsi->access_log.header_log = lws_malloc(l, "access log"); - if (wsi->access_log.header_log) { - - tmp = localtime(&t); - if (tmp) - strftime(da, sizeof(da), "%d/%b/%Y:%H:%M:%S %z", tmp); - else - strcpy(da, "01/Jan/1970:00:00:00 +0000"); - - pa = lws_get_peer_simple(wsi, ads, sizeof(ads)); - if (!pa) - pa = "(unknown)"; - - if (wsi->http2_substream) - me = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_METHOD); - else - me = method_names[meth]; - if (!me) - me = "(null)"; - - lws_snprintf(wsi->access_log.header_log, l, - "%s - - [%s] \"%s %s %s\"", - pa, da, me, uri_ptr, - hver[wsi->u.http.request_version]); - - l = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_USER_AGENT); - if (l) { - wsi->access_log.user_agent = lws_malloc(l + 2, "access log"); - if (!wsi->access_log.user_agent) { - lwsl_err("OOM getting user agent\n"); - lws_free_set_NULL(wsi->access_log.header_log); - return; - } - - lws_hdr_copy(wsi, wsi->access_log.user_agent, - l + 1, WSI_TOKEN_HTTP_USER_AGENT); - - for (m = 0; m < l; m++) - if (wsi->access_log.user_agent[m] == '\"') - wsi->access_log.user_agent[m] = '\''; - } - l = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_REFERER); - if (l) { - wsi->access_log.referrer = lws_malloc(l + 2, "referrer"); - if (!wsi->access_log.referrer) { - lwsl_err("OOM getting user agent\n"); - lws_free_set_NULL(wsi->access_log.user_agent); - lws_free_set_NULL(wsi->access_log.header_log); - return; - } - lws_hdr_copy(wsi, wsi->access_log.referrer, - l + 1, WSI_TOKEN_HTTP_REFERER); - - for (m = 0; m < l; m++) - if (wsi->access_log.referrer[m] == '\"') - wsi->access_log.referrer[m] = '\''; - } - wsi->access_log_pending = 1; - } -} -#endif static const unsigned char methods[] = { WSI_TOKEN_GET_URI, @@ -892,7 +808,8 @@ lws_http_action(struct lws *wsi) goto bail_nuke_ah; } - lwsl_info("Method: '%s' (%d), request for '%s'\n", method_names[meth], meth, uri_ptr); + lwsl_info("Method: '%s' (%d), request for '%s'\n", method_names[meth], + meth, uri_ptr); if (lws_ensure_user_space(wsi)) goto bail_nuke_ah; @@ -922,8 +839,10 @@ lws_http_action(struct lws *wsi) http_version_len = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP); if (http_version_len > 7) { lws_hdr_copy(wsi, http_version_str, - sizeof(http_version_str) - 1, WSI_TOKEN_HTTP); - if (http_version_str[5] == '1' && http_version_str[7] == '1') + sizeof(http_version_str) - 1, + WSI_TOKEN_HTTP); + if (http_version_str[5] == '1' && + http_version_str[7] == '1') request_version = HTTP_VERSION_1_1; } wsi->u.http.request_version = request_version; @@ -936,7 +855,8 @@ lws_http_action(struct lws *wsi) /* Override default if http "Connection:" header: */ if (lws_hdr_total_length(wsi, WSI_TOKEN_CONNECTION)) { - lws_hdr_copy(wsi, http_conn_str, sizeof(http_conn_str) - 1, + lws_hdr_copy(wsi, http_conn_str, + sizeof(http_conn_str) - 1, WSI_TOKEN_CONNECTION); http_conn_str[sizeof(http_conn_str) - 1] = '\0'; if (!strcasecmp(http_conn_str, "keep-alive")) @@ -1045,11 +965,13 @@ lws_http_action(struct lws *wsi) hit->origin); else { if (!lws_hdr_total_length(wsi, WSI_TOKEN_HOST)) { - if (!lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COLON_AUTHORITY)) + if (!lws_hdr_total_length(wsi, + WSI_TOKEN_HTTP_COLON_AUTHORITY)) goto bail_nuke_ah; n = lws_snprintf((char *)end, 256, "%s%s%s/", oprot[!!lws_is_ssl(wsi)], - lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_AUTHORITY), + lws_hdr_simple_ptr(wsi, + WSI_TOKEN_HTTP_COLON_AUTHORITY), uri_ptr); } else n = lws_snprintf((char *)end, 256, @@ -1080,7 +1002,8 @@ lws_http_action(struct lws *wsi) n = HTTP_STATUS_FORBIDDEN; - m = lws_hdr_copy(wsi, b64, sizeof(b64), WSI_TOKEN_HTTP_AUTHORIZATION); + m = lws_hdr_copy(wsi, b64, sizeof(b64), + WSI_TOKEN_HTTP_AUTHORIZATION); if (m < 7) { lwsl_err("b64 auth too long\n"); goto transaction_result_n; @@ -1100,7 +1023,8 @@ lws_http_action(struct lws *wsi) goto transaction_result_n; } - if (!lws_find_string_in_file(hit->basic_auth_login_file, plain, m)) { + if (!lws_find_string_in_file(hit->basic_auth_login_file, + plain, m)) { lwsl_err("basic auth lookup failed\n"); return lws_unauthorised_basic_auth(wsi); } @@ -1128,7 +1052,8 @@ lws_http_action(struct lws *wsi) pcolon = strchr(hit->origin, ':'); pslash = strchr(hit->origin, '/'); if (!pslash) { - lwsl_err("Proxy mount origin '%s' must have /\n", hit->origin); + lwsl_err("Proxy mount origin '%s' must have /\n", + hit->origin); return -1; } if (pcolon > pslash) @@ -1154,13 +1079,15 @@ lws_http_action(struct lws *wsi) if (pcolon) i.port = atoi(pcolon + 1); - lws_snprintf(rpath, sizeof(rpath) - 1, "/%s/%s", pslash + 1, uri_ptr + hit->mountpoint_len); + lws_snprintf(rpath, sizeof(rpath) - 1, "/%s/%s", pslash + 1, + uri_ptr + hit->mountpoint_len); lws_clean_url(rpath); na = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_URI_ARGS); if (na) { p = rpath + strlen(rpath); *p++ = '?'; - lws_hdr_copy(wsi, p, &rpath[sizeof(rpath) - 1] - p, WSI_TOKEN_HTTP_URI_ARGS); + lws_hdr_copy(wsi, p, &rpath[sizeof(rpath) - 1] - p, + WSI_TOKEN_HTTP_URI_ARGS); while (--na) { if (*p == '\0') *p = '&'; @@ -1178,7 +1105,8 @@ lws_http_action(struct lws *wsi) i.uri_replace_to = hit->mountpoint; lwsl_notice("proxying to %s port %d url %s, ssl %d, from %s, to %s\n", - i.address, i.port, i.path, i.ssl_connection, i.uri_replace_from, i.uri_replace_to); + i.address, i.port, i.path, i.ssl_connection, + i.uri_replace_from, i.uri_replace_to); if (!lws_client_connect_via_info(&i)) { lwsl_err("proxy connect fail\n"); @@ -1319,11 +1247,15 @@ deal_body: */ if (wsi->state != LWSS_HTTP_ISSUING_FILE) { /* Prepare to read body if we have a content length: */ - lwsl_debug("wsi->u.http.rx_content_length %lld %d %d\n", (long long)wsi->u.http.rx_content_length, wsi->upgraded_to_http2, wsi->http2_substream); + lwsl_debug("wsi->u.http.rx_content_length %lld %d %d\n", + (long long)wsi->u.http.rx_content_length, + wsi->upgraded_to_http2, wsi->http2_substream); if (wsi->u.http.rx_content_length > 0) { - lwsl_notice("%s: %p: LWSS_HTTP_BODY state set\n", __func__, wsi); + lwsl_notice("%s: %p: LWSS_HTTP_BODY state set\n", + __func__, wsi); wsi->state = LWSS_HTTP_BODY; - wsi->u.http.rx_content_remain = wsi->u.http.rx_content_length; + wsi->u.http.rx_content_remain = + wsi->u.http.rx_content_length; } } @@ -1514,7 +1446,8 @@ raw_transition: msg = strchr(rej->value, ' '); if (msg) msg++; - lws_return_http_status(wsi, atoi(rej->value), msg); + lws_return_http_status(wsi, + atoi(rej->value), msg); #ifdef LWS_WITH_ACCESS_LOG meth = lws_http_get_uri_and_method(wsi, &uri_ptr, &uri_len); @@ -1628,7 +1561,8 @@ upgrade_h2c: lws_h2_settings(wsi, &wsi->u.h2.h2n->set, (unsigned char *)protocol_list, n); - lws_hpack_dynamic_size(wsi, wsi->u.h2.h2n->set.s[H2SET_HEADER_TABLE_SIZE]); + lws_hpack_dynamic_size(wsi, wsi->u.h2.h2n->set.s[ + H2SET_HEADER_TABLE_SIZE]); strcpy(protocol_list, "HTTP/1.1 101 Switching Protocols\x0d\x0a" "Connection: Upgrade\x0d\x0a" @@ -1690,7 +1624,8 @@ upgrade_ws: n = 0; while (wsi->vhost->protocols[n].callback) { - lwsl_info("try %s\n", wsi->vhost->protocols[n].name); + lwsl_info("try %s\n", + wsi->vhost->protocols[n].name); if (wsi->vhost->protocols[n].name && !strcmp(wsi->vhost->protocols[n].name, @@ -1770,7 +1705,7 @@ upgrade_ws: * upgrade request and to already be in the ah rx buffer. */ - lwsl_info("%s: %p: inheriting ah in ws mode (rxpos:%d, rxlen:%d)\n", + lwsl_info("%s: %p: inheriting ws ah (rxpos:%d, rxlen:%d)\n", __func__, wsi, wsi->u.hdr.ah->rxpos, wsi->u.hdr.ah->rxlen); lws_pt_lock(pt); @@ -1817,238 +1752,6 @@ bail_nuke_ah: return 1; } -#if defined(LWS_WITH_PEER_LIMITS) - -/* requires context->lock */ -static void -__lws_peer_remove_from_peer_wait_list(struct lws_context *context, - struct lws_peer *peer) -{ - struct lws_peer *df; - - lws_start_foreach_llp(struct lws_peer **, p, context->peer_wait_list) { - if (*p == peer) { - df = *p; - - *p = df->peer_wait_list; - df->peer_wait_list = NULL; - - return; - } - } lws_end_foreach_llp(p, peer_wait_list); -} - -/* requires context->lock */ -static void -__lws_peer_add_to_peer_wait_list(struct lws_context *context, - struct lws_peer *peer) -{ - __lws_peer_remove_from_peer_wait_list(context, peer); - - peer->peer_wait_list = context->peer_wait_list; - context->peer_wait_list = peer; -} - - -static struct lws_peer * -lws_get_or_create_peer(struct lws_vhost *vhost, lws_sockfd_type sockfd) -{ - struct lws_context *context = vhost->context; - socklen_t rlen = 0; - void *q; - uint8_t *q8; - struct lws_peer *peer; - uint32_t hash = 0; - int n, af = AF_INET; - struct sockaddr_storage addr; - -#ifdef LWS_WITH_IPV6 - if (LWS_IPV6_ENABLED(vhost)) { - af = AF_INET6; - } -#endif - rlen = sizeof(addr); - if (getpeername(sockfd, (struct sockaddr*)&addr, &rlen)) - return NULL; - - if (af == AF_INET) { - struct sockaddr_in *s = (struct sockaddr_in *)&addr; - q = &s->sin_addr; - rlen = sizeof(s->sin_addr); - } else -#ifdef LWS_WITH_IPV6 - { - struct sockaddr_in6 *s = (struct sockaddr_in6 *)&addr; - q = &s->sin6_addr; - rlen = sizeof(s->sin6_addr); - } -#else - return NULL; -#endif - - q8 = q; - for (n = 0; n < rlen; n++) - hash = (((hash << 4) | (hash >> 28)) * n) ^ q8[n]; - - hash = hash % context->pl_hash_elements; - - lws_context_lock(context); /* <====================================== */ - - lws_start_foreach_ll(struct lws_peer *, peerx, - context->pl_hash_table[hash]) { - if (peerx->af == af && !memcmp(q, peerx->addr, rlen)) { - lws_context_unlock(context); /* === */ - return peerx; - } - } lws_end_foreach_ll(peerx, next); - - lwsl_info("%s: creating new peer\n", __func__); - - peer = lws_zalloc(sizeof(*peer), "peer"); - if (!peer) { - lws_context_unlock(context); /* === */ - return NULL; - } - - context->count_peers++; - peer->next = context->pl_hash_table[hash]; - peer->hash = hash; - peer->af = af; - context->pl_hash_table[hash] = peer; - memcpy(peer->addr, q, rlen); - time(&peer->time_created); - /* - * On creation, the peer has no wsi attached, so is created on the - * wait list. When a wsi is added it is removed from the wait list. - */ - time(&peer->time_closed_all); - __lws_peer_add_to_peer_wait_list(context, peer); - - lws_context_unlock(context); /* ====================================> */ - - return peer; -} - -/* requires context->lock */ -static int -__lws_peer_destroy(struct lws_context *context, struct lws_peer *peer) -{ - lws_start_foreach_llp(struct lws_peer **, p, - context->pl_hash_table[peer->hash]) { - if (*p == peer) { - struct lws_peer *df = *p; - *p = df->next; - lws_free(df); - context->count_peers--; - - return 0; - } - } lws_end_foreach_llp(p, next); - - return 1; -} - -void -lws_peer_cull_peer_wait_list(struct lws_context *context) -{ - struct lws_peer *df; - time_t t; - - time(&t); - - if (context->next_cull && t < context->next_cull) - return; - - lws_context_lock(context); /* <====================================== */ - - context->next_cull = t + 5; - - lws_start_foreach_llp(struct lws_peer **, p, context->peer_wait_list) { - if (t - (*p)->time_closed_all > 10) { - df = *p; - - /* remove us from the peer wait list */ - *p = df->peer_wait_list; - df->peer_wait_list = NULL; - - __lws_peer_destroy(context, df); - continue; /* we already point to next, if any */ - } - } lws_end_foreach_llp(p, peer_wait_list); - - lws_context_unlock(context); /* ====================================> */ -} - -static void -lws_peer_add_wsi(struct lws_context *context, struct lws_peer *peer, - struct lws *wsi) -{ - if (!peer) - return; - - lws_context_lock(context); /* <====================================== */ - - peer->count_wsi++; - wsi->peer = peer; - __lws_peer_remove_from_peer_wait_list(context, peer); - - lws_context_unlock(context); /* ====================================> */ -} - -void -lws_peer_track_wsi_close(struct lws_context *context, struct lws_peer *peer) -{ - if (!peer) - return; - - lws_context_lock(context); /* <====================================== */ - - assert(peer->count_wsi); - peer->count_wsi--; - - if (!peer->count_wsi && !peer->count_ah) { - /* - * in order that we can accumulate peer activity correctly - * allowing for periods when the peer has no connections, - * we don't synchronously destroy the peer when his last - * wsi closes. Instead we mark the time his last wsi - * closed and add him to a peer_wait_list to be reaped - * later if no further activity is coming. - */ - time(&peer->time_closed_all); - __lws_peer_add_to_peer_wait_list(context, peer); - } - - lws_context_unlock(context); /* ====================================> */ -} - -int -lws_peer_confirm_ah_attach_ok(struct lws_context *context, struct lws_peer *peer) -{ - if (!peer) - return 0; - - if (context->ip_limit_ah && peer->count_ah >= context->ip_limit_ah) { - lwsl_info("peer reached ah limit %d, deferring\n", - context->ip_limit_ah); - - return 1; - } - - return 0; -} - -void -lws_peer_track_ah_detach(struct lws_context *context, struct lws_peer *peer) -{ - if (!peer) - return; - - assert(peer->count_ah); - peer->count_ah--; -} - -#endif static int lws_get_idlest_tsi(struct lws_context *context) @@ -2202,7 +1905,8 @@ lws_http_transaction_completed(struct lws *wsi) wsi->context->simultaneous_ssl_restriction && wsi->context->simultaneous_ssl == wsi->context->simultaneous_ssl_restriction) { - lwsl_info("%s: simultaneous_ssl_restriction and nothing pipelined\n", __func__); + lwsl_info("%s: simultaneous_ssl_restriction\n", + __func__); return 1; } #endif @@ -2514,14 +2218,17 @@ LWS_VISIBLE struct lws * lws_adopt_socket_readbuf(struct lws_context *context, lws_sockfd_type accept_fd, const char *readbuf, size_t len) { - return adopt_socket_readbuf(lws_adopt_socket(context, accept_fd), readbuf, len); + return adopt_socket_readbuf(lws_adopt_socket(context, accept_fd), + readbuf, len); } LWS_VISIBLE struct lws * -lws_adopt_socket_vhost_readbuf(struct lws_vhost *vhost, lws_sockfd_type accept_fd, - const char *readbuf, size_t len) +lws_adopt_socket_vhost_readbuf(struct lws_vhost *vhost, + lws_sockfd_type accept_fd, + const char *readbuf, size_t len) { - return adopt_socket_readbuf(lws_adopt_socket_vhost(vhost, accept_fd), readbuf, len); + return adopt_socket_readbuf(lws_adopt_socket_vhost(vhost, accept_fd), + readbuf, len); } LWS_VISIBLE int @@ -2594,7 +2301,7 @@ lws_server_socket_service(struct lws_context *context, struct lws *wsi, if (!wsi->u.hdr.ah) { /* no autoservice beacuse we will do it next */ if (lws_header_table_attach(wsi, 0)) { - lwsl_info("wsi %p: failed to acquire ah\n", wsi); + lwsl_info("wsi %p: ah get fail\n", wsi); goto try_pollout; } } @@ -2734,13 +2441,17 @@ try_pollout: } if (wsi->mode == LWSCM_RAW) { - lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_WRITEABLE_CB, 1); + lws_stats_atomic_bump(wsi->context, pt, + LWSSTATS_C_WRITEABLE_CB, 1); #if defined(LWS_WITH_STATS) if (wsi->active_writable_req_us) { - uint64_t ul = time_in_microseconds() - wsi->active_writable_req_us; + uint64_t ul = time_in_microseconds() - + wsi->active_writable_req_us; - lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_MS_WRITABLE_DELAY, ul); - lws_stats_atomic_max(wsi->context, pt, LWSSTATS_MS_WORST_WRITABLE_DELAY, ul); + lws_stats_atomic_bump(wsi->context, pt, + LWSSTATS_MS_WRITABLE_DELAY, ul); + lws_stats_atomic_max(wsi->context, pt, + LWSSTATS_MS_WORST_WRITABLE_DELAY, ul); wsi->active_writable_req_us = 0; } #endif @@ -2759,13 +2470,17 @@ try_pollout: if (wsi->state != LWSS_HTTP_ISSUING_FILE) { - lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_WRITEABLE_CB, 1); + lws_stats_atomic_bump(wsi->context, pt, + LWSSTATS_C_WRITEABLE_CB, 1); #if defined(LWS_WITH_STATS) if (wsi->active_writable_req_us) { - uint64_t ul = time_in_microseconds() - wsi->active_writable_req_us; + uint64_t ul = time_in_microseconds() - + wsi->active_writable_req_us; - lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_MS_WRITABLE_DELAY, ul); - lws_stats_atomic_max(wsi->context, pt, LWSSTATS_MS_WORST_WRITABLE_DELAY, ul); + lws_stats_atomic_bump(wsi->context, pt, + LWSSTATS_MS_WRITABLE_DELAY, ul); + lws_stats_atomic_max(wsi->context, pt, + LWSSTATS_MS_WORST_WRITABLE_DELAY, ul); wsi->active_writable_req_us = 0; } #endif @@ -2798,7 +2513,8 @@ try_pollout: /* pollin means a client has connected to us then */ do { - if (!(pollfd->revents & LWS_POLLIN) || !(pollfd->events & LWS_POLLIN)) + if (!(pollfd->revents & LWS_POLLIN) || + !(pollfd->events & LWS_POLLIN)) break; #ifdef LWS_OPENSSL_SUPPORT @@ -2846,14 +2562,14 @@ try_pollout: #if defined(LWS_WITH_IPV6) lwsl_debug("accepted new conn port %u on fd=%d\n", - ((cli_addr.ss_family == AF_INET6) ? - ntohs(((struct sockaddr_in6 *) &cli_addr)->sin6_port) : - ntohs(((struct sockaddr_in *) &cli_addr)->sin_port)), - accept_fd); + ((cli_addr.ss_family == AF_INET6) ? + ntohs(((struct sockaddr_in6 *) &cli_addr)->sin6_port) : + ntohs(((struct sockaddr_in *) &cli_addr)->sin_port)), + accept_fd); #else lwsl_debug("accepted new conn port %u on fd=%d\n", - ntohs(((struct sockaddr_in *) &cli_addr)->sin_port), - accept_fd); + ntohs(((struct sockaddr_in *) &cli_addr)->sin_port), + accept_fd); #endif #else @@ -2960,7 +2676,8 @@ lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type, */ if (ranges < 0) { /* it means he expressed a range in Range:, but it was illegal */ - lws_return_http_status(wsi, HTTP_STATUS_REQ_RANGE_NOT_SATISFIABLE, NULL); + lws_return_http_status(wsi, HTTP_STATUS_REQ_RANGE_NOT_SATISFIABLE, + NULL); if (lws_http_transaction_completed(wsi)) return -1; /* <0 means just hang up */ @@ -3002,8 +2719,8 @@ lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type, wsi->u.http.multipart_content_type[ sizeof(wsi->u.http.multipart_content_type) - 1] = '\0'; if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, - (unsigned char *)"multipart/byteranges; boundary=_lws", - 20, &p, end)) + (unsigned char *)"multipart/byteranges; boundary=_lws", + 20, &p, end)) return -1; /* @@ -3028,10 +2745,12 @@ lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type, rp->start, rp->end, rp->extent); computed_total_content_length += - 6 /* header _lws\r\n */ + - 14 + strlen(content_type) + 2 + /* Content-Type: xxx/xxx\r\n */ - 15 + n + 2 + /* Content-Range: xxxx\r\n */ - 2; /* /r/n */ + 6 /* header _lws\r\n */ + + /* Content-Type: xxx/xxx\r\n */ + 14 + strlen(content_type) + 2 + + /* Content-Range: xxxx\r\n */ + 15 + n + 2 + + 2; /* /r/n */ } lws_ranges_reset(rp); @@ -3040,8 +2759,9 @@ lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type, if (ranges == 1) { computed_total_content_length = (lws_filepos_t)rp->agg; - n = lws_snprintf(cache_control, sizeof(cache_control), "bytes %llu-%llu/%llu", - rp->start, rp->end, rp->extent); + n = lws_snprintf(cache_control, sizeof(cache_control), + "bytes %llu-%llu/%llu", + rp->start, rp->end, rp->extent); if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_RANGE, (unsigned char *)cache_control, @@ -3062,7 +2782,8 @@ lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type, &p, end)) return -1; } else { - if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_TRANSFER_ENCODING, + if (lws_add_http_header_by_token(wsi, + WSI_TOKEN_HTTP_TRANSFER_ENCODING, (unsigned char *)"chunked", 7, &p, end)) return -1; @@ -3101,7 +2822,8 @@ lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type, ret = lws_write(wsi, response, p - response, LWS_WRITE_HTTP_HEADERS); if (ret != (p - response)) { - lwsl_err("_write returned %d from %ld\n", ret, (long)(p - response)); + lwsl_err("_write returned %d from %ld\n", ret, + (long)(p - response)); return -1; } @@ -3173,7 +2895,8 @@ LWS_VISIBLE void lws_server_get_canonical_hostname(struct lws_context *context, struct lws_context_creation_info *info) { - if (lws_check_opt(info->options, LWS_SERVER_OPTION_SKIP_SERVER_CANONICAL_NAME)) + if (lws_check_opt(info->options, + LWS_SERVER_OPTION_SKIP_SERVER_CANONICAL_NAME)) return; #if LWS_POSIX && !defined(LWS_WITH_ESP32) /* find canonical hostname */ @@ -3186,584 +2909,7 @@ lws_server_get_canonical_hostname(struct lws_context *context, #endif } -#define LWS_MAX_ELEM_NAME 32 -enum urldecode_stateful { - US_NAME, - US_IDLE, - US_PC1, - US_PC2, - - MT_LOOK_BOUND_IN, - MT_HNAME, - MT_DISP, - MT_TYPE, - MT_IGNORE1, - MT_IGNORE2, -}; - -static const char * const mp_hdr[] = { - "content-disposition: ", - "content-type: ", - "\x0d\x0a" -}; - -typedef int (*lws_urldecode_stateful_cb)(void *data, - const char *name, char **buf, int len, int final); - -struct lws_urldecode_stateful { - char *out; - void *data; - char name[LWS_MAX_ELEM_NAME]; - char temp[LWS_MAX_ELEM_NAME]; - char content_type[32]; - char content_disp[32]; - char content_disp_filename[256]; - char mime_boundary[128]; - int out_len; - int pos; - int hdr_idx; - int mp; - int sum; - - unsigned int multipart_form_data:1; - unsigned int inside_quote:1; - unsigned int subname:1; - unsigned int boundary_real_crlf:1; - - enum urldecode_stateful state; - - lws_urldecode_stateful_cb output; -}; - -static struct lws_urldecode_stateful * -lws_urldecode_s_create(struct lws *wsi, char *out, int out_len, void *data, - lws_urldecode_stateful_cb output) -{ - struct lws_urldecode_stateful *s = lws_zalloc(sizeof(*s), "stateful urldecode"); - char buf[200], *p; - int m = 0; - - if (!s) - return NULL; - - s->out = out; - s->out_len = out_len; - s->output = output; - s->pos = 0; - s->sum = 0; - s->mp = 0; - s->state = US_NAME; - s->name[0] = '\0'; - s->data = data; - - if (lws_hdr_copy(wsi, buf, sizeof(buf), WSI_TOKEN_HTTP_CONTENT_TYPE) > 0) { - /* multipart/form-data; boundary=----WebKitFormBoundarycc7YgAPEIHvgE9Bf */ - - if (!strncmp(buf, "multipart/form-data", 19)) { - s->multipart_form_data = 1; - s->state = MT_LOOK_BOUND_IN; - s->mp = 2; - p = strstr(buf, "boundary="); - if (p) { - p += 9; - s->mime_boundary[m++] = '\x0d'; - s->mime_boundary[m++] = '\x0a'; - s->mime_boundary[m++] = '-'; - s->mime_boundary[m++] = '-'; - while (m < sizeof(s->mime_boundary) - 1 && - *p && *p != ' ') - s->mime_boundary[m++] = *p++; - - s->mime_boundary[m] = '\0'; - - lwsl_notice("boundary '%s'\n", s->mime_boundary); - } - } - } - - return s; -} - -static int -lws_urldecode_s_process(struct lws_urldecode_stateful *s, const char *in, int len) -{ - int n, m, hit = 0; - char c, was_end = 0; - - while (len--) { - if (s->pos == s->out_len - s->mp - 1) { - if (s->output(s->data, s->name, &s->out, s->pos, 0)) - return -1; - - was_end = s->pos; - s->pos = 0; - } - switch (s->state) { - - /* states for url arg style */ - - case US_NAME: - s->inside_quote = 0; - if (*in == '=') { - s->name[s->pos] = '\0'; - s->pos = 0; - s->state = US_IDLE; - in++; - continue; - } - if (*in == '&') { - s->name[s->pos] = '\0'; - if (s->output(s->data, s->name, &s->out, s->pos, 1)) - return -1; - s->pos = 0; - s->state = US_IDLE; - in++; - continue; - } - if (s->pos >= sizeof(s->name) - 1) { - lwsl_notice("Name too long\n"); - return -1; - } - s->name[s->pos++] = *in++; - break; - case US_IDLE: - if (*in == '%') { - s->state++; - in++; - continue; - } - if (*in == '&') { - s->out[s->pos] = '\0'; - if (s->output(s->data, s->name, &s->out, s->pos, 1)) - return -1; - s->pos = 0; - s->state = US_NAME; - in++; - continue; - } - if (*in == '+') { - in++; - s->out[s->pos++] = ' '; - continue; - } - s->out[s->pos++] = *in++; - break; - case US_PC1: - n = char_to_hex(*in); - if (n < 0) - return -1; - - in++; - s->sum = n << 4; - s->state++; - break; - - case US_PC2: - n = char_to_hex(*in); - if (n < 0) - return -1; - - in++; - s->out[s->pos++] = s->sum | n; - s->state = US_IDLE; - break; - - - /* states for multipart / mime style */ - - case MT_LOOK_BOUND_IN: -retry_as_first: - if (*in == s->mime_boundary[s->mp] && - s->mime_boundary[s->mp]) { - in++; - s->mp++; - if (!s->mime_boundary[s->mp]) { - s->mp = 0; - s->state = MT_IGNORE1; - - if (s->pos || was_end) - if (s->output(s->data, s->name, - &s->out, s->pos, 1)) - return -1; - - s->pos = 0; - - s->content_disp[0] = '\0'; - s->name[0] = '\0'; - s->content_disp_filename[0] = '\0'; - s->boundary_real_crlf = 1; - } - continue; - } - if (s->mp) { - n = 0; - if (!s->boundary_real_crlf) - n = 2; - - memcpy(s->out + s->pos, s->mime_boundary + n, s->mp - n); - s->pos += s->mp; - s->mp = 0; - goto retry_as_first; - } - - s->out[s->pos++] = *in; - in++; - s->mp = 0; - break; - - case MT_HNAME: - m = 0; - c =*in; - if (c >= 'A' && c <= 'Z') - c += 'a' - 'A'; - for (n = 0; n < ARRAY_SIZE(mp_hdr); n++) - if (c == mp_hdr[n][s->mp]) { - m++; - hit = n; - } - in++; - if (!m) { - s->mp = 0; - continue; - } - - s->mp++; - if (m != 1) - continue; - - if (mp_hdr[hit][s->mp]) - continue; - - s->mp = 0; - s->temp[0] = '\0'; - s->subname = 0; - - if (hit == 2) - s->state = MT_LOOK_BOUND_IN; - else - s->state += hit + 1; - break; - - case MT_DISP: - /* form-data; name="file"; filename="t.txt" */ - - if (*in == '\x0d') { - if (s->content_disp_filename[0]) - if (s->output(s->data, s->name, - &s->out, s->pos, LWS_UFS_OPEN)) - return -1; - s->state = MT_IGNORE2; - goto done; - } - if (*in == ';') { - s->subname = 1; - s->temp[0] = '\0'; - s->mp = 0; - goto done; - } - - if (*in == '\"') { - s->inside_quote ^= 1; - goto done; - } - - if (s->subname) { - if (*in == '=') { - s->temp[s->mp] = '\0'; - s->subname = 0; - s->mp = 0; - goto done; - } - if (s->mp < sizeof(s->temp) - 1 && - (*in != ' ' || s->inside_quote)) - s->temp[s->mp++] = *in; - goto done; - } - - if (!s->temp[0]) { - if (s->mp < sizeof(s->content_disp) - 1) - s->content_disp[s->mp++] = *in; - s->content_disp[s->mp] = '\0'; - goto done; - } - - if (!strcmp(s->temp, "name")) { - if (s->mp < sizeof(s->name) - 1) - s->name[s->mp++] = *in; - s->name[s->mp] = '\0'; - goto done; - } - - if (!strcmp(s->temp, "filename")) { - if (s->mp < sizeof(s->content_disp_filename) - 1) - s->content_disp_filename[s->mp++] = *in; - s->content_disp_filename[s->mp] = '\0'; - goto done; - } -done: - in++; - break; - - case MT_TYPE: - if (*in == '\x0d') - s->state = MT_IGNORE2; - else { - if (s->mp < sizeof(s->content_type) - 1) - s->content_type[s->mp++] = *in; - s->content_type[s->mp] = '\0'; - } - in++; - break; - - case MT_IGNORE1: - if (*in == '\x0d') - s->state = MT_IGNORE2; - in++; - break; - - case MT_IGNORE2: - s->mp = 0; - if (*in == '\x0a') - s->state = MT_HNAME; - in++; - break; - } - } - - return 0; -} - -static int -lws_urldecode_s_destroy(struct lws_urldecode_stateful *s) -{ - int ret = 0; - - if (s->state != US_IDLE) - ret = -1; - - if (!ret) - if (s->output(s->data, s->name, &s->out, s->pos, 1)) - ret = -1; - - lws_free(s); - - return ret; -} - -struct lws_spa { - struct lws_urldecode_stateful *s; - lws_spa_fileupload_cb opt_cb; - const char * const *param_names; - int count_params; - char **params; - int *param_length; - void *opt_data; - - char *storage; - char *end; - int max_storage; - - char finalized; -}; - -static int -lws_urldecode_spa_lookup(struct lws_spa *spa, - const char *name) -{ - int n; - - for (n = 0; n < spa->count_params; n++) - if (!strcmp(spa->param_names[n], name)) - return n; - - return -1; -} - -static int -lws_urldecode_spa_cb(void *data, const char *name, char **buf, int len, - int final) -{ - struct lws_spa *spa = - (struct lws_spa *)data; - int n; - - if (spa->s->content_disp_filename[0]) { - if (spa->opt_cb) { - n = spa->opt_cb(spa->opt_data, name, - spa->s->content_disp_filename, - *buf, len, final); - - if (n < 0) - return -1; - } - return 0; - } - n = lws_urldecode_spa_lookup(spa, name); - - if (n == -1 || !len) /* unrecognized */ - return 0; - - if (!spa->params[n]) - spa->params[n] = *buf; - - if ((*buf) + len >= spa->end) { - lwsl_notice("%s: exceeded storage\n", __func__); - return -1; - } - - spa->param_length[n] += len; - - /* move it on inside storage */ - (*buf) += len; - *((*buf)++) = '\0'; - - spa->s->out_len -= len + 1; - - return 0; -} - -LWS_VISIBLE LWS_EXTERN struct lws_spa * -lws_spa_create(struct lws *wsi, const char * const *param_names, - int count_params, int max_storage, - lws_spa_fileupload_cb opt_cb, void *opt_data) -{ - struct lws_spa *spa = lws_zalloc(sizeof(*spa), "spa"); - - if (!spa) - return NULL; - - spa->param_names = param_names; - spa->count_params = count_params; - spa->max_storage = max_storage; - spa->opt_cb = opt_cb; - spa->opt_data = opt_data; - - spa->storage = lws_malloc(max_storage, "spa"); - if (!spa->storage) - goto bail2; - spa->end = spa->storage + max_storage - 1; - - spa->params = lws_zalloc(sizeof(char *) * count_params, "spa params"); - if (!spa->params) - goto bail3; - - spa->s = lws_urldecode_s_create(wsi, spa->storage, max_storage, spa, - lws_urldecode_spa_cb); - if (!spa->s) - goto bail4; - - spa->param_length = lws_zalloc(sizeof(int) * count_params, "spa param len"); - if (!spa->param_length) - goto bail5; - - lwsl_info("%s: Created SPA %p\n", __func__, spa); - - return spa; - -bail5: - lws_urldecode_s_destroy(spa->s); -bail4: - lws_free(spa->params); -bail3: - lws_free(spa->storage); -bail2: - lws_free(spa); - - return NULL; -} - -LWS_VISIBLE LWS_EXTERN int -lws_spa_process(struct lws_spa *ludspa, const char *in, int len) -{ - if (!ludspa) { - lwsl_err("%s: NULL spa\n", __func__); - return -1; - } - /* we reject any junk after the last part arrived and we finalized */ - if (ludspa->finalized) - return 0; - - return lws_urldecode_s_process(ludspa->s, in, len); -} - -LWS_VISIBLE LWS_EXTERN int -lws_spa_get_length(struct lws_spa *ludspa, int n) -{ - if (n >= ludspa->count_params) - return 0; - - return ludspa->param_length[n]; -} - -LWS_VISIBLE LWS_EXTERN const char * -lws_spa_get_string(struct lws_spa *ludspa, int n) -{ - if (n >= ludspa->count_params) - return NULL; - - return ludspa->params[n]; -} - -LWS_VISIBLE LWS_EXTERN int -lws_spa_finalize(struct lws_spa *spa) -{ - if (spa->s) { - lws_urldecode_s_destroy(spa->s); - spa->s = NULL; - } - - spa->finalized = 1; - - return 0; -} - -LWS_VISIBLE LWS_EXTERN int -lws_spa_destroy(struct lws_spa *spa) -{ - int n = 0; - - lwsl_notice("%s: destroy spa %p\n", __func__, spa); - - if (spa->s) - lws_urldecode_s_destroy(spa->s); - - lwsl_debug("%s %p %p %p %p\n", __func__, - spa->param_length, - spa->params, - spa->storage, - spa - ); - - lws_free(spa->param_length); - lws_free(spa->params); - lws_free(spa->storage); - lws_free(spa); - - return n; -} - -#if 0 -LWS_VISIBLE LWS_EXTERN int -lws_spa_destroy(struct lws_spa *spa) -{ - int n = 0; - - lwsl_info("%s: destroy spa %p\n", __func__, spa); - - if (spa->s) - lws_urldecode_s_destroy(spa->s); - - lwsl_debug("%s\n", __func__); - - lws_free(spa->param_length); - lws_free(spa->params); - lws_free(spa->storage); - lws_free(spa); - - return n; -} -#endif LWS_VISIBLE LWS_EXTERN int lws_chunked_html_process(struct lws_process_html_args *args, struct lws_process_html_state *s) diff --git a/lib/ssl-server.c b/lib/server/ssl-server.c similarity index 100% rename from lib/ssl-server.c rename to lib/server/ssl-server.c diff --git a/lib/ssl.c b/lib/ssl.c index 15bc810b9..0a647b469 100644 --- a/lib/ssl.c +++ b/lib/ssl.c @@ -383,6 +383,26 @@ lws_ssl_destroy(struct lws_vhost *vhost) #endif } +int +lws_ssl_anybody_has_buffered_read_tsi(struct lws_context *context, int tsi) +{ + struct lws_context_per_thread *pt = &context->pt[tsi]; + struct lws *wsi, *wsi_next; + + wsi = pt->pending_read_list; + while (wsi) { + wsi_next = wsi->pending_read_list_next; + pt->fds[wsi->position_in_fds_table].revents |= + pt->fds[wsi->position_in_fds_table].events & LWS_POLLIN; + if (pt->fds[wsi->position_in_fds_table].revents & LWS_POLLIN) + return 1; + + wsi = wsi_next; + } + + return 0; +} + LWS_VISIBLE void lws_ssl_remove_wsi_from_buffered_list(struct lws *wsi) {