diff --git a/include/libwebsockets/lws-client.h b/include/libwebsockets/lws-client.h index 0c7839ef6..41f6569c4 100644 --- a/include/libwebsockets/lws-client.h +++ b/include/libwebsockets/lws-client.h @@ -43,6 +43,7 @@ enum lws_client_connect_ssl_connection_flags { LCCSCF_H2_QUIRK_NGHTTP2_END_STREAM = (1 << 5), LCCSCF_H2_QUIRK_OVERFLOWS_TXCR = (1 << 6), LCCSCF_H2_AUTH_BEARER = (1 << 7), + LCCSCF_HTTP_MULTIPART_MIME = (1 << 8), LCCSCF_PIPELINE = (1 << 16), /**< Serialize / pipeline multiple client connections @@ -244,7 +245,7 @@ lws_tls_client_vhost_extra_cert_mem(struct lws_vhost *vh, const uint8_t *der, size_t der_len); /** - * lws_client_http_body_pending() - control if client connection neeeds to send body + * lws_client_http_body_pending() - control if client connection needs to send body * * \param wsi: client connection * \param something_left_to_send: nonzero if need to send more body, 0 (default) @@ -265,4 +266,23 @@ lws_tls_client_vhost_extra_cert_mem(struct lws_vhost *vh, LWS_VISIBLE LWS_EXTERN void lws_client_http_body_pending(struct lws *wsi, int something_left_to_send); +/** + * lws_client_http_multipart() - issue appropriate multipart header or trailer + * + * \param wsi: client connection + * \param name: multipart header name field, or NULL if end of multipart + * \param filename: multipart header filename field, or NULL if none + * \param content_type: multipart header content-type part, or NULL if none + * \param p: pointer to position in buffer + * \param end: end of buffer + * + * This issues a multipart mime boundary, or terminator if name = NULL. + * + * Returns 0 if OK or nonzero if couldn't fit in buffer + */ +LWS_VISIBLE LWS_EXTERN int +lws_client_http_multipart(struct lws *wsi, const char *name, + const char *filename, const char *content_type, + char **p, char *end); + ///@} diff --git a/lib/misc/base64-decode.c b/lib/misc/base64-decode.c index 4bd9e0102..3a88c84ee 100644 --- a/lib/misc/base64-decode.c +++ b/lib/misc/base64-decode.c @@ -71,10 +71,10 @@ _lws_b64_encode_string(const char *encode, const char *in, int in_len, return -1; *out++ = encode[triple[0] >> 2]; - *out++ = encode[((triple[0] & 0x03) << 4) | - ((triple[1] & 0xf0) >> 4)]; - *out++ = (len > 1 ? encode[((triple[1] & 0x0f) << 2) | - ((triple[2] & 0xc0) >> 6)] : '='); + *out++ = encode[(((triple[0] & 0x03) << 4) & 0x30) | + (((triple[1] & 0xf0) >> 4) & 0x0f)]; + *out++ = (len > 1 ? encode[(((triple[1] & 0x0f) << 2) & 0x3c) | + (((triple[2] & 0xc0) >> 6) & 3)] : '='); *out++ = (len > 2 ? encode[triple[2] & 0x3f] : '='); done += 4; diff --git a/lib/roles/h2/http2.c b/lib/roles/h2/http2.c index a625368b2..5bb65b0cb 100644 --- a/lib/roles/h2/http2.c +++ b/lib/roles/h2/http2.c @@ -2203,7 +2203,7 @@ int lws_h2_client_handshake(struct lws *wsi) { struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; - uint8_t *buf, *start, *p, *end, *q; + uint8_t *buf, *start, *p, *p1, *end, *q; char *meth = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_METHOD), *uri = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_URI); struct lws *nwsi = lws_get_network_wsi(wsi); @@ -2289,6 +2289,13 @@ lws_h2_client_handshake(struct lws *wsi) &p, end)) goto fail_length; + if (wsi->flags & LCCSCF_HTTP_MULTIPART_MIME) { + p1 = lws_http_multipart_headers(wsi, p); + if (!p1) + goto fail_length; + p = p1; + } + if (wsi->flags & LCCSCF_H2_AUTH_BEARER) { uint8_t *qend = q + (wsi->context->pt_serv_buf_size / 2) - 1; diff --git a/lib/roles/http/client/client-http.c b/lib/roles/http/client/client-http.c index a57853ab9..5235439f4 100644 --- a/lib/roles/http/client/client-http.c +++ b/lib/roles/http/client/client-http.c @@ -1040,13 +1040,75 @@ bail2: } #endif +/* + * set the boundary string and the content-type for client multipart mime + */ + +uint8_t * +lws_http_multipart_headers(struct lws *wsi, uint8_t *p) +{ + char buf[10], arg[48]; + int n; + + lws_get_random(wsi->context, (uint8_t *)buf, sizeof(buf)); + lws_b64_encode_string(buf, sizeof(buf), + wsi->http.multipart_boundary, + sizeof(wsi->http.multipart_boundary)); + + n = lws_snprintf(arg, sizeof(arg), "multipart/form-data;boundary=%s", + wsi->http.multipart_boundary); + + if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, + (uint8_t *)arg, n, &p, p + 100)) + return NULL; + + wsi->http.multipart = wsi->http.multipart_issue_boundary = 1; + + return p; +} + +int +lws_client_http_multipart(struct lws *wsi, const char *name, + const char *filename, const char *content_type, + char **p, char *end) +{ + /* + * Client conn must have been created with LCCSCF_HTTP_MULTIPART_MIME + * flag to use this api + */ + assert(wsi->http.multipart); + + if (!name) { + *p += lws_snprintf((char *)(*p), lws_ptr_diff(end, p), + "\xd\xa--%s--\xd\xa", + wsi->http.multipart_boundary); + + return 0; + } + + *p += lws_snprintf((char *)(*p), lws_ptr_diff(end, p), "\xd\xa--%s\xd\xa" + "Content-Disposition: form-data; " + "name=\"%s\"", + wsi->http.multipart_boundary, name); + if (filename) + *p += lws_snprintf((char *)(*p), lws_ptr_diff(end, p), + "; filename=\"%s\"", filename); + + if (content_type) + *p += lws_snprintf((char *)(*p), lws_ptr_diff(end, p), "\xd\xa" + "Content-Type: %s", content_type); + + *p += lws_snprintf((char *)(*p), lws_ptr_diff(end, p), "\xd\xa\xd\xa"); + + return *p == end; +} + char * lws_generate_client_handshake(struct lws *wsi, char *pkt) { - char *p = pkt; - const char *meth; - const char *pp = lws_hdr_simple_ptr(wsi, + const char *meth, *pp = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS); + char *p = pkt, *p1; meth = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_METHOD); if (!meth) { @@ -1119,6 +1181,13 @@ lws_generate_client_handshake(struct lws *wsi, char *pkt) _WSI_TOKEN_CLIENT_ORIGIN)); } + if (wsi->flags & LCCSCF_HTTP_MULTIPART_MIME) { + p1 = (char *)lws_http_multipart_headers(wsi, (uint8_t *)p); + if (!p1) + return NULL; + p = p1; + } + #if defined(LWS_WITH_HTTP_PROXY) if (wsi->parent && lws_hdr_total_length(wsi->parent, WSI_TOKEN_HTTP_CONTENT_LENGTH)) { diff --git a/lib/roles/http/private-lib-roles-http.h b/lib/roles/http/private-lib-roles-http.h index 50a332d6e..e9ce4e5c1 100644 --- a/lib/roles/http/private-lib-roles-http.h +++ b/lib/roles/http/private-lib-roles-http.h @@ -233,7 +233,9 @@ struct _lws_http_mode_related { lws_filepos_t filelen; lws_fop_fd_t fop_fd; #endif - +#if defined(LWS_WITH_CLIENT) + char multipart_boundary[16]; +#endif #if defined(LWS_WITH_RANGES) struct lws_range_parsing range; char multipart_content_type[64]; @@ -266,6 +268,8 @@ struct _lws_http_mode_related { unsigned int deferred_transaction_completed:1; unsigned int content_length_explicitly_zero:1; unsigned int did_stream_close:1; + unsigned int multipart:1; + unsigned int multipart_issue_boundary:1; }; @@ -313,3 +317,6 @@ lws_http_proxy_start(struct lws *wsi, const struct lws_http_mount *hit, void lws_sul_http_ah_lifecheck(lws_sorted_usec_list_t *sul); + +uint8_t * +lws_http_multipart_headers(struct lws *wsi, uint8_t *p); diff --git a/lib/roles/http/server/lws-spa.c b/lib/roles/http/server/lws-spa.c index d45e54f76..73a047d7d 100644 --- a/lib/roles/http/server/lws-spa.c +++ b/lib/roles/http/server/lws-spa.c @@ -186,7 +186,8 @@ lws_urldecode_s_process(struct lws_urldecode_stateful *s, const char *in, continue; } if (s->pos >= (int)sizeof(s->name) - 1) { - lwsl_notice("Name too long\n"); + lwsl_hexdump_notice(s->name, s->pos); + lwsl_notice("Name too long...\n"); return -1; } s->name[s->pos++] = *in++; diff --git a/minimal-examples/http-client/minimal-http-client-post/minimal-http-client-post.c b/minimal-examples/http-client/minimal-http-client-post/minimal-http-client-post.c index d6b080a52..3f139f1e1 100644 --- a/minimal-examples/http-client/minimal-http-client-post/minimal-http-client-post.c +++ b/minimal-examples/http-client/minimal-http-client-post/minimal-http-client-post.c @@ -21,7 +21,6 @@ static int interrupted, bad = 0, status, count_clients = 1, completed; static struct lws *client_wsi[4]; struct pss { - char boundary[32]; char body_part; }; @@ -31,9 +30,7 @@ callback_http(struct lws *wsi, enum lws_callback_reasons reason, { struct pss *pss = (struct pss *)user; char buf[LWS_PRE + 1024], *start = &buf[LWS_PRE], *p = start, - *end = &buf[sizeof(buf) - 1]; - uint8_t **up, *uend; - uint32_t r; + *end = &buf[sizeof(buf) - LWS_PRE - 1]; int n; switch (reason) { @@ -96,29 +93,7 @@ callback_http(struct lws *wsi, enum lws_callback_reasons reason, /* ...callbacks related to generating the POST... */ case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER: - lwsl_user("LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER\n"); - up = (uint8_t **)in; - uend = *up + len - 1; - - /* generate a random boundary string */ - - lws_get_random(lws_get_context(wsi), &r, sizeof(r)); - lws_snprintf(pss->boundary, sizeof(pss->boundary) - 1, - "---boundary-%08x", r); - - n = lws_snprintf(buf, sizeof(buf) - 1, - "multipart/form-data; boundary=%s", pss->boundary); - if (lws_add_http_header_by_token(wsi, - WSI_TOKEN_HTTP_CONTENT_TYPE, - (uint8_t *)buf, n, up, uend)) - return 1; /* - * Notice because we are sending multipart/form-data we can - * usually rely on the server to understand where the form - * payload ends without having to give it an overall - * content-length (which can be troublesome to compute ahead - * of generating the data to send). - * * Tell lws we are going to send the body next... */ lws_client_http_body_pending(wsi, 1); @@ -138,28 +113,25 @@ callback_http(struct lws *wsi, enum lws_callback_reasons reason, switch (pss->body_part++) { case 0: + if (lws_client_http_multipart(wsi, "text", NULL, NULL, + &p, end)) + return -1; /* notice every usage of the boundary starts with -- */ - p += lws_snprintf(p, end - p, "--%s\xd\xa" - "content-disposition: " - "form-data; name=\"text\"\xd\xa" - "\xd\xa" - "my text field" - "\xd\xa", pss->boundary); + p += lws_snprintf(p, end - p, "my text field\xd\xa"); break; case 1: + if (lws_client_http_multipart(wsi, "file", "myfile.txt", + "text/plain", &p, end)) + return -1; p += lws_snprintf(p, end - p, - "--%s\xd\xa" - "content-disposition: form-data; name=\"file\";" - "filename=\"myfile.txt\"\xd\xa" - "content-type: text/plain\xd\xa" - "\xd\xa" "This is the contents of the " "uploaded file.\xd\xa" - "\xd\xa", pss->boundary); + "\xd\xa"); break; case 2: - p += lws_snprintf(p, end - p, "--%s--\xd\xa", - pss->boundary); + if (lws_client_http_multipart(wsi, NULL, NULL, NULL, + &p, end)) + return -1; lws_client_http_body_pending(wsi, 0); /* necessary to support H2, it means we will write no * more on this stream */ @@ -211,29 +183,17 @@ int main(int argc, const char **argv) struct lws_context_creation_info info; struct lws_client_connect_info i; struct lws_context *context; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* - * For LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE - * - * | LLL_INFO | LLL_PARSER | LLL_HEADER | LLL_EXT | - * LLL_CLIENT | LLL_LATENCY | LLL_DEBUG - */ ; + int n = 0; signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, "-d"))) - logs = atoi(p); - - lws_set_log_level(logs, NULL); + memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_cmdline_option_handle_builtin(argc, argv, &info); lwsl_user("LWS minimal http client - POST [-d] [-l] [--h1]\n"); if (lws_cmdline_option(argc, argv, "-m")) count_clients = LWS_ARRAY_SIZE(client_wsi); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */ info.protocols = protocols; @@ -263,7 +223,7 @@ int main(int argc, const char **argv) memset(&i, 0, sizeof i); /* otherwise uninitialized garbage */ i.context = context; - i.ssl_connection = LCCSCF_USE_SSL; + i.ssl_connection = LCCSCF_USE_SSL | LCCSCF_HTTP_MULTIPART_MIME; if (lws_cmdline_option(argc, argv, "-l")) { i.port = 7681;