mirror of
https://github.com/warmcat/libwebsockets.git
synced 2025-03-09 00:00:04 +01:00
client: multipart mime generation helpers
lws has been able to generate client multipart mime as shown in minimal-http-client-post, but it requires a lot of user boilerplate to handle the boundary, related transaction header, and multipart headers. This patch adds a client creation flag to indicate it will carry multipart mime, which autocreates the boundary string and applies the transaction header with it, and an api to form the boundary headers between the different mime parts and the terminating boundary.
This commit is contained in:
parent
ba8402b43f
commit
127e53cf98
7 changed files with 131 additions and 67 deletions
|
@ -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);
|
||||
|
||||
///@}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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++;
|
||||
|
|
|
@ -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<verbosity>] [-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;
|
||||
|
|
Loading…
Add table
Reference in a new issue