This patch add HTTP parsing to gweb, it will parse the HTTP
response then send the body to caller. This patch also add
support to chunk encoding.
---
 gweb/gweb.c      |  314 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
 gweb/gweb.h      |    3 +
 tools/web-test.c |   18 +++-
 3 files changed, 332 insertions(+), 3 deletions(-)

diff --git a/gweb/gweb.c b/gweb/gweb.c
index 39f8ecf..c3990c8 100644
--- a/gweb/gweb.c
+++ b/gweb/gweb.c
@@ -35,6 +35,39 @@
 #include "gresolv.h"
 #include "gweb.h"
 
+#define MAX_LINE_LEN   600
+#define MAX_CHUNK_LEN  17
+
+enum chunk_state {
+       CHUNK_SIZE,
+       CHUNK_R,
+       CHUNK_R_BODY,
+       CHUNK_N,
+       CHUNK_N_BODY,
+       CHUNK_DATA,
+};
+
+struct web_chunk_data {
+       enum chunk_state chunck_state;
+       int chunk_size;
+       int chunk_left;
+};
+
+struct web_data {
+       char line_buf[MAX_LINE_LEN];
+       int line_index;
+
+       unsigned long int total_len;
+       unsigned long int content_len;
+       unsigned long int http_status;
+
+       gboolean use_chunk;
+       gboolean header_ready;
+       gboolean done;
+
+       struct web_chunk_data chunk;
+};
+
 struct web_session {
        GWeb *web;
 
@@ -49,8 +82,11 @@ struct web_session {
        guint resolv_action;
        char *request;
 
+       GWebReceivedFunc received_func;
        GWebResultFunc result_func;
        gpointer result_data;
+
+       struct web_data data;
 };
 
 struct _GWeb {
@@ -191,12 +227,267 @@ gboolean g_web_add_nameserver(GWeb *web, const char 
*address)
        return TRUE;
 }
 
+static void init_chunk_data(struct web_session *session)
+{
+       memset(session->data.line_buf, 0, MAX_LINE_LEN);
+       session->data.line_index = 0;
+
+       session->data.chunk.chunck_state = CHUNK_SIZE;
+       session->data.chunk.chunk_size = 0;
+       session->data.chunk.chunk_left = 0;
+}
+
+static void init_download_data(struct web_session *session)
+{
+
+       init_chunk_data(session);
+
+       session->data.http_status = 0;
+       session->data.total_len = 0;
+       session->data.content_len = 0;
+
+       session->data.use_chunk = FALSE;
+       session->data.header_ready = FALSE;
+       session->data.done = FALSE;
+}
+
+static int append_to_line(struct web_session *session, char *buf, int len)
+{
+       char *ptr;
+
+       if ((session->data.line_index + len) >= MAX_LINE_LEN)
+               return -EXFULL;
+
+       ptr = session->data.line_buf + session->data.line_index;
+       memcpy(ptr, buf, len);
+
+       session->data.line_index += len;
+       session->data.line_buf[session->data.line_index] = '\0';
+
+       return 0;
+}
+
+static int append_to_chunk_size(struct web_session *session, char *buf, int 
len)
+{
+       if ((session->data.line_index + len) >= MAX_CHUNK_LEN)
+               return -EXFULL;
+
+       return append_to_line(session, buf, len);
+}
+
+static void send_client_payload(struct web_session *session, char *buf, int 
len)
+{
+       if (session->received_func != NULL)
+               session->received_func(buf, len, session->result_data);
+}
+
+static int decode_chunked(struct web_session *session, char *buf, int len)
+{
+       int ret;
+       unsigned int counter;
+       struct web_data *data;
+
+       data = &session->data;
+       while (len > 0) {
+               switch (data->chunk.chunck_state) {
+               case CHUNK_SIZE:
+                       if (g_ascii_isxdigit(*buf) == TRUE) {
+                               ret = append_to_chunk_size(session, buf, 1);
+                               if (ret != 0)
+                                       return ret;
+                       } else {
+                               ret = sscanf(session->data.line_buf,
+                                                       "%x", &counter);
+                               if (ret != 1)
+                                       return -EILSEQ;
+
+                               data->chunk.chunk_size = counter;
+                               data->chunk.chunk_left = counter;
+
+                               data->chunk.chunck_state = CHUNK_R;
+                               break;
+                       }
+                       buf++;
+                       len--;
+                       break;
+               case CHUNK_R:
+               case CHUNK_R_BODY:
+                       if (*buf == ' ') {
+                               buf++;
+                               len--;
+                               break;
+                       }
+
+                       if (*buf != '\r')
+                               return -EILSEQ;
+
+                       if (data->chunk.chunck_state == CHUNK_R)
+                               data->chunk.chunck_state = CHUNK_N;
+                       else
+                               data->chunk.chunck_state = CHUNK_N_BODY;
+                       buf++;
+                       len--;
+                       break;
+               case CHUNK_N:
+               case CHUNK_N_BODY:
+                       if (*buf != '\n')
+                               return -EILSEQ;
+
+                       if (data->chunk.chunck_state == CHUNK_N)
+                               data->chunk.chunck_state = CHUNK_DATA;
+                       else
+                               session->data.chunk.chunck_state = CHUNK_SIZE;
+                       buf++;
+                       len--;
+                       break;
+               case CHUNK_DATA:
+                       if (data->chunk.chunk_size == 0) {
+                               session->data.done = TRUE;
+                               debug(session->web, "Download Done in chunk");
+                               return 0;
+                       }
+
+                       if (data->chunk.chunk_left <= len) {
+                               send_client_payload(session, buf,
+                                               data->chunk.chunk_left);
+                               data->chunk.chunck_state = CHUNK_R_BODY;
+                               len -= data->chunk.chunk_left;
+                               buf += data->chunk.chunk_left;
+                               data->total_len += data->chunk.chunk_left;
+                               init_chunk_data(session);
+                               data->chunk.chunck_state = CHUNK_R_BODY;
+                               break;
+                       }
+                       /* more data */
+                       send_client_payload(session, buf, len);
+                       data->chunk.chunk_left -= len;
+                       len -= len;
+                       buf += len;
+                       data->total_len += len;
+                       break;
+               }
+       }
+
+       return 0;
+}
+
+static int decode_header(struct web_session *session,
+                               char *buf, int len)
+{
+       char *ptr, *end_line, *str;
+       int line_len;
+       int ret;
+
+       ptr = buf;
+
+       while (len > 0) {
+               end_line = memchr(ptr, '\n', len);
+               if (end_line == NULL) {
+                       if (append_to_line(session, ptr, len) < 0)
+                               return -EXFULL;
+                       return 0;
+               }
+
+               line_len = end_line - ptr;
+               line_len += 1;
+
+               if (append_to_line(session, ptr, line_len) < 0)
+                       return -EXFULL;
+
+               if ((session->data.line_buf[0] == '\r') ||
+                       (session->data.line_buf[0] == '\n')) {
+                       /* empty line http header is done */
+                       session->data.header_ready = TRUE;
+                       debug(session->web, "content len:%lu http status%lu",
+                                               session->data.content_len,
+                                               session->data.http_status);
+                       if (session->data.http_status != 0)
+                               return (end_line - buf) + 1;
+                       else
+                               return -1;
+               }
+               /* first line should be http status */
+               if (session->data.http_status == 0) {
+
+                       ret = sscanf(session->data.line_buf,
+                                               "HTTP/1.%*d %lu",
+                                               &session->data.http_status);
+                       if ((ret != 1)) {
+                               debug(session->web, "error status %lu",
+                                               session->data.http_status);
+                               return -1;
+                       }
+                } else if (g_ascii_strncasecmp("Transfer-Encoding:",
+                                       session->data.line_buf, 18) == 0) {
+                       str = g_strrstr(session->data.line_buf, "chunked");
+                       if (str != NULL) {
+                               init_chunk_data(session);
+                               session->data.use_chunk = TRUE;
+                       }
+               } else if (g_ascii_strncasecmp("Content-Length:",
+                                       session->data.line_buf, 15) == 0) {
+                       sscanf(session->data.line_buf,
+                               "Content-Length: %lu",
+                               &session->data.content_len);
+               }
+
+               ptr += line_len;
+               len -= line_len;
+
+               memset(session->data.line_buf, 0, MAX_LINE_LEN);
+               session->data.line_index = 0;
+       }
+
+       return 0;
+}
+
+static int decode_function(struct web_session *session,
+                                       char *buf, int len)
+{
+       int ret;
+
+       /* check if we still reading HTTP header */
+       if (session->data.header_ready == FALSE) {
+               ret = decode_header(session, buf, len);
+
+               if (ret < 0)
+                       return ret;
+               else if (ret == 0)
+                       return 0;
+
+               memset(session->data.line_buf, 0, MAX_LINE_LEN);
+               session->data.line_index = 0;
+
+               buf += ret;
+               len -= ret;
+       }
+
+       if (len <= 0)
+               return 0;
+
+       if (session->data.use_chunk)
+               return decode_chunked(session, buf, len);
+
+       session->data.total_len += len;
+
+       send_client_payload(session, buf, len);
+
+       if ((session->data.content_len != 0) &&
+               (session->data.total_len >= session->data.content_len)) {
+               debug(session->web, "Downloan complete");
+               session->data.done = TRUE;
+       }
+
+       return 0;
+}
+
 static gboolean received_data(GIOChannel *channel, GIOCondition cond,
                                                        gpointer user_data)
 {
        struct web_session *session = user_data;
-       unsigned char buf[4096];
+       char buf[4096];
        int sk, len;
+       int ret;
 
        if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
                session->transport_watch = 0;
@@ -216,7 +507,22 @@ static gboolean received_data(GIOChannel *channel, 
GIOCondition cond,
                        session->result_func(200, session->result_data);
                return FALSE;
        }
-       printf("%s", buf);
+
+       ret = decode_function(session, buf, len);
+
+       if (ret != 0) {
+               session->transport_watch = 0;
+               if (session->result_func != NULL)
+                       session->result_func(ret, session->result_data);
+               return FALSE;
+       }
+       if (session->data.done == TRUE) {
+               session->transport_watch = 0;
+               if (session->result_func != NULL)
+                       session->result_func(session->data.http_status,
+                                                       session->result_data);
+               return FALSE;
+       }
 
        return TRUE;
 }
@@ -265,6 +571,8 @@ static void start_request(struct web_session *session)
        debug(session->web, "request %s from %s",
                                        session->request, session->host);
 
+       init_download_data(session);
+
        sk = g_io_channel_unix_get_fd(session->transport_channel);
 
        buf = g_string_new(NULL);
@@ -366,6 +674,7 @@ static void resolv_result(GResolvResultStatus status,
 }
 
 guint g_web_request(GWeb *web, GWebMethod method, const char *url,
+                               GWebReceivedFunc rec_func,
                                GWebResultFunc func, gpointer user_data)
 {
        struct web_session *session;
@@ -390,6 +699,7 @@ guint g_web_request(GWeb *web, GWebMethod method, const 
char *url,
 
        session->result_func = func;
        session->result_data = user_data;
+       session->received_func = rec_func;
 
        session->resolv_action = g_resolv_lookup_hostname(web->resolv,
                                        session->host, resolv_result, session);
diff --git a/gweb/gweb.h b/gweb/gweb.h
index 1ab2a9f..c044b63 100644
--- a/gweb/gweb.h
+++ b/gweb/gweb.h
@@ -42,6 +42,8 @@ typedef void (*GWebResultFunc)(uint16_t status, gpointer 
user_data);
 
 typedef void (*GWebDebugFunc)(const char *str, gpointer user_data);
 
+typedef void (*GWebReceivedFunc)(const char *str, int len, gpointer user_data);
+
 GWeb *g_web_new(int index);
 
 GWeb *g_web_ref(GWeb *web);
@@ -52,6 +54,7 @@ void g_web_set_debug(GWeb *web, GWebDebugFunc func, gpointer 
user_data);
 gboolean g_web_add_nameserver(GWeb *web, const char *address);
 
 guint g_web_request(GWeb *web, GWebMethod method, const char *url,
+                               GWebReceivedFunc rec_func,
                                GWebResultFunc func, gpointer user_data);
 
 gboolean g_web_cancel(GWeb *web, guint id);
diff --git a/tools/web-test.c b/tools/web-test.c
index 253948f..f9b1257 100644
--- a/tools/web-test.c
+++ b/tools/web-test.c
@@ -34,6 +34,8 @@ static GTimer *timer;
 
 static GMainLoop *main_loop;
 
+static GString *content;
+
 static void web_debug(const char *str, void *data)
 {
        g_print("%s: %s\n", (const char *) data, str);
@@ -50,6 +52,7 @@ static void web_result(uint16_t status, gpointer user_data)
 
        elapsed = g_timer_elapsed(timer, NULL);
 
+       g_print("%s", content->str);
        g_print("elapse: %f seconds\n", elapsed);
 
        g_print("status: %03u\n", status);
@@ -57,6 +60,11 @@ static void web_result(uint16_t status, gpointer user_data)
        g_main_loop_quit(main_loop);
 }
 
+static void received_data(const char *str, int len, gpointer user_data)
+{
+       g_string_append_len(content, str, len);
+}
+
 static gboolean option_debug = FALSE;
 static gchar *option_nameserver = NULL;
 
@@ -95,6 +103,12 @@ int main(int argc, char *argv[])
                return 1;
        }
 
+       content = g_string_sized_new(1024);
+       if (content == NULL) {
+               printf("failed to allocate buf\n");
+               return 1;
+       }
+
        web = g_web_new(index);
        if (web == NULL) {
                printf("failed to web service\n");
@@ -114,7 +128,7 @@ int main(int argc, char *argv[])
        timer = g_timer_new();
 
        if (g_web_request(web, G_WEB_METHOD_GET, argv[1],
-                                       web_result, NULL) == 0) {
+                               received_data, web_result, NULL) == 0) {
                printf("failed to start request\n");
                return 1;
        }
@@ -130,6 +144,8 @@ int main(int argc, char *argv[])
 
        g_web_unref(web);
 
+       g_string_free(content, TRUE);
+
        g_main_loop_unref(main_loop);
 
        return 0;
-- 
1.7.2.3

_______________________________________________
connman mailing list
[email protected]
http://lists.connman.net/listinfo/connman

Reply via email to