From 986bb37c888639f2112871d4c6a813f16925f275 Mon Sep 17 00:00:00 2001 From: Andy Green Date: Tue, 26 Feb 2019 17:18:24 +0800 Subject: [PATCH] ah: custom headers for h1 Until now lws only parses headers it knows at build-time from its prebuilt lexical analyzer. This adds an on-by-default cmake option and a couple of apis to also store and query "custom", ie, unknown-to-lws headers. A minimal example is also provided. At the moment it only works on h1, h2 support needs improvements to the hpack implementation. Since it bloats ah memory usage compared to without it if custom headers are present, the related code and ah footprint can be disabled with the cmake option LWS_WITH_CUSTOM_HEADERS, but it's on by default normally. ESP32 platform disables it. https://github.com/warmcat/libwebsockets/pull/1499 --- CMakeLists.txt | 2 + cmake/lws_config.h.in | 1 + include/libwebsockets/lws-http.h | 44 +++ lib/roles/h2/hpack.c | 5 +- lib/roles/http/client/client.c | 9 + lib/roles/http/private.h | 16 +- lib/roles/http/server/parsers.c | 266 ++++++++++++++++-- minimal-examples/http-server/README.md | 1 + .../CMakeLists.txt | 78 +++++ .../README.md | 20 ++ .../localhost-100y.cert | 34 +++ .../localhost-100y.key | 52 ++++ .../minimal-http-server-custom-headers.c | 220 +++++++++++++++ .../mount-origin/404.html | 9 + .../mount-origin/error.css | 0 .../mount-origin/favicon.ico | Bin 0 -> 1406 bytes .../mount-origin/index.html | 20 ++ .../mount-origin/libwebsockets.org-logo.svg | 120 ++++++++ .../mount-origin/strict-csp.svg | 53 ++++ 19 files changed, 929 insertions(+), 21 deletions(-) create mode 100644 minimal-examples/http-server/minimal-http-server-custom-headers/CMakeLists.txt create mode 100644 minimal-examples/http-server/minimal-http-server-custom-headers/README.md create mode 100644 minimal-examples/http-server/minimal-http-server-custom-headers/localhost-100y.cert create mode 100644 minimal-examples/http-server/minimal-http-server-custom-headers/localhost-100y.key create mode 100644 minimal-examples/http-server/minimal-http-server-custom-headers/minimal-http-server-custom-headers.c create mode 100644 minimal-examples/http-server/minimal-http-server-custom-headers/mount-origin/404.html create mode 100644 minimal-examples/http-server/minimal-http-server-custom-headers/mount-origin/error.css create mode 100644 minimal-examples/http-server/minimal-http-server-custom-headers/mount-origin/favicon.ico create mode 100644 minimal-examples/http-server/minimal-http-server-custom-headers/mount-origin/index.html create mode 100644 minimal-examples/http-server/minimal-http-server-custom-headers/mount-origin/libwebsockets.org-logo.svg create mode 100644 minimal-examples/http-server/minimal-http-server-custom-headers/mount-origin/strict-csp.svg diff --git a/CMakeLists.txt b/CMakeLists.txt index e808f1261..0d1b50722 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -114,6 +114,7 @@ option(LWS_WITH_EXPORT_LWSTARGETS "Export libwebsockets CMake targets. Disable option(LWS_REPRODUCIBLE "Build libwebsockets reproducible. It removes the build user and hostname from the build" ON) option(LWS_WITH_MINIMAL_EXAMPLES "Also build the normally standalone minimal examples, for QA" OFF) option(LWS_WITH_LWSAC "lwsac Chunk Allocation api" ON) +option(LWS_WITH_CUSTOM_HEADERS "Store and allow querying custom HTTP headers (H1 only)" ON) option(LWS_WITH_DISKCACHE "Hashed cache directory with lazy LRU deletion to size limit" OFF) option(LWS_WITH_ASAN "Build with gcc runtime sanitizer options enabled (needs libasan)" OFF) # @@ -368,6 +369,7 @@ if (LWS_WITH_ESP32) set(LWS_HAVE_REALLOC 1) set(LWS_HAVE_GETIFADDRS 1) set(LWS_WITH_ZIP_FOPS 1) + set(LWS_WITH_CUSTOM_HEADERS 0) endif() diff --git a/cmake/lws_config.h.in b/cmake/lws_config.h.in index d54cbf662..079f611b2 100644 --- a/cmake/lws_config.h.in +++ b/cmake/lws_config.h.in @@ -80,6 +80,7 @@ #cmakedefine LWS_WITH_ACME #cmakedefine LWS_WITH_BORINGSSL #cmakedefine LWS_WITH_CGI +#cmakedefine LWS_WITH_CUSTOM_HEADERS #cmakedefine LWS_WITH_ESP32 #cmakedefine LWS_WITH_FTS #cmakedefine LWS_WITH_GENCRYPTO diff --git a/include/libwebsockets/lws-http.h b/include/libwebsockets/lws-http.h index 22b195b9f..7316ae960 100644 --- a/include/libwebsockets/lws-http.h +++ b/include/libwebsockets/lws-http.h @@ -323,6 +323,9 @@ enum lws_token_indexes { /* parser state additions, no storage associated */ WSI_TOKEN_NAME_PART, +#if defined(LWS_WITH_CUSTOM_HEADERS) + WSI_TOKEN_UNKNOWN_VALUE_PART, +#endif WSI_TOKEN_SKIPPING, WSI_TOKEN_SKIPPING_SAW_CR, WSI_PARSING_COMPLETE, @@ -403,6 +406,47 @@ LWS_VISIBLE LWS_EXTERN int lws_hdr_copy_fragment(struct lws *wsi, char *dest, int len, enum lws_token_indexes h, int frag_idx); +/** + * lws_hdr_custom_length() - return length of a custom header + * + * \param wsi: websocket connection + * \param name: header string (including terminating :) + * \param nlen: length of name + * + * Lws knows about 100 common http headers, and parses them into indexes when + * it recognizes them. When it meets a header that it doesn't know, it stores + * the name and value directly, and you can look them up using + * lws_hdr_custom_length() and lws_hdr_custom_copy(). + * + * This api returns -1, or the length of the value part of the header if it + * exists. Lws must be built with LWS_WITH_CUSTOM_HEADERS (on by default) to + * use this api. + */ +LWS_VISIBLE LWS_EXTERN int +lws_hdr_custom_length(struct lws *wsi, const char *name, int nlen); + +/** + * lws_hdr_custom_copy() - copy value part of a custom header + * + * \param wsi: websocket connection + * \param dst: pointer to buffer to receive the copy + * \param len: number of bytes available at dst + * \param name: header string (including terminating :) + * \param nlen: length of name + * + * Lws knows about 100 common http headers, and parses them into indexes when + * it recognizes them. When it meets a header that it doesn't know, it stores + * the name and value directly, and you can look them up using + * lws_hdr_custom_length() and lws_hdr_custom_copy(). + * + * This api returns -1, or the length of the string it copied into dst if it + * was big enough to contain both the string and an extra terminating NUL. Lws + * must be built with LWS_WITH_CUSTOM_HEADERS (on by default) to use this api. + */ +LWS_VISIBLE LWS_EXTERN int +lws_hdr_custom_copy(struct lws *wsi, char *dst, int len, const char *name, + int nlen); + /** * lws_get_urlarg_by_name() - return pointer to arg value if present * \param wsi: the connection to check diff --git a/lib/roles/h2/hpack.c b/lib/roles/h2/hpack.c index e127dcd51..9c218e543 100644 --- a/lib/roles/h2/hpack.c +++ b/lib/roles/h2/hpack.c @@ -1,7 +1,7 @@ /* * lib/hpack.c * - * Copyright (C) 2014-2018 Andy Green + * Copyright (C) 2014-2019 Andy Green * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -1190,6 +1190,9 @@ swallow: } if (ah->parser_state == WSI_TOKEN_NAME_PART || +#if defined(LWS_WITH_CUSTOM_HEADERS) + ah->parser_state == WSI_TOKEN_UNKNOWN_VALUE_PART || +#endif ah->parser_state == WSI_TOKEN_SKIPPING) { h2n->unknown_header = 1; ah->parser_state = -1; diff --git a/lib/roles/http/client/client.c b/lib/roles/http/client/client.c index c63facab3..9a1aa00d4 100644 --- a/lib/roles/http/client/client.c +++ b/lib/roles/http/client/client.c @@ -433,6 +433,9 @@ start_ws_handshake: #if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) w->http.ah->parser_state = WSI_TOKEN_NAME_PART; w->http.ah->lextable_pos = 0; +#if defined(LWS_WITH_CUSTOM_HEADERS) + w->http.ah->unk_pos = 0; +#endif /* If we're (re)starting on hdr, need other implied init */ wsi->http.ah->ues = URIES_IDLE; #endif @@ -458,6 +461,9 @@ client_http_body_sent: /* prepare ourselves to do the parsing */ wsi->http.ah->parser_state = WSI_TOKEN_NAME_PART; wsi->http.ah->lextable_pos = 0; +#if defined(LWS_WITH_CUSTOM_HEADERS) + wsi->http.ah->unk_pos = 0; +#endif #endif lwsi_set_state(wsi, LRS_WAITING_SERVER_REPLY); lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_SERVER_RESPONSE, @@ -634,6 +640,9 @@ lws_http_transaction_completed_client(struct lws *wsi) wsi->http.ah->parser_state = WSI_TOKEN_NAME_PART; wsi->http.ah->lextable_pos = 0; +#if defined(LWS_WITH_CUSTOM_HEADERS) + wsi->http.ah->unk_pos = 0; +#endif lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_SERVER_RESPONSE, wsi->context->timeout_secs); diff --git a/lib/roles/http/private.h b/lib/roles/http/private.h index a0f9d9fe9..08fd105de 100644 --- a/lib/roles/http/private.h +++ b/lib/roles/http/private.h @@ -117,12 +117,20 @@ struct allocated_headers { #ifndef LWS_NO_CLIENT char initial_handshake_hash_base64[30]; #endif - - uint32_t pos; - uint32_t http_response; - uint32_t current_token_limit; int hdr_token_idx; + ah_data_idx_t pos; + ah_data_idx_t http_response; + ah_data_idx_t current_token_limit; + +#if defined(LWS_WITH_CUSTOM_HEADERS) + ah_data_idx_t unk_pos; /* to undo speculative unknown header */ + ah_data_idx_t unk_value_pos; + + ah_data_idx_t unk_ll_head; + ah_data_idx_t unk_ll_tail; +#endif + int16_t lextable_pos; uint8_t in_use; diff --git a/lib/roles/http/server/parsers.c b/lib/roles/http/server/parsers.c index 4c7db995d..2d0386cb7 100644 --- a/lib/roles/http/server/parsers.c +++ b/lib/roles/http/server/parsers.c @@ -27,6 +27,50 @@ static const unsigned char lextable[] = { #define FAIL_CHAR 0x08 +#if defined(LWS_WITH_CUSTOM_HEADERS) + +#define UHO_NLEN 0 +#define UHO_VLEN 2 +#define UHO_LL 4 +#define UHO_NAME 8 + +static uint16_t +lws_un16be_get(const void *_p) +{ + const uint8_t *p = _p; + + return ((uint16_t)p[0] << 8) | p[1]; +} + +static void +lws_un16be_set(void *_p, uint16_t v) +{ + uint8_t *p = _p; + + *p++ = (uint8_t)(v >> 8); + *p++ = (uint8_t)v; +} + +static uint32_t +lws_un32be_get(const void *_p) +{ + const uint8_t *p = _p; + + return (uint32_t)((p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]); +} + +static void +lws_un32be_set(void *_p, uint32_t v) +{ + uint8_t *p = _p; + + *p++ = (uint8_t)(v >> 24); + *p++ = (uint8_t)(v >> 16); + *p++ = (uint8_t)(v >> 8); + *p = (uint8_t)v; +} +#endif + static struct allocated_headers * _lws_create_ah(struct lws_context_per_thread *pt, ah_data_idx_t data_size) { @@ -83,6 +127,11 @@ _lws_header_table_reset(struct allocated_headers *ah) ah->http_response = 0; ah->parser_state = WSI_TOKEN_NAME_PART; ah->lextable_pos = 0; +#if defined(LWS_WITH_CUSTOM_HEADERS) + ah->unk_pos = 0; + ah->unk_ll_head = 0; + ah->unk_ll_tail = 0; +#endif } // doesn't scrub the ah rxbuffer by default, parent must do if needed @@ -551,6 +600,62 @@ LWS_VISIBLE int lws_hdr_copy(struct lws *wsi, char *dst, int len, return toklen; } +#if defined(LWS_WITH_CUSTOM_HEADERS) +LWS_VISIBLE int +lws_hdr_custom_length(struct lws *wsi, const char *name, int nlen) +{ + ah_data_idx_t ll; + + if (!wsi->http.ah || wsi->http2_substream) + return -1; + + ll = wsi->http.ah->unk_ll_head; + while (ll) { + if (ll >= wsi->http.ah->data_length) + return -1; + if (nlen == lws_un16be_get(&wsi->http.ah->data[ll + UHO_NLEN]) && + !strncmp(name, &wsi->http.ah->data[ll + UHO_NAME], nlen)) + return lws_un16be_get(&wsi->http.ah->data[ll + UHO_VLEN]); + + ll = lws_un32be_get(&wsi->http.ah->data[ll + UHO_LL]); + } + + return -1; +} + +LWS_VISIBLE int +lws_hdr_custom_copy(struct lws *wsi, char *dst, int len, const char *name, + int nlen) +{ + ah_data_idx_t ll; + int n; + + if (!wsi->http.ah || wsi->http2_substream) + return -1; + + *dst = '\0'; + + ll = wsi->http.ah->unk_ll_head; + while (ll) { + if (ll >= wsi->http.ah->data_length) + return -1; + if (nlen == lws_un16be_get(&wsi->http.ah->data[ll + UHO_NLEN]) && + !strncmp(name, &wsi->http.ah->data[ll + UHO_NAME], nlen)) { + n = lws_un16be_get(&wsi->http.ah->data[ll + UHO_VLEN]); + if (n + 1 > len) + return -1; + strncpy(dst, &wsi->http.ah->data[ll + UHO_NAME + nlen], n); + dst[n] = '\0'; + + return n; + } + ll = lws_un32be_get(&wsi->http.ah->data[ll + UHO_LL]); + } + + return -1; +} +#endif + char *lws_hdr_simple_ptr(struct lws *wsi, enum lws_token_indexes h) { int n; @@ -873,6 +978,32 @@ lws_parse(struct lws *wsi, unsigned char *buf, int *len) c = *buf++; switch (ah->parser_state) { +#if defined(LWS_WITH_CUSTOM_HEADERS) + case WSI_TOKEN_UNKNOWN_VALUE_PART: + + if (c == '\r') + break; + if (c == '\n') { + lws_un16be_set(&ah->data[ah->unk_pos + 2], + ah->pos - ah->unk_value_pos); + ah->parser_state = WSI_TOKEN_NAME_PART; + ah->unk_pos = 0; + ah->lextable_pos = 0; + break; + } + + /* trim leading whitespace */ + if (ah->pos != ah->unk_value_pos || + (c != ' ' && c != '\t')) { + + if (lws_pos_in_bounds(wsi)) + return -1; + + ah->data[ah->pos++] = c; + } + pos = ah->lextable_pos; + break; +#endif default: lwsl_parser("WSI_TOK_(%d) '%c'\n", ah->parser_state, c); @@ -970,8 +1101,75 @@ swallow: if (c >= 'A' && c <= 'Z') c += 'a' - 'A'; +#if defined(LWS_WITH_CUSTOM_HEADERS) + /* + * ...in case it's an unknown header, speculatively + * store it as the name comes in. If we recognize it as + * a known header, we'll snip this. + */ + + if (!ah->unk_pos) { + ah->unk_pos = ah->pos; + /* + * Prepare new unknown header linked-list entry + * + * - 16-bit BE: name part length + * - 16-bit BE: value part length + * - 32-bit BE: data offset of next, or 0 + */ + for (n = 0; n < 8; n++) + if (!lws_pos_in_bounds(wsi)) + ah->data[ah->pos++] = 0; + } +#endif + + if (lws_pos_in_bounds(wsi)) + return -1; + + ah->data[ah->pos++] = c; pos = ah->lextable_pos; +#if defined(LWS_WITH_CUSTOM_HEADERS) + if (pos < 0 && c == ':') { + /* + * process unknown headers + * + * register us in the unknown hdr ll + */ + + if (!ah->unk_ll_head) + ah->unk_ll_head = ah->unk_pos; + + if (ah->unk_ll_tail) + lws_un32be_set(&ah->data[ah->unk_ll_tail + UHO_LL], + ah->unk_pos); + + ah->unk_ll_tail = ah->unk_pos; + + lwsl_debug("%s: unk header %d '%.*s'\n", + __func__, + ah->pos - (ah->unk_pos + UHO_NAME), + ah->pos - (ah->unk_pos + UHO_NAME), + &ah->data[ah->unk_pos + UHO_NAME]); + + /* set the unknown header name part length */ + + lws_un16be_set(&ah->data[ah->unk_pos], + (ah->pos - ah->unk_pos) - UHO_NAME); + + ah->unk_value_pos = ah->pos; + + /* + * collect whatever's coming for the unknown header + * argument until the next CRLF + */ + ah->parser_state = WSI_TOKEN_UNKNOWN_VALUE_PART; + break; + } +#endif + if (pos < 0) + break; + while (1) { if (lextable[pos] & (1 << 7)) { /* 1-byte, fail on mismatch */ @@ -993,7 +1191,16 @@ nope: goto nope; /* b7 = 0, end or 3-byte */ - if (lextable[pos] < FAIL_CHAR) { /* term mark */ + if (lextable[pos] < FAIL_CHAR) { +#if defined(LWS_WITH_CUSTOM_HEADERS) + /* + * We hit a terminal marker, so we + * recognized this header... drop the + * speculative name part storage + */ + ah->pos = ah->unk_pos; + ah->unk_pos = 0; +#endif ah->lextable_pos = pos; break; } @@ -1011,30 +1218,43 @@ nope: } /* - * If it's h1, server needs to look out for unknown - * methods... + * If it's h1, server needs to be on the look out for + * unknown methods... */ if (ah->lextable_pos < 0 && lwsi_role_h1(wsi) && lwsi_role_server(wsi)) { - /* this is not a header we know about */ + /* + * this is not a header we know about... did + * we get a valid method (GET, POST etc) + * already, or is this the bogus method? + */ for (m = 0; m < LWS_ARRAY_SIZE(methods); m++) if (ah->frag_index[methods[m]]) { /* - * already had the method, no idea what - * this crap from the client is, ignore + * already had the method */ +#if !defined(LWS_WITH_CUSTOM_HEADERS) ah->parser_state = WSI_TOKEN_SKIPPING; +#endif break; } - /* - * hm it's an unknown http method from a client in fact, - * it cannot be valid http - */ + if (m != LWS_ARRAY_SIZE(methods)) +#if defined(LWS_WITH_CUSTOM_HEADERS) + /* + * We have the method, this is just an + * unknown header then + */ + goto unknown_hdr; +#else break; +#endif /* - * are we set up to transition to - * another role in these cases? + * ...it's an unknown http method from a client + * in fact, it cannot be valid http. + * + * Are we set up to transition to another role + * in these cases? */ if (lws_check_opt(wsi->vhost->options, LWS_SERVER_OPTION_FALLBACK_TO_APPLY_LISTEN_ACCEPT_CONFIG)) { @@ -1047,13 +1267,17 @@ nope: lwsl_info("Unknown method - dropping\n"); goto forbid; } - /* - * ...otherwise for a client, let him ignore unknown headers - * coming from the server - */ if (ah->lextable_pos < 0) { +#if defined(LWS_WITH_CUSTOM_HEADERS) + goto unknown_hdr; +#else + /* + * ...otherwise for a client, let him ignore + * unknown headers coming from the server + */ ah->parser_state = WSI_TOKEN_SKIPPING; break; +#endif } if (lextable[ah->lextable_pos] < FAIL_CHAR) { @@ -1096,6 +1320,13 @@ nope: } break; +#if defined(LWS_WITH_CUSTOM_HEADERS) +unknown_hdr: + //ah->parser_state = WSI_TOKEN_SKIPPING; + //break; + break; +#endif + start_fragment: ah->nfrag++; excessive: @@ -1138,6 +1369,9 @@ excessive: goto forbid; if (c == '\x0a') { ah->parser_state = WSI_TOKEN_NAME_PART; +#if defined(LWS_WITH_CUSTOM_HEADERS) + ah->unk_pos = 0; +#endif ah->lextable_pos = 0; } else ah->parser_state = WSI_TOKEN_SKIPPING; diff --git a/minimal-examples/http-server/README.md b/minimal-examples/http-server/README.md index 48644bc8e..82d29f456 100644 --- a/minimal-examples/http-server/README.md +++ b/minimal-examples/http-server/README.md @@ -1,6 +1,7 @@ |Example|Demonstrates| ---|--- minimal-http-server-basicauth|Shows how to protect a mount using a password file and basic auth +minimal-http-server-custom-headers|Shows how to query custom headers that lws doesn't already know minimal-http-server-deaddrop|Shows how to use the deaddrop drag and drop file upload + sharing plugin minimal-http-server-dynamic|Serves both static and dynamically generated http content minimal-http-server-eventlib-foreign|Demonstrates integrating lws with a foreign event library diff --git a/minimal-examples/http-server/minimal-http-server-custom-headers/CMakeLists.txt b/minimal-examples/http-server/minimal-http-server-custom-headers/CMakeLists.txt new file mode 100644 index 000000000..92d0d1825 --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-custom-headers/CMakeLists.txt @@ -0,0 +1,78 @@ +cmake_minimum_required(VERSION 2.8) +include(CheckCSourceCompiles) + +set(SAMP lws-minimal-http-server-custom-headers) +set(SRCS minimal-http-server-custom-headers.c) + +# If we are being built as part of lws, confirm current build config supports +# reqconfig, else skip building ourselves. +# +# If we are being built externally, confirm installed lws was configured to +# support reqconfig, else error out with a helpful message about the problem. +# +MACRO(require_lws_config reqconfig _val result) + + if (DEFINED ${reqconfig}) + if (${reqconfig}) + set (rq 1) + else() + set (rq 0) + endif() + else() + set(rq 0) + endif() + + if (${_val} EQUAL ${rq}) + set(SAME 1) + else() + set(SAME 0) + endif() + + if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME}) + if (${_val}) + message("${SAMP}: skipping as lws being built without ${reqconfig}") + else() + message("${SAMP}: skipping as lws built with ${reqconfig}") + endif() + set(${result} 0) + else() + if (LWS_WITH_MINIMAL_EXAMPLES) + set(MET ${SAME}) + else() + CHECK_C_SOURCE_COMPILES("#include \nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig}) + if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig}) + set(HAS_${reqconfig} 0) + else() + set(HAS_${reqconfig} 1) + endif() + if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val})) + set(MET 1) + else() + set(MET 0) + endif() + endif() + if (NOT MET) + if (${_val}) + message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}") + else() + message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project") + endif() + endif() + endif() +ENDMACRO() + +set(requirements 1) +require_lws_config(LWS_ROLE_H1 1 requirements) +require_lws_config(LWS_WITH_CUSTOM_HEADERS 1 requirements) +require_lws_config(LWS_WITHOUT_SERVER 0 requirements) + +if (requirements) + add_executable(${SAMP} ${SRCS}) + + if (websockets_shared) + target_link_libraries(${SAMP} websockets_shared) + add_dependencies(${SAMP} websockets_shared) + else() + target_link_libraries(${SAMP} websockets) + endif() +endif() diff --git a/minimal-examples/http-server/minimal-http-server-custom-headers/README.md b/minimal-examples/http-server/minimal-http-server-custom-headers/README.md new file mode 100644 index 000000000..9666d028d --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-custom-headers/README.md @@ -0,0 +1,20 @@ +# lws minimal http server dynamic content + +## build + +``` + $ cmake . && make +``` + +## usage + +``` + $ ./lws-minimal-http-server-dynamic +[2018/03/20 10:24:24:7099] USER: LWS minimal http server dynamic | visit http://localhost:7681 +[2018/03/20 10:24:24:7099] NOTICE: Creating Vhost 'default' port 7681, 1 protocols, IPv6 off +``` + +Visit http://localhost:7681, which is all static content. + +Click on the link to /dyn/anything, this opens a new tab with dynamicly-produced content. + diff --git a/minimal-examples/http-server/minimal-http-server-custom-headers/localhost-100y.cert b/minimal-examples/http-server/minimal-http-server-custom-headers/localhost-100y.cert new file mode 100644 index 000000000..6f372db40 --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-custom-headers/localhost-100y.cert @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +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 +e2Hl/7M0c/lbZ4FvXgARlex2rkgS0Ka06HE= +-----END CERTIFICATE----- diff --git a/minimal-examples/http-server/minimal-http-server-custom-headers/localhost-100y.key b/minimal-examples/http-server/minimal-http-server-custom-headers/localhost-100y.key new file mode 100644 index 000000000..148f8598e --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-custom-headers/localhost-100y.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCjYtuWaICCY0tJ +PubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8Di3DAmHK +nSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTekLWcfI5ZZ +toGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnHKT/m6DSU +0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6jzhNyMBT +J1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQUjy5N8pS +Np7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAzTK4l2pHN +uC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBKIzv9cgi9 +fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0nPN1IMSn +zXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzoGMTvP/Au +ehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9psNcjTMaB +QLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABAoICAFWe8MQZb37k2gdAV3Y6aq8f +qokKQqbCNLd3giGFwYkezHXoJfg6Di7oZxNcKyw35LFEghkgtQqErQqo35VPIoH+ +vXUpWOjnCmM4muFA9/cX6mYMc8TmJsg0ewLdBCOZVw+wPABlaqz+0UOiSMMftpk9 +fz9JwGd8ERyBsT+tk3Qi6D0vPZVsC1KqxxL/cwIFd3Hf2ZBtJXe0KBn1pktWht5A +Kqx9mld2Ovl7NjgiC1Fx9r+fZw/iOabFFwQA4dr+R8mEMK/7bd4VXfQ1o/QGGbMT +G+ulFrsiDyP+rBIAaGC0i7gDjLAIBQeDhP409ZhswIEc/GBtODU372a2CQK/u4Q/ +HBQvuBtKFNkGUooLgCCbFxzgNUGc83GB/6IwbEM7R5uXqsFiE71LpmroDyjKTlQ8 +YZkpIcLNVLw0usoGYHFm2rvCyEVlfsE3Ub8cFyTFk50SeOcF2QL2xzKmmbZEpXgl +xBHR0hjgon0IKJDGfor4bHO7Nt+1Ece8u2oTEKvpz5aIn44OeC5mApRGy83/0bvs +esnWjDE/bGpoT8qFuy+0urDEPNId44XcJm1IRIlG56ErxC3l0s11wrIpTmXXckqw +zFR9s2z7f0zjeyxqZg4NTPI7wkM3M8BXlvp2GTBIeoxrWB4V3YArwu8QF80QBgVz +mgHl24nTg00UH1OjZsABAoIBAQDOxftSDbSqGytcWqPYP3SZHAWDA0O4ACEM+eCw +au9ASutl0IDlNDMJ8nC2ph25BMe5hHDWp2cGQJog7pZ/3qQogQho2gUniKDifN77 +40QdykllTzTVROqmP8+efreIvqlzHmuqaGfGs5oTkZaWj5su+B+bT+9rIwZcwfs5 +YRINhQRx17qa++xh5mfE25c+M9fiIBTiNSo4lTxWMBShnK8xrGaMEmN7W0qTMbFH +PgQz5FcxRjCCqwHilwNBeLDTp/ZECEB7y34khVh531mBE2mNzSVIQcGZP1I/DvXj +W7UUNdgFwii/GW+6M0uUDy23UVQpbFzcV8o1C2nZc4Fb4zwBAoIBAQDKSJkFwwuR +naVJS6WxOKjX8MCu9/cKPnwBv2mmI2jgGxHTw5sr3ahmF5eTb8Zo19BowytN+tr6 +2ZFoIBA9Ubc9esEAU8l3fggdfM82cuR9sGcfQVoCh8tMg6BP8IBLOmbSUhN3PG2m +39I802u0fFNVQCJKhx1m1MFFLOu7lVcDS9JN+oYVPb6MDfBLm5jOiPuYkFZ4gH79 +J7gXI0/YKhaJ7yXthYVkdrSF6Eooer4RZgma62Dd1VNzSq3JBo6rYjF7Lvd+RwDC +R1thHrmf/IXplxpNVkoMVxtzbrrbgnC25QmvRYc0rlS/kvM4yQhMH3eA7IycDZMp +Y+0xm7I7jTT7AoIBAGKzKIMDXdCxBWKhNYJ8z7hiItNl1IZZMW2TPUiY0rl6yaCh +BVXjM9W0r07QPnHZsUiByqb743adkbTUjmxdJzjaVtxN7ZXwZvOVrY7I7fPWYnCE +fXCr4+IVpZI/ZHZWpGX6CGSgT6EOjCZ5IUufIvEpqVSmtF8MqfXO9o9uIYLokrWQ +x1dBl5UnuTLDqw8bChq7O5y6yfuWaOWvL7nxI8NvSsfj4y635gIa/0dFeBYZEfHI +UlGdNVomwXwYEzgE/c19ruIowX7HU/NgxMWTMZhpazlxgesXybel+YNcfDQ4e3RM +OMz3ZFiaMaJsGGNf4++d9TmMgk4Ns6oDs6Tb9AECggEBAJYzd+SOYo26iBu3nw3L +65uEeh6xou8pXH0Tu4gQrPQTRZZ/nT3iNgOwqu1gRuxcq7TOjt41UdqIKO8vN7/A +aJavCpaKoIMowy/aGCbvAvjNPpU3unU8jdl/t08EXs79S5IKPcgAx87sTTi7KDN5 +SYt4tr2uPEe53NTXuSatilG5QCyExIELOuzWAMKzg7CAiIlNS9foWeLyVkBgCQ6S +me/L8ta+mUDy37K6vC34jh9vK9yrwF6X44ItRoOJafCaVfGI+175q/eWcqTX4q+I +G4tKls4sL4mgOJLq+ra50aYMxbcuommctPMXU6CrrYyQpPTHMNVDQy2ttFdsq9iK +TncCggEBAMmt/8yvPflS+xv3kg/ZBvR9JB1In2n3rUCYYD47ReKFqJ03Vmq5C9nY +56s9w7OUO8perBXlJYmKZQhO4293lvxZD2Iq4NcZbVSCMoHAUzhzY3brdgtSIxa2 +gGveGAezZ38qKIU26dkz7deECY4vrsRkwhpTW0LGVCpjcQoaKvymAoCmAs8V2oMr +Ziw1YQ9uOUoWwOqm1wZqmVcOXvPIS2gWAs3fQlWjH9hkcQTMsUaXQDOD0aqkSY3E +NqOvbCV1/oUpRi3076khCoAXI1bKSn/AvR3KDP14B5toHI/F5OTSEiGhhHesgRrs +fBrpEY1IATtPq1taBZZogRqI3rOkkPk= +-----END PRIVATE KEY----- diff --git a/minimal-examples/http-server/minimal-http-server-custom-headers/minimal-http-server-custom-headers.c b/minimal-examples/http-server/minimal-http-server-custom-headers/minimal-http-server-custom-headers.c new file mode 100644 index 000000000..83f309b0d --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-custom-headers/minimal-http-server-custom-headers.c @@ -0,0 +1,220 @@ +/* + * lws-minimal-http-server-custom-headers + * + * Copyright (C) 2019 Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + * + * This demonstrates a minimal http server that can produce dynamic http + * content as well as static content. + * + * To keep it simple, it serves the static stuff from the subdirectory + * "./mount-origin" of the directory it was started in. + * + * You can change that by changing mount.origin below. + */ + +#include +#include +#include +#include + +static int interrupted; + +struct pss { + char result[128 + LWS_PRE]; + int len; +}; + +/* + * This just lets us override LWS_CALLBACK_HTTP handling before passing it + * and everything else to the default handler. + */ + +static int +callback_http(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + uint8_t buf[LWS_PRE + 2048], *start = &buf[LWS_PRE], *p = start, + *end = &buf[sizeof(buf) - LWS_PRE - 1]; + struct pss *pss = (struct pss *)user; + char value[32], *pr = &pss->result[LWS_PRE]; + int n, e = sizeof(pss->result) - LWS_PRE; + + switch (reason) { + case LWS_CALLBACK_HTTP: + /* + * LWS doesn't have the "DNT" header built-in. But we can + * query it using the "custom" versions of the header apis. + * + * You can set your modern browser to issue DNT, look in the + * privacy settings of your browser. + */ + + pss->len = 0; + n = lws_hdr_custom_length(wsi, "dnt:", 4); + if (n < 0) + pss->len = lws_snprintf(pr, e, + "%s: DNT header not found\n", __func__); + else { + + pss->len = lws_snprintf(pr, e, + "%s: DNT length %d
", __func__, n); + n = lws_hdr_custom_copy(wsi, value, sizeof(value), "dnt:", 4); + if (n < 0) + pss->len += lws_snprintf(pr + pss->len, e - pss->len, + "%s: unable to get DNT value\n", __func__); + else + + pss->len += lws_snprintf(pr + pss->len , e - pss->len, + "%s: DNT value '%s'\n", __func__, value); + } + + lwsl_user("%s\n", pr); + + if (lws_add_http_common_headers(wsi, HTTP_STATUS_OK, + "text/html", pss->len, &p, end)) + return 1; + + if (lws_finalize_write_http_header(wsi, start, &p, end)) + return 1; + + + /* write the body separately */ + lws_callback_on_writable(wsi); + return 0; + + case LWS_CALLBACK_HTTP_WRITEABLE: + + strcpy((char *)start, "hello"); + + if (lws_write(wsi, (uint8_t *)pr, pss->len, LWS_WRITE_HTTP_FINAL) != pss->len) + return 1; + + if (lws_http_transaction_completed(wsi)) + return -1; + return 0; + + default: + break; + } + + return lws_callback_http_dummy(wsi, reason, user, in, len); +} + +static struct lws_protocols protocols[] = { + { "http", callback_http, sizeof(struct pss), 0 }, + { NULL, NULL, 0, 0 } /* terminator */ +}; + +static const struct lws_http_mount mount_dyn = { + /* .mount_next */ NULL, /* linked-list "next" */ + /* .mountpoint */ "/dyn", /* mountpoint URL */ + /* .origin */ NULL, /* protocol */ + /* .def */ NULL, + /* .protocol */ "http", + /* .cgienv */ NULL, + /* .extra_mimetypes */ NULL, + /* .interpret */ NULL, + /* .cgi_timeout */ 0, + /* .cache_max_age */ 0, + /* .auth_mask */ 0, + /* .cache_reusable */ 0, + /* .cache_revalidate */ 0, + /* .cache_intermediaries */ 0, + /* .origin_protocol */ LWSMPRO_CALLBACK, /* dynamic */ + /* .mountpoint_len */ 4, /* char count */ + /* .basic_auth_login_file */ NULL, +}; + +/* default mount serves the URL space from ./mount-origin */ + +static const struct lws_http_mount mount = { + /* .mount_next */ &mount_dyn, /* linked-list "next" */ + /* .mountpoint */ "/", /* mountpoint URL */ + /* .origin */ "./mount-origin", /* serve from dir */ + /* .def */ "index.html", /* default filename */ + /* .protocol */ NULL, + /* .cgienv */ NULL, + /* .extra_mimetypes */ NULL, + /* .interpret */ NULL, + /* .cgi_timeout */ 0, + /* .cache_max_age */ 0, + /* .auth_mask */ 0, + /* .cache_reusable */ 0, + /* .cache_revalidate */ 0, + /* .cache_intermediaries */ 0, + /* .origin_protocol */ LWSMPRO_FILE, /* files in a dir */ + /* .mountpoint_len */ 1, /* char count */ + /* .basic_auth_login_file */ NULL, +}; + +void sigint_handler(int sig) +{ + interrupted = 1; +} + +int main(int argc, const char **argv) +{ + struct lws_context_creation_info info; + struct lws_context *context; + const char *p; + int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + + signal(SIGINT, sigint_handler); + + if ((p = lws_cmdline_option(argc, argv, "-d"))) + logs = atoi(p); + + lws_set_log_level(logs, NULL); + lwsl_user("LWS minimal http server custom headers | visit http://localhost:7681\n"); + + memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | + LWS_SERVER_OPTION_EXPLICIT_VHOSTS | + LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE; + + /* for testing ah queue, not useful in real world */ + if (lws_cmdline_option(argc, argv, "--ah1")) + info.max_http_header_pool = 1; + + context = lws_create_context(&info); + if (!context) { + lwsl_err("lws init failed\n"); + return 1; + } + + /* http on 7681 */ + + info.port = 7681; + info.protocols = protocols; + info.mounts = &mount; + info.vhost_name = "http"; + + if (!lws_create_vhost(context, &info)) { + lwsl_err("Failed to create tls vhost\n"); + goto bail; + } + + /* https on 7682 */ + + info.port = 7682; + info.error_document_404 = "/404.html"; + info.ssl_cert_filepath = "localhost-100y.cert"; + info.ssl_private_key_filepath = "localhost-100y.key"; + info.vhost_name = "https"; + + if (!lws_create_vhost(context, &info)) { + lwsl_err("Failed to create tls vhost\n"); + goto bail; + } + + while (n >= 0 && !interrupted) + n = lws_service(context, 1000); + +bail: + lws_context_destroy(context); + + return 0; +} diff --git a/minimal-examples/http-server/minimal-http-server-custom-headers/mount-origin/404.html b/minimal-examples/http-server/minimal-http-server-custom-headers/mount-origin/404.html new file mode 100644 index 000000000..6f85f256f --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-custom-headers/mount-origin/404.html @@ -0,0 +1,9 @@ + + + +
+

404

+ Sorry, that file doesn't exist. + + + diff --git a/minimal-examples/http-server/minimal-http-server-custom-headers/mount-origin/error.css b/minimal-examples/http-server/minimal-http-server-custom-headers/mount-origin/error.css new file mode 100644 index 000000000..e69de29bb diff --git a/minimal-examples/http-server/minimal-http-server-custom-headers/mount-origin/favicon.ico b/minimal-examples/http-server/minimal-http-server-custom-headers/mount-origin/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c0cc2e3dff34012ba3d4a7848a7ed17579788ec5 GIT binary patch literal 1406 zcmZQzU<5(|0R}M0U}azs1F|%L7$l?s#Ec9aKoZP=&`9i!<^REA8>%80(yxAC$j<-A zkb5S8;qL6446ipNFl>5#fuVR6L=8goC~GtXMnhmYga9MSfQgBTk&TUw5$JocUP63y z3phA97+G0a8QIy{!BT|y==xb$SQt4uIT@LmnZZ(o_~`mk`Tv1M8w?+DXJCL~kQj^& JqOtKoVgQl$ETjMc literal 0 HcmV?d00001 diff --git a/minimal-examples/http-server/minimal-http-server-custom-headers/mount-origin/index.html b/minimal-examples/http-server/minimal-http-server-custom-headers/mount-origin/index.html new file mode 100644 index 000000000..a5fbbd776 --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-custom-headers/mount-origin/index.html @@ -0,0 +1,20 @@ + + + + + + +
+ + Hello from the minimal http server custom headers example. +

+ The idea is it will tell you what your browser sent for DNT, a header lws doesn't already know. +

+ At the moment the custom header api only works on h1. +

+ + Show DNT header using h1 over http
+ Show DNT header using h1 (h2 if enabled) over https
+ + + diff --git a/minimal-examples/http-server/minimal-http-server-custom-headers/mount-origin/libwebsockets.org-logo.svg b/minimal-examples/http-server/minimal-http-server-custom-headers/mount-origin/libwebsockets.org-logo.svg new file mode 100644 index 000000000..7baea649f --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-custom-headers/mount-origin/libwebsockets.org-logo.svg @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/minimal-examples/http-server/minimal-http-server-custom-headers/mount-origin/strict-csp.svg b/minimal-examples/http-server/minimal-http-server-custom-headers/mount-origin/strict-csp.svg new file mode 100644 index 000000000..cd128f1d2 --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-custom-headers/mount-origin/strict-csp.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +