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