diff --git a/CMakeLists.txt b/CMakeLists.txt index bec9b4027..31824282f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,7 +29,7 @@ option(LWS_WITH_CGI "Include CGI (spawn process with network-connected stdin/out option(LWS_IPV6 "Compile with support for ipv6" OFF) option(LWS_UNIX_SOCK "Compile with support for UNIX domain socket" ON) option(LWS_WITH_PLUGINS "Support plugins for protocols and extensions" OFF) -option(LWS_WITH_HTTP_PROXY "Support for rewriting HTTP proxying (requires libhubbub)" OFF) +option(LWS_WITH_HTTP_PROXY "Support for HTTP proxying" ON) option(LWS_WITH_ZIP_FOPS "Support serving pre-zipped files" OFF) option(LWS_WITH_SOCKS5 "Allow use of SOCKS5 proxy on client connections" OFF) option(LWS_WITH_GENERIC_SESSIONS "With the Generic Sessions plugin" OFF) @@ -41,6 +41,7 @@ option(LWS_WITH_THREADPOOL "Managed worker thread pool support (relies on pthrea option(LWS_WITH_HTTP_STREAM_COMPRESSION "Support HTTP stream compression" OFF) option(LWS_WITH_HTTP_BROTLI "Also offer brotli http stream compression (requires LWS_WITH_HTTP_STREAM_COMPRESSION)" OFF) option(LWS_WITH_ACME "Enable support for ACME automatic cert acquisition + maintenance (letsencrypt etc)" OFF) +option(LWS_WITH_HUBBUB "Enable libhubbub rewriting support" OFF) # # TLS library options... all except mbedTLS are basically OpenSSL variants. # @@ -134,6 +135,12 @@ if(NOT DEFINED CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type") endif() +# microsoft... that's why you can't have nice things + +if (WIN32 OR LWS_WITH_ESP32) + set(LWS_UNIX_SOCK 0) +endif() + project(libwebsockets C) set(PACKAGE "libwebsockets") @@ -333,7 +340,8 @@ endif() if (LWS_WITH_HTTP_PROXY AND (LWS_WITHOUT_CLIENT OR LWS_WITHOUT_SERVER)) - message(FATAL_ERROR "You have to enable both client and server for http proxy") + message("You have to enable both client and server for http proxy") + set(LWS_WITH_HTTP_PROXY 0) endif() # Allow the user to override installation directories. @@ -744,6 +752,7 @@ set(HDR_PUBLIC set(SOURCES lib/core/alloc.c lib/core/context.c + lib/core/dummy-callback.c lib/core/libwebsockets.c lib/core/output.c lib/core/pollfd.c @@ -1402,7 +1411,7 @@ if (LWS_WITH_SQLITE3) endif() -if (LWS_WITH_HTTP_PROXY) +if (LWS_WITH_HUBBUB) find_library(LIBHUBBUB_LIBRARIES NAMES hubbub) list(APPEND LIB_LIST ${LIBHUBBUB_LIBRARIES} ) endif() diff --git a/READMEs/README.unix-domain-reverse-proxy.md b/READMEs/README.unix-domain-reverse-proxy.md new file mode 100644 index 000000000..ef61968d3 --- /dev/null +++ b/READMEs/README.unix-domain-reverse-proxy.md @@ -0,0 +1,95 @@ +## Unix Domain Sockets Reverse Proxy + +### Introduction + +lws is able to use a mount to place reverse proxies into the URL space. + +These are particularly useful when using Unix Domain Sockets, basically +files in the server filesystem, to communicate between lws and a separate +server process and integrate the result into a coherent URL namespace on +the lws side. + +This has the advantage that the actual web server that forwards the +data from the unix socket owner is in a different process than the server +that serves on the unix socket. If it has problems, they do not affect +the actual public-facing web server. The unix domain socket server may +be in a completely different language than the web server. + +Compared to CGI, there are no forks to make a connection to the unix +domain socket server. + +### Mount origin format + +Unix Domain Sockets are effectively "files" in the server filesystem, and +are defined by their filepath. The "server" side that is to be proxied opens +the socket and listens on it, which creates a file in the server filesystem. +The socket understands either http or https protocol. + +Lws can be told to act as a proxy for that at a mountpoint in the lws vhost +url space. + +If your mount is expressed in C code, then the mount type is LWSMPRO_HTTP or +LWSMPRO_HTTPS depending on the protocol the unix socket understands, and the +origin address has the form `+/path/to/unix/socket:/path/inside/mount`. + +The + at the start indicates it is a local unix socket we are proxying, and +the ':' acts as a delimiter for the socket path, since unlike other addresses +the unix socket path can contain '/' itself. + +### Connectivity rules and translations + +Onward proxy connections from lws to the Unix Domain Socket happen using +http/1.1. That implies `transfer-encoding: chunking` in the case that the +length of the output is not known beforehand. + +Lws takes care of stripping any chunking (which is illegal in h2) and +translating between h1 and h2 header formats if the return connection is +actually in http/2. + +The h1 onward proxy connection translates the following headers from the return +connection, which may be h1 or h2: + +Header|Function +---|--- +host|Which vhost +etag|Information on any etag the client has cached for this URI +if-modified-since|Information on the freshness of any etag the client has cached for this URI +accept-language|Which languages the return path client prefers +accept-encoding|Which compression encodings the client can accept +cache-control|Information from the return path client about cache acceptability +x-forwarded-for|The IP address of the return path client + +This implies that the proxied connection can + + - return 301 etc to say the return path client's etag is still valid + + - choose to compress using an acceptable content-encoding + +The following headers are translated from the headers replied via the onward +connection (always h1) back to the return path (which may be h1 or h2) + +Header|Function +---|--- +content-length|If present, an assertion of how much payload is expected +content-type|The mimetype of the payload +etag|The canonical etag for the content at this URI +accept-language|This is returned to the return path client because there is no easy way for the return path client to know what it sent originally. It allows clientside selection of i18n. +content-encoding|Any compression format on the payload (selected from what the client sent in accept-encoding, if anything) +cache-control|The onward server's response about cacheability of its payload + +### h1 -> h2 conversion + +Chunked encoding that may have been used on the outgoing proxy client connection +is removed for h2 return connections (chunked encoding is illegal for h2). + +Headers are converted to all lower-case and hpack format for h2 return connections. + +Header and payload proxying is staged according to when the return connection +(which may be an h2 child stream) is writable. + +### Behaviour is unix domain socket server unavailable + +If the server that listens on the unix domain socket is down or being restarted, +lws understands that it couldn't connect to it and returns a clean 503 response +`HTTP_STATUS_SERVICE_UNAVAILABLE` along with a brief human-readable explanation. + diff --git a/include/libwebsockets/lws-callbacks.h b/include/libwebsockets/lws-callbacks.h index c0a65cbb8..688fbda4e 100644 --- a/include/libwebsockets/lws-callbacks.h +++ b/include/libwebsockets/lws-callbacks.h @@ -485,7 +485,7 @@ enum lws_callback_reasons { /**< after your client connection completed the websocket upgrade * handshake with the remote server */ - LWS_CALLBACK_CLIENT_CLOSED = 75, + LWS_CALLBACK_CLIENT_CLOSED = 75, /**< when a client websocket session ends */ LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER = 24, @@ -497,21 +497,11 @@ enum lws_callback_reasons { * which is typically some hundreds of bytes. So, to add a canned * cookie, your handler code might look similar to: * - * char **p = (char **)in; + * char **p = (char **)in, *end = (*p) + len; * - * if (len < 100) - * return 1; - * - * *p += sprintf(*p, "Cookie: a=b\x0d\x0a"); - * - * return 0; - * - * Notice if you add anything, you just have to take care about - * the CRLF on the line you added. Obviously this callback is - * optional, if you don't handle it everything is fine. - * - * Notice the callback is coming to protocols[0] all the time, - * because there is no specific protocol negotiated yet. + * if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_COOKIE, + * (unsigned char)"a=b", 3, p, end)) + * return -1; * * See LWS_CALLBACK_ADD_HEADERS for adding headers to server * transactions. @@ -798,4 +788,6 @@ lws_callback_function(struct lws *wsi, enum lws_callback_reasons reason, #define LWS_CB_REASON_AUX_BF__PROXY 2 #define LWS_CB_REASON_AUX_BF__CGI_CHUNK_END 4 #define LWS_CB_REASON_AUX_BF__CGI_HEADERS 8 +#define LWS_CB_REASON_AUX_BF__PROXY_TRANS_END 16 +#define LWS_CB_REASON_AUX_BF__PROXY_HEADERS 32 ///@} diff --git a/include/libwebsockets/lws-logs.h b/include/libwebsockets/lws-logs.h index 2f4c369f3..d74489205 100644 --- a/include/libwebsockets/lws-logs.h +++ b/include/libwebsockets/lws-logs.h @@ -45,8 +45,9 @@ enum lws_log_levels { LLL_CLIENT = 1 << 8, LLL_LATENCY = 1 << 9, LLL_USER = 1 << 10, + LLL_THREAD = 1 << 11, - LLL_COUNT = 11 /* set to count of valid flags */ + LLL_COUNT = 12 /* set to count of valid flags */ }; LWS_VISIBLE LWS_EXTERN void _lws_log(int filter, const char *format, ...) LWS_FORMAT(2); @@ -92,6 +93,7 @@ lwsl_timestamp(int level, char *p, int len); #define lwsl_ext(...) _lws_log(LLL_EXT, __VA_ARGS__) #define lwsl_client(...) _lws_log(LLL_CLIENT, __VA_ARGS__) #define lwsl_latency(...) _lws_log(LLL_LATENCY, __VA_ARGS__) +#define lwsl_thread(...) _lws_log(LLL_THREAD, __VA_ARGS__) #else /* no debug */ #if defined(LWS_WITH_NO_LOGS) @@ -105,6 +107,7 @@ lwsl_timestamp(int level, char *p, int len); #define lwsl_ext(...) do {} while(0) #define lwsl_client(...) do {} while(0) #define lwsl_latency(...) do {} while(0) +#define lwsl_thread(...) do {} while(0) #endif diff --git a/include/libwebsockets/lws-misc.h b/include/libwebsockets/lws-misc.h index 6a110a6c1..02fe432d5 100644 --- a/include/libwebsockets/lws-misc.h +++ b/include/libwebsockets/lws-misc.h @@ -392,6 +392,17 @@ lws_set_wsi_user(struct lws *wsi, void *user); * \param ads: result pointer for address part * \param port: result pointer for port part * \param path: result pointer for path part + * + * You may also refer to unix socket addresses, using a '+' at the start of + * the address. In this case, the address should end with ':', which is + * treated as the separator between the address and path (the normal separator + * '/' is a valid part of the socket path). Eg, + * + * http://+/var/run/mysocket:/my/path + * + * If the first character after the + is '@', it's interpreted by lws client + * processing as meaning to use linux abstract namespace sockets, the @ is + * replaced with a '\0' before use. */ LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT lws_parse_uri(char *p, const char **prot, const char **ads, int *port, @@ -422,6 +433,12 @@ lws_cmdline_option(int argc, const char **argv, const char *val); LWS_VISIBLE LWS_EXTERN unsigned long lws_now_secs(void); +/** + * lws_now_usecs(): return useconds since 1970-1-1 + */ +LWS_VISIBLE LWS_EXTERN lws_usec_t +lws_now_usecs(void); + /** * lws_compare_time_t(): return relationship between two time_t * diff --git a/include/libwebsockets/lws-timeout-timer.h b/include/libwebsockets/lws-timeout-timer.h index b7f04e582..9411866cc 100644 --- a/include/libwebsockets/lws-timeout-timer.h +++ b/include/libwebsockets/lws-timeout-timer.h @@ -63,6 +63,7 @@ enum pending_timeout { PENDING_TIMEOUT_LAGGING = 28, PENDING_TIMEOUT_THREADPOOL = 29, PENDING_TIMEOUT_THREADPOOL_TASK = 30, + PENDING_TIMEOUT_KILLED_BY_PROXY_CLIENT_CLOSE = 31, /****** add new things just above ---^ ******/ diff --git a/include/libwebsockets/lws-write.h b/include/libwebsockets/lws-write.h index 6708b8589..93e1396e7 100644 --- a/include/libwebsockets/lws-write.h +++ b/include/libwebsockets/lws-write.h @@ -86,6 +86,14 @@ enum lws_write_protocol { /* flags */ + LWS_WRITE_BUFLIST = 0x20, + /**< Don't actually write it... stick it on the output buflist and + * write it as soon as possible. Useful if you learn you have to + * write something, have the data to write to hand but the timing is + * unrelated as to whether the connection is writable or not, and were + * otherwise going to have to allocate a temp buffer and write it + * later anyway */ + LWS_WRITE_NO_FIN = 0x40, /**< This part of the message is not the end of the message */ diff --git a/lib/core/adopt.c b/lib/core/adopt.c index 5eafefd2d..99ca856cc 100644 --- a/lib/core/adopt.c +++ b/lib/core/adopt.c @@ -241,6 +241,9 @@ bail: parent->child_list = new_wsi->sibling_list; if (new_wsi->user_space) lws_free(new_wsi->user_space); + + vh->context->count_wsi_allocated--; + lws_vhost_unbind_wsi(new_wsi); lws_free(new_wsi); diff --git a/lib/core/connect.c b/lib/core/connect.c index 6e730f3da..2b6517c5e 100644 --- a/lib/core/connect.c +++ b/lib/core/connect.c @@ -42,7 +42,7 @@ lws_client_stash_destroy(struct lws *wsi) LWS_VISIBLE struct lws * lws_client_connect_via_info(const struct lws_client_connect_info *i) { - struct lws *wsi; + struct lws *wsi, *safe = NULL; const struct lws_protocols *p; const char *local = i->protocol; #if LWS_MAX_SMP > 1 @@ -219,6 +219,21 @@ lws_client_connect_via_info(const struct lws_client_connect_info *i) goto bail1; } + /* + * at this point user callbacks like + * LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER will be interested to + * know the parent... eg for proxying we can grab extra headers from + * the parent's incoming ah and add them to the child client handshake + */ + + if (i->parent_wsi) { + lwsl_info("%s: created child %p of parent %p\n", __func__, + wsi, i->parent_wsi); + wsi->parent = i->parent_wsi; + safe = wsi->sibling_list = i->parent_wsi->child_list; + i->parent_wsi->child_list = wsi; + } + /* * PHASE 7: Do any role-specific finalization processing. We can still * see important info things via wsi->stash @@ -227,6 +242,12 @@ lws_client_connect_via_info(const struct lws_client_connect_info *i) if (wsi->role_ops->client_bind) { int n = wsi->role_ops->client_bind(wsi, NULL); + if (n && i->parent_wsi) { + /* unpick from parent */ + + i->parent_wsi->child_list = safe; + } + if (n < 0) /* we didn't survive, wsi is freed */ goto bail2; @@ -241,14 +262,8 @@ lws_client_connect_via_info(const struct lws_client_connect_info *i) if (i->pwsi) *i->pwsi = wsi; - if (i->parent_wsi) { - lwsl_info("%s: created child %p of parent %p\n", __func__, - wsi, i->parent_wsi); - wsi->parent = i->parent_wsi; - wsi->sibling_list = i->parent_wsi->child_list; - i->parent_wsi->child_list = wsi; - } -#ifdef LWS_WITH_HTTP_PROXY + +#if defined(LWS_WITH_HUBBUB) if (i->uri_replace_to) wsi->http.rw = lws_rewrite_create(wsi, html_parser_cb, i->uri_replace_from, diff --git a/lib/core/context.c b/lib/core/context.c index 34d5346ca..e3ae08bbb 100644 --- a/lib/core/context.c +++ b/lib/core/context.c @@ -339,347 +339,6 @@ next: return 0; } -LWS_VISIBLE int -lws_callback_http_dummy(struct lws *wsi, enum lws_callback_reasons reason, - void *user, void *in, size_t len) -{ - struct lws_ssl_info *si; -#ifdef LWS_WITH_CGI - struct lws_cgi_args *args; -#endif -#if defined(LWS_WITH_CGI) || defined(LWS_WITH_HTTP_PROXY) - char buf[128]; - int n; -#endif - - switch (reason) { -#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) - case LWS_CALLBACK_HTTP: -#ifndef LWS_NO_SERVER - if (lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL)) - return -1; - - if (lws_http_transaction_completed(wsi)) -#endif - return -1; - break; -#if !defined(LWS_NO_SERVER) - case LWS_CALLBACK_HTTP_BODY_COMPLETION: - case LWS_CALLBACK_HTTP_FILE_COMPLETION: - if (lws_http_transaction_completed(wsi)) - return -1; - break; -#endif - - case LWS_CALLBACK_HTTP_WRITEABLE: -#ifdef LWS_WITH_CGI - if (wsi->reason_bf & (LWS_CB_REASON_AUX_BF__CGI_HEADERS | - LWS_CB_REASON_AUX_BF__CGI)) { - n = lws_cgi_write_split_stdout_headers(wsi); - if (n < 0) { - lwsl_debug("AUX_BF__CGI forcing close\n"); - return -1; - } - if (!n) - lws_rx_flow_control( - wsi->http.cgi->stdwsi[LWS_STDOUT], 1); - - if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__CGI_HEADERS) - wsi->reason_bf &= - ~LWS_CB_REASON_AUX_BF__CGI_HEADERS; - else - wsi->reason_bf &= ~LWS_CB_REASON_AUX_BF__CGI; - - if (wsi->http.cgi && wsi->http.cgi->cgi_transaction_over) - return -1; - break; - } - - if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__CGI_CHUNK_END) { - if (!wsi->http2_substream) { - memcpy(buf + LWS_PRE, "0\x0d\x0a\x0d\x0a", 5); - lwsl_debug("writing chunk term and exiting\n"); - n = lws_write(wsi, (unsigned char *)buf + - LWS_PRE, 5, LWS_WRITE_HTTP); - } else - n = lws_write(wsi, (unsigned char *)buf + - LWS_PRE, 0, - LWS_WRITE_HTTP_FINAL); - - /* always close after sending it */ - return -1; - } -#endif -#if defined(LWS_WITH_HTTP_PROXY) - if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__PROXY) { - char *px = buf + LWS_PRE; - int lenx = sizeof(buf) - LWS_PRE; - - /* - * our sink is writeable and our source has something - * to read. So read a lump of source material of - * suitable size to send or what's available, whichever - * is the smaller. - */ - wsi->reason_bf &= ~LWS_CB_REASON_AUX_BF__PROXY; - if (!lws_get_child(wsi)) - break; - if (lws_http_client_read(lws_get_child(wsi), &px, - &lenx) < 0) - return -1; - break; - } -#endif - break; - -#if defined(LWS_WITH_HTTP_PROXY) - case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: - assert(lws_get_parent(wsi)); - if (!lws_get_parent(wsi)) - break; - lws_get_parent(wsi)->reason_bf |= LWS_CB_REASON_AUX_BF__PROXY; - lws_callback_on_writable(lws_get_parent(wsi)); - break; - - case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: - assert(lws_get_parent(wsi)); - n = lws_write(lws_get_parent(wsi), (unsigned char *)in, - len, LWS_WRITE_HTTP); - if (n < 0) - return -1; - break; - - case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: { - unsigned char *p, *end; - char ctype[64], ctlen = 0; - - p = (unsigned char *)buf + LWS_PRE; - end = p + sizeof(buf) - LWS_PRE; - - if (lws_add_http_header_status(lws_get_parent(wsi), - HTTP_STATUS_OK, &p, end)) - return 1; - if (lws_add_http_header_by_token(lws_get_parent(wsi), - WSI_TOKEN_HTTP_SERVER, - (unsigned char *)"libwebsockets", - 13, &p, end)) - return 1; - - ctlen = lws_hdr_copy(wsi, ctype, sizeof(ctype), - WSI_TOKEN_HTTP_CONTENT_TYPE); - if (ctlen > 0) { - if (lws_add_http_header_by_token(lws_get_parent(wsi), - WSI_TOKEN_HTTP_CONTENT_TYPE, - (unsigned char *)ctype, ctlen, &p, end)) - return 1; - } - - if (lws_finalize_http_header(lws_get_parent(wsi), &p, end)) - return 1; - - *p = '\0'; - n = lws_write(lws_get_parent(wsi), - (unsigned char *)buf + LWS_PRE, - p - ((unsigned char *)buf + LWS_PRE), - LWS_WRITE_HTTP_HEADERS); - if (n < 0) - return -1; - - break; } - -#endif - -#ifdef LWS_WITH_CGI - /* CGI IO events (POLLIN/OUT) appear here, our default policy is: - * - * - POST data goes on subprocess stdin - * - subprocess stdout goes on http via writeable callback - * - subprocess stderr goes to the logs - */ - case LWS_CALLBACK_CGI: - args = (struct lws_cgi_args *)in; - switch (args->ch) { /* which of stdin/out/err ? */ - case LWS_STDIN: - /* TBD stdin rx flow control */ - break; - case LWS_STDOUT: - /* quench POLLIN on STDOUT until MASTER got writeable */ - lws_rx_flow_control(args->stdwsi[LWS_STDOUT], 0); - wsi->reason_bf |= LWS_CB_REASON_AUX_BF__CGI; - /* when writing to MASTER would not block */ - lws_callback_on_writable(wsi); - break; - case LWS_STDERR: - n = lws_get_socket_fd(args->stdwsi[LWS_STDERR]); - if (n < 0) - break; - n = read(n, buf, sizeof(buf) - 2); - if (n > 0) { - if (buf[n - 1] != '\n') - buf[n++] = '\n'; - buf[n] = '\0'; - lwsl_notice("CGI-stderr: %s\n", buf); - } - break; - } - break; - - case LWS_CALLBACK_CGI_TERMINATED: - lwsl_debug("LWS_CALLBACK_CGI_TERMINATED: %d %" PRIu64 "\n", - wsi->http.cgi->explicitly_chunked, - (uint64_t)wsi->http.cgi->content_length); - if (!wsi->http.cgi->explicitly_chunked && - !wsi->http.cgi->content_length) { - /* send terminating chunk */ - lwsl_debug("LWS_CALLBACK_CGI_TERMINATED: ending\n"); - wsi->reason_bf |= LWS_CB_REASON_AUX_BF__CGI_CHUNK_END; - lws_callback_on_writable(wsi); - lws_set_timeout(wsi, PENDING_TIMEOUT_CGI, 3); - break; - } - return -1; - - case LWS_CALLBACK_CGI_STDIN_DATA: /* POST body for stdin */ - args = (struct lws_cgi_args *)in; - args->data[args->len] = '\0'; - if (!args->stdwsi[LWS_STDIN]) - return -1; - n = lws_get_socket_fd(args->stdwsi[LWS_STDIN]); - if (n < 0) - return -1; - -#if defined(LWS_WITH_ZLIB) - if (wsi->http.cgi->gzip_inflate) { - /* gzip handling */ - - if (!wsi->http.cgi->gzip_init) { - lwsl_err("inflating gzip\n"); - - memset(&wsi->http.cgi->inflate, 0, sizeof(wsi->http.cgi->inflate)); - - if (inflateInit2(&wsi->http.cgi->inflate, 16 + 15) != Z_OK) { - lwsl_err("%s: iniflateInit failed\n", __func__); - return -1; - } - - wsi->http.cgi->gzip_init = 1; - } - - wsi->http.cgi->inflate.next_in = args->data; - wsi->http.cgi->inflate.avail_in = args->len; - - do { - - wsi->http.cgi->inflate.next_out = wsi->http.cgi->inflate_buf; - wsi->http.cgi->inflate.avail_out = sizeof(wsi->http.cgi->inflate_buf); - - n = inflate(&wsi->http.cgi->inflate, Z_SYNC_FLUSH); - lwsl_err("inflate: %d\n", n); - switch (n) { - case Z_NEED_DICT: - case Z_STREAM_ERROR: - case Z_DATA_ERROR: - case Z_MEM_ERROR: - inflateEnd(&wsi->http.cgi->inflate); - wsi->http.cgi->gzip_init = 0; - lwsl_err("zlib error inflate %d\n", n); - return -1; - } - - if (wsi->http.cgi->inflate.avail_out != sizeof(wsi->http.cgi->inflate_buf)) { - int written; - -// lwsl_hexdump_notice(wsi->http.cgi->inflate_buf, - // sizeof(wsi->http.cgi->inflate_buf) - wsi->http.cgi->inflate.avail_out); - - written = write(args->stdwsi[LWS_STDIN]->desc.filefd, wsi->http.cgi->inflate_buf, - sizeof(wsi->http.cgi->inflate_buf) - wsi->http.cgi->inflate.avail_out); - - if (written != (int)(sizeof(wsi->http.cgi->inflate_buf) - wsi->http.cgi->inflate.avail_out)) { - lwsl_notice("LWS_CALLBACK_CGI_STDIN_DATA: " - "sent %d only %d went", n, args->len); - } - lwsl_err("send inflated on fd %d says %d\n", args->stdwsi[LWS_STDIN]->desc.filefd, written); - - if (n == Z_STREAM_END) { - lwsl_err("gzip inflate end\n"); - inflateEnd(&wsi->http.cgi->inflate); - wsi->http.cgi->gzip_init = 0; - - //compatible_close(args->stdwsi[LWS_STDIN]->desc.filefd); - //args->stdwsi[LWS_STDIN]->desc.filefd = -1; - break; - } - - } else - break; - - if (wsi->http.cgi->inflate.avail_out) - break; - - } while (1); - - return args->len; - } -#endif /* WITH_ZLIB */ - - n = write(n, args->data, args->len); -// lwsl_hexdump_notice(args->data, args->len); - if (n < args->len) - lwsl_notice("LWS_CALLBACK_CGI_STDIN_DATA: " - "sent %d only %d went", n, args->len); - - if (wsi->http.cgi->post_in_expected && args->stdwsi[LWS_STDIN] && - args->stdwsi[LWS_STDIN]->desc.filefd > 0) { - wsi->http.cgi->post_in_expected -= n; - if (!wsi->http.cgi->post_in_expected) { - struct lws *siwsi = args->stdwsi[LWS_STDIN]; - - lwsl_debug("%s: expected POST in end: " - "closing stdin wsi %p, fd %d\n", - __func__, siwsi, siwsi->desc.sockfd); - - __remove_wsi_socket_from_fds(siwsi); - lwsi_set_state(siwsi, LRS_DEAD_SOCKET); - siwsi->socket_is_permanently_unusable = 1; - lws_remove_child_from_any_parent(siwsi); - if (wsi->context->event_loop_ops-> - close_handle_manually) { - wsi->context->event_loop_ops-> - close_handle_manually(siwsi); - siwsi->told_event_loop_closed = 1; - } else { - compatible_close(siwsi->desc.sockfd); - __lws_free_wsi(siwsi); - } - wsi->http.cgi->pipe_fds[LWS_STDIN][1] = -1; - - args->stdwsi[LWS_STDIN] = NULL; - } - } - - return n; -#endif /* WITH_CGI */ -#endif /* ROLE_ H1 / H2 */ - case LWS_CALLBACK_SSL_INFO: - si = in; - - (void)si; - lwsl_notice("LWS_CALLBACK_SSL_INFO: where: 0x%x, ret: 0x%x\n", - si->where, si->ret); - break; - -#if LWS_MAX_SMP > 1 - case LWS_CALLBACK_GET_THREAD_ID: - return (int)(unsigned long long)pthread_self(); -#endif - - default: - break; - } - - return 0; -} /* list of supported protocols and callbacks */ @@ -1142,6 +801,7 @@ lws_destroy_event_pipe(struct lws *wsi) if (wsi->context->event_loop_ops->wsi_logical_close) { wsi->context->event_loop_ops->wsi_logical_close(wsi); lws_plat_pipe_close(wsi); + wsi->context->count_wsi_allocated--; return; } @@ -1668,13 +1328,14 @@ lws_vhost_destroy1(struct lws_vhost *vh) assert(v->lserv_wsi == NULL); v->lserv_wsi = vh->lserv_wsi; + lwsl_notice("%s: listen skt from %s to %s\n", + __func__, vh->name, v->name); + if (v->lserv_wsi) { lws_vhost_unbind_wsi(vh->lserv_wsi); lws_vhost_bind_wsi(v, v->lserv_wsi); } - lwsl_notice("%s: listen skt from %s to %s\n", - __func__, vh->name, v->name); break; } } lws_end_foreach_ll(v, vhost_next); diff --git a/lib/core/dummy-callback.c b/lib/core/dummy-callback.c new file mode 100644 index 000000000..b5fa7656a --- /dev/null +++ b/lib/core/dummy-callback.c @@ -0,0 +1,605 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * 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 "core/private.h" + +#if defined(LWS_WITH_HTTP_PROXY) +static int +proxy_header(struct lws *wsi, struct lws *par, unsigned char *temp, + int temp_len, int index, unsigned char **p, unsigned char *end) +{ + int n = lws_hdr_total_length(par, index); + + if (n < 1) { + lwsl_debug("%s: no index %d:\n", __func__, index); + return 0; + } + + if (lws_hdr_copy(par, (char *)temp, temp_len, index) < 0) + return -1; + + lwsl_debug("%s: index %d: %s\n", __func__, index, (char *)temp); + + if (lws_add_http_header_by_token(wsi, index, temp, n, p, end)) + return -1; + + return 0; +} + +static int +stream_close(struct lws *wsi) +{ + char buf[LWS_PRE + 6], *out = buf + LWS_PRE; + + if (wsi->http.did_stream_close) + return 0; + + wsi->http.did_stream_close = 1; + + if (wsi->http2_substream) { + if (lws_write(wsi, (unsigned char *)buf + LWS_PRE, 0, + LWS_WRITE_HTTP_FINAL) < 0) { + lwsl_info("%s: COMPL_CLIENT_HTTP: h2 fin wr failed\n", + __func__); + + return -1; + } + } else { + *out++ = '0'; + *out++ = '\x0d'; + *out++ = '\x0a'; + *out++ = '\x0d'; + *out++ = '\x0a'; + + if (lws_write(wsi, (unsigned char *)buf + LWS_PRE, 5, + LWS_WRITE_HTTP_FINAL) < 0) { + lwsl_err("%s: COMPL_CLIENT_HTTP: " + "h2 final write failed\n", __func__); + + return -1; + } + } + + return 0; +} + +#endif + +LWS_VISIBLE int +lws_callback_http_dummy(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + struct lws_ssl_info *si; +#ifdef LWS_WITH_CGI + struct lws_cgi_args *args; +#endif +#if defined(LWS_WITH_CGI) || defined(LWS_WITH_HTTP_PROXY) + char buf[8192]; + int n; +#endif +#if defined(LWS_WITH_HTTP_PROXY) + unsigned char **p, *end; + struct lws *parent; +#endif + + switch (reason) { +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + case LWS_CALLBACK_HTTP: +#ifndef LWS_NO_SERVER + if (lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL)) + return -1; + + if (lws_http_transaction_completed(wsi)) +#endif + return -1; + break; +#if !defined(LWS_NO_SERVER) + case LWS_CALLBACK_HTTP_BODY_COMPLETION: + case LWS_CALLBACK_HTTP_FILE_COMPLETION: + if (lws_http_transaction_completed(wsi)) + return -1; + break; +#endif + + case LWS_CALLBACK_HTTP_WRITEABLE: +#ifdef LWS_WITH_CGI + if (wsi->reason_bf & (LWS_CB_REASON_AUX_BF__CGI_HEADERS | + LWS_CB_REASON_AUX_BF__CGI)) { + n = lws_cgi_write_split_stdout_headers(wsi); + if (n < 0) { + lwsl_debug("AUX_BF__CGI forcing close\n"); + return -1; + } + if (!n) + lws_rx_flow_control( + wsi->http.cgi->stdwsi[LWS_STDOUT], 1); + + if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__CGI_HEADERS) + wsi->reason_bf &= + ~LWS_CB_REASON_AUX_BF__CGI_HEADERS; + else + wsi->reason_bf &= ~LWS_CB_REASON_AUX_BF__CGI; + + if (wsi->http.cgi && wsi->http.cgi->cgi_transaction_over) + return -1; + break; + } + + if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__CGI_CHUNK_END) { + if (!wsi->http2_substream) { + memcpy(buf + LWS_PRE, "0\x0d\x0a\x0d\x0a", 5); + lwsl_debug("writing chunk term and exiting\n"); + n = lws_write(wsi, (unsigned char *)buf + + LWS_PRE, 5, LWS_WRITE_HTTP); + } else + n = lws_write(wsi, (unsigned char *)buf + + LWS_PRE, 0, + LWS_WRITE_HTTP_FINAL); + + /* always close after sending it */ + return -1; + } +#endif +#if defined(LWS_WITH_HTTP_PROXY) + + if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__PROXY_HEADERS) { + + wsi->reason_bf &= ~LWS_CB_REASON_AUX_BF__PROXY_HEADERS; + + lwsl_notice("%s: %p: issuing proxy headers\n", + __func__, wsi); + n = lws_write(wsi, wsi->http.pending_return_headers + LWS_PRE, + wsi->http.pending_return_headers_len, + LWS_WRITE_HTTP_HEADERS); + + lws_free_set_NULL(wsi->http.pending_return_headers); + + if (n < 0) { + lwsl_err("%s: EST_CLIENT_HTTP: write failed\n", + __func__); + return -1; + } + lws_callback_on_writable(wsi); + break; + } + + if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__PROXY) { + char *px = buf + LWS_PRE; + int lenx = sizeof(buf) - LWS_PRE - 32; + + /* + * our sink is writeable and our source has something + * to read. So read a lump of source material of + * suitable size to send or what's available, whichever + * is the smaller. + */ + wsi->reason_bf &= ~LWS_CB_REASON_AUX_BF__PROXY; + if (!lws_get_child(wsi)) + break; + + /* this causes LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ */ + if (lws_http_client_read(lws_get_child(wsi), &px, + &lenx) < 0) { + lwsl_info("%s: LWS_CB_REASON_AUX_BF__PROXY: " + "client closed\n", __func__); + + stream_close(wsi); + + return -1; + } + break; + } + + if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__PROXY_TRANS_END) { + lwsl_info("%s: LWS_CB_REASON_AUX_BF__PROXY_TRANS_END\n", + __func__); + + wsi->reason_bf &= ~LWS_CB_REASON_AUX_BF__PROXY_TRANS_END; + + if (stream_close(wsi)) + return -1; + + if (lws_http_transaction_completed(wsi)) + return -1; + } +#endif + break; + +#if defined(LWS_WITH_HTTP_PROXY) + case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: + assert(lws_get_parent(wsi)); + if (!lws_get_parent(wsi)) + break; + lws_get_parent(wsi)->reason_bf |= LWS_CB_REASON_AUX_BF__PROXY; + lws_callback_on_writable(lws_get_parent(wsi)); + break; + + case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: { + char *out = buf + LWS_PRE; + + assert(lws_get_parent(wsi)); + + if (wsi->http.proxy_parent_chunked) { + + if (len > sizeof(buf) - LWS_PRE - 16) { + lwsl_err("oversize buf %d %d\n", (int)len, + (int)sizeof(buf) - LWS_PRE - 16); + return -1; + } + + /* + * this only needs dealing with on http/1.1 to allow + * pipelining + */ + n = lws_snprintf(out, 14, "%X\x0d\x0a", (int)len); + out += n; + memcpy(out, in, len); + out += len; + *out++ = '\x0d'; + *out++ = '\x0a'; + + n = lws_write(lws_get_parent(wsi), + (unsigned char *)buf + LWS_PRE, + len + n + 2, LWS_WRITE_HTTP); + } else + n = lws_write(lws_get_parent(wsi), (unsigned char *)in, + len, LWS_WRITE_HTTP); + if (n < 0) + return -1; + break; } + + + /* this handles the proxy case... */ + case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: { + unsigned char *start, *p, *end; + + /* + * We want to proxy these headers, but we are being called + * at the point the onward client was established, which is + * unrelated to the state or writability of our proxy + * connection. + * + * Therefore produce the headers using the onward client ah + * while we have it, and stick them on the output buflist to be + * written on the proxy connection as soon as convenient. + */ + + parent = lws_get_parent(wsi); + + if (!parent) + return 0; + + start = p = (unsigned char *)buf + LWS_PRE; + end = p + sizeof(buf) - LWS_PRE - 256; + + if (lws_add_http_header_status(lws_get_parent(wsi), + lws_http_client_http_response(wsi), &p, end)) + return 1; + + /* + * copy these headers from the client connection to the parent + */ + + proxy_header(parent, wsi, end, 256, + WSI_TOKEN_HTTP_CONTENT_LENGTH, &p, end); + proxy_header(parent, wsi, end, 256, + WSI_TOKEN_HTTP_CONTENT_TYPE, &p, end); + proxy_header(parent, wsi, end, 256, + WSI_TOKEN_HTTP_ETAG, &p, end); + proxy_header(parent, wsi, end, 256, + WSI_TOKEN_HTTP_ACCEPT_LANGUAGE, &p, end); + proxy_header(parent, wsi, end, 256, + WSI_TOKEN_HTTP_CONTENT_ENCODING, &p, end); + proxy_header(parent, wsi, end, 256, + WSI_TOKEN_HTTP_CACHE_CONTROL, &p, end); + + if (!parent->http2_substream) + if (lws_add_http_header_by_token(parent, + WSI_TOKEN_CONNECTION, (unsigned char *)"close", + 5, &p, end)) + return -1; + + /* + * We proxy using h1 only atm, and strip any chunking so it + * can go back out on h2 just fine. + * + * However if we are actually going out on h1, we need to add + * our own chunking since we still don't know the size. + */ + + if (!parent->http2_substream && + !lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) { + lwsl_debug("downstream parent chunked\n"); + if (lws_add_http_header_by_token(parent, + WSI_TOKEN_HTTP_TRANSFER_ENCODING, + (unsigned char *)"chunked", 7, &p, end)) + return -1; + + wsi->http.proxy_parent_chunked = 1; + } + + if (lws_finalize_http_header(parent, &p, end)) + return 1; + + parent->http.pending_return_headers_len = lws_ptr_diff(p, start); + parent->http.pending_return_headers = + lws_malloc(parent->http.pending_return_headers_len + LWS_PRE, + "return proxy headers"); + if (!parent->http.pending_return_headers) + return -1; + + memcpy(parent->http.pending_return_headers + LWS_PRE, start, + parent->http.pending_return_headers_len); + + parent->reason_bf |= LWS_CB_REASON_AUX_BF__PROXY_HEADERS; + + lwsl_notice("%s: LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: prepared headers\n", __func__); + lws_callback_on_writable(parent); + + break; } + + case LWS_CALLBACK_COMPLETED_CLIENT_HTTP: + lwsl_info("%s: COMPLETED_CLIENT_HTTP: %p (parent %p)\n", + __func__, wsi, lws_get_parent(wsi)); + if (!lws_get_parent(wsi)) + break; + lws_get_parent(wsi)->reason_bf |= + LWS_CB_REASON_AUX_BF__PROXY_TRANS_END; + lws_callback_on_writable(lws_get_parent(wsi)); + break; + + case LWS_CALLBACK_CLOSED_CLIENT_HTTP: + if (!lws_get_parent(wsi)) + break; + lwsl_err("%s: LWS_CALLBACK_CLOSED_CLIENT_HTTP\n", __func__); + lws_set_timeout(lws_get_parent(wsi), LWS_TO_KILL_ASYNC, + PENDING_TIMEOUT_KILLED_BY_PROXY_CLIENT_CLOSE); + break; + + case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER: + parent = lws_get_parent(wsi); + + if (!parent) + break; + + p = (unsigned char **)in; + end = (*p) + len; + + /* + * copy these headers from the parent request to the client + * connection's request + */ + + proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf), + WSI_TOKEN_HOST, p, end); + proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf), + WSI_TOKEN_HTTP_ETAG, p, end); + proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf), + WSI_TOKEN_HTTP_IF_MODIFIED_SINCE, p, end); + proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf), + WSI_TOKEN_HTTP_ACCEPT_LANGUAGE, p, end); + proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf), + WSI_TOKEN_HTTP_ACCEPT_ENCODING, p, end); + proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf), + WSI_TOKEN_HTTP_CACHE_CONTROL, p, end); + + buf[0] = '\0'; + lws_get_peer_simple(parent, buf, sizeof(buf)); + if (lws_add_http_header_by_token(wsi, WSI_TOKEN_X_FORWARDED_FOR, + (unsigned char *)buf, strlen(buf), p, end)) + return -1; + + break; + +#endif + +#ifdef LWS_WITH_CGI + /* CGI IO events (POLLIN/OUT) appear here, our default policy is: + * + * - POST data goes on subprocess stdin + * - subprocess stdout goes on http via writeable callback + * - subprocess stderr goes to the logs + */ + case LWS_CALLBACK_CGI: + args = (struct lws_cgi_args *)in; + switch (args->ch) { /* which of stdin/out/err ? */ + case LWS_STDIN: + /* TBD stdin rx flow control */ + break; + case LWS_STDOUT: + /* quench POLLIN on STDOUT until MASTER got writeable */ + lws_rx_flow_control(args->stdwsi[LWS_STDOUT], 0); + wsi->reason_bf |= LWS_CB_REASON_AUX_BF__CGI; + /* when writing to MASTER would not block */ + lws_callback_on_writable(wsi); + break; + case LWS_STDERR: + n = lws_get_socket_fd(args->stdwsi[LWS_STDERR]); + if (n < 0) + break; + n = read(n, buf, sizeof(buf) - 2); + if (n > 0) { + if (buf[n - 1] != '\n') + buf[n++] = '\n'; + buf[n] = '\0'; + lwsl_notice("CGI-stderr: %s\n", buf); + } + break; + } + break; + + case LWS_CALLBACK_CGI_TERMINATED: + lwsl_debug("LWS_CALLBACK_CGI_TERMINATED: %d %" PRIu64 "\n", + wsi->http.cgi->explicitly_chunked, + (uint64_t)wsi->http.cgi->content_length); + if (!wsi->http.cgi->explicitly_chunked && + !wsi->http.cgi->content_length) { + /* send terminating chunk */ + lwsl_debug("LWS_CALLBACK_CGI_TERMINATED: ending\n"); + wsi->reason_bf |= LWS_CB_REASON_AUX_BF__CGI_CHUNK_END; + lws_callback_on_writable(wsi); + lws_set_timeout(wsi, PENDING_TIMEOUT_CGI, 3); + break; + } + return -1; + + case LWS_CALLBACK_CGI_STDIN_DATA: /* POST body for stdin */ + args = (struct lws_cgi_args *)in; + args->data[args->len] = '\0'; + if (!args->stdwsi[LWS_STDIN]) + return -1; + n = lws_get_socket_fd(args->stdwsi[LWS_STDIN]); + if (n < 0) + return -1; + +#if defined(LWS_WITH_ZLIB) + if (wsi->http.cgi->gzip_inflate) { + /* gzip handling */ + + if (!wsi->http.cgi->gzip_init) { + lwsl_err("inflating gzip\n"); + + memset(&wsi->http.cgi->inflate, 0, sizeof(wsi->http.cgi->inflate)); + + if (inflateInit2(&wsi->http.cgi->inflate, 16 + 15) != Z_OK) { + lwsl_err("%s: iniflateInit failed\n", __func__); + return -1; + } + + wsi->http.cgi->gzip_init = 1; + } + + wsi->http.cgi->inflate.next_in = args->data; + wsi->http.cgi->inflate.avail_in = args->len; + + do { + + wsi->http.cgi->inflate.next_out = + wsi->http.cgi->inflate_buf; + wsi->http.cgi->inflate.avail_out = + sizeof(wsi->http.cgi->inflate_buf); + + n = inflate(&wsi->http.cgi->inflate, + Z_SYNC_FLUSH); + + switch (n) { + case Z_NEED_DICT: + case Z_STREAM_ERROR: + case Z_DATA_ERROR: + case Z_MEM_ERROR: + inflateEnd(&wsi->http.cgi->inflate); + wsi->http.cgi->gzip_init = 0; + lwsl_err("zlib error inflate %d\n", n); + return -1; + } + + if (wsi->http.cgi->inflate.avail_out != + sizeof(wsi->http.cgi->inflate_buf)) { + int written; + + written = write(args->stdwsi[LWS_STDIN]->desc.filefd, + wsi->http.cgi->inflate_buf, + sizeof(wsi->http.cgi->inflate_buf) - + wsi->http.cgi->inflate.avail_out); + + if (written != (int)( + sizeof(wsi->http.cgi->inflate_buf) - + wsi->http.cgi->inflate.avail_out)) { + lwsl_notice("LWS_CALLBACK_CGI_STDIN_DATA: " + "sent %d only %d went", n, args->len); + } + + if (n == Z_STREAM_END) { + lwsl_err("gzip inflate end\n"); + inflateEnd(&wsi->http.cgi->inflate); + wsi->http.cgi->gzip_init = 0; + break; + } + + } else + break; + + if (wsi->http.cgi->inflate.avail_out) + break; + + } while (1); + + return args->len; + } +#endif /* WITH_ZLIB */ + + n = write(n, args->data, args->len); +// lwsl_hexdump_notice(args->data, args->len); + if (n < args->len) + lwsl_notice("LWS_CALLBACK_CGI_STDIN_DATA: " + "sent %d only %d went", n, args->len); + + if (wsi->http.cgi->post_in_expected && args->stdwsi[LWS_STDIN] && + args->stdwsi[LWS_STDIN]->desc.filefd > 0) { + wsi->http.cgi->post_in_expected -= n; + if (!wsi->http.cgi->post_in_expected) { + struct lws *siwsi = args->stdwsi[LWS_STDIN]; + + lwsl_debug("%s: expected POST in end: " + "closing stdin wsi %p, fd %d\n", + __func__, siwsi, siwsi->desc.sockfd); + + __remove_wsi_socket_from_fds(siwsi); + lwsi_set_state(siwsi, LRS_DEAD_SOCKET); + siwsi->socket_is_permanently_unusable = 1; + lws_remove_child_from_any_parent(siwsi); + if (wsi->context->event_loop_ops-> + close_handle_manually) { + wsi->context->event_loop_ops-> + close_handle_manually(siwsi); + siwsi->told_event_loop_closed = 1; + } else { + compatible_close(siwsi->desc.sockfd); + __lws_free_wsi(siwsi); + } + wsi->http.cgi->pipe_fds[LWS_STDIN][1] = -1; + + args->stdwsi[LWS_STDIN] = NULL; + } + } + + return n; +#endif /* WITH_CGI */ +#endif /* ROLE_ H1 / H2 */ + case LWS_CALLBACK_SSL_INFO: + si = in; + + (void)si; + lwsl_notice("LWS_CALLBACK_SSL_INFO: where: 0x%x, ret: 0x%x\n", + si->where, si->ret); + break; + +#if LWS_MAX_SMP > 1 + case LWS_CALLBACK_GET_THREAD_ID: + return (int)(unsigned long long)pthread_self(); +#endif + + default: + break; + } + + return 0; +} diff --git a/lib/core/libwebsockets.c b/lib/core/libwebsockets.c index 91ce98c40..21a63e09e 100644 --- a/lib/core/libwebsockets.c +++ b/lib/core/libwebsockets.c @@ -53,6 +53,7 @@ static const char * const log_level_names[] = { "CLIENT", "LATENCY", "USER", + "THREAD", "?", "?" }; @@ -374,6 +375,15 @@ __lws_set_timer_usecs(struct lws *wsi, lws_usec_t usecs) // lws_dll_dump(&pt->dll_head_hrtimer, "after set_timer_usec"); } +LWS_VISIBLE lws_usec_t +lws_now_usecs(void) +{ + struct timeval now; + + gettimeofday(&now, NULL); + return (now.tv_sec * 1000000ll) + now.tv_usec; +} + LWS_VISIBLE void lws_set_timer_usecs(struct lws *wsi, lws_usec_t usecs) { @@ -867,12 +877,16 @@ just_kill_connection: lwsl_debug("%s: real just_kill_connection: %p (sockfd %d)\n", __func__, wsi, wsi->desc.sockfd); -#ifdef LWS_WITH_HTTP_PROXY +#ifdef LWS_WITH_HUBBUB if (wsi->http.rw) { lws_rewrite_destroy(wsi->http.rw); wsi->http.rw = NULL; } #endif + + if (wsi->http.pending_return_headers) + lws_free_set_NULL(wsi->http.pending_return_headers); + /* * we won't be servicing or receiving anything further from this guy * delete socket from the internal poll list if still present @@ -2029,6 +2043,7 @@ static const char * const colours[] = { "[33m", /* LLL_CLIENT */ "[33;1m", /* LLL_LATENCY */ "[30;1m", /* LLL_USER */ + "[31m", /* LLL_THREAD */ }; LWS_VISIBLE void lwsl_emit_stderr(int level, const char *line) @@ -2394,7 +2409,7 @@ lws_parse_uri(char *p, const char **prot, const char **ads, int *port, const char **path) { const char *end; - static const char *slash = "/"; + char unix_skt = 0; /* cut up the location into address, port and path */ *prot = p; @@ -2408,32 +2423,32 @@ lws_parse_uri(char *p, const char **prot, const char **ads, int *port, *p = '\0'; p += 3; } + if (*p == '+') /* unix skt */ + unix_skt = 1; + *ads = p; if (!strcmp(*prot, "http") || !strcmp(*prot, "ws")) *port = 80; else if (!strcmp(*prot, "https") || !strcmp(*prot, "wss")) *port = 443; - if (*p == '[') - { - ++(*ads); - while (*p && *p != ']') - p++; - if (*p) - *p++ = '\0'; - } - else - { - while (*p && *p != ':' && *p != '/') - p++; - } + if (*p == '[') { + ++(*ads); + while (*p && *p != ']') + p++; + if (*p) + *p++ = '\0'; + } else + while (*p && *p != ':' && (unix_skt || *p != '/')) + p++; + if (*p == ':') { *p++ = '\0'; *port = atoi(p); while (*p && *p != '/') p++; } - *path = slash; + *path = "/"; if (*p) { *p++ = '\0'; if (*p) diff --git a/lib/core/output.c b/lib/core/output.c index d43735ade..9baa1d9d7 100644 --- a/lib/core/output.c +++ b/lib/core/output.c @@ -251,6 +251,10 @@ lws_ssl_capable_read_no_ssl(struct lws *wsi, unsigned char *buf, int len) n = recv(wsi->desc.sockfd, (char *)buf, len, 0); if (n >= 0) { + + if (!n && wsi->unix_skt) + return LWS_SSL_CAPABLE_ERROR; + if (wsi->vhost) wsi->vhost->conn_stats.rx += n; lws_stats_atomic_bump(context, pt, LWSSTATS_B_READ, n); diff --git a/lib/core/private.h b/lib/core/private.h index af9741fd7..c25dcc45f 100644 --- a/lib/core/private.h +++ b/lib/core/private.h @@ -959,6 +959,7 @@ struct lws { unsigned int on_same_vh_list:1; unsigned int handling_404:1; unsigned int protocol_bind_balance:1; + unsigned int unix_skt:1; unsigned int could_have_pending:1; /* detect back-to-back writes */ unsigned int outer_will_close:1; @@ -1156,7 +1157,8 @@ user_callback_handle_rxflow(lws_callback_function, struct lws *wsi, void *in, size_t len); LWS_EXTERN int -lws_plat_set_socket_options(struct lws_vhost *vhost, lws_sockfd_type fd); +lws_plat_set_socket_options(struct lws_vhost *vhost, lws_sockfd_type fd, + int unix_skt); LWS_EXTERN int lws_plat_check_connection_error(struct lws *wsi); @@ -1493,7 +1495,7 @@ void lws_peer_dump_from_wsi(struct lws *wsi); #endif -#ifdef LWS_WITH_HTTP_PROXY +#ifdef LWS_WITH_HUBBUB hubbub_error html_parser_cb(const hubbub_token *token, void *pw); #endif diff --git a/lib/roles/h1/ops-h1.c b/lib/roles/h1/ops-h1.c index 61910c7c1..29bc6aa4c 100644 --- a/lib/roles/h1/ops-h1.c +++ b/lib/roles/h1/ops-h1.c @@ -974,6 +974,26 @@ cleanup: } #endif +static int +rops_close_kill_connection_h1(struct lws *wsi, enum lws_close_status reason) +{ +#if defined(LWS_WITH_HTTP_PROXY) + struct lws *wsi_eff = lws_client_wsi_effective(wsi); + + if (!wsi_eff->http.proxy_clientside) + return 0; + + wsi_eff->http.proxy_clientside = 0; + + if (user_callback_handle_rxflow(wsi_eff->protocol->callback, wsi_eff, + LWS_CALLBACK_COMPLETED_CLIENT_HTTP, + wsi_eff->user_space, NULL, 0)) + return 0; +#endif + return 0; +} + + struct lws_role_ops role_ops_h1 = { /* role name */ "h1", /* alpn id */ "http/1.1", @@ -993,7 +1013,7 @@ struct lws_role_ops role_ops_h1 = { /* alpn_negotiated */ rops_alpn_negotiated_h1, /* close_via_role_protocol */ NULL, /* close_role */ NULL, - /* close_kill_connection */ NULL, + /* close_kill_connection */ rops_close_kill_connection_h1, /* destroy_role */ rops_destroy_role_h1, #if !defined(LWS_NO_SERVER) /* adoption_bind */ rops_adoption_bind_h1, diff --git a/lib/roles/h2/http2.c b/lib/roles/h2/http2.c index 38e53d578..be4b0e1c6 100644 --- a/lib/roles/h2/http2.c +++ b/lib/roles/h2/http2.c @@ -219,6 +219,8 @@ bail1: parent_wsi->h2.child_list = wsi->h2.sibling_list; parent_wsi->h2.child_count--; + vh->context->count_wsi_allocated--; + if (wsi->user_space) lws_free_set_NULL(wsi->user_space); vh->protocols[0].callback(wsi, LWS_CALLBACK_WSI_DESTROY, NULL, NULL, 0); @@ -381,6 +383,9 @@ lws_h2_rst_stream(struct lws *wsi, uint32_t err, const char *reason) struct lws_h2_netconn *h2n = nwsi->h2.h2n; struct lws_h2_protocol_send *pps; + if (!h2n) + return 0; + if (h2n->type == LWS_H2_FRAME_TYPE_COUNT) return 0; diff --git a/lib/roles/h2/ops-h2.c b/lib/roles/h2/ops-h2.c index 2c6b2675d..fa1fd93fa 100644 --- a/lib/roles/h2/ops-h2.c +++ b/lib/roles/h2/ops-h2.c @@ -578,6 +578,19 @@ rops_close_kill_connection_h2(struct lws *wsi, enum lws_close_status reason) { struct lws *wsi2; +#if defined(LWS_WITH_HTTP_PROXY) + if (wsi->http.proxy_clientside) { + struct lws *wsi_eff = lws_client_wsi_effective(wsi); + + wsi->http.proxy_clientside = 0; + + if (user_callback_handle_rxflow(wsi_eff->protocol->callback, wsi_eff, + LWS_CALLBACK_COMPLETED_CLIENT_HTTP, + wsi_eff->user_space, NULL, 0)) + wsi->http.proxy_clientside = 0; + } +#endif + if (wsi->http2_substream && wsi->h2_stream_carries_ws) lws_h2_rst_stream(wsi, 0, "none"); @@ -960,7 +973,8 @@ rops_perform_user_POLLOUT_h2(struct lws *wsi) * states. In those cases we will hear about * END_STREAM going out in the POLLOUT handler. */ - if (n || w->h2.send_END_STREAM) { + if (!w->h2.pending_status_body && + (n || w->h2.send_END_STREAM)) { lwsl_info("closing stream after h2 action\n"); lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS, "h2 end stream"); diff --git a/lib/roles/http/client/client-handshake.c b/lib/roles/http/client/client-handshake.c index 737e56b4d..88c7fb1ed 100644 --- a/lib/roles/http/client/client-handshake.c +++ b/lib/roles/http/client/client-handshake.c @@ -37,8 +37,13 @@ lws_client_connect_2(struct lws *wsi) ssize_t plen = 0; #endif struct addrinfo *result; +#if defined(LWS_WITH_UNIX_SOCK) + struct sockaddr_un sau; + char unix_skt = 0; +#endif const char *ads; sockaddr46 sa46; + const struct sockaddr *psa; int n, port; const char *cce = "", *iface; const char *meth = NULL; @@ -183,6 +188,29 @@ create_new_conn: lws_vhost_unlock(wsi->vhost); } + /* + * unix socket destination? + */ + + ads = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS); +#if defined(LWS_WITH_UNIX_SOCK) + if (*ads == '+') { + ads++; + memset(&sau, 0, sizeof(sau)); + sau.sun_family = AF_UNIX; + strncpy(sau.sun_path, ads, sizeof(sau.sun_path)); + sau.sun_path[sizeof(sau.sun_path) - 1] = '\0'; + + lwsl_info("%s: Unix skt: %s\n", __func__, ads); + + if (sau.sun_path[0] == '@') + sau.sun_path[0] = '\0'; + + unix_skt = 1; + goto ads_known; + } +#endif + /* * start off allowing ipv6 on connection if vhost allows it */ @@ -341,6 +369,10 @@ create_new_conn: if (result) freeaddrinfo(result); +#if defined(LWS_WITH_UNIX_SOCK) +ads_known: +#endif + /* now we decided on ipv4 or ipv6, set the port */ if (!lws_socket_is_valid(wsi->desc.sockfd)) { @@ -351,12 +383,21 @@ create_new_conn: goto oom4; } +#if defined(LWS_WITH_UNIX_SOCK) + if (unix_skt) { + wsi->unix_skt = 1; + wsi->desc.sockfd = socket(AF_UNIX, SOCK_STREAM, 0); + } else +#endif + { + #ifdef LWS_WITH_IPV6 if (wsi->ipv6) wsi->desc.sockfd = socket(AF_INET6, SOCK_STREAM, 0); else #endif wsi->desc.sockfd = socket(AF_INET, SOCK_STREAM, 0); + } if (!lws_socket_is_valid(wsi->desc.sockfd)) { lwsl_warn("Unable to open socket\n"); @@ -364,7 +405,12 @@ create_new_conn: goto oom4; } - if (lws_plat_set_socket_options(wsi->vhost, wsi->desc.sockfd)) { + if (lws_plat_set_socket_options(wsi->vhost, wsi->desc.sockfd, +#if defined(LWS_WITH_UNIX_SOCK) + unix_skt)) { +#else + 0)) { +#endif lwsl_err("Failed to set wsi socket options\n"); compatible_close(wsi->desc.sockfd); cce = "set socket opts failed"; @@ -409,18 +455,29 @@ create_new_conn: } } -#ifdef LWS_WITH_IPV6 - if (wsi->ipv6) { - sa46.sa6.sin6_port = htons(port); - n = sizeof(struct sockaddr_in6); +#if defined(LWS_WITH_UNIX_SOCK) + if (unix_skt) { + psa = (const struct sockaddr *)&sau; + n = sizeof(sau); } else #endif + { - sa46.sa4.sin_port = htons(port); - n = sizeof(struct sockaddr); +#ifdef LWS_WITH_IPV6 + if (wsi->ipv6) { + sa46.sa6.sin6_port = htons(port); + n = sizeof(struct sockaddr_in6); + psa = (const struct sockaddr *)&sa46; + } else +#endif + { + sa46.sa4.sin_port = htons(port); + n = sizeof(struct sockaddr); + psa = (const struct sockaddr *)&sa46; + } } - if (connect(wsi->desc.sockfd, (const struct sockaddr *)&sa46, n) == -1 || + if (connect(wsi->desc.sockfd, (const struct sockaddr *)psa, n) == -1 || LWS_ERRNO == LWS_EISCONN) { if (LWS_ERRNO == LWS_EALREADY || LWS_ERRNO == LWS_EINPROGRESS || @@ -729,7 +786,7 @@ lws_client_reset(struct lws **pwsi, int ssl, const char *address, int port, return *pwsi; } -#ifdef LWS_WITH_HTTP_PROXY +#if defined(LWS_WITH_HTTP_PROXY) && defined(LWS_WITH_HUBBUB) hubbub_error html_parser_cb(const hubbub_token *token, void *pw) { diff --git a/lib/roles/http/client/client.c b/lib/roles/http/client/client.c index 673262540..46a5afe9a 100644 --- a/lib/roles/http/client/client.c +++ b/lib/roles/http/client/client.c @@ -882,7 +882,7 @@ lws_client_interpret_server_handshake(struct lws *wsi) if (!strncmp(lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE), "text/html", 9)) - wsi->http.perform_rewrite = 1; + wsi->http.perform_rewrite = 0; } #endif @@ -993,7 +993,9 @@ bail2: } wsi->already_did_cce = 1; - lwsl_info("closing connection due to bail2 connection error\n"); + lwsl_info("closing connection (prot %s) " + "due to bail2 connection error: %s\n", wsi->protocol ? + wsi->protocol->name : "unknown", cce); /* closing will free up his parsing allocations */ lws_close_free_wsi(wsi, close_reason, "c hs interp"); @@ -1065,6 +1067,9 @@ lws_generate_client_handshake(struct lws *wsi, char *pkt) p += sprintf(p, "Pragma: no-cache\x0d\x0a" "Cache-Control: no-cache\x0d\x0a"); + if (!wsi->client_pipeline) + p += sprintf(p, "connection: close\x0d\x0a"); + p += sprintf(p, "Host: %s\x0d\x0a", lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_HOST)); @@ -1196,7 +1201,7 @@ spin_chunks: wsi->chunk_remaining < n) n = wsi->chunk_remaining; -#ifdef LWS_WITH_HTTP_PROXY +#if defined(LWS_WITH_HTTP_PROXY) && defined(LWS_WITH_HUBBUB) /* hubbub */ if (wsi->http.perform_rewrite) lws_rewrite_parse(wsi->http.rw, (unsigned char *)*buf, n); diff --git a/lib/roles/http/header.c b/lib/roles/http/header.c index 85939343a..5bf3d371d 100644 --- a/lib/roles/http/header.c +++ b/lib/roles/http/header.c @@ -384,7 +384,7 @@ lws_return_http_status(struct lws *wsi, unsigned int code, #if defined(LWS_WITH_HTTP2) if (wsi->http2_substream) { - unsigned char *body = p + 512; + char *body = (char *)start + context->pt_serv_buf_size - 512; /* * for HTTP/2, the headers must be sent separately, since they @@ -398,7 +398,8 @@ lws_return_http_status(struct lws *wsi, unsigned int code, * * Solve it by writing the headers now... */ - m = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS); + m = lws_write(wsi, start, lws_ptr_diff(p, start), + LWS_WRITE_HTTP_HEADERS); if (m != lws_ptr_diff(p, start)) return 1; @@ -407,8 +408,7 @@ lws_return_http_status(struct lws *wsi, unsigned int code, * handle_POLLOUT */ - len = sprintf((char *)body, - "

%u

%s", + len = sprintf(body, "

%u

%s", code, html_body); wsi->http.tx_content_length = len; wsi->http.tx_content_remain = len; @@ -418,8 +418,7 @@ lws_return_http_status(struct lws *wsi, unsigned int code, if (!wsi->h2.pending_status_body) return -1; - strcpy(wsi->h2.pending_status_body + LWS_PRE, - (const char *)body); + strcpy(wsi->h2.pending_status_body + LWS_PRE, body); lws_callback_on_writable(wsi); return 0; diff --git a/lib/roles/http/private.h b/lib/roles/http/private.h index 366435396..ff8b0581c 100644 --- a/lib/roles/http/private.h +++ b/lib/roles/http/private.h @@ -22,7 +22,7 @@ * enabled */ -#if defined(LWS_WITH_HTTP_PROXY) +#if defined(LWS_WITH_HUBBUB) #include #include #endif @@ -137,7 +137,7 @@ struct allocated_headers { -#if defined(LWS_WITH_HTTP_PROXY) +#if defined(LWS_WITH_HUBBUB) struct lws_rewrite { hubbub_parser *parser; hubbub_parser_optparams params; @@ -202,6 +202,9 @@ struct lws_access_log { struct _lws_http_mode_related { struct lws *new_wsi_list; + unsigned char *pending_return_headers; + size_t pending_return_headers_len; + #if defined(LWS_WITH_HTTP_PROXY) struct lws_rewrite *rw; #endif @@ -238,9 +241,12 @@ struct _lws_http_mode_related { #if defined(LWS_WITH_HTTP_PROXY) unsigned int perform_rewrite:1; + unsigned int proxy_clientside:1; + unsigned int proxy_parent_chunked:1; #endif unsigned int deferred_transaction_completed:1; unsigned int content_length_explicitly_zero:1; + unsigned int did_stream_close:1; }; diff --git a/lib/roles/http/server/access-log.c b/lib/roles/http/server/access-log.c index dc016a5be..07e6207fe 100644 --- a/lib/roles/http/server/access-log.c +++ b/lib/roles/http/server/access-log.c @@ -93,7 +93,7 @@ lws_prepare_access_log_info(struct lws *wsi, char *uri_ptr, int uri_len, int met pa, da, me, uri, hver[wsi->http.request_version]); - lwsl_notice("%s\n", wsi->http.access_log.header_log); + //lwsl_notice("%s\n", wsi->http.access_log.header_log); l = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_USER_AGENT); if (l) { diff --git a/lib/roles/http/server/rewrite.c b/lib/roles/http/server/rewrite.c index 1afcce151..3b923ecef 100644 --- a/lib/roles/http/server/rewrite.c +++ b/lib/roles/http/server/rewrite.c @@ -1,5 +1,6 @@ #include "core/private.h" +#if defined(LWS_WITH_HUBBUB) LWS_EXTERN struct lws_rewrite * lws_rewrite_create(struct lws *wsi, hubbub_callback_t cb, const char *from, const char *to) @@ -50,3 +51,4 @@ lws_rewrite_destroy(struct lws_rewrite *r) lws_free(r); } +#endif diff --git a/lib/roles/http/server/server.c b/lib/roles/http/server/server.c index 8cff753ba..1bd76938d 100644 --- a/lib/roles/http/server/server.c +++ b/lib/roles/http/server/server.c @@ -223,7 +223,7 @@ done_list: } #endif #endif - lws_plat_set_socket_options(vhost, sockfd); + lws_plat_set_socket_options(vhost, sockfd, 0); is = lws_socket_bind(vhost, sockfd, vhost->listen_port, vhost->iface); /* @@ -236,15 +236,24 @@ done_list: compatible_close(sockfd); goto deal; } - vhost->listen_port = is; - - lwsl_debug("%s: lws_socket_bind says %d\n", __func__, is); wsi = lws_zalloc(sizeof(struct lws), "listen wsi"); if (wsi == NULL) { lwsl_err("Out of mem\n"); goto bail; } + +#ifdef LWS_WITH_UNIX_SOCK + if (!LWS_UNIX_SOCK_ENABLED(vhost)) +#endif + { + wsi->unix_skt = 1; + vhost->listen_port = is; + + lwsl_debug("%s: lws_socket_bind says %d\n", __func__, + is); + } + wsi->context = vhost->context; wsi->desc.sockfd = sockfd; lws_role_transition(wsi, 0, LRS_UNCONNECTED, &role_ops_listen); @@ -327,7 +336,7 @@ lws_select_vhost(struct lws_context *context, int port, const char *servername) vhost = context->vhost_list; while (vhost) { m = (int)strlen(vhost->name); - if (port == vhost->listen_port && + if (port && port == vhost->listen_port && m <= (colon - 2) && servername[colon - m - 1] == '.' && !strncmp(vhost->name, servername + colon - m, m)) { @@ -342,7 +351,7 @@ lws_select_vhost(struct lws_context *context, int port, const char *servername) vhost = context->vhost_list; while (vhost) { - if (port == vhost->listen_port) { + if (port && port == vhost->listen_port) { lwsl_info("%s: vhost match to %s based on port %d\n", __func__, vhost->name, port); return vhost; @@ -784,7 +793,7 @@ lws_unauthorised_basic_auth(struct lws *wsi) { struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; unsigned char *start = pt->serv_buf + LWS_PRE, - *p = start, *end = p + 512; + *p = start, *end = p + 2048; char buf[64]; int n; @@ -1011,7 +1020,7 @@ lws_http_action(struct lws *wsi) * URI from the host: header and ignore the path part */ unsigned char *start = pt->serv_buf + LWS_PRE, *p = start, - *end = p + 512; + *end = p + wsi->context->pt_serv_buf_size - LWS_PRE; if (!lws_hdr_total_length(wsi, WSI_TOKEN_HOST)) goto bail_nuke_ah; @@ -1077,9 +1086,10 @@ lws_http_action(struct lws *wsi) (hit->origin_protocol != LWSMPRO_CGI && hit->origin_protocol != LWSMPRO_CALLBACK)) { unsigned char *start = pt->serv_buf + LWS_PRE, - *p = start, *end = p + 512; + *p = start, *end = p + wsi->context->pt_serv_buf_size - + LWS_PRE - 512; - lwsl_debug("Doing 301 '%s' org %s\n", s, hit->origin); + lwsl_info("Doing 301 '%s' org %s\n", s, hit->origin); /* > at start indicates deal with by redirect */ if (hit->origin_protocol == LWSMPRO_REDIR_HTTP || @@ -1162,15 +1172,21 @@ lws_http_action(struct lws *wsi) * The mount is a reverse proxy? */ + // lwsl_notice("%s: origin_protocol: %d\n", __func__, hit->origin_protocol); + if (hit->origin_protocol == LWSMPRO_HTTPS || hit->origin_protocol == LWSMPRO_HTTP) { struct lws_client_connect_info i; - char ads[96], rpath[256], *pcolon, *pslash, *p; + struct lws *cwsi; + char ads[96], rpath[256], *pcolon, *pslash, *p, unix_skt = 0; int n, na; memset(&i, 0, sizeof(i)); i.context = lws_get_context(wsi); + if (hit->origin[0] == '+') + unix_skt = 1; + pcolon = strchr(hit->origin, ':'); pslash = strchr(hit->origin, '/'); if (!pslash) { @@ -1178,16 +1194,28 @@ lws_http_action(struct lws *wsi) hit->origin); return -1; } - if (pcolon > pslash) - pcolon = NULL; - if (pcolon) - n = pcolon - hit->origin; - else - n = pslash - hit->origin; + if (unix_skt) { + if (!pcolon) { + lwsl_err("Proxy mount origin for unix skt must " + "have address delimited by :\n"); - if (n >= (int)sizeof(ads) - 2) - n = sizeof(ads) - 2; + return -1; + } + n = lws_ptr_diff(pcolon, hit->origin); + pslash = pcolon; + } else { + if (pcolon > pslash) + pcolon = NULL; + + if (pcolon) + n = pcolon - hit->origin; + else + n = pslash - hit->origin; + + if (n >= (int)sizeof(ads) - 2) + n = sizeof(ads) - 2; + } memcpy(ads, hit->origin, n); ads[n] = '\0'; @@ -1217,25 +1245,48 @@ lws_http_action(struct lws *wsi) } } - i.path = rpath; - i.host = i.address; + if (i.address[0] != '+' || + !lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST)) + i.host = i.address; + else + i.host = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST); i.origin = NULL; i.method = "GET"; + i.alpn = "http/1.1"; i.parent_wsi = wsi; - i.uri_replace_from = hit->origin; - i.uri_replace_to = hit->mountpoint; + i.pwsi = &cwsi; - lwsl_notice("proxying to %s port %d url %s, ssl %d, " + // i.uri_replace_from = hit->origin; + // i.uri_replace_to = hit->mountpoint; + + lwsl_info("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); if (!lws_client_connect_via_info(&i)) { lwsl_err("proxy connect fail\n"); + + /* + * ... we can't do the proxy action, but we can + * cleanly return him a 503 and a description + */ + + lws_return_http_status(wsi, + HTTP_STATUS_SERVICE_UNAVAILABLE, + "

Service Temporarily Unavailable

" + "The server is temporarily unable to service " + "your request due to maintenance downtime or " + "capacity problems. Please try again later."); + return 1; } + lwsl_info("%s: setting proxy clientside on %p (parent %p)\n", + __func__, cwsi, lws_get_parent(cwsi)); + cwsi->http.proxy_clientside = 1; + return 0; } #endif @@ -1531,7 +1582,8 @@ raw_transition: /* select vhost */ - if (lws_hdr_total_length(wsi, WSI_TOKEN_HOST)) { + if (wsi->vhost->listen_port && + lws_hdr_total_length(wsi, WSI_TOKEN_HOST)) { struct lws_vhost *vhost = lws_select_vhost( context, wsi->vhost->listen_port, lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST)); @@ -1744,7 +1796,7 @@ lws_http_transaction_completed(struct lws *wsi) return 0; } - lwsl_debug("%s: wsi %p\n", __func__, wsi); + lwsl_info("%s: wsi %p\n", __func__, wsi); #if defined(LWS_WITH_HTTP_STREAM_COMPRESSION) lws_http_compression_destroy(wsi); @@ -2056,20 +2108,24 @@ lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type, total_content_length, &p, end)) return -1; } else { - /* ...otherwise, for http 1 it must go chunked. For - * the compression case, the reason is we compress on - * the fly and do not know the compressed content-length - * until it has all been sent. Http/1.1 pipelining must - * be able to know where the transaction boundaries are - * ... so chunking... - */ - if (lws_add_http_header_by_token(wsi, - WSI_TOKEN_HTTP_TRANSFER_ENCODING, - (unsigned char *)"chunked", 7, &p, end)) - return -1; #if defined(LWS_WITH_HTTP_STREAM_COMPRESSION) if (wsi->http.lcs) { + + /* ...otherwise, for http 1 it must go chunked. + * For the compression case, the reason is we + * compress on the fly and do not know the + * compressed content-length until it has all + * been sent. Http/1.1 pipelining must be able + * to know where the transaction boundaries are + * ... so chunking... + */ + if (lws_add_http_header_by_token(wsi, + WSI_TOKEN_HTTP_TRANSFER_ENCODING, + (unsigned char *)"chunked", 7, + &p, end)) + return -1; + /* * ...this is fun, isn't it :-) For h1 that is * using an http compression translation, the @@ -2093,7 +2149,8 @@ lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type, wsi->cache_secs); } else { cc = cache_control; - cclen = sprintf(cache_control, "must-revalidate, %s, max-age=%u", + cclen = sprintf(cache_control, + "must-revalidate, %s, max-age=%u", intermediates[wsi->cache_intermediaries], wsi->cache_secs); diff --git a/lib/roles/listen/ops-listen.c b/lib/roles/listen/ops-listen.c index bd39640e2..92f8cd856 100644 --- a/lib/roles/listen/ops-listen.c +++ b/lib/roles/listen/ops-listen.c @@ -93,7 +93,7 @@ rops_handle_POLLIN_listen(struct lws_context_per_thread *pt, struct lws *wsi, break; } - lws_plat_set_socket_options(wsi->vhost, accept_fd); + lws_plat_set_socket_options(wsi->vhost, accept_fd, 0); #if defined(LWS_WITH_IPV6) lwsl_debug("accepted new conn port %u on fd=%d\n", diff --git a/lib/roles/ws/ops-ws.c b/lib/roles/ws/ops-ws.c index 91d8c92da..52d369fed 100644 --- a/lib/roles/ws/ops-ws.c +++ b/lib/roles/ws/ops-ws.c @@ -1495,7 +1495,11 @@ rops_close_via_role_protocol_ws(struct lws *wsi, enum lws_close_status reason) static int rops_close_role_ws(struct lws_context_per_thread *pt, struct lws *wsi) { + if (!wsi->ws) + return 0; + #if !defined(LWS_WITHOUT_EXTENSIONS) + if (wsi->ws->rx_draining_ext) { struct lws **w = &pt->ws.rx_draining_ext_list; diff --git a/test-apps/test-client.c b/test-apps/test-client.c index d5b6f5f79..dbb8ead99 100644 --- a/test-apps/test-client.c +++ b/test-apps/test-client.c @@ -672,9 +672,12 @@ int main(int argc, char **argv) goto usage; /* add back the leading / on path */ - path[0] = '/'; - lws_strncpy(path + 1, p, sizeof(path) - 1); - i.path = path; + if (p[0] != '/') { + path[0] = '/'; + lws_strncpy(path + 1, p, sizeof(path) - 1); + i.path = path; + } else + i.path = p; if (!strcmp(prot, "http") || !strcmp(prot, "ws")) use_ssl = 0; @@ -682,6 +685,8 @@ int main(int argc, char **argv) if (!use_ssl) use_ssl = LCCSCF_USE_SSL; + lwsl_debug("'%s' %p '%s' %p\n", i.address, i.address, i.path, i.path); + /* * create the websockets context. This tracks open connections and * knows how to route any traffic and which protocol version to use,