gweb: Make debug for write simpler
[framework/connectivity/connman.git] / gweb / gweb.c
index dcd5be5..1183cc6 100644 (file)
@@ -31,6 +31,7 @@
 #include <string.h>
 #include <sys/socket.h>
 #include <arpa/inet.h>
+#include <netdb.h>
 
 #include "giognutls.h"
 #include "gresolv.h"
@@ -52,6 +53,8 @@ struct _GWebResult {
        const guint8 *buffer;
        gsize length;
        gboolean use_chunk;
+       gchar *last_key;
+       GHashTable *headers;
 };
 
 struct web_session {
@@ -61,6 +64,7 @@ struct web_session {
        char *host;
        uint16_t port;
        unsigned long flags;
+       struct addrinfo *addr;
 
        char *content_type;
 
@@ -104,6 +108,7 @@ struct _GWeb {
        char *proxy;
        char *accept_option;
        char *user_agent;
+       char *user_agent_profile;
        char *http_version;
        gboolean close_connection;
 
@@ -148,14 +153,26 @@ static void free_session(struct web_session *session)
        if (session->transport_channel != NULL)
                g_io_channel_unref(session->transport_channel);
 
-       g_string_free(session->send_buffer, TRUE);
-       g_string_free(session->current_header, TRUE);
+       g_free(session->result.last_key);
+
+       if (session->result.headers != NULL)
+               g_hash_table_destroy(session->result.headers);
+
+       if (session->send_buffer != NULL)
+               g_string_free(session->send_buffer, TRUE);
+
+       if (session->current_header != NULL)
+               g_string_free(session->current_header, TRUE);
+
        g_free(session->receive_buffer);
 
        g_free(session->content_type);
 
        g_free(session->host);
        g_free(session->address);
+       if (session->addr != NULL)
+               freeaddrinfo(session->addr);
+
        g_free(session);
 }
 
@@ -228,6 +245,7 @@ void g_web_unref(GWeb *web)
 
        g_free(web->accept_option);
        g_free(web->user_agent);
+       g_free(web->user_agent_profile);
        g_free(web->http_version);
 
        g_free(web);
@@ -250,7 +268,14 @@ gboolean g_web_set_proxy(GWeb *web, const char *proxy)
                return FALSE;
 
        g_free(web->proxy);
-       web->proxy = g_strdup(proxy);
+
+       if (proxy == NULL) {
+               web->proxy = NULL;
+               debug(web, "clearing proxy");
+       } else {
+               web->proxy = g_strdup(proxy);
+               debug(web, "setting proxy %s", web->proxy);
+       }
 
        return TRUE;
 }
@@ -325,6 +350,19 @@ gboolean g_web_set_user_agent(GWeb *web, const char *format, ...)
        return result;
 }
 
+gboolean g_web_set_ua_profile(GWeb *web, const char *profile)
+{
+       if (web == NULL)
+               return FALSE;
+
+       g_free(web->user_agent_profile);
+
+       web->user_agent_profile = g_strdup(profile);
+       debug(web, "setting user agent profile %s", web->user_agent);
+
+       return TRUE;
+}
+
 gboolean g_web_set_http_version(GWeb *web, const char *version)
 {
        if (web == NULL)
@@ -337,7 +375,7 @@ gboolean g_web_set_http_version(GWeb *web, const char *version)
                debug(web, "clearing HTTP version");
        } else {
                web->http_version = g_strdup(version);
-                debug(web, "setting HTTP version %s", web->http_version);
+               debug(web, "setting HTTP version %s", web->http_version);
        }
 
        return TRUE;
@@ -384,19 +422,18 @@ static gboolean process_send_buffer(struct web_session *session)
        count = buf->len;
 
        if (count == 0) {
-               if (session->more_data == FALSE)
+               if (session->request_started == TRUE &&
+                                       session->more_data == FALSE)
                        session->body_done = TRUE;
 
                return FALSE;
        }
 
-       debug(session->web, "bytes to write %zu", count);
-
        status = g_io_channel_write_chars(session->transport_channel,
                                        buf->str, count, &bytes_written, NULL);
 
