client chunked transfer encoding
authorAndy Green <andy.green@linaro.org>
Sun, 13 Mar 2016 08:44:19 +0000 (16:44 +0800)
committerAndy Green <andy@warmcat.com>
Sat, 19 Mar 2016 09:21:45 +0000 (17:21 +0800)
Signed-off-by: Andy Green <andy.green@linaro.org>
changelog
lib/client.c
lib/parsers.c
lib/private-libwebsockets.h
lib/service.c

index fc87206..ef5a406 100644 (file)
--- 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,
index fdf1eff..37779be 100644 (file)
@@ -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
index 96be75d..879db99 100644 (file)
@@ -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';
index 5f118ad..4d69bd5 100644 (file)
@@ -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;
index ee872fe..9acdcb1 100644 (file)
@@ -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;