gweb: Make debug for write simpler
[platform/upstream/connman.git] / gweb / gweb.c
1 /*
2  *
3  *  Web service library with GLib integration
4  *
5  *  Copyright (C) 2009-2010  Intel Corporation. All rights reserved.
6  *
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.
10  *
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.
15  *
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
19  *
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <stdio.h>
27 #include <errno.h>
28 #include <unistd.h>
29 #include <stdlib.h>
30 #include <stdarg.h>
31 #include <string.h>
32 #include <sys/socket.h>
33 #include <arpa/inet.h>
34 #include <netdb.h>
35
36 #include "giognutls.h"
37 #include "gresolv.h"
38 #include "gweb.h"
39
40 #define DEFAULT_BUFFER_SIZE  2048
41
42 #define SESSION_FLAG_USE_TLS    (1 << 0)
43
44 enum chunk_state {
45         CHUNK_SIZE,
46         CHUNK_R_BODY,
47         CHUNK_N_BODY,
48         CHUNK_DATA,
49 };
50
51 struct _GWebResult {
52         guint16 status;
53         const guint8 *buffer;
54         gsize length;
55         gboolean use_chunk;
56         gchar *last_key;
57         GHashTable *headers;
58 };
59
60 struct web_session {
61         GWeb *web;
62
63         char *address;
64         char *host;
65         uint16_t port;
66         unsigned long flags;
67         struct addrinfo *addr;
68
69         char *content_type;
70
71         GIOChannel *transport_channel;
72         guint transport_watch;
73         guint send_watch;
74
75         guint resolv_action;
76         char *request;
77
78         guint8 *receive_buffer;
79         gsize receive_space;
80         GString *send_buffer;
81         GString *current_header;
82         gboolean header_done;
83         gboolean body_done;
84         gboolean more_data;
85         gboolean request_started;
86
87         enum chunk_state chunck_state;
88         gsize chunk_size;
89         gsize chunk_left;
90         gsize total_len;
91
92         GWebResult result;
93
94         GWebResultFunc result_func;
95         GWebInputFunc input_func;
96         gpointer user_data;
97 };
98
99 struct _GWeb {
100         gint ref_count;
101
102         guint next_query_id;
103
104         int index;
105         GList *session_list;
106
107         GResolv *resolv;
108         char *proxy;
109         char *accept_option;
110         char *user_agent;
111         char *user_agent_profile;
112         char *http_version;
113         gboolean close_connection;
114
115         GWebDebugFunc debug_func;
116         gpointer debug_data;
117 };
118
119 static inline void debug(GWeb *web, const char *format, ...)
120 {
121         char str[256];
122         va_list ap;
123
124         if (web->debug_func == NULL)
125                 return;
126
127         va_start(ap, format);
128
129         if (vsnprintf(str, sizeof(str), format, ap) > 0)
130                 web->debug_func(str, web->debug_data);
131
132         va_end(ap);
133 }
134
135 static void free_session(struct web_session *session)
136 {
137         GWeb *web = session->web;
138
139         if (session == NULL)
140                 return;
141
142         g_free(session->request);
143
144         if (session->resolv_action > 0)
145                 g_resolv_cancel_lookup(web->resolv, session->resolv_action);
146
147         if (session->transport_watch > 0)
148                 g_source_remove(session->transport_watch);
149
150         if (session->send_watch > 0)
151                 g_source_remove(session->send_watch);
152
153         if (session->transport_channel != NULL)
154                 g_io_channel_unref(session->transport_channel);
155
156         g_free(session->result.last_key);
157
158         if (session->result.headers != NULL)
159                 g_hash_table_destroy(session->result.headers);
160
161         if (session->send_buffer != NULL)
162                 g_string_free(session->send_buffer, TRUE);
163
164         if (session->current_header != NULL)
165                 g_string_free(session->current_header, TRUE);
166
167         g_free(session->receive_buffer);
168
169         g_free(session->content_type);
170
171         g_free(session->host);
172         g_free(session->address);
173         if (session->addr != NULL)
174                 freeaddrinfo(session->addr);
175
176         g_free(session);
177 }
178
179 static void flush_sessions(GWeb *web)
180 {
181         GList *list;
182
183         for (list = g_list_first(web->session_list);
184                                         list; list = g_list_next(list))
185                 free_session(list->data);
186
187         g_list_free(web->session_list);
188         web->session_list = NULL;
189 }
190
191 GWeb *g_web_new(int index)
192 {
193         GWeb *web;
194
195         if (index < 0)
196                 return NULL;
197
198         web = g_try_new0(GWeb, 1);
199         if (web == NULL)
200                 return NULL;
201
202         web->ref_count = 1;
203
204         web->next_query_id = 1;
205
206         web->index = index;
207         web->session_list = NULL;
208
209         web->resolv = g_resolv_new(index);
210         if (web->resolv == NULL) {
211                 g_free(web);
212                 return NULL;
213         }
214
215         web->accept_option = g_strdup("*/*");
216         web->user_agent = g_strdup_printf("GWeb/%s", VERSION);
217         web->close_connection = FALSE;
218
219         return web;
220 }
221
222 GWeb *g_web_ref(GWeb *web)
223 {
224         if (web == NULL)
225                 return NULL;
226
227         g_atomic_int_inc(&web->ref_count);
228
229         return web;
230 }
231
232 void g_web_unref(GWeb *web)
233 {
234         if (web == NULL)
235                 return;
236
237         if (g_atomic_int_dec_and_test(&web->ref_count) == FALSE)
238                 return;
239
240         flush_sessions(web);
241
242         g_resolv_unref(web->resolv);
243
244         g_free(web->proxy);
245
246         g_free(web->accept_option);
247         g_free(web->user_agent);
248         g_free(web->user_agent_profile);
249         g_free(web->http_version);
250
251         g_free(web);
252 }
253
254 void g_web_set_debug(GWeb *web, GWebDebugFunc func, gpointer user_data)
255 {
256         if (web == NULL)
257                 return;
258
259         web->debug_func = func;
260         web->debug_data = user_data;
261
262         g_resolv_set_debug(web->resolv, func, user_data);
263 }
264
265 gboolean g_web_set_proxy(GWeb *web, const char *proxy)
266 {
267         if (web == NULL)
268                 return FALSE;
269
270         g_free(web->proxy);
271
272         if (proxy == NULL) {
273                 web->proxy = NULL;
274                 debug(web, "clearing proxy");
275         } else {
276                 web->proxy = g_strdup(proxy);
277                 debug(web, "setting proxy %s", web->proxy);
278         }
279
280         return TRUE;
281 }
282
283 gboolean g_web_add_nameserver(GWeb *web, const char *address)
284 {
285         if (web == NULL)
286                 return FALSE;
287
288         g_resolv_add_nameserver(web->resolv, address, 53, 0);
289
290         return TRUE;
291 }
292
293 static gboolean set_accept_option(GWeb *web, const char *format, va_list args)
294 {
295         g_free(web->accept_option);
296
297         if (format == NULL) {
298                 web->accept_option = NULL;
299                 debug(web, "clearing accept option");
300         } else {
301                 web->accept_option = g_strdup_vprintf(format, args);
302                 debug(web, "setting accept %s", web->accept_option);
303         }
304
305         return TRUE;
306 }
307
308 gboolean g_web_set_accept(GWeb *web, const char *format, ...)
309 {
310         va_list args;
311         gboolean result;
312
313         if (web == NULL)
314                 return FALSE;
315
316         va_start(args, format);
317         result = set_accept_option(web, format, args);
318         va_end(args);
319
320         return result;
321 }
322
323 static gboolean set_user_agent(GWeb *web, const char *format, va_list args)
324 {
325         g_free(web->user_agent);
326
327         if (format == NULL) {
328                 web->user_agent = NULL;
329                 debug(web, "clearing user agent");
330         } else {
331                 web->user_agent = g_strdup_vprintf(format, args);
332                 debug(web, "setting user agent %s", web->user_agent);
333         }
334
335         return TRUE;
336 }
337
338 gboolean g_web_set_user_agent(GWeb *web, const char *format, ...)
339 {
340         va_list args;
341         gboolean result;
342
343         if (web == NULL)
344                 return FALSE;
345
346         va_start(args, format);
347         result = set_user_agent(web, format, args);
348         va_end(args);
349
350         return result;
351 }
352
353 gboolean g_web_set_ua_profile(GWeb *web, const char *profile)
354 {
355         if (web == NULL)
356                 return FALSE;
357
358         g_free(web->user_agent_profile);
359
360         web->user_agent_profile = g_strdup(profile);
361         debug(web, "setting user agent profile %s", web->user_agent);
362
363         return TRUE;
364 }
365
366 gboolean g_web_set_http_version(GWeb *web, const char *version)
367 {
368         if (web == NULL)
369                 return FALSE;
370
371         g_free(web->http_version);
372
373         if (version == NULL) {
374                 web->http_version = NULL;
375                 debug(web, "clearing HTTP version");
376         } else {
377                 web->http_version = g_strdup(version);
378                 debug(web, "setting HTTP version %s", web->http_version);
379         }
380
381         return TRUE;
382 }
383
384 void g_web_set_close_connection(GWeb *web, gboolean enabled)
385 {
386         if (web == NULL)
387                 return;
388
389         web->close_connection = enabled;
390 }
391
392 gboolean g_web_get_close_connection(GWeb *web)
393 {
394         if (web == NULL)
395                 return FALSE;
396
397         return web->close_connection;
398 }
399
400 static inline void call_result_func(struct web_session *session, guint16 status)
401 {
402         gboolean result;
403
404         if (session->result_func == NULL)
405                 return;
406
407         if (status != 0)
408                 session->result.status = status;
409
410         result = session->result_func(&session->result, session->user_data);
411
412         debug(session->web, "[result function] %s",
413                                         result == TRUE ? "continue" : "stop");
414 }
415
416 static gboolean process_send_buffer(struct web_session *session)
417 {
418         GString *buf = session->send_buffer;
419         gsize count, bytes_written;
420         GIOStatus status;
421
422         count = buf->len;
423
424         if (count == 0) {
425                 if (session->request_started == TRUE &&
426                                         session->more_data == FALSE)
427                         session->body_done = TRUE;
428
429                 return FALSE;
430         }
431
432         status = g_io_channel_write_chars(session->transport_channel,
433                                         buf->str, count, &bytes_written, NULL);
434
435         debug(session->web, "status %u bytes to write %zu bytes written %zu",
436                                         status, count, bytes_written);
437
438         if (status != G_IO_STATUS_NORMAL && status != G_IO_STATUS_AGAIN)
439                 return FALSE;
440
441         g_string_erase(buf, 0, bytes_written);
442
443         return TRUE;
444 }
445
446 static void process_next_chunk(struct web_session *session)
447 {
448         GString *buf = session->send_buffer;
449         const guint8 *body;
450         gsize length;
451
452         if (session->input_func == NULL) {
453                 session->more_data = FALSE;
454                 return;
455         }
456
457         session->more_data = session->input_func(&body, &length,
458                                                 session->user_data);
459
460         if (length > 0) {
461                 g_string_append_printf(buf, "%zx\r\n", length);
462                 g_string_append_len(buf, (char *) body, length);
463                 g_string_append(buf, "\r\n");
464         }
465
466         if (session->more_data == FALSE)
467                 g_string_append(buf, "0\r\n\r\n");
468 }
469
470 static void start_request(struct web_session *session)
471 {
472         GString *buf = session->send_buffer;
473         const char *version;
474         const guint8 *body;
475         gsize length;
476
477         debug(session->web, "request %s from %s",
478                                         session->request, session->host);
479
480         g_string_truncate(buf, 0);
481
482         if (session->web->http_version == NULL)
483                 version = "1.1";
484         else
485                 version = session->web->http_version;
486
487         if (session->content_type == NULL)
488                 g_string_append_printf(buf, "GET %s HTTP/%s\r\n",
489                                                 session->request, version);
490         else
491                 g_string_append_printf(buf, "POST %s HTTP/%s\r\n",
492                                                 session->request, version);
493
494         g_string_append_printf(buf, "Host: %s\r\n", session->host);
495
496         if (session->web->user_agent != NULL)
497                 g_string_append_printf(buf, "User-Agent: %s\r\n",
498                                                 session->web->user_agent);
499
500         if (session->web->user_agent_profile != NULL) {
501                 g_string_append_printf(buf, "x-wap-profile: %s\r\n",
502                                        session->web->user_agent_profile);
503         }
504
505         if (session->web->accept_option != NULL)
506                 g_string_append_printf(buf, "Accept: %s\r\n",
507                                                 session->web->accept_option);
508
509         if (session->content_type != NULL) {
510                 g_string_append_printf(buf, "Content-Type: %s\r\n",
511                                                         session->content_type);
512                 if (session->input_func == NULL) {
513                         session->more_data = FALSE;
514                         length = 0;
515                 } else
516                         session->more_data = session->input_func(&body, &length,
517                                                         session->user_data);
518                 if (session->more_data == FALSE)
519                         g_string_append_printf(buf, "Content-Length: %zu\r\n",
520                                                                         length);
521                 else
522                         g_string_append(buf, "Transfer-Encoding: chunked\r\n");
523         }
524
525         if (session->web->close_connection == TRUE)
526                 g_string_append(buf, "Connection: close\r\n");
527
528         g_string_append(buf, "\r\n");
529
530         if (session->content_type != NULL && length > 0) {
531                 if (session->more_data == TRUE) {
532                         g_string_append_printf(buf, "%zx\r\n", length);
533                         g_string_append_len(buf, (char *) body, length);
534                         g_string_append(buf, "\r\n");
535                 } else
536                         g_string_append_len(buf, (char *) body, length);
537         }
538 }
539
540 static gboolean send_data(GIOChannel *channel, GIOCondition cond,
541                                                 gpointer user_data)
542 {
543         struct web_session *session = user_data;
544
545         if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
546                 session->send_watch = 0;
547                 return FALSE;
548         }
549
550         if (process_send_buffer(session) == TRUE)
551                 return TRUE;
552
553         if (session->request_started == FALSE) {
554                 session->request_started = TRUE;
555                 start_request(session);
556         } else if (session->more_data == TRUE)
557                 process_next_chunk(session);
558
559         process_send_buffer(session);
560
561         if (session->body_done == TRUE) {
562                 session->send_watch = 0;
563                 return FALSE;
564         }
565
566         return TRUE;
567 }
568
569 static int decode_chunked(struct web_session *session,
570                                         const guint8 *buf, gsize len)
571 {
572         const guint8 *ptr = buf;
573         gsize counter;
574
575         while (len > 0) {
576                 guint8 *pos;
577                 gsize count;
578                 char *str;
579
580                 switch (session->chunck_state) {
581                 case CHUNK_SIZE:
582                         pos = memchr(ptr, '\n', len);
583                         if (pos == NULL) {
584                                 g_string_append_len(session->current_header,
585                                                 (gchar *) ptr, len);
586                                 return 0;
587                         }
588
589                         count = pos - ptr;
590                         if (count < 1 || ptr[count - 1] != '\r')
591                                 return -EILSEQ;
592
593                         g_string_append_len(session->current_header,
594                                                 (gchar *) ptr, count);
595
596                         len -= count + 1;
597                         ptr = pos + 1;
598
599                         str = session->current_header->str;
600
601                         counter = strtoul(str, NULL, 16);
602                         if ((counter == 0 && errno == EINVAL) ||
603                                                 counter == ULONG_MAX)
604                                 return -EILSEQ;
605
606                         session->chunk_size = counter;
607                         session->chunk_left = counter;
608
609                         session->chunck_state = CHUNK_DATA;
610                         break;
611                 case CHUNK_R_BODY:
612                         if (*ptr != '\r')
613                                 return -EILSEQ;
614                         ptr++;
615                         len--;
616                         session->chunck_state = CHUNK_N_BODY;
617                         break;
618                 case CHUNK_N_BODY:
619                         if (*ptr != '\n')
620                                 return -EILSEQ;
621                         ptr++;
622                         len--;
623                         session->chunck_state = CHUNK_SIZE;
624                         break;
625                 case CHUNK_DATA:
626                         if (session->chunk_size == 0) {
627                                 debug(session->web, "Download Done in chunk");
628                                 g_string_truncate(session->current_header, 0);
629                                 return 0;
630                         }
631
632                         if (session->chunk_left <= len) {
633                                 session->result.buffer = ptr;
634                                 session->result.length = session->chunk_left;
635                                 call_result_func(session, 0);
636
637                                 len -= session->chunk_left;
638                                 ptr += session->chunk_left;
639
640                                 session->total_len += session->chunk_left;
641                                 session->chunk_left = 0;
642
643                                 g_string_truncate(session->current_header, 0);
644                                 session->chunck_state = CHUNK_R_BODY;
645                                 break;
646                         }
647                         /* more data */
648                         session->result.buffer = ptr;
649                         session->result.length = len;
650                         call_result_func(session, 0);
651
652                         session->chunk_left -= len;
653                         session->total_len += len;
654
655                         len -= len;
656                         ptr += len;
657                         break;
658                 }
659         }
660
661         return 0;
662 }
663
664 static int handle_body(struct web_session *session,
665                                 const guint8 *buf, gsize len)
666 {
667         int err;
668
669         debug(session->web, "[body] length %zu", len);
670
671         if (session->result.use_chunk == FALSE) {
672                 if (len > 0) {
673                         session->result.buffer = buf;
674                         session->result.length = len;
675                         call_result_func(session, 0);
676                 }
677                 return 0;
678         }
679
680         err = decode_chunked(session, buf, len);
681         if (err < 0) {
682                 debug(session->web, "Error in chunk decode %d", err);
683
684                 session->result.buffer = NULL;
685                 session->result.length = 0;
686                 call_result_func(session, 400);
687         }
688
689         return err;
690 }
691
692 static void handle_multi_line(struct web_session *session)
693 {
694         gsize count;
695         char *str;
696         gchar *value;
697
698         str = session->current_header->str;
699
700         if (str[0] != ' ' && str[0] != '\t')
701                 return;
702
703         while (str[0] == ' ' || str[0] == '\t')
704                 str++;
705
706         count = str - session->current_header->str;
707         if (count > 0) {
708                 g_string_erase(session->current_header, 0, count);
709                 g_string_insert_c(session->current_header, 0, ' ');
710         }
711
712         value = g_hash_table_lookup(session->result.headers,
713                                         session->result.last_key);
714         if (value != NULL) {
715                 g_string_insert(session->current_header, 0, value);
716
717                 str = session->current_header->str;
718
719                 g_hash_table_replace(session->result.headers,
720                                         g_strdup(session->result.last_key),
721                                         g_strdup(str));
722         }
723 }
724
725 static void add_header_field(struct web_session *session)
726 {
727         gsize count;
728         guint8 *pos;
729         char *str;
730         gchar *value;
731         gchar *key;
732
733         str = session->current_header->str;
734
735         pos = memchr(str, ':', session->current_header->len);
736         if (pos != NULL) {
737                 *pos = '\0';
738                 pos++;
739
740                 key = g_strdup(str);
741
742                 /* remove preceding white spaces */
743                 while (*pos == ' ')
744                         pos++;
745
746                 count = (char *) pos - str;
747
748                 g_string_erase(session->current_header, 0, count);
749
750                 value = g_hash_table_lookup(session->result.headers, key);
751                 if (value != NULL) {
752                         g_string_insert_c(session->current_header, 0, ' ');
753                         g_string_insert_c(session->current_header, 0, ';');
754
755                         g_string_insert(session->current_header, 0, value);
756                 }
757
758                 str = session->current_header->str;
759                 g_hash_table_replace(session->result.headers, key,
760                                                         g_strdup(str));
761
762                 g_free(session->result.last_key);
763                 session->result.last_key = g_strdup(key);
764         }
765 }
766
767 static gboolean received_data(GIOChannel *channel, GIOCondition cond,
768                                                         gpointer user_data)
769 {
770         struct web_session *session = user_data;
771         guint8 *ptr = session->receive_buffer;
772         gsize bytes_read;
773         GIOStatus status;
774
775         if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
776                 session->transport_watch = 0;
777                 session->result.buffer = NULL;
778                 session->result.length = 0;
779                 call_result_func(session, 400);
780                 return FALSE;
781         }
782
783         status = g_io_channel_read_chars(channel,
784                                 (gchar *) session->receive_buffer,
785                                 session->receive_space - 1, &bytes_read, NULL);
786
787         debug(session->web, "bytes read %zu", bytes_read);
788
789         if (status != G_IO_STATUS_NORMAL && status != G_IO_STATUS_AGAIN) {
790                 session->transport_watch = 0;
791                 session->result.buffer = NULL;
792                 session->result.length = 0;
793                 call_result_func(session, 0);
794                 return FALSE;
795         }
796
797         session->receive_buffer[bytes_read] = '\0';
798
799         if (session->header_done == TRUE) {
800                 if (handle_body(session, session->receive_buffer,
801                                                         bytes_read) < 0) {
802                         session->transport_watch = 0;
803                         return FALSE;
804                 }
805                 return TRUE;
806         }
807
808         while (bytes_read > 0) {
809                 guint8 *pos;
810                 gsize count;
811                 char *str;
812
813                 pos = memchr(ptr, '\n', bytes_read);
814                 if (pos == NULL) {
815                         g_string_append_len(session->current_header,
816                                                 (gchar *) ptr, bytes_read);
817                         return TRUE;
818                 }
819
820                 *pos = '\0';
821                 count = strlen((char *) ptr);
822                 if (count > 0 && ptr[count - 1] == '\r') {
823                         ptr[--count] = '\0';
824                         bytes_read--;
825                 }
826
827                 g_string_append_len(session->current_header,
828                                                 (gchar *) ptr, count);
829
830                 bytes_read -= count + 1;
831                 if (bytes_read > 0)
832                         ptr = pos + 1;
833                 else
834                         ptr = NULL;
835
836                 if (session->current_header->len == 0) {
837                         char *val;
838
839                         session->header_done = TRUE;
840
841                         val = g_hash_table_lookup(session->result.headers,
842                                                         "Transfer-Encoding");
843                         if (val != NULL) {
844                                 val = g_strrstr(val, "chunked");
845                                 if (val != NULL) {
846                                         session->result.use_chunk = TRUE;
847
848                                         session->chunck_state = CHUNK_SIZE;
849                                         session->chunk_left = 0;
850                                         session->total_len = 0;
851                                 }
852                         }
853
854                         if (handle_body(session, ptr, bytes_read) < 0) {
855                                 session->transport_watch = 0;
856                                 return FALSE;
857                         }
858                         break;
859                 }
860
861                 str = session->current_header->str;
862
863                 if (session->result.status == 0) {
864                         unsigned int code;
865
866                         if (sscanf(str, "HTTP/%*s %u %*s", &code) == 1)
867                                 session->result.status = code;
868                 }
869
870                 debug(session->web, "[header] %s", str);
871
872                 /* handle multi-line header */
873                 if (str[0] == ' ' || str[0] == '\t')
874                         handle_multi_line(session);
875                 else
876                         add_header_field(session);
877
878                 g_string_truncate(session->current_header, 0);
879         }
880
881         return TRUE;
882 }
883
884 static int connect_session_transport(struct web_session *session)
885 {
886         GIOFlags flags;
887         int sk;
888
889         sk = socket(session->addr->ai_family, SOCK_STREAM, IPPROTO_TCP);
890         if (sk < 0)
891                 return -EIO;
892
893         if (session->flags & SESSION_FLAG_USE_TLS) {
894                 debug(session->web, "using TLS encryption");
895                 session->transport_channel = g_io_channel_gnutls_new(sk);
896         } else {
897                 debug(session->web, "no encryption");
898                 session->transport_channel = g_io_channel_unix_new(sk);
899         }
900
901         if (session->transport_channel == NULL) {
902                 close(sk);
903                 return -ENOMEM;
904         }
905
906         flags = g_io_channel_get_flags(session->transport_channel);
907         g_io_channel_set_flags(session->transport_channel,
908                                         flags | G_IO_FLAG_NONBLOCK, NULL);
909
910         g_io_channel_set_encoding(session->transport_channel, NULL, NULL);
911         g_io_channel_set_buffered(session->transport_channel, FALSE);
912
913         g_io_channel_set_close_on_unref(session->transport_channel, TRUE);
914
915         if (connect(sk, session->addr->ai_addr,
916                         session->addr->ai_addrlen) < 0) {
917                 if (errno != EINPROGRESS) {
918                         close(sk);
919                         return -EIO;
920                 }
921         }
922
923         session->transport_watch = g_io_add_watch(session->transport_channel,
924                                 G_IO_IN | G_IO_HUP | G_IO_NVAL | G_IO_ERR,
925                                                 received_data, session);
926
927         session->send_watch = g_io_add_watch(session->transport_channel,
928                                 G_IO_OUT | G_IO_HUP | G_IO_NVAL | G_IO_ERR,
929                                                 send_data, session);
930
931         return 0;
932 }
933
934 static int create_transport(struct web_session *session)
935 {
936         int err;
937
938         err = connect_session_transport(session);
939         if (err < 0)
940                 return err;
941
942         debug(session->web, "creating session %s:%u",
943                                         session->address, session->port);
944
945         return 0;
946 }
947
948 static int parse_url(struct web_session *session,
949                                 const char *url, const char *proxy)
950 {
951         char *scheme, *host, *port, *path;
952
953         scheme = g_strdup(url);
954         if (scheme == NULL)
955                 return -EINVAL;
956
957         host = strstr(scheme, "://");
958         if (host != NULL) {
959                 *host = '\0';
960                 host += 3;
961
962                 if (strcasecmp(scheme, "https") == 0) {
963                         session->port = 443;
964                         session->flags |= SESSION_FLAG_USE_TLS;
965                 } else if (strcasecmp(scheme, "http") == 0) {
966                         session->port = 80;
967                 } else {
968                         g_free(scheme);
969                         return -EINVAL;
970                 }
971         } else {
972                 host = scheme;
973                 session->port = 80;
974         }
975
976         path = strchr(host, '/');
977         if (path != NULL)
978                 *(path++) = '\0';
979
980         if (proxy == NULL)
981                 session->request = g_strdup_printf("/%s", path ? path : "");
982         else
983                 session->request = g_strdup(url);
984
985         port = strrchr(host, ':');
986         if (port != NULL) {
987                 char *end;
988                 int tmp = strtol(port + 1, &end, 10);
989
990                 if (*end == '\0') {
991                         *port = '\0';
992                         session->port = tmp;
993                 }
994
995                 if (proxy == NULL)
996                         session->host = g_strdup(host);
997                 else
998                         session->host = g_strdup_printf("%s:%u", host, tmp);
999         } else
1000                 session->host = g_strdup(host);
1001
1002         g_free(scheme);
1003
1004         if (proxy == NULL)
1005                 return 0;
1006
1007         scheme = g_strdup(proxy);
1008         if (scheme == NULL)
1009                 return -EINVAL;
1010
1011         host = strstr(proxy, "://");
1012         if (host != NULL) {
1013                 *host = '\0';
1014                 host += 3;
1015
1016                 if (strcasecmp(scheme, "http") != 0) {
1017                         g_free(scheme);
1018                         return -EINVAL;
1019                 }
1020         } else
1021                 host = scheme;
1022
1023         path = strchr(host, '/');
1024         if (path != NULL)
1025                 *(path++) = '\0';
1026
1027         port = strrchr(host, ':');
1028         if (port != NULL) {
1029                 char *end;
1030                 int tmp = strtol(port + 1, &end, 10);
1031
1032                 if (*end == '\0') {
1033                         *port = '\0';
1034                         session->port = tmp;
1035                 }
1036         }
1037
1038         session->address = g_strdup(host);
1039
1040         g_free(scheme);
1041
1042         return 0;
1043 }
1044
1045 static void resolv_result(GResolvResultStatus status,
1046                                         char **results, gpointer user_data)
1047 {
1048         struct web_session *session = user_data;
1049         struct addrinfo hints;
1050         char *port;
1051         int ret;
1052
1053         if (results == NULL || results[0] == NULL) {
1054                 call_result_func(session, 404);
1055                 return;
1056         }
1057
1058         debug(session->web, "address %s", results[0]);
1059
1060         memset(&hints, 0, sizeof(struct addrinfo));
1061         hints.ai_flags = AI_NUMERICHOST;
1062
1063         if (session->addr != NULL) {
1064                 freeaddrinfo(session->addr);
1065                 session->addr = NULL;
1066         }
1067
1068         port = g_strdup_printf("%u", session->port);
1069         ret = getaddrinfo(results[0], port, &hints, &session->addr);
1070         g_free(port);
1071         if (ret != 0 || session->addr == NULL) {
1072                 call_result_func(session, 400);
1073                 return;
1074         }
1075
1076         session->address = g_strdup(results[0]);
1077
1078         if (create_transport(session) < 0) {
1079                 call_result_func(session, 409);
1080                 return;
1081         }
1082 }
1083
1084 static guint do_request(GWeb *web, const char *url,
1085                                 const char *type, GWebInputFunc input,
1086                                 GWebResultFunc func, gpointer user_data)
1087 {
1088         struct web_session *session;
1089
1090         if (web == NULL || url == NULL)
1091                 return 0;
1092
1093         debug(web, "request %s", url);
1094
1095         session = g_try_new0(struct web_session, 1);
1096         if (session == NULL)
1097                 return 0;
1098
1099         if (parse_url(session, url, web->proxy) < 0) {
1100                 free_session(session);
1101                 return 0;
1102         }
1103
1104         debug(web, "address %s", session->address);
1105         debug(web, "port %u", session->port);
1106         debug(web, "host %s", session->host);
1107         debug(web, "flags %lu", session->flags);
1108         debug(web, "request %s", session->request);
1109
1110         if (type != NULL) {
1111                 session->content_type = g_strdup(type);
1112
1113                 debug(web, "content-type %s", session->content_type);
1114         }
1115
1116         session->web = web;
1117
1118         session->result_func = func;
1119         session->input_func = input;
1120         session->user_data = user_data;
1121
1122         session->receive_buffer = g_try_malloc(DEFAULT_BUFFER_SIZE);
1123         if (session->receive_buffer == NULL) {
1124                 free_session(session);
1125                 return 0;
1126         }
1127
1128         session->result.headers = g_hash_table_new_full(g_str_hash, g_str_equal,
1129                                                         g_free, g_free);
1130         if (session->result.headers == NULL) {
1131                 free_session(session);
1132                 return 0;
1133         }
1134
1135         session->receive_space = DEFAULT_BUFFER_SIZE;
1136         session->send_buffer = g_string_sized_new(0);
1137         session->current_header = g_string_sized_new(0);
1138         session->header_done = FALSE;
1139         session->body_done = FALSE;
1140
1141         if (session->address == NULL && inet_aton(session->host, NULL) == 0) {
1142                 session->resolv_action = g_resolv_lookup_hostname(web->resolv,
1143                                         session->host, resolv_result, session);
1144                 if (session->resolv_action == 0) {
1145                         free_session(session);
1146                         return 0;
1147                 }
1148         } else {
1149                 struct addrinfo hints;
1150                 char *port;
1151                 int ret;
1152
1153                 if (session->address == NULL)
1154                         session->address = g_strdup(session->host);
1155
1156                 memset(&hints, 0, sizeof(struct addrinfo));
1157                 hints.ai_flags = AI_NUMERICHOST;
1158
1159                 if (session->addr != NULL) {
1160                         freeaddrinfo(session->addr);
1161                         session->addr = NULL;
1162                 }
1163
1164                 port = g_strdup_printf("%u", session->port);
1165                 ret = getaddrinfo(session->address, port, &hints,
1166                                                         &session->addr);
1167                 g_free(port);
1168                 if (ret != 0 || session->addr == NULL) {
1169                         free_session(session);
1170                         return 0;
1171                 }
1172
1173                 if (create_transport(session) < 0) {
1174                         free_session(session);
1175                         return 0;
1176                 }
1177         }
1178
1179         web->session_list = g_list_append(web->session_list, session);
1180
1181         return web->next_query_id++;
1182 }
1183
1184 guint g_web_request_get(GWeb *web, const char *url,
1185                                 GWebResultFunc func, gpointer user_data)
1186 {
1187         return do_request(web, url, NULL, NULL, func, user_data);
1188 }
1189
1190 guint g_web_request_post(GWeb *web, const char *url,
1191                                 const char *type, GWebInputFunc input,
1192                                 GWebResultFunc func, gpointer user_data)
1193 {
1194         return do_request(web, url, type, input, func, user_data);
1195 }
1196
1197 gboolean g_web_cancel_request(GWeb *web, guint id)
1198 {
1199         if (web == NULL)
1200                 return FALSE;
1201
1202         return TRUE;
1203 }
1204
1205 guint16 g_web_result_get_status(GWebResult *result)
1206 {
1207         if (result == NULL)
1208                 return 0;
1209
1210         return result->status;
1211 }
1212
1213 gboolean g_web_result_get_chunk(GWebResult *result,
1214                                 const guint8 **chunk, gsize *length)
1215 {
1216         if (result == NULL)
1217                 return FALSE;
1218
1219         if (chunk == NULL)
1220                 return FALSE;
1221
1222         *chunk = result->buffer;
1223
1224         if (length != NULL)
1225                 *length = result->length;
1226
1227         return TRUE;
1228 }
1229
1230 gboolean g_web_result_get_header(GWebResult *result,
1231                                 const char *header, const char **value)
1232 {
1233         if (result == NULL)
1234                 return FALSE;
1235
1236         if (value == NULL)
1237                 return FALSE;
1238
1239         *value = g_hash_table_lookup(result->headers, header);
1240
1241         if (*value == NULL)
1242                 return FALSE;
1243
1244         return TRUE;
1245 }
1246
1247 struct _GWebParser {
1248         gint ref_count;
1249         char *begin_token;
1250         char *end_token;
1251         const char *token_str;
1252         size_t token_len;
1253         size_t token_pos;
1254         gboolean intoken;
1255         GString *content;
1256         GWebParserFunc func;
1257         gpointer user_data;
1258 };
1259
1260 GWebParser *g_web_parser_new(const char *begin, const char *end,
1261                                 GWebParserFunc func, gpointer user_data)
1262 {
1263         GWebParser *parser;
1264
1265         parser = g_try_new0(GWebParser, 1);
1266         if (parser == NULL)
1267                 return NULL;
1268
1269         parser->ref_count = 1;
1270
1271         parser->begin_token = g_strdup(begin);
1272         parser->end_token = g_strdup(end);
1273
1274         if (parser->begin_token == NULL) {
1275                 g_free(parser);
1276                 return NULL;
1277         }
1278
1279         parser->func = func;
1280         parser->user_data = user_data;
1281
1282         parser->token_str = parser->begin_token;
1283         parser->token_len = strlen(parser->token_str);
1284         parser->token_pos = 0;
1285
1286         parser->intoken = FALSE;
1287         parser->content = g_string_sized_new(0);
1288
1289         return parser;
1290 }
1291
1292 GWebParser *g_web_parser_ref(GWebParser *parser)
1293 {
1294         if (parser == NULL)
1295                 return NULL;
1296
1297         g_atomic_int_inc(&parser->ref_count);
1298
1299         return parser;
1300 }
1301
1302 void g_web_parser_unref(GWebParser *parser)
1303 {
1304         if (parser == NULL)
1305                 return;
1306
1307         if (g_atomic_int_dec_and_test(&parser->ref_count) == FALSE)
1308                 return;
1309
1310         g_string_free(parser->content, TRUE);
1311
1312         g_free(parser->begin_token);
1313         g_free(parser->end_token);
1314         g_free(parser);
1315 }
1316
1317 void g_web_parser_feed_data(GWebParser *parser,
1318                                 const guint8 *data, gsize length)
1319 {
1320         const guint8 *ptr = data;
1321
1322         if (parser == NULL)
1323                 return;
1324
1325         while (length > 0) {
1326                 guint8 chr = parser->token_str[parser->token_pos];
1327
1328                 if (parser->token_pos == 0) {
1329                         guint8 *pos;
1330
1331                         pos = memchr(ptr, chr, length);
1332                         if (pos == NULL) {
1333                                 if (parser->intoken == TRUE)
1334                                         g_string_append_len(parser->content,
1335                                                         (gchar *) ptr, length);
1336                                 break;
1337                         }
1338
1339                         if (parser->intoken == TRUE)
1340                                 g_string_append_len(parser->content,
1341                                                 (gchar *) ptr, (pos - ptr) + 1);
1342
1343                         length -= (pos - ptr) + 1;
1344                         ptr = pos + 1;
1345
1346                         parser->token_pos++;
1347                         continue;
1348                 }
1349
1350                 if (parser->intoken == TRUE)
1351                         g_string_append_c(parser->content, ptr[0]);
1352
1353                 if (ptr[0] != chr) {
1354                         length--;
1355                         ptr++;
1356
1357                         parser->token_pos = 0;
1358                         continue;
1359                 }
1360
1361                 length--;
1362                 ptr++;
1363
1364                 parser->token_pos++;
1365
1366                 if (parser->token_pos == parser->token_len) {
1367                         if (parser->intoken == FALSE) {
1368                                 g_string_append(parser->content,
1369                                                         parser->token_str);
1370
1371                                 parser->intoken = TRUE;
1372                                 parser->token_str = parser->end_token;
1373                                 parser->token_len = strlen(parser->end_token);
1374                                 parser->token_pos = 0;
1375                         } else {
1376                                 char *str;
1377                                 str = g_string_free(parser->content, FALSE);
1378                                 parser->content = g_string_sized_new(0);
1379                                 if (parser->func)
1380                                         parser->func(str, parser->user_data);
1381                                 g_free(str);
1382
1383                                 parser->intoken = FALSE;
1384                                 parser->token_str = parser->begin_token;
1385                                 parser->token_len = strlen(parser->begin_token);
1386                                 parser->token_pos = 0;
1387                         }
1388                 }
1389         }
1390 }
1391
1392 void g_web_parser_end_data(GWebParser *parser)
1393 {
1394         if (parser == NULL)
1395                 return;
1396 }