3 * Web service library with GLib integration
5 * Copyright (C) 2009-2010 Intel Corporation. All rights reserved.
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
32 #include <sys/socket.h>
33 #include <arpa/inet.h>
35 #include "giognutls.h"
39 #define DEFAULT_BUFFER_SIZE 2048
41 #define SESSION_FLAG_USE_TLS (1 << 0)
69 GIOChannel *transport_channel;
70 guint transport_watch;
76 guint8 *receive_buffer;
79 GString *current_header;
83 gboolean request_started;
85 enum chunk_state chunck_state;
92 GWebResultFunc result_func;
93 GWebInputFunc input_func;
110 gboolean close_connection;
112 GWebDebugFunc debug_func;
116 static inline void debug(GWeb *web, const char *format, ...)
121 if (web->debug_func == NULL)
124 va_start(ap, format);
126 if (vsnprintf(str, sizeof(str), format, ap) > 0)
127 web->debug_func(str, web->debug_data);
132 static void free_session(struct web_session *session)
134 GWeb *web = session->web;
139 g_free(session->request);
141 if (session->resolv_action > 0)
142 g_resolv_cancel_lookup(web->resolv, session->resolv_action);
144 if (session->transport_watch > 0)
145 g_source_remove(session->transport_watch);
147 if (session->send_watch > 0)
148 g_source_remove(session->send_watch);
150 if (session->transport_channel != NULL)
151 g_io_channel_unref(session->transport_channel);
153 g_free(session->result.last_key);
155 if (session->result.headers != NULL)
156 g_hash_table_destroy(session->result.headers);
158 if (session->send_buffer != NULL)
159 g_string_free(session->send_buffer, TRUE);
161 if (session->current_header != NULL)
162 g_string_free(session->current_header, TRUE);
164 g_free(session->receive_buffer);
166 g_free(session->content_type);
168 g_free(session->host);
169 g_free(session->address);
173 static void flush_sessions(GWeb *web)
177 for (list = g_list_first(web->session_list);
178 list; list = g_list_next(list))
179 free_session(list->data);
181 g_list_free(web->session_list);
182 web->session_list = NULL;
185 GWeb *g_web_new(int index)
192 web = g_try_new0(GWeb, 1);
198 web->next_query_id = 1;
201 web->session_list = NULL;
203 web->resolv = g_resolv_new(index);
204 if (web->resolv == NULL) {
209 web->accept_option = g_strdup("*/*");
210 web->user_agent = g_strdup_printf("GWeb/%s", VERSION);
211 web->close_connection = FALSE;
216 GWeb *g_web_ref(GWeb *web)
221 g_atomic_int_inc(&web->ref_count);
226 void g_web_unref(GWeb *web)
231 if (g_atomic_int_dec_and_test(&web->ref_count) == FALSE)
236 g_resolv_unref(web->resolv);
240 g_free(web->accept_option);
241 g_free(web->user_agent);
242 g_free(web->http_version);
247 void g_web_set_debug(GWeb *web, GWebDebugFunc func, gpointer user_data)
252 web->debug_func = func;
253 web->debug_data = user_data;
255 g_resolv_set_debug(web->resolv, func, user_data);
258 gboolean g_web_set_proxy(GWeb *web, const char *proxy)
267 debug(web, "clearing proxy");
269 web->proxy = g_strdup(proxy);
270 debug(web, "setting proxy %s", web->proxy);
276 gboolean g_web_add_nameserver(GWeb *web, const char *address)
281 g_resolv_add_nameserver(web->resolv, address, 53, 0);
286 static gboolean set_accept_option(GWeb *web, const char *format, va_list args)
288 g_free(web->accept_option);
290 if (format == NULL) {
291 web->accept_option = NULL;
292 debug(web, "clearing accept option");
294 web->accept_option = g_strdup_vprintf(format, args);
295 debug(web, "setting accept %s", web->accept_option);
301 gboolean g_web_set_accept(GWeb *web, const char *format, ...)
309 va_start(args, format);
310 result = set_accept_option(web, format, args);
316 static gboolean set_user_agent(GWeb *web, const char *format, va_list args)
318 g_free(web->user_agent);
320 if (format == NULL) {
321 web->user_agent = NULL;
322 debug(web, "clearing user agent");
324 web->user_agent = g_strdup_vprintf(format, args);
325 debug(web, "setting user agent %s", web->user_agent);
331 gboolean g_web_set_user_agent(GWeb *web, const char *format, ...)
339 va_start(args, format);
340 result = set_user_agent(web, format, args);
346 gboolean g_web_set_http_version(GWeb *web, const char *version)
351 g_free(web->http_version);
353 if (version == NULL) {
354 web->http_version = NULL;
355 debug(web, "clearing HTTP version");
357 web->http_version = g_strdup(version);
358 debug(web, "setting HTTP version %s", web->http_version);
364 void g_web_set_close_connection(GWeb *web, gboolean enabled)
369 web->close_connection = enabled;
372 gboolean g_web_get_close_connection(GWeb *web)
377 return web->close_connection;
380 static inline void call_result_func(struct web_session *session, guint16 status)
384 if (session->result_func == NULL)
388 session->result.status = status;
390 result = session->result_func(&session->result, session->user_data);
392 debug(session->web, "[result function] %s",
393 result == TRUE ? "continue" : "stop");
396 static gboolean process_send_buffer(struct web_session *session)
398 GString *buf = session->send_buffer;
399 gsize count, bytes_written;
405 if (session->request_started == TRUE &&
406 session->more_data == FALSE)
407 session->body_done = TRUE;
412 debug(session->web, "bytes to write %zu", count);
414 status = g_io_channel_write_chars(session->transport_channel,
415 buf->str, count, &bytes_written, NULL);
417 debug(session->web, "status %u bytes written %zu",
418 status, bytes_written);
420 if (status != G_IO_STATUS_NORMAL && status != G_IO_STATUS_AGAIN)
423 g_string_erase(buf, 0, bytes_written);
428 static void process_next_chunk(struct web_session *session)
430 GString *buf = session->send_buffer;
434 if (session->input_func == NULL) {
435 session->more_data = FALSE;
439 session->more_data = session->input_func(&body, &length,
443 g_string_append_printf(buf, "%zx\r\n", length);
444 g_string_append_len(buf, (char *) body, length);
445 g_string_append(buf, "\r\n");
448 if (session->more_data == FALSE)
449 g_string_append(buf, "0\r\n\r\n");
452 static void start_request(struct web_session *session)
454 GString *buf = session->send_buffer;
459 debug(session->web, "request %s from %s",
460 session->request, session->host);
462 g_string_truncate(buf, 0);
464 if (session->web->http_version == NULL)
467 version = session->web->http_version;
469 if (session->content_type == NULL)
470 g_string_append_printf(buf, "GET %s HTTP/%s\r\n",
471 session->request, version);
473 g_string_append_printf(buf, "POST %s HTTP/%s\r\n",
474 session->request, version);
476 g_string_append_printf(buf, "Host: %s\r\n", session->host);
478 if (session->web->user_agent != NULL)
479 g_string_append_printf(buf, "User-Agent: %s\r\n",
480 session->web->user_agent);
482 if (session->web->accept_option != NULL)
483 g_string_append_printf(buf, "Accept: %s\r\n",
484 session->web->accept_option);
486 if (session->content_type != NULL) {
487 g_string_append_printf(buf, "Content-Type: %s\r\n",
488 session->content_type);
489 if (session->input_func == NULL) {
490 session->more_data = FALSE;
493 session->more_data = session->input_func(&body, &length,
495 if (session->more_data == FALSE)
496 g_string_append_printf(buf, "Content-Length: %zu\r\n",
499 g_string_append(buf, "Transfer-Encoding: chunked\r\n");
502 if (session->web->close_connection == TRUE)
503 g_string_append(buf, "Connection: close\r\n");
505 g_string_append(buf, "\r\n");
507 if (session->content_type != NULL && length > 0) {
508 if (session->more_data == TRUE) {
509 g_string_append_printf(buf, "%zx\r\n", length);
510 g_string_append_len(buf, (char *) body, length);
511 g_string_append(buf, "\r\n");
513 g_string_append_len(buf, (char *) body, length);
517 static gboolean send_data(GIOChannel *channel, GIOCondition cond,
520 struct web_session *session = user_data;
522 if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
523 session->send_watch = 0;
527 if (process_send_buffer(session) == TRUE)
530 if (session->request_started == FALSE) {
531 session->request_started = TRUE;
532 start_request(session);
533 } else if (session->more_data == TRUE)
534 process_next_chunk(session);
536 process_send_buffer(session);
538 if (session->body_done == TRUE) {
539 session->send_watch = 0;
546 static int decode_chunked(struct web_session *session,
547 const guint8 *buf, gsize len)
549 const guint8 *ptr = buf;
557 switch (session->chunck_state) {
559 pos = memchr(ptr, '\n', len);
561 g_string_append_len(session->current_header,
567 if (count < 1 || ptr[count - 1] != '\r')
570 g_string_append_len(session->current_header,
571 (gchar *) ptr, count);
576 str = session->current_header->str;
578 counter = strtoul(str, NULL, 16);
579 if ((counter == 0 && errno == EINVAL) ||
580 counter == ULONG_MAX)
583 session->chunk_size = counter;
584 session->chunk_left = counter;
586 session->chunck_state = CHUNK_DATA;
593 session->chunck_state = CHUNK_N_BODY;
600 session->chunck_state = CHUNK_SIZE;
603 if (session->chunk_size == 0) {
604 debug(session->web, "Download Done in chunk");
605 g_string_truncate(session->current_header, 0);
609 if (session->chunk_left <= len) {
610 session->result.buffer = ptr;
611 session->result.length = session->chunk_left;
612 call_result_func(session, 0);
614 len -= session->chunk_left;
615 ptr += session->chunk_left;
617 session->total_len += session->chunk_left;
618 session->chunk_left = 0;
620 g_string_truncate(session->current_header, 0);
621 session->chunck_state = CHUNK_R_BODY;
625 session->result.buffer = ptr;
626 session->result.length = len;
627 call_result_func(session, 0);
629 session->chunk_left -= len;
630 session->total_len += len;
641 static int handle_body(struct web_session *session,
642 const guint8 *buf, gsize len)
646 debug(session->web, "[body] length %zu", len);
648 if (session->result.use_chunk == FALSE) {
650 session->result.buffer = buf;
651 session->result.length = len;
652 call_result_func(session, 0);
657 err = decode_chunked(session, buf, len);
659 debug(session->web, "Error in chunk decode %d", err);
661 session->result.buffer = NULL;
662 session->result.length = 0;
663 call_result_func(session, 400);
669 static void handle_multi_line(struct web_session *session)
675 str = session->current_header->str;
677 if (str[0] != ' ' && str[0] != '\t')
680 while (str[0] == ' ' || str[0] == '\t')
683 count = str - session->current_header->str;
685 g_string_erase(session->current_header, 0, count);
686 g_string_insert_c(session->current_header, 0, ' ');
689 value = g_hash_table_lookup(session->result.headers,
690 session->result.last_key);
692 g_string_insert(session->current_header, 0, value);
694 str = session->current_header->str;
696 g_hash_table_replace(session->result.headers,
697 g_strdup(session->result.last_key),
702 static void add_header_field(struct web_session *session)
710 str = session->current_header->str;
712 pos = memchr(str, ':', session->current_header->len);
719 /* remove preceding white spaces */
723 count = (char *) pos - str;
725 g_string_erase(session->current_header, 0, count);
727 value = g_hash_table_lookup(session->result.headers, key);
729 g_string_insert_c(session->current_header, 0, ' ');
730 g_string_insert_c(session->current_header, 0, ';');
732 g_string_insert(session->current_header, 0, value);
735 str = session->current_header->str;
736 g_hash_table_replace(session->result.headers, key,
739 g_free(session->result.last_key);
740 session->result.last_key = g_strdup(key);
744 static gboolean received_data(GIOChannel *channel, GIOCondition cond,
747 struct web_session *session = user_data;
748 guint8 *ptr = session->receive_buffer;
752 if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
753 session->transport_watch = 0;
754 session->result.buffer = NULL;
755 session->result.length = 0;
756 call_result_func(session, 400);
760 status = g_io_channel_read_chars(channel,
761 (gchar *) session->receive_buffer,
762 session->receive_space - 1, &bytes_read, NULL);
764 debug(session->web, "bytes read %zu", bytes_read);
766 if (status != G_IO_STATUS_NORMAL && status != G_IO_STATUS_AGAIN) {
767 session->transport_watch = 0;
768 session->result.buffer = NULL;
769 session->result.length = 0;
770 call_result_func(session, 0);
774 session->receive_buffer[bytes_read] = '\0';
776 if (session->header_done == TRUE) {
777 if (handle_body(session, session->receive_buffer,
779 session->transport_watch = 0;
785 while (bytes_read > 0) {
790 pos = memchr(ptr, '\n', bytes_read);
792 g_string_append_len(session->current_header,
793 (gchar *) ptr, bytes_read);
798 count = strlen((char *) ptr);
799 if (count > 0 && ptr[count - 1] == '\r') {
804 g_string_append_len(session->current_header,
805 (gchar *) ptr, count);
807 bytes_read -= count + 1;
813 if (session->current_header->len == 0) {
816 session->header_done = TRUE;
818 val = g_hash_table_lookup(session->result.headers,
819 "Transfer-Encoding");
821 val = g_strrstr(val, "chunked");
823 session->result.use_chunk = TRUE;
825 session->chunck_state = CHUNK_SIZE;
826 session->chunk_left = 0;
827 session->total_len = 0;
831 if (handle_body(session, ptr, bytes_read) < 0) {
832 session->transport_watch = 0;
838 str = session->current_header->str;
840 if (session->result.status == 0) {
843 if (sscanf(str, "HTTP/%*s %u %*s", &code) == 1)
844 session->result.status = code;
847 debug(session->web, "[header] %s", str);
849 /* handle multi-line header */
850 if (str[0] == ' ' || str[0] == '\t')
851 handle_multi_line(session);
853 add_header_field(session);
855 g_string_truncate(session->current_header, 0);
861 static int connect_session_transport(struct web_session *session)
864 struct sockaddr_in sin;
867 sk = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
871 if (session->flags & SESSION_FLAG_USE_TLS) {
872 debug(session->web, "using TLS encryption");
873 session->transport_channel = g_io_channel_gnutls_new(sk);
875 debug(session->web, "no encryption");
876 session->transport_channel = g_io_channel_unix_new(sk);
879 if (session->transport_channel == NULL) {
884 flags = g_io_channel_get_flags(session->transport_channel);
885 g_io_channel_set_flags(session->transport_channel,
886 flags | G_IO_FLAG_NONBLOCK, NULL);
888 g_io_channel_set_encoding(session->transport_channel, NULL, NULL);
889 g_io_channel_set_buffered(session->transport_channel, FALSE);
891 g_io_channel_set_close_on_unref(session->transport_channel, TRUE);
893 memset(&sin, 0, sizeof(sin));
894 sin.sin_family = AF_INET;
895 sin.sin_port = htons(session->port);
896 sin.sin_addr.s_addr = inet_addr(session->address);
898 if (connect(sk, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
899 if (errno != EINPROGRESS) {
905 session->transport_watch = g_io_add_watch(session->transport_channel,
906 G_IO_IN | G_IO_HUP | G_IO_NVAL | G_IO_ERR,
907 received_data, session);
909 session->send_watch = g_io_add_watch(session->transport_channel,
910 G_IO_OUT | G_IO_HUP | G_IO_NVAL | G_IO_ERR,
916 static int create_transport(struct web_session *session)
920 err = connect_session_transport(session);
924 debug(session->web, "creating session %s:%u",
925 session->address, session->port);
930 static int parse_url(struct web_session *session,
931 const char *url, const char *proxy)
933 char *scheme, *host, *port, *path;
935 scheme = g_strdup(url);
939 host = strstr(scheme, "://");
944 if (strcasecmp(scheme, "https") == 0) {
946 session->flags |= SESSION_FLAG_USE_TLS;
947 } else if (strcasecmp(scheme, "http") == 0) {
958 path = strchr(host, '/');
963 session->request = g_strdup_printf("/%s", path ? path : "");
965 session->request = g_strdup(url);
967 port = strrchr(host, ':');
970 int tmp = strtol(port + 1, &end, 10);
978 session->host = g_strdup(host);
980 session->host = g_strdup_printf("%s:%u", host, tmp);
982 session->host = g_strdup(host);
989 scheme = g_strdup(proxy);
993 host = strstr(proxy, "://");
998 if (strcasecmp(scheme, "http") != 0) {
1005 path = strchr(host, '/');
1009 port = strrchr(host, ':');
1012 int tmp = strtol(port + 1, &end, 10);
1016 session->port = tmp;
1020 session->address = g_strdup(host);
1027 static void resolv_result(GResolvResultStatus status,
1028 char **results, gpointer user_data)
1030 struct web_session *session = user_data;
1032 if (results == NULL || results[0] == NULL) {
1033 call_result_func(session, 404);
1037 debug(session->web, "address %s", results[0]);
1039 if (inet_aton(results[0], NULL) == 0) {
1040 call_result_func(session, 400);
1044 session->address = g_strdup(results[0]);
1046 if (create_transport(session) < 0) {
1047 call_result_func(session, 409);
1052 static guint do_request(GWeb *web, const char *url,
1053 const char *type, GWebInputFunc input,
1054 GWebResultFunc func, gpointer user_data)
1056 struct web_session *session;
1058 if (web == NULL || url == NULL)
1061 debug(web, "request %s", url);
1063 session = g_try_new0(struct web_session, 1);
1064 if (session == NULL)
1067 if (parse_url(session, url, web->proxy) < 0) {
1068 free_session(session);
1072 debug(web, "address %s", session->address);
1073 debug(web, "port %u", session->port);
1074 debug(web, "host %s", session->host);
1075 debug(web, "flags %lu", session->flags);
1076 debug(web, "request %s", session->request);
1079 session->content_type = g_strdup(type);
1081 debug(web, "content-type %s", session->content_type);
1086 session->result_func = func;
1087 session->input_func = input;
1088 session->user_data = user_data;
1090 session->receive_buffer = g_try_malloc(DEFAULT_BUFFER_SIZE);
1091 if (session->receive_buffer == NULL) {
1092 free_session(session);
1096 session->result.headers = g_hash_table_new_full(g_str_hash, g_str_equal,
1098 if (session->result.headers == NULL) {
1099 free_session(session);
1103 session->receive_space = DEFAULT_BUFFER_SIZE;
1104 session->send_buffer = g_string_sized_new(0);
1105 session->current_header = g_string_sized_new(0);
1106 session->header_done = FALSE;
1107 session->body_done = FALSE;
1109 if (session->address == NULL && inet_aton(session->host, NULL) == 0) {
1110 session->resolv_action = g_resolv_lookup_hostname(web->resolv,
1111 session->host, resolv_result, session);
1112 if (session->resolv_action == 0) {
1113 free_session(session);
1117 if (session->address == NULL)
1118 session->address = g_strdup(session->host);
1120 if (create_transport(session) < 0) {
1121 free_session(session);
1126 web->session_list = g_list_append(web->session_list, session);
1128 return web->next_query_id++;
1131 guint g_web_request_get(GWeb *web, const char *url,
1132 GWebResultFunc func, gpointer user_data)
1134 return do_request(web, url, NULL, NULL, func, user_data);
1137 guint g_web_request_post(GWeb *web, const char *url,
1138 const char *type, GWebInputFunc input,
1139 GWebResultFunc func, gpointer user_data)
1141 return do_request(web, url, type, input, func, user_data);
1144 gboolean g_web_cancel_request(GWeb *web, guint id)
1152 guint16 g_web_result_get_status(GWebResult *result)
1157 return result->status;
1160 gboolean g_web_result_get_chunk(GWebResult *result,
1161 const guint8 **chunk, gsize *length)
1169 *chunk = result->buffer;
1172 *length = result->length;
1177 gboolean g_web_result_get_header(GWebResult *result,
1178 const char *header, const char **value)
1186 *value = g_hash_table_lookup(result->headers, header);
1194 struct _GWebParser {
1198 const char *token_str;
1203 GWebParserFunc func;
1207 GWebParser *g_web_parser_new(const char *begin, const char *end,
1208 GWebParserFunc func, gpointer user_data)
1212 parser = g_try_new0(GWebParser, 1);
1216 parser->ref_count = 1;
1218 parser->begin_token = g_strdup(begin);
1219 parser->end_token = g_strdup(end);
1221 if (parser->begin_token == NULL) {
1226 parser->func = func;
1227 parser->user_data = user_data;
1229 parser->token_str = parser->begin_token;
1230 parser->token_len = strlen(parser->token_str);
1231 parser->token_pos = 0;
1233 parser->intoken = FALSE;
1234 parser->content = g_string_sized_new(0);
1239 GWebParser *g_web_parser_ref(GWebParser *parser)
1244 g_atomic_int_inc(&parser->ref_count);
1249 void g_web_parser_unref(GWebParser *parser)
1254 if (g_atomic_int_dec_and_test(&parser->ref_count) == FALSE)
1257 g_string_free(parser->content, TRUE);
1259 g_free(parser->begin_token);
1260 g_free(parser->end_token);
1264 void g_web_parser_feed_data(GWebParser *parser,
1265 const guint8 *data, gsize length)
1267 const guint8 *ptr = data;
1272 while (length > 0) {
1273 guint8 chr = parser->token_str[parser->token_pos];
1275 if (parser->token_pos == 0) {
1278 pos = memchr(ptr, chr, length);
1280 if (parser->intoken == TRUE)
1281 g_string_append_len(parser->content,
1282 (gchar *) ptr, length);
1286 if (parser->intoken == TRUE)
1287 g_string_append_len(parser->content,
1288 (gchar *) ptr, (pos - ptr) + 1);
1290 length -= (pos - ptr) + 1;
1293 parser->token_pos++;
1297 if (parser->intoken == TRUE)
1298 g_string_append_c(parser->content, ptr[0]);
1300 if (ptr[0] != chr) {
1304 parser->token_pos = 0;
1311 parser->token_pos++;
1313 if (parser->token_pos == parser->token_len) {
1314 if (parser->intoken == FALSE) {
1315 g_string_append(parser->content,
1318 parser->intoken = TRUE;
1319 parser->token_str = parser->end_token;
1320 parser->token_len = strlen(parser->end_token);
1321 parser->token_pos = 0;
1324 str = g_string_free(parser->content, FALSE);
1325 parser->content = g_string_sized_new(0);
1327 parser->func(str, parser->user_data);
1330 parser->intoken = FALSE;
1331 parser->token_str = parser->begin_token;
1332 parser->token_len = strlen(parser->begin_token);
1333 parser->token_pos = 0;
1339 void g_web_parser_end_data(GWebParser *parser)