From 5c8906e93163d314eba9dc42e24cfa419d28a0cb Mon Sep 17 00:00:00 2001 From: Andy Green Date: Sun, 13 Mar 2016 16:44:19 +0800 Subject: [PATCH] client chunked transfer encoding Signed-off-by: Andy Green --- changelog | 2 +- lib/client.c | 16 ++++++- lib/parsers.c | 2 +- lib/private-libwebsockets.h | 18 ++++++++ lib/service.c | 102 ++++++++++++++++++++++++++++++++++++++++---- 5 files changed, 128 insertions(+), 12 deletions(-) diff --git a/changelog b/changelog index fc87206..ef5a406 100644 --- a/changelog +++ b/changelog @@ -79,7 +79,7 @@ just deferred until an ah becomes available. 8) The test client pays attention to if you give it an http:/ or https:// protocol string to its argument in URL format. If so, it stays in http[s] client mode and doesn't upgrade to ws[s], allowing you to do generic http client -operations. +operations. Receiving transfer-encoding: chunked is supported. 9) The test server has a new URI path http://localhost:7681/proxytest If you visit here, a client connection to http://example.com:80 is spawned, diff --git a/lib/client.c b/lib/client.c index fdf1eff..37779be 100644 --- a/lib/client.c +++ b/lib/client.c @@ -628,6 +628,17 @@ lws_client_interpret_server_handshake(struct lws *wsi) goto bail2; } + /* he may choose to send us stuff in chunked transfer-coding */ + wsi->chunked = 0; + wsi->chunk_remaining = 0; /* ie, next thing is chunk size */ + if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_TRANSFER_ENCODING)) { + wsi->chunked = !strcmp(lws_hdr_simple_ptr(wsi, + WSI_TOKEN_HTTP_TRANSFER_ENCODING), + "chunked"); + /* first thing is hex, after payload there is crlf */ + wsi->chunk_parser = ELCP_HEX; + } + if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) { wsi->u.http.content_length = atoi(lws_hdr_simple_ptr(wsi, @@ -635,8 +646,9 @@ lws_client_interpret_server_handshake(struct lws *wsi) lwsl_notice("%s: incoming content length %d\n", __func__, wsi->u.http.content_length); wsi->u.http.content_remain = wsi->u.http.content_length; - } else /* can't do 1.1 without a content length */ - wsi->u.http.connection_type = HTTP_CONNECTION_CLOSE; + } else /* can't do 1.1 without a content length or chunked */ + if (!wsi->chunked) + wsi->u.http.connection_type = HTTP_CONNECTION_CLOSE; /* * we seem to be good to go, give client last chance to check diff --git a/lib/parsers.c b/lib/parsers.c index 96be75d..879db99 100644 --- a/lib/parsers.c +++ b/lib/parsers.c @@ -496,7 +496,7 @@ lws_hdr_simple_create(struct lws *wsi, enum lws_token_indexes h, const char *s) return 0; } -static signed char char_to_hex(const char c) +signed char char_to_hex(const char c) { if (c >= '0' && c <= '9') return c - '0'; diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index 5f118ad..4d69bd5 100644 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -1054,7 +1054,18 @@ struct lws_cgi { unsigned int being_closed:1; }; +#endif + +signed char char_to_hex(const char c); +#ifndef LWS_NO_CLIENT +enum lws_chunk_parser { + ELCP_HEX, + ELCP_CR, + ELCP_CONTENT, + ELCP_POST_CR, + ELCP_POST_LF, +}; #endif struct lws { @@ -1121,6 +1132,9 @@ struct lws { unsigned int trunc_alloc_len; /* size of malloc */ unsigned int trunc_offset; /* where we are in terms of spilling */ unsigned int trunc_len; /* how much is buffered */ +#ifndef LWS_NO_CLIENT + int chunk_remaining; +#endif unsigned int hdr_parsing_completed:1; unsigned int user_space_externally_allocated:1; @@ -1129,6 +1143,7 @@ struct lws { unsigned int more_rx_waiting:1; /* has to live here since ah may stick to end */ #ifndef LWS_NO_CLIENT unsigned int do_ws:1; /* whether we are doing http or ws flow */ + unsigned int chunked:1; /* if the clientside connection is chunked */ #endif #ifndef LWS_NO_EXTENSIONS unsigned int extension_data_pending:1; @@ -1161,6 +1176,9 @@ struct lws { char cgi_channel; /* which of stdin/out/err */ char hdr_state; #endif +#ifndef LWS_NO_CLIENT + char chunk_parser; /* enum lws_chunk_parser */ +#endif }; LWS_EXTERN int log_level; diff --git a/lib/service.c b/lib/service.c index ee872fe..9acdcb1 100644 --- a/lib/service.c +++ b/lib/service.c @@ -760,24 +760,110 @@ read: */ drain: if (wsi->mode == LWSCM_HTTP_CLIENT_ACCEPTED) { - lwsl_notice("%s: calling LWS_CALLBACK_RECEIVE_CLIENT_HTTP, " - "rem %d len %d\n", __func__, - wsi->u.http.content_remain, eff_buf.token_len); - if ((int)wsi->u.http.content_remain < eff_buf.token_len) + /* + * server may insist on transfer-encoding: chunked, + * so http client must deal with it + */ +spin_chunks: + while (wsi->chunked && + (wsi->chunk_parser != ELCP_CONTENT) && + eff_buf.token_len) { + switch (wsi->chunk_parser) { + case ELCP_HEX: + if (eff_buf.token[0] == '\x0d') { + wsi->chunk_parser = ELCP_CR; + break; + } + n = char_to_hex(eff_buf.token[0]); + if (n < 0) + goto close_and_handled; + wsi->chunk_remaining <<= 4; + wsi->chunk_remaining |= n; + break; + case ELCP_CR: + if (eff_buf.token[0] != '\x0a') + goto close_and_handled; + wsi->chunk_parser = ELCP_CONTENT; + lwsl_info("chunk %d\n", + wsi->chunk_remaining); + if (wsi->chunk_remaining) + break; + lwsl_info("final chunk\n"); + if (user_callback_handle_rxflow( + wsi->protocol->callback, + wsi, LWS_CALLBACK_COMPLETED_CLIENT_HTTP, + wsi->user_space, NULL, 0)) + goto close_and_handled; + if (lws_http_transaction_completed(wsi)) + goto close_and_handled; + n = 0; + goto handled; + + case ELCP_CONTENT: + break; + + case ELCP_POST_CR: + if (eff_buf.token[0] == '\x0d') { + wsi->chunk_parser = ELCP_POST_LF; + break; + } + goto close_and_handled; + + case ELCP_POST_LF: + if (eff_buf.token[0] == '\x0a') { + wsi->chunk_parser = ELCP_HEX; + wsi->chunk_remaining = 0; + break; + } + goto close_and_handled; + } + eff_buf.token++; + eff_buf.token_len--; + } + + if (wsi->chunked && !wsi->chunk_remaining) { + n = 0; + goto handled; + } + + if (wsi->u.http.content_remain && + wsi->u.http.content_remain < eff_buf.token_len) n = wsi->u.http.content_remain; else n = eff_buf.token_len; + + if (wsi->chunked && wsi->chunk_remaining && + wsi->chunk_remaining < n) + n = wsi->chunk_remaining; + if (user_callback_handle_rxflow(wsi->protocol->callback, wsi, LWS_CALLBACK_RECEIVE_CLIENT_HTTP, wsi->user_space, (void *)eff_buf.token, - eff_buf.token_len)) + n)) goto close_and_handled; + + if (wsi->chunked && wsi->chunk_remaining) { + eff_buf.token += n; + wsi->chunk_remaining -= n; + eff_buf.token_len -= n; + } + + if (wsi->chunked && !wsi->chunk_remaining) + wsi->chunk_parser = ELCP_POST_CR; + + if (wsi->chunked && eff_buf.token_len) { + goto spin_chunks; + } + + if (wsi->chunked) { + n = 0; + goto handled; + } + wsi->u.http.content_remain -= n; if (wsi->u.http.content_remain) goto handled; - lwsl_notice("%s: client http receved all content\n", - __func__); if (user_callback_handle_rxflow(wsi->protocol->callback, wsi, LWS_CALLBACK_COMPLETED_CLIENT_HTTP, wsi->user_space, NULL, 0)) @@ -821,7 +907,7 @@ drain: } while (more); if (wsi->u.hdr.ah) { - lwsl_err("%s: %p: detaching inherited used ah\n", + lwsl_info("%s: %p: detaching inherited used ah\n", __func__, wsi); /* show we used all the pending rx up */ wsi->u.hdr.ah->rxpos = wsi->u.hdr.ah->rxlen; -- 2.7.4