-       debug(session->web, "status %u bytes written %zu",
-                                       status, bytes_written);
+       debug(session->web, "status %u bytes to write %zu bytes written %zu",
+                                       status, count, bytes_written);
 
        if (status != G_IO_STATUS_NORMAL && status != G_IO_STATUS_AGAIN)
                return FALSE;
@@ -460,6 +497,11 @@ static void start_request(struct web_session *session)
                g_string_append_printf(buf, "User-Agent: %s\r\n",
                                                session->web->user_agent);
 
+       if (session->web->user_agent_profile != NULL) {
+               g_string_append_printf(buf, "x-wap-profile: %s\r\n",
+                                      session->web->user_agent_profile);
+       }
+
        if (session->web->accept_option != NULL)
                g_string_append_printf(buf, "Accept: %s\r\n",
                                                session->web->accept_option);
@@ -627,9 +669,11 @@ static int handle_body(struct web_session *session,
        debug(session->web, "[body] length %zu", len);
 
        if (session->result.use_chunk == FALSE) {
-               session->result.buffer = buf;
-               session->result.length = len;
-               call_result_func(session, 0);
+               if (len > 0) {
+                       session->result.buffer = buf;
+                       session->result.length = len;
+                       call_result_func(session, 0);
+               }
                return 0;
        }
 
@@ -645,6 +689,81 @@ static int handle_body(struct web_session *session,
        return err;
 }
 
+static void handle_multi_line(struct web_session *session)
+{
+       gsize count;
+       char *str;
+       gchar *value;
+
+       str = session->current_header->str;
+
+       if (str[0] != ' ' && str[0] != '\t')
+               return;
+
+       while (str[0] == ' ' || str[0] == '\t')
+               str++;
+
+       count = str - session->current_header->str;
+       if (count > 0) {
+               g_string_erase(session->current_header, 0, count);
+               g_string_insert_c(session->current_header, 0, ' ');
+       }
+
+       value = g_hash_table_lookup(session->result.headers,
+                                       session->result.last_key);
+       if (value != NULL) {
+               g_string_insert(session->current_header, 0, value);
+
+               str = session->current_header->str;
+
+               g_hash_table_replace(session->result.headers,
+                                       g_strdup(session->result.last_key),
+                                       g_strdup(str));
+       }
+}
+
+static void add_header_field(struct web_session *session)
+{
+       gsize count;
+       guint8 *pos;
+       char *str;
+       gchar *value;
+       gchar *key;
+
+       str = session->current_header->str;
+
+       pos = memchr(str, ':', session->current_header->len);
+       if (pos != NULL) {
+               *pos = '\0';
+               pos++;
+
+               key = g_strdup(str);
+
+               /* remove preceding white spaces */
+               while (*pos == ' ')
+                       pos++;
+
+               count = (char *) pos - str;
+
+               g_string_erase(session->current_header, 0, count);
+
+               value = g_hash_table_lookup(session->result.headers, key);
+               if (value != NULL) {
+                       g_string_insert_c(session->current_header, 0, ' ');
+                       g_string_insert_c(session->current_header, 0, ';');
+
+                       g_string_insert(session->current_header, 0, value);
+               }
+
+               str = session->current_header->str;
+               g_hash_table_replace(session->result.headers, key,
+                                                       g_strdup(str));
+
+               g_free(session->result.last_key);
+               session->result.last_key = g_strdup(key);
+       }
+}
+
 static gboolean received_data(GIOChannel *channel, GIOCondition cond,
                                                        gpointer user_data)
 {
@@ -715,7 +834,23 @@ static gboolean received_data(GIOChannel *channel, GIOCondition cond,
                        ptr = NULL;
 
                if (session->current_header->len == 0) {
+                       char *val;
+
                        session->header_done = TRUE;
+
+                       val = g_hash_table_lookup(session->result.headers,
+                                                       "Transfer-Encoding");
+                       if (val != NULL) {
+                               val = g_strrstr(val, "chunked");
+                               if (val != NULL) {
+                                       session->result.use_chunk = TRUE;
+
+                                       session->chunck_state = CHUNK_SIZE;
+                                       session->chunk_left = 0;
+                                       session->total_len = 0;
+                               }
+                       }
+
                        if (handle_body(session, ptr, bytes_read) < 0) {
                                session->transport_watch = 0;
                                return FALSE;
@@ -730,24 +865,16 @@ static gboolean received_data(GIOChannel *channel, GIOCondition cond,
 
                        if (sscanf(str, "HTTP/%*s %u %*s", &code) == 1)
                                session->result.status = code;
-               } else if (session->result.use_chunk == FALSE &&
-                               g_ascii_strncasecmp("Transfer-Encoding:",
-                                                               str, 18) == 0) {
-                       char *val;
-
-                       val = g_strrstr(str + 18, "chunked");
-                       if (val != NULL) {
-                               session->result.use_chunk = TRUE;
-
-                               session->chunck_state = CHUNK_SIZE;
-                               session->chunk_left = 0;
-                               session->chunk_left = 0;
-                               session->total_len = 0;
-                       }
                }
 
                debug(session->web, "[header] %s", str);
 
+               /* handle multi-line header */
+               if (str[0] == ' ' || str[0] == '\t')
+                       handle_multi_line(session);
+               else
+                       add_header_field(session);
+
                g_string_truncate(session->current_header, 0);
        }
 
@@ -757,10 +884,9 @@ static gboolean received_data(GIOChannel *channel, GIOCondition cond,
 static int connect_session_transport(struct web_session *session)
 {
        GIOFlags flags;
-       struct sockaddr_in sin;
        int sk;
 
-       sk = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+       sk = socket(session->addr->ai_family, SOCK_STREAM, IPPROTO_TCP);
        if (sk < 0)
                return -EIO;
 
@@ -786,12 +912,8 @@ static int connect_session_transport(struct web_session *session)
 
        g_io_channel_set_close_on_unref(session->transport_channel, TRUE);
 
-       memset(&sin, 0, sizeof(sin));
-       sin.sin_family = AF_INET;
-       sin.sin_port = htons(session->port);
-       sin.sin_addr.s_addr = inet_addr(session->address);
-
-       if (connect(sk, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
+       if (connect(sk, session->addr->ai_addr,
+                       session->addr->ai_addrlen) < 0) {
                if (errno != EINPROGRESS) {
                        close(sk);
                        return -EIO;
@@ -823,7 +945,8 @@ static int create_transport(struct web_session *session)
        return 0;
 }
 
-static int parse_url(struct web_session *session, const char *url)
+static int parse_url(struct web_session *session,
+                               const char *url, const char *proxy)
 {
        char *scheme, *host, *port, *path;
 
@@ -854,7 +977,52 @@ static int parse_url(struct web_session *session, const char *url)
        if (path != NULL)
                *(path++) = '\0';
 
-       session->request = g_strdup_printf("/%s", path ? path : "");
+       if (proxy == NULL)
+               session->request = g_strdup_printf("/%s", path ? path : "");
+       else
+               session->request = g_strdup(url);
+
+       port = strrchr(host, ':');
+       if (port != NULL) {
+               char *end;
+               int tmp = strtol(port + 1, &end, 10);
+
+               if (*end == '\0') {
+                       *port = '\0';
+                       session->port = tmp;
+               }
+
+               if (proxy == NULL)
+                       session->host = g_strdup(host);
+               else
+                       session->host = g_strdup_printf("%s:%u", host, tmp);
+       } else
+               session->host = g_strdup(host);
+
+       g_free(scheme);
+
+       if (proxy == NULL)
+               return 0;
+
+       scheme = g_strdup(proxy);
+       if (scheme == NULL)
+               return -EINVAL;
+
+       host = strstr(proxy, "://");
+       if (host != NULL) {
+               *host = '\0';
+               host += 3;
+
+               if (strcasecmp(scheme, "http") != 0) {
+                       g_free(scheme);
+                       return -EINVAL;
+               }
+       } else
+               host = scheme;
+
+       path = strchr(host, '/');
+       if (path != NULL)
+               *(path++) = '\0';
 
        port = strrchr(host, ':');
        if (port != NULL) {
@@ -867,7 +1035,7 @@ static int parse_url(struct web_session *session, const char *url)
                }
        }
 
-       session->host = g_strdup(host);
+       session->address = g_strdup(host);
 
        g_free(scheme);
 
@@ -878,6 +1046,9 @@ static void resolv_result(GResolvResultStatus status,
                                        char **results, gpointer user_data)
 {
        struct web_session *session = user_data;
+       struct addrinfo hints;
+       char *port;
+       int ret;
 
        if (results == NULL || results[0] == NULL) {
                call_result_func(session, 404);
@@ -886,7 +1057,18 @@ static void resolv_result(GResolvResultStatus status,
 
        debug(session->web, "address %s", results[0]);
 
-       if (inet_aton(results[0], NULL) == 0) {
+       memset(&hints, 0, sizeof(struct addrinfo));
+       hints.ai_flags = AI_NUMERICHOST;
+
+       if (session->addr != NULL) {
+               freeaddrinfo(session->addr);
+               session->addr = NULL;
+       }
+
+       port = g_strdup_printf("%u", session->port);
+       ret = getaddrinfo(results[0], port, &hints, &session->addr);
+       g_free(port);
+       if (ret != 0 || session->addr == NULL) {
                call_result_func(session, 400);
                return;
        }
@@ -914,13 +1096,16 @@ static guint do_request(GWeb *web, const char *url,
        if (session == NULL)
                return 0;
 
-       if (parse_url(session, url) < 0) {
+       if (parse_url(session, url, web->proxy) < 0) {
                free_session(session);
                return 0;
        }
 
-       debug(web, "host %s:%u", session->host, session->port);
+       debug(web, "address %s", session->address);
+       debug(web, "port %u", session->port);
+       debug(web, "host %s", session->host);
        debug(web, "flags %lu", session->flags);
+       debug(web, "request %s", session->request);
 
        if (type != NULL) {
                session->content_type = g_strdup(type);
@@ -940,13 +1125,20 @@ static guint do_request(GWeb *web, const char *url,
                return 0;
        }
 
+       session->result.headers = g_hash_table_new_full(g_str_hash, g_str_equal,
+                                                       g_free, g_free);
+       if (session->result.headers == NULL) {
+               free_session(session);
+               return 0;
+       }
+
        session->receive_space = DEFAULT_BUFFER_SIZE;
        session->send_buffer = g_string_sized_new(0);
        session->current_header = g_string_sized_new(0);
        session->header_done = FALSE;
        session->body_done = FALSE;
 
-       if (inet_aton(session->host, NULL) == 0) {
+       if (session->address == NULL && inet_aton(session->host, NULL) == 0) {
                session->resolv_action = g_resolv_lookup_hostname(web->resolv,
                                        session->host, resolv_result, session);
                if (session->resolv_action == 0) {
@@ -954,7 +1146,29 @@ static guint do_request(GWeb *web, const char *url,
                        return 0;
                }
        } else {
-               session->address = g_strdup(session->host);
+               struct addrinfo hints;
+               char *port;
+               int ret;
+
+               if (session->address == NULL)
+                       session->address = g_strdup(session->host);
+
+               memset(&hints, 0, sizeof(struct addrinfo));
+               hints.ai_flags = AI_NUMERICHOST;
+
+               if (session->addr != NULL) {
+                       freeaddrinfo(session->addr);
+                       session->addr = NULL;
+               }
+
+               port = g_strdup_printf("%u", session->port);
+               ret = getaddrinfo(session->address, port, &hints,
+                                                       &session->addr);
+               g_free(port);
+               if (ret != 0 || session->addr == NULL) {
+                       free_session(session);
+                       return 0;
+               }
 
                if (create_transport(session) < 0) {
                        free_session(session);
@@ -1013,6 +1227,23 @@ gboolean g_web_result_get_chunk(GWebResult *result,
        return TRUE;
 }
 
+gboolean g_web_result_get_header(GWebResult *result,
+                               const char *header, const char **value)
+{
+       if (result == NULL)
+               return FALSE;
+
+       if (value == NULL)
+               return FALSE;
+
+       *value = g_hash_table_lookup(result->headers, header);
+
+       if (*value == NULL)
+               return FALSE;
+
+       return TRUE;
+}
+
 struct _GWebParser {
        gint ref_count;
        char *begin_token;