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 000000000..c0cc2e3df Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-custom-headers/mount-origin/favicon.ico differ 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +