diff --git a/CMakeLists.txt b/CMakeLists.txt index bd6bb367..490abd6a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -88,6 +88,8 @@ option(LWS_WITH_HTTP2 "Compile with support for http2" OFF) option(LWS_MBED3 "Platform is MBED3" OFF) option(LWS_SSL_SERVER_WITH_ECDH_CERT "Include SSL server use ECDH certificate" OFF) option(LWS_WITH_CGI "Include CGI (spawn process with network-connected stdin/out/err) APIs" OFF) +option(LWS_WITH_HTTP_PROXY "Support for rewriting HTTP proxying" OFF) + if (DEFINED YOTTA_WEBSOCKETS_VERSION_STRING) @@ -108,6 +110,9 @@ if (WIN32) set(LWS_MAX_SMP 1) 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") +endif() # Allow the user to override installation directories. set(LWS_INSTALL_LIB_DIR lib CACHE PATH "Installation directory for libraries") @@ -211,6 +216,7 @@ if (LWS_WITH_LIBUV) endif() endif() + # FIXME: This must be runtime-only option. # The base dir where the test-apps look for the SSL certs. set(LWS_OPENSSL_CLIENT_CERTS ../share CACHE PATH "Server SSL certificate directory") @@ -489,6 +495,11 @@ if (NOT LWS_WITHOUT_EXTENSIONS) lib/extension-permessage-deflate.c) endif() +if (LWS_WITH_HTTP_PROXY) + list(APPEND SOURCES + lib/rewrite.c) +endif() + if (LWS_WITH_LIBEV) list(APPEND SOURCES lib/libev.c) @@ -734,6 +745,11 @@ if (LWS_WITH_LIBUV) include_directories("${LIBUV_INCLUDE_DIRS}") list(APPEND LIB_LIST ${LIBUV_LIBRARIES}) endif() +if (LWS_WITH_HTTP_PROXY) + find_library(LIBHUBBUB_LIBRARIES NAMES libhubbub) + list(APPEND LIB_LIST ${LIBHUBBUB_LIBRARIES} ) +endif() + # # Platform specific libs. @@ -1227,6 +1243,8 @@ message(" LWS_SSL_SERVER_WITH_ECDH_CERT = ${LWS_SSL_SERVER_WITH_ECDH_CERT}") message(" LWS_MAX_SMP = ${LWS_MAX_SMP}") message(" LWS_WITH_CGI = ${LWS_WITH_CGI}") message(" LWS_HAVE_OPENSSL_ECDH_H = ${LWS_HAVE_OPENSSL_ECDH_H}") +message(" LWS_WITH_HTTP_PROXY = ${LWS_WITH_HTTP_PROXY}") +message(" LIBHUBBUB_LIBRARIES = ${LIBHUBBUB_LIBRARIES}") message("---------------------------------------------------------------------") # These will be available to parent projects including libwebsockets using add_subdirectory() diff --git a/changelog b/changelog index ef5a406f..bc580d68 100644 --- a/changelog +++ b/changelog @@ -81,9 +81,14 @@ protocol string to its argument in URL format. If so, it stays in http[s] client mode and doesn't upgrade to ws[s], allowing you to do generic http client operations. Receiving transfer-encoding: chunked is supported. -9) The test server has a new URI path http://localhost:7681/proxytest -If you visit here, a client connection to http://example.com:80 is spawned, -and the results piped on to your original connection. +9) If you enable -DLWS_WITH_HTTP_PROXY=1 at cmake, the test server has a +new URI path http://localhost:7681/proxytest If you visit here, a client +connection to http://example.com:80 is spawned, and the results piped on +to your original connection. + +10) Also with LWS_WITH_HTTP_PROXY enabled at cmake, lws wants to link to an +additional library, "libhubbub". This allows lws to do html rewriting on the +fly, adjusting proxied urls in a lightweight and fast way. User API additions diff --git a/lib/client-handshake.c b/lib/client-handshake.c index 5504abe8..b3419a1c 100644 --- a/lib/client-handshake.c +++ b/lib/client-handshake.c @@ -338,6 +338,115 @@ lws_client_reset(struct lws *wsi, int ssl, const char *address, int port, const return lws_client_connect_2(wsi); } + +static hubbub_error +html_parser_cb(const hubbub_token *token, void *pw) +{ + struct lws_rewrite *r = (struct lws_rewrite *)pw; + char buf[1024], *start = buf + LWS_PRE, *p = start, + *end = &buf[sizeof(buf) - 1]; + size_t i; + + switch (token->type) { + case HUBBUB_TOKEN_DOCTYPE: + + p += snprintf(p, end - p, "data.doctype.name.len, + token->data.doctype.name.ptr, + token->data.doctype.force_quirks ? + "(force-quirks) " : ""); + + if (token->data.doctype.public_missing) + printf("\tpublic: missing\n"); + else + p += snprintf(p, end - p, "PUBLIC \"%.*s\"\n", + (int) token->data.doctype.public_id.len, + token->data.doctype.public_id.ptr); + + if (token->data.doctype.system_missing) + printf("\tsystem: missing\n"); + else + p += snprintf(p, end - p, " \"%.*s\">\n", + (int) token->data.doctype.system_id.len, + token->data.doctype.system_id.ptr); + + break; + case HUBBUB_TOKEN_START_TAG: + p += snprintf(p, end - p, "<%.*s", (int)token->data.tag.name.len, + token->data.tag.name.ptr); + +/* (token->data.tag.self_closing) ? + "(self-closing) " : "", + (token->data.tag.n_attributes > 0) ? + "attributes:" : ""); +*/ + for (i = 0; i < token->data.tag.n_attributes; i++) { + if (!hstrcmp(&token->data.tag.attributes[i].name, "href", 4) || + !hstrcmp(&token->data.tag.attributes[i].name, "action", 6) || + !hstrcmp(&token->data.tag.attributes[i].name, "src", 3)) { + const char *pp = (const char *)token->data.tag.attributes[i].value.ptr; + int plen = (int) token->data.tag.attributes[i].value.len; + + if (!hstrcmp(&token->data.tag.attributes[i].value, + r->from, r->from_len)) { + pp += r->from_len; + plen -= r->from_len; + } + p += snprintf(p, end - p, " %.*s=\"%s/%.*s\"", + (int) token->data.tag.attributes[i].name.len, + token->data.tag.attributes[i].name.ptr, + r->to, plen, pp); + + } else + + p += snprintf(p, end - p, " %.*s=\"%.*s\"", + (int) token->data.tag.attributes[i].name.len, + token->data.tag.attributes[i].name.ptr, + (int) token->data.tag.attributes[i].value.len, + token->data.tag.attributes[i].value.ptr); + } + p += snprintf(p, end - p, ">\n"); + break; + case HUBBUB_TOKEN_END_TAG: + p += snprintf(p, end - p, "data.tag.name.len, + token->data.tag.name.ptr); +/* + (token->data.tag.self_closing) ? + "(self-closing) " : "", + (token->data.tag.n_attributes > 0) ? + "attributes:" : ""); +*/ + for (i = 0; i < token->data.tag.n_attributes; i++) { + p += snprintf(p, end - p, " %.*s='%.*s'\n", + (int) token->data.tag.attributes[i].name.len, + token->data.tag.attributes[i].name.ptr, + (int) token->data.tag.attributes[i].value.len, + token->data.tag.attributes[i].value.ptr); + } + p += snprintf(p, end - p, ">\n"); + break; + case HUBBUB_TOKEN_COMMENT: + p += snprintf(p, end - p, "\n", + (int) token->data.comment.len, + token->data.comment.ptr); + break; + case HUBBUB_TOKEN_CHARACTER: + p += snprintf(p, end - p, "%.*s", (int) token->data.character.len, + token->data.character.ptr); + break; + case HUBBUB_TOKEN_EOF: + p += snprintf(p, end - p, "\n"); + break; + } + + if (user_callback_handle_rxflow(r->wsi->protocol->callback, + r->wsi, LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ, + r->wsi->user_space, start, p - start)) + return -1; + + return HUBBUB_OK; +} + /** * lws_client_connect_via_info() - Connect to another websocket server * @i:pointer to lws_client_connect_info struct @@ -452,6 +561,11 @@ lws_client_connect_via_info(struct lws_client_connect_info *i) i->parent_wsi->child_list = wsi; } + if (i->uri_replace_to) + wsi->rw = lws_rewrite_create(wsi, html_parser_cb, + i->uri_replace_from, + i->uri_replace_to); + return wsi; bail: diff --git a/lib/client.c b/lib/client.c index 37779be4..77eb7f08 100644 --- a/lib/client.c +++ b/lib/client.c @@ -622,6 +622,15 @@ lws_client_interpret_server_handshake(struct lws *wsi) goto bail2; } +#ifndef LWS_NO_CLIENT + wsi->perform_rewrite = 0; + if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE)) { + if (!strncmp(lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE), + "text/html", 9)) + wsi->perform_rewrite = 1; + } +#endif + /* allocate the per-connection user memory (if any) */ if (lws_ensure_user_space(wsi)) { lwsl_err("Problem allocating wsi user mem\n"); diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c index 04c2ef5f..7942ac1d 100644 --- a/lib/libwebsockets.c +++ b/lib/libwebsockets.c @@ -158,12 +158,18 @@ lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason) if (wsi->child_list) { wsi2 = wsi->child_list; while (wsi2) { - lwsl_notice("%s: closing %p: close child %p\n", - __func__, wsi, wsi2); + //lwsl_notice("%s: closing %p: close child %p\n", + // __func__, wsi, wsi2); wsi1 = wsi2->sibling_list; + //lwsl_notice("%s: closing %p: next sibling %p\n", + // __func__, wsi2, wsi1); + wsi2->parent = NULL; + /* stop it doing shutdown processing */ + wsi2->socket_is_permanently_unusable = 1; lws_close_free_wsi(wsi2, reason); wsi2 = wsi1; } + wsi->child_list = NULL; } #ifdef LWS_WITH_CGI @@ -186,7 +192,6 @@ lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason) if (wsi->mode == LWSCM_HTTP_SERVING_ACCEPTED && wsi->u.http.fd != LWS_INVALID_FILE) { - lwsl_debug("closing http file\n"); lws_plat_file_close(wsi, wsi->u.http.fd); wsi->u.http.fd = LWS_INVALID_FILE; context->protocols[0].callback(wsi, LWS_CALLBACK_CLOSED_HTTP, @@ -363,7 +368,7 @@ just_kill_connection: if (wsi->state != LWSS_SHUTDOWN && reason != LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY && !wsi->socket_is_permanently_unusable) { - lwsl_info("%s: shutting down connection: %p\n", __func__, wsi); + lwsl_info("%s: shutting down connection: %p (sock %d)\n", __func__, wsi, wsi->sock); n = shutdown(wsi->sock, SHUT_WR); if (n) lwsl_debug("closing: shutdown ret %d\n", LWS_ERRNO); @@ -384,7 +389,13 @@ just_kill_connection: } #endif - lwsl_info("%s: real just_kill_connection: %p\n", __func__, wsi); + lwsl_info("%s: real just_kill_connection: %p (sockfd %d)\n", __func__, + wsi, wsi->sock); + + if (wsi->rw) { + lws_rewrite_destroy(wsi->rw); + wsi->rw = NULL; + } /* * we won't be servicing or receiving anything further from this guy @@ -502,6 +513,7 @@ lws_close_free_wsi_final(struct lws *wsi) if (!lws_ssl_close(wsi) && lws_socket_is_valid(wsi->sock)) { #if LWS_POSIX + //lwsl_err("*** closing sockfd %d\n", wsi->sock); n = compatible_close(wsi->sock); if (n) lwsl_debug("closing: close ret %d\n", LWS_ERRNO); @@ -1742,12 +1754,11 @@ lws_cgi_write_split_stdout_headers(struct lws *wsi) /* ran out of input, ended the headers, or filled up the headers buf */ if (!n || wsi->hdr_state == LHCS_PAYLOAD || (p + 4) == end) { -lwsl_err("a\n"); + m = lws_write(wsi, (unsigned char *)start, p - start, LWS_WRITE_HTTP_HEADERS); if (m < 0) return -1; -lwsl_err("b\n"); /* writeability becomes uncertain now we wrote * something, we must return to the event loop */ @@ -1755,7 +1766,7 @@ lwsl_err("b\n"); return 0; } } - lwsl_err("%s: stdout\n", __func__); + //lwsl_err("%s: stdout\n", __func__); n = read(lws_get_socket_fd(wsi->cgi->stdwsi[LWS_STDOUT]), start, sizeof(buf) - LWS_PRE); diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index f4b20c62..72b88657 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -364,6 +364,7 @@ enum lws_callback_reasons { LWS_CALLBACK_CLOSED_CLIENT_HTTP = 45, LWS_CALLBACK_RECEIVE_CLIENT_HTTP = 46, LWS_CALLBACK_COMPLETED_CLIENT_HTTP = 47, + LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ = 48, /****** add new things just above ---^ ******/ @@ -1415,6 +1416,9 @@ struct lws_context_creation_info { * @parent_wsi: if another wsi is responsible for this connection, give it here. * this is used to make sure if the parent closes so do any * child connections first. + * @uri_replace_from: if non-NULL, when this string is found in URIs in + * text/html content-encoding, it's replaced with @uri_replace_to + * @uri_replace_to: see above */ struct lws_client_connect_info { @@ -1431,6 +1435,8 @@ struct lws_client_connect_info { const struct lws_extension *client_exts; const char *method; struct lws *parent_wsi; + const char *uri_replace_from; + const char *uri_replace_to; /* Add new things just above here ---^ * This is part of the ABI, don't needlessly break compatibility @@ -1893,6 +1899,9 @@ LWS_VISIBLE LWS_EXTERN int lws_cgi_kill(struct lws *wsi); #endif +LWS_VISIBLE LWS_EXTERN int +lws_http_client_read(struct lws *wsi, char **buf, int *len); + /* * Wsi-associated File Operations access helpers * diff --git a/lib/output.c b/lib/output.c index 3204490f..b527e9c5 100644 --- a/lib/output.c +++ b/lib/output.c @@ -600,7 +600,6 @@ LWS_VISIBLE int lws_serve_http_file_fragment(struct lws *wsi) all_sent: if (!wsi->trunc_len && wsi->u.http.filepos == wsi->u.http.filelen) { wsi->state = LWSS_HTTP; - /* we might be in keepalive, so close it off here */ lws_plat_file_close(wsi, wsi->u.http.fd); wsi->u.http.fd = LWS_INVALID_FILE; diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index 4d69bd56..b70a31ba 100644 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -112,6 +112,10 @@ #include #include #include +#ifdef LWS_WITH_HTTP_PROXY +#include +#include +#endif #ifdef LWS_BUILTIN_GETIFADDRS #include #else @@ -157,7 +161,7 @@ #define LWS_POLLHUP (POLLHUP|POLLERR) #define LWS_POLLIN (POLLIN) #define LWS_POLLOUT (POLLOUT) -#define compatible_close(fd) close(fd) +static inline int compatible_close(int fd) { return close(fd); } #define lws_set_blocking_send(wsi) #ifdef MBED_OPERATORS @@ -1068,6 +1072,8 @@ enum lws_chunk_parser { }; #endif +struct lws_rewrite; + struct lws { /* structs */ @@ -1118,6 +1124,9 @@ struct lws { BIO *client_bio; struct lws *pending_read_list_prev, *pending_read_list_next; #endif +#ifndef LWS_NO_CLIENT + struct lws_rewrite *rw; +#endif #ifdef LWS_LATENCY unsigned long action_start; unsigned long latency_start; @@ -1144,6 +1153,8 @@ struct lws { #ifndef LWS_NO_CLIENT unsigned int do_ws:1; /* whether we are doing http or ws flow */ unsigned int chunked:1; /* if the clientside connection is chunked */ + unsigned int client_rx_avail:1; + unsigned int perform_rewrite:1; #endif #ifndef LWS_NO_EXTENSIONS unsigned int extension_data_pending:1; @@ -1502,10 +1513,35 @@ lws_ssl_capable_write_no_ssl(struct lws *wsi, unsigned char *buf, int len); LWS_EXTERN int LWS_WARN_UNUSED_RESULT lws_ssl_pending_no_ssl(struct lws *wsi); -#ifndef LWS_NO_CLIENT +#ifdef LWS_WITH_HTTP_PROXY +struct lws_rewrite { + hubbub_parser *parser; + hubbub_parser_optparams params; + const char *from, *to; + int from_len, to_len; + unsigned char *p, *end; + struct lws *wsi; +}; +static LWS_INLINE int hstrcmp(hubbub_string *s, const char *p, int len) +{ + if (s->len != len) + return 1; + + return strncmp((const char *)s->ptr, p, len); +} +typedef hubbub_error (*hubbub_callback_t)(const hubbub_token *token, void *pw); LWS_EXTERN int lws_client_socket_service(struct lws_context *context, struct lws *wsi, struct lws_pollfd *pollfd); +LWS_EXTERN struct lws_rewrite * +lws_rewrite_create(struct lws *wsi, hubbub_callback_t cb, const char *from, const char *to); +LWS_EXTERN void +lws_rewrite_destroy(struct lws_rewrite *r); +LWS_EXTERN int +lws_rewrite_parse(struct lws_rewrite *r, const unsigned char *in, int in_len); +#endif + +#ifndef LWS_NO_CLIENT #ifdef LWS_OPENSSL_SUPPORT LWS_EXTERN int lws_context_init_client_ssl(struct lws_context_creation_info *info, diff --git a/lib/rewrite.c b/lib/rewrite.c new file mode 100644 index 00000000..db98e556 --- /dev/null +++ b/lib/rewrite.c @@ -0,0 +1,47 @@ +#include "private-libwebsockets.h" + + +LWS_EXTERN struct lws_rewrite * +lws_rewrite_create(struct lws *wsi, hubbub_callback_t cb, const char *from, const char *to) +{ + struct lws_rewrite *r = lws_malloc(sizeof(*r)); + + if (hubbub_parser_create("UTF-8", false, &r->parser) != HUBBUB_OK) { + lws_free(r); + + return NULL; + } + r->from = from; + r->from_len = strlen(from); + r->to = to; + r->to_len = strlen(to); + r->params.token_handler.handler = cb; + r->wsi = wsi; + r->params.token_handler.pw = (void *)r; + if (hubbub_parser_setopt(r->parser, HUBBUB_PARSER_TOKEN_HANDLER, + &r->params) != HUBBUB_OK) { + lws_free(r); + + return NULL; + } + + return r; +} + +LWS_EXTERN int +lws_rewrite_parse(struct lws_rewrite *r, + const unsigned char *in, int in_len) +{ + if (hubbub_parser_parse_chunk(r->parser, in, in_len) != HUBBUB_OK) + return -1; + + return 0; +} + +LWS_EXTERN void +lws_rewrite_destroy(struct lws_rewrite *r) +{ + hubbub_parser_destroy(r->parser); + lws_free(r); +} + diff --git a/lib/server.c b/lib/server.c index 07e20af4..5a19a16a 100644 --- a/lib/server.c +++ b/lib/server.c @@ -779,7 +779,7 @@ lws_adopt_socket(struct lws_context *context, lws_sockfd_type accept_fd) return NULL; } - lwsl_debug("%s: new wsi %p\n", __func__, new_wsi); + lwsl_info("%s: new wsi %p, sockfd %d\n", __func__, new_wsi, accept_fd); new_wsi->sock = accept_fd; diff --git a/lib/service.c b/lib/service.c index 9acdcb11..368043dd 100644 --- a/lib/service.c +++ b/lib/service.c @@ -473,6 +473,134 @@ lws_service_flag_pending(struct lws_context *context, int tsi) return forced; } + +/* + * + */ +LWS_VISIBLE int +lws_http_client_read(struct lws *wsi, char **buf, int *len) +{ + int rlen, n; + + rlen = lws_ssl_capable_read(wsi, (unsigned char *)*buf, *len); + if (rlen < 0) + return -1; + + *len = rlen; + if (rlen == 0) + return 0; + +// lwsl_err("%s: read %d\n", __func__, rlen); + + /* allow the source to signal he has data again next time */ + wsi->client_rx_avail = 0; + lws_change_pollfd(wsi, 0, LWS_POLLIN); + + /* + * server may insist on transfer-encoding: chunked, + * so http client must deal with it + */ +spin_chunks: + while (wsi->chunked && (wsi->chunk_parser != ELCP_CONTENT) && *len) { + switch (wsi->chunk_parser) { + case ELCP_HEX: + if ((*buf)[0] == '\x0d') { + wsi->chunk_parser = ELCP_CR; + break; + } + n = char_to_hex((*buf)[0]); + if (n < 0) + return -1; + wsi->chunk_remaining <<= 4; + wsi->chunk_remaining |= n; + break; + case ELCP_CR: + if ((*buf)[0] != '\x0a') + return -1; + wsi->chunk_parser = ELCP_CONTENT; + lwsl_info("chunk %d\n", wsi->chunk_remaining); + if (wsi->chunk_remaining) + break; + lwsl_info("final chunk\n"); + goto completed; + + case ELCP_CONTENT: + break; + + case ELCP_POST_CR: + if ((*buf)[0] != '\x0d') + return -1; + + wsi->chunk_parser = ELCP_POST_LF; + break; + + case ELCP_POST_LF: + if ((*buf)[0] != '\x0a') + return -1; + + wsi->chunk_parser = ELCP_HEX; + wsi->chunk_remaining = 0; + break; + } + (*buf)++; + (*len)--; + } + + if (wsi->chunked && !wsi->chunk_remaining) + return 0; + + if (wsi->u.http.content_remain && + wsi->u.http.content_remain < *len) + n = wsi->u.http.content_remain; + else + n = *len; + + if (wsi->chunked && wsi->chunk_remaining && + wsi->chunk_remaining < n) + n = wsi->chunk_remaining; + + /* hubbub */ + if (wsi->perform_rewrite) + lws_rewrite_parse(wsi->rw, (unsigned char *)*buf, n); + else + + if (user_callback_handle_rxflow(wsi->protocol->callback, + wsi, LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ, + wsi->user_space, *buf, n)) + return -1; + + if (wsi->chunked && wsi->chunk_remaining) { + (*buf) += n; + wsi->chunk_remaining -= n; + *len -= n; + } + + if (wsi->chunked && !wsi->chunk_remaining) + wsi->chunk_parser = ELCP_POST_CR; + + if (wsi->chunked && *len) { + goto spin_chunks; + } + + if (wsi->chunked) + return 0; + + wsi->u.http.content_remain -= n; + if (wsi->u.http.content_remain || !wsi->u.http.content_length) + return 0; + +completed: + if (user_callback_handle_rxflow(wsi->protocol->callback, + wsi, LWS_CALLBACK_COMPLETED_CLIENT_HTTP, + wsi->user_space, NULL, 0)) + return -1; + + if (lws_http_transaction_completed(wsi)) + return -1; + + return 0; +} + /** * lws_service_fd() - Service polled socket with something waiting * @context: Websocket context @@ -716,37 +844,59 @@ lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, int t if (!(pollfd->revents & pollfd->events & LWS_POLLIN)) break; -read: +read: /* all the union members start with hdr, so even in ws mode * we can deal with the ah via u.hdr */ if (wsi->u.hdr.ah) { - lwsl_err("%s: %p: using inherited ah rx\n", __func__, wsi); + lwsl_info("%s: %p: inherited ah rx\n", __func__, wsi); eff_buf.token_len = wsi->u.hdr.ah->rxlen - wsi->u.hdr.ah->rxpos; eff_buf.token = (char *)wsi->u.hdr.ah->rx + wsi->u.hdr.ah->rxpos; } else { + if (wsi->mode != LWSCM_HTTP_CLIENT_ACCEPTED) { + eff_buf.token_len = lws_ssl_capable_read(wsi, + pt->serv_buf, pending ? pending : + LWS_MAX_SOCKET_IO_BUF); + switch (eff_buf.token_len) { + case 0: + lwsl_info("%s: zero length read\n", __func__); + goto close_and_handled; + case LWS_SSL_CAPABLE_MORE_SERVICE: + lwsl_info("SSL Capable more service\n"); + n = 0; + goto handled; + case LWS_SSL_CAPABLE_ERROR: + lwsl_info("Closing when error\n"); + goto close_and_handled; + } - eff_buf.token_len = lws_ssl_capable_read(wsi, pt->serv_buf, - pending ? pending : LWS_MAX_SOCKET_IO_BUF); - switch (eff_buf.token_len) { - case 0: - lwsl_info("service_fd: closing due to 0 length read\n"); - goto close_and_handled; - case LWS_SSL_CAPABLE_MORE_SERVICE: - lwsl_info("SSL Capable more service\n"); - n = 0; - goto handled; - case LWS_SSL_CAPABLE_ERROR: - lwsl_info("Closing when error\n"); - goto close_and_handled; + eff_buf.token = (char *)pt->serv_buf; } - - eff_buf.token = (char *)pt->serv_buf; } +drain: + if (wsi->mode == LWSCM_HTTP_CLIENT_ACCEPTED) { + + /* + * simply mark ourselves as having readable data + * and turn off our POLLIN + */ + wsi->client_rx_avail = 1; + lws_change_pollfd(wsi, LWS_POLLIN, 0); + + /* let user code know, he'll usually ask for writeable + * callback and drain / reenable it there + */ + if (user_callback_handle_rxflow(wsi->protocol->callback, + wsi, LWS_CALLBACK_RECEIVE_CLIENT_HTTP, + wsi->user_space, NULL, 0)) + goto close_and_handled; + + + } /* * give any active extensions a chance to munge the buffer * before parse. We pass in a pointer to an lws_tokens struct @@ -758,121 +908,6 @@ read: * extension callback handling, just the normal input buffer is * used then so it is efficient. */ -drain: - if (wsi->mode == LWSCM_HTTP_CLIENT_ACCEPTED) { - /* - * server may insist on transfer-encoding: chunked, - * so http client must deal with it - */ -spin_chunks: - while (wsi->chunked && - (wsi->chunk_parser != ELCP_CONTENT) && - eff_buf.token_len) { - switch (wsi->chunk_parser) { - case ELCP_HEX: - if (eff_buf.token[0] == '\x0d') { - wsi->chunk_parser = ELCP_CR; - break; - } - n = char_to_hex(eff_buf.token[0]); - if (n < 0) - goto close_and_handled; - wsi->chunk_remaining <<= 4; - wsi->chunk_remaining |= n; - break; - case ELCP_CR: - if (eff_buf.token[0] != '\x0a') - goto close_and_handled; - wsi->chunk_parser = ELCP_CONTENT; - lwsl_info("chunk %d\n", - wsi->chunk_remaining); - if (wsi->chunk_remaining) - break; - lwsl_info("final chunk\n"); - if (user_callback_handle_rxflow( - wsi->protocol->callback, - wsi, LWS_CALLBACK_COMPLETED_CLIENT_HTTP, - wsi->user_space, NULL, 0)) - goto close_and_handled; - if (lws_http_transaction_completed(wsi)) - goto close_and_handled; - n = 0; - goto handled; - - case ELCP_CONTENT: - break; - - case ELCP_POST_CR: - if (eff_buf.token[0] == '\x0d') { - wsi->chunk_parser = ELCP_POST_LF; - break; - } - goto close_and_handled; - - case ELCP_POST_LF: - if (eff_buf.token[0] == '\x0a') { - wsi->chunk_parser = ELCP_HEX; - wsi->chunk_remaining = 0; - break; - } - goto close_and_handled; - } - eff_buf.token++; - eff_buf.token_len--; - } - - if (wsi->chunked && !wsi->chunk_remaining) { - n = 0; - goto handled; - } - - if (wsi->u.http.content_remain && - wsi->u.http.content_remain < eff_buf.token_len) - n = wsi->u.http.content_remain; - else - n = eff_buf.token_len; - - if (wsi->chunked && wsi->chunk_remaining && - wsi->chunk_remaining < n) - n = wsi->chunk_remaining; - - if (user_callback_handle_rxflow(wsi->protocol->callback, - wsi, LWS_CALLBACK_RECEIVE_CLIENT_HTTP, - wsi->user_space, (void *)eff_buf.token, - n)) - goto close_and_handled; - - if (wsi->chunked && wsi->chunk_remaining) { - eff_buf.token += n; - wsi->chunk_remaining -= n; - eff_buf.token_len -= n; - } - - if (wsi->chunked && !wsi->chunk_remaining) - wsi->chunk_parser = ELCP_POST_CR; - - if (wsi->chunked && eff_buf.token_len) { - goto spin_chunks; - } - - if (wsi->chunked) { - n = 0; - goto handled; - } - - wsi->u.http.content_remain -= n; - if (wsi->u.http.content_remain) - goto handled; - - if (user_callback_handle_rxflow(wsi->protocol->callback, - wsi, LWS_CALLBACK_COMPLETED_CLIENT_HTTP, - wsi->user_space, NULL, 0)) - goto close_and_handled; - - if (wsi->u.http.connection_type == HTTP_CONNECTION_CLOSE) - goto close_and_handled; - goto handled; - } do { more = 0; diff --git a/lws_config.h.in b/lws_config.h.in index a647a4b5..341515f0 100644 --- a/lws_config.h.in +++ b/lws_config.h.in @@ -80,6 +80,9 @@ /* whether the Openssl is recent enough, and / or built with, ecdh */ #cmakedefine LWS_HAVE_OPENSSL_ECDH_H +/* HTTP Proxy support */ +#cmakedefine LWS_WITH_HTTP_PROXY + /* Maximum supported service threads */ #define LWS_MAX_SMP ${LWS_MAX_SMP} diff --git a/test-server/test-server-http.c b/test-server/test-server-http.c index 594c8dff..1535e437 100644 --- a/test-server/test-server-http.c +++ b/test-server/test-server-http.c @@ -126,6 +126,7 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, unsigned char *end; struct timeval tv; unsigned char *p; + struct lws *wsi1; char buf[256]; char b64[64]; int n, m; @@ -161,15 +162,11 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, goto try_to_reuse; } - /* this example server has no concept of directories */ - if (strchr((const char *)in + 1, '/')) { - lws_return_http_status(wsi, HTTP_STATUS_FORBIDDEN, NULL); - goto try_to_reuse; - } - #ifndef LWS_NO_CLIENT - if (!strcmp(in, "/proxytest")) { + if (!strncmp(in, "/proxytest", 10)) { struct lws_client_connect_info i; + char *rootpath = "/"; + const char *p = (const char *)in; if (lws_get_child(wsi)) break; @@ -177,22 +174,36 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, pss->client_finished = 0; memset(&i,0, sizeof(i)); i.context = lws_get_context(wsi); - i.address = "example.com"; + i.address = "git.libwebsockets.org"; i.port = 80; i.ssl_connection = 0; - i.path = "/"; - i.host = "example.com"; + if (p[10]) + i.path = in + 10; + else + i.path = rootpath; + i.host = "git.libwebsockets.org"; i.origin = NULL; i.method = "GET"; i.parent_wsi = wsi; + i.uri_replace_from = "git.libwebsockets.org/"; + i.uri_replace_to = "/proxytest/"; if (!lws_client_connect_via_info(&i)) { lwsl_err("proxy connect fail\n"); break; } + + + break; } #endif + /* this example server has no concept of directories */ + if (strchr((const char *)in + 1, '/')) { + lws_return_http_status(wsi, HTTP_STATUS_FORBIDDEN, NULL); + goto try_to_reuse; + } + #ifdef LWS_WITH_CGI if (!strcmp(in, "/cgitest")) { static char *cmd[] = { @@ -396,11 +407,33 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, if (pss->fd == LWS_INVALID_FILE) goto try_to_reuse; #ifdef LWS_WITH_CGI - if (pss->reason_bf) { + if (pss->reason_bf & 1) { if (lws_cgi_write_split_stdout_headers(wsi) < 0) goto bail; - pss->reason_bf = 0; + pss->reason_bf &= ~1; + break; + } +#endif +#ifndef LWS_NO_CLIENT + if (pss->reason_bf & 2) { + 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. + */ + pss->reason_bf &= ~2; + wsi1 = lws_get_child(wsi); + if (!wsi1) + break; + if (lws_http_client_read(wsi1, &px, &lenx) < 0) + goto bail; + + if (pss->client_finished) + return -1; break; } #endif @@ -470,7 +503,7 @@ bail: * callback for confirming to continue with client IP appear in * protocol 0 callback since no websocket protocol has been agreed * yet. You can just ignore this if you won't filter on client IP - * since the default uhandled callback return is 0 meaning let the + * since the default unhandled callback return is 0 meaning let the * connection continue. */ case LWS_CALLBACK_FILTER_NETWORK_CONNECTION: @@ -478,7 +511,9 @@ bail: break; #ifndef LWS_WITH_CLIENT - case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: + case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: { + char ctype[64], ctlen = 0; + lwsl_err("LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP\n"); p = buffer + LWS_PRE; end = p + sizeof(buffer) - LWS_PRE; if (lws_add_http_header_status(lws_get_parent(wsi), 200, &p, end)) @@ -488,14 +523,17 @@ bail: (unsigned char *)"libwebsockets", 13, &p, end)) return 1; - if (lws_add_http_header_by_token(lws_get_parent(wsi), + + 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 *)"text/html", 9, &p, end)) - return 1; + (unsigned char *)ctype, ctlen, &p, end)) + return 1; + } #if 0 if (lws_add_http_header_content_length(lws_get_parent(wsi), - file_len, &p, - end)) + file_len, &p, end)) return 1; #endif if (lws_finalize_http_header(lws_get_parent(wsi), &p, end)) @@ -510,20 +548,39 @@ bail: if (n < 0) return -1; - break; + break; } case LWS_CALLBACK_CLOSED_CLIENT_HTTP: + //lwsl_err("LWS_CALLBACK_CLOSED_CLIENT_HTTP\n"); return -1; break; case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: - m = lws_write(lws_get_parent(wsi), in, len, LWS_WRITE_HTTP); + //lwsl_err("LWS_CALLBACK_RECEIVE_CLIENT_HTTP: wsi %p\n", wsi); + assert(lws_get_parent(wsi)); + if (!lws_get_parent(wsi)) + break; + // lwsl_err("LWS_CALLBACK_RECEIVE_CLIENT_HTTP: wsi %p: sock: %d, parent_wsi: %p, parent_sock:%d, len %d\n", + // wsi, lws_get_socket_fd(wsi), + // lws_get_parent(wsi), + // lws_get_socket_fd(lws_get_parent(wsi)), len); + pss1 = lws_wsi_user(lws_get_parent(wsi)); + pss1->reason_bf |= 2; + lws_callback_on_writable(lws_get_parent(wsi)); + break; + case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: + //lwsl_err("LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ len %d\n", len); + assert(lws_get_parent(wsi)); + m = lws_write(lws_get_parent(wsi), (unsigned char *)in, + len, LWS_WRITE_HTTP); if (m < 0) - return 1; + return -1; break; case LWS_CALLBACK_COMPLETED_CLIENT_HTTP: + //lwsl_err("LWS_CALLBACK_COMPLETED_CLIENT_HTTP\n"); + assert(lws_get_parent(wsi)); + if (!lws_get_parent(wsi)) + break; pss1 = lws_wsi_user(lws_get_parent(wsi)); pss1->client_finished = 1; - lws_callback_on_writable(lws_get_parent(wsi)); - return -1; break; #endif @@ -542,7 +599,7 @@ bail: /* TBD stdin rx flow control */ break; case LWS_STDOUT: - pss->reason_bf |= 1 << pss->args.ch; + pss->reason_bf |= 1; /* when writing to MASTER would not block */ lws_callback_on_writable(wsi); break;