diff --git a/include/libwebsockets/lws-secure-streams-policy.h b/include/libwebsockets/lws-secure-streams-policy.h index 41d2a9654..0337d72ff 100644 --- a/include/libwebsockets/lws-secure-streams-policy.h +++ b/include/libwebsockets/lws-secure-streams-policy.h @@ -126,6 +126,8 @@ enum { /**< we listen on a socket as a server */ LWSSSPOLF_ALLOW_REDIRECTS = (1 << 16), /**< follow redirects */ + LWSSSPOLF_HTTP_MULTIPART_IN = (1 << 17), + /**< handle inbound multipart mime at SS level */ }; typedef struct lws_ss_trust_store { diff --git a/lib/secure-streams/README.md b/lib/secure-streams/README.md index 5d064e7d4..d4050b8fa 100644 --- a/lib/secure-streams/README.md +++ b/lib/secure-streams/README.md @@ -403,6 +403,10 @@ sent an `END_STREAM`, even though we have sent headers with `END_HEADERS`. Set this to `true` if the peer server has the quirk it sends an maximum initial tx credit of 0x7fffffff and then later increments it illegally. +### `http_multipart_ss_in` + +Indicates that SS should parse any incoming multipart mime on this stream + ### `http_multipart_name` Indicates this stream goes out using multipart mime, and provides the name part of the diff --git a/lib/secure-streams/policy-json.c b/lib/secure-streams/policy-json.c index 1a6bc29e0..718e6c4d9 100644 --- a/lib/secure-streams/policy-json.c +++ b/lib/secure-streams/policy-json.c @@ -80,6 +80,7 @@ static const char * const lejp_tokens_policy[] = { "s[].*.http_www_form_urlencoded", "s[].*.http_expect", "s[].*.http_fail_redirect", + "s[].*.http_multipart_ss_in", "s[].*.ws_subprotocol", "s[].*.ws_binary", "s[].*.local_sink", @@ -151,6 +152,7 @@ typedef enum { LSSPPT_HTTP_WWW_FORM_URLENCODED, LSSPPT_HTTP_EXPECT, LSSPPT_HTTP_FAIL_REDIRECT, + LSSPPT_HTTP_MULTIPART_SS_IN, LSSPPT_WS_SUBPROTOCOL, LSSPPT_WS_BINARY, LSSPPT_LOCAL_SINK, @@ -584,6 +586,11 @@ lws_ss_policy_parser_cb(struct lejp_ctx *ctx, char reason) a->curr[LTY_POLICY].p->flags |= LWSSSPOLF_ALLOW_REDIRECTS; break; + case LSSPPT_HTTP_MULTIPART_SS_IN: + if (reason == LEJPCB_VAL_TRUE) + a->curr[LTY_POLICY].p->flags |= + LWSSSPOLF_HTTP_MULTIPART_IN; + return 0; case LSSPPT_RETRYPTR: bot = a->heads[LTY_BACKOFF].b; diff --git a/lib/secure-streams/protocols/ss-h1.c b/lib/secure-streams/protocols/ss-h1.c index 3fef53469..9f6e1d9bd 100644 --- a/lib/secure-streams/protocols/ss-h1.c +++ b/lib/secure-streams/protocols/ss-h1.c @@ -318,7 +318,11 @@ secstream_h1(struct lws *wsi, enum lws_callback_reasons reason, void *user, #if defined(LWS_WITH_SS_RIDESHARE) /* - * We should only especially process multipart ourselves if + * There are two ways we might want to deal with multipart, + * one is pass it through raw (although the user code needs + * a helping hand for learning the boundary), and the other + * is to deframe it and provide basically submessages in the + * different parts. */ if (lws_hdr_copy(wsi, (char *)buf, sizeof(buf), @@ -367,7 +371,8 @@ secstream_h1(struct lws *wsi, enum lws_callback_reasons reason, void *user, /* inform the ss that a related message group begins */ - if (h->u.http.boundary[0]) + if ((h->policy->flags & LWSSSPOLF_HTTP_MULTIPART_IN) && + h->u.http.boundary[0]) h->info.rx(ss_to_userobj(h), NULL, 0, LWSSS_FLAG_RELATED_START); @@ -460,7 +465,8 @@ malformed: return 0; #if defined(LWS_WITH_SS_RIDESHARE) - if (h->u.http.boundary[0]) + if ((h->policy->flags & LWSSSPOLF_HTTP_MULTIPART_IN) && + h->u.http.boundary[0]) return ss_http_multipart_parser(h, in, len); #endif diff --git a/minimal-examples/secure-streams/minimal-secure-streams-alexa/main.c b/minimal-examples/secure-streams/minimal-secure-streams-alexa/main.c index d498b42a2..02f7ba03d 100644 --- a/minimal-examples/secure-streams/minimal-secure-streams-alexa/main.c +++ b/minimal-examples/secure-streams/minimal-secure-streams-alexa/main.c @@ -203,6 +203,7 @@ static const char * const default_ss_policy = "\"h2q_oflow_txcr\":" "true," "\"http_auth_header\":" "\"authorization:\"," "\"http_auth_preamble\":" "\"Bearer \"," + "\"http_multipart_ss_in\":" "true," "\"nailed_up\":" "true," "\"long_poll\":" "true," "\"retry\":" "\"default\"," @@ -229,6 +230,7 @@ static const char * const default_ss_policy = "\"http_multipart_name\":" "\"metadata\"," "\"http_mime_content_type\":" "\"application/json; charset=UTF-8\"," "\"http_no_content_length\":" "true," + "\"http_multipart_ss_in\":" "true," "\"rideshare\":" "\"avs_audio\"," "\"retry\":" "\"default\"," "\"plugins\":" "[]," @@ -246,6 +248,7 @@ static const char * const default_ss_policy = "\"h2q_oflow_txcr\":" "true," "\"http_auth_header\":" "\"authorization:\"," "\"http_auth_preamble\":" "\"Bearer \"," + "\"http_multipart_ss_in\":" "true," "\"http_multipart_name\":" "\"audio\"," "\"http_mime_content_type\":" "\"application/octet-stream\"," "\"http_no_content_length\":" "true," diff --git a/minimal-examples/secure-streams/minimal-secure-streams-avs/avs.c b/minimal-examples/secure-streams/minimal-secure-streams-avs/avs.c index 0121d6783..49e6ca11e 100644 --- a/minimal-examples/secure-streams/minimal-secure-streams-avs/avs.c +++ b/minimal-examples/secure-streams/minimal-secure-streams-avs/avs.c @@ -120,7 +120,9 @@ ss_avs_metadata_rx(void *userobj, const uint8_t *buf, size_t len, int flags) lwsl_notice("%s: rideshare %s, len %d, flags 0x%x\n", __func__, lws_ss_rideshare(m->ss), (int)len, flags); - // lwsl_hexdump_warn(buf, len); +#if 0 + lwsl_hexdump_warn(buf, len); +#endif n = sizeof(m->buf) - ((m->head - m->tail) % sizeof(m->buf)); lwsl_info("%s: len %d, buf h %d, t %d, space %d\n", __func__, diff --git a/minimal-examples/secure-streams/minimal-secure-streams-avs/main.c b/minimal-examples/secure-streams/minimal-secure-streams-avs/main.c index fc154329d..9bae7d54f 100644 --- a/minimal-examples/secure-streams/minimal-secure-streams-avs/main.c +++ b/minimal-examples/secure-streams/minimal-secure-streams-avs/main.c @@ -195,6 +195,9 @@ static const char * const default_ss_policy = "\"http_auth_preamble\":" "\"Bearer \"," "\"http_multipart_name\":" "\"metadata\"," "\"http_mime_content_type\":" "\"application/json; charset=UTF-8\"," +#if 1 + "\"http_multipart_ss_in\":" "true," +#endif "\"rideshare\":" "\"avs_audio\"," "\"retry\":" "\"default\"," "\"plugins\":" "[]," @@ -211,6 +214,9 @@ static const char * const default_ss_policy = "\"plugins\":" "[]," "\"tls\":" "true," "\"h2q_oflow_txcr\":" "true," +#if 1 + "\"http_multipart_ss_in\":" "true," +#endif "\"http_auth_header\":" "\"authorization:\"," "\"http_auth_preamble\":" "\"Bearer \"," "\"http_multipart_name\":" "\"audio\"," diff --git a/minimal-examples/secure-streams/minimal-secure-streams-server/main.c b/minimal-examples/secure-streams/minimal-secure-streams-server/main.c index 78363e7b6..1c244c2af 100644 --- a/minimal-examples/secure-streams/minimal-secure-streams-server/main.c +++ b/minimal-examples/secure-streams/minimal-secure-streams-server/main.c @@ -14,7 +14,7 @@ extern const lws_ss_info_t ssi_client, ssi_server; static struct lws_context *context; -int interrupted, bad = 1; +int interrupted, bad = 1, multipart; static const char * const default_ss_policy = "{" "\"release\":" "\"01234567\"," @@ -302,6 +302,10 @@ int main(int argc, const char **argv) memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ lws_cmdline_option_handle_builtin(argc, argv, &info); + + if (lws_cmdline_option(argc, argv, "-m")) + multipart = 1; + lwsl_user("LWS Secure Streams Server\n"); info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS | diff --git a/minimal-examples/secure-streams/minimal-secure-streams-server/ss-server.c b/minimal-examples/secure-streams/minimal-secure-streams-server/ss-server.c index 716f3e101..5c2c2c176 100644 --- a/minimal-examples/secure-streams/minimal-secure-streams-server/ss-server.c +++ b/minimal-examples/secure-streams/minimal-secure-streams-server/ss-server.c @@ -10,9 +10,10 @@ #include #include -extern int interrupted, bad; +extern int interrupted, bad, multipart; static const char *html = + /* normally we serve this... */ "" "Hello from the web server
" "
" - ""; + "", + +*multipart_html = + /* + * If you use -m commandline switch we send this instead, as + * multipart/form-data + */ + "--aBoundaryString\r\n" + "Content-Disposition: form-data; name=\"myFile\"; filename=\"xxx.txt\"\r\n" + "Content-Type: text/plain\r\n" + "\r\n" + "The file contents\r\n" + "--aBoundaryString\r\n" + "Content-Disposition: form-data; name=\"myField\"\r\n" + "\r\n" + "(data)\r\n" + "--aBoundaryString--\r\n"; typedef struct myss { @@ -68,14 +85,18 @@ myss_srv_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, size_t *len, int *flags) { myss_srv_t *m = (myss_srv_t *)userobj; + const char *send = html; if (m->upgraded) return LWSSSSRET_TX_DONT_SEND; + if (multipart) + send = multipart_html; + *flags = LWSSS_FLAG_SOM | LWSSS_FLAG_EOM; - lws_strncpy((char *)buf, html, *len); - *len = strlen(html); + lws_strncpy((char *)buf, send, *len); + *len = strlen(send); return 0; } @@ -168,13 +189,18 @@ myss_srv_state(void *userobj, void *sh, lws_ss_constate_t state, */ lws_ss_server_ack(m->ss, 0); /* - * ... it's going to be text/html... + * ... it's going to be either text/html or multipart ... */ - lws_ss_set_metadata(m->ss, "mime", "text/html", 9); + if (multipart) + lws_ss_set_metadata(m->ss, "mime", + "multipart/form-data; boundary=aBoundaryString", 45); + else + lws_ss_set_metadata(m->ss, "mime", "text/html", 9); /* - * ...it's going to be 128 byte (and request tx) + * ...it's going to be whatever size it is (and request tx) */ - lws_ss_request_tx_len(m->ss, strlen(html)); + lws_ss_request_tx_len(m->ss, multipart ? strlen(multipart_html) : + strlen(html)); break; case LWSSSCS_SERVER_UPGRADE: