Add interface for setting proxy for GWeb sessions
[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
35 #include "giognutls.h"
36 #include "gresolv.h"
37 #include "gweb.h"
38
39 #define DEFAULT_BUFFER_SIZE  2048
40
41 #define SESSION_FLAG_USE_TLS    (1 << 0)
42
43 enum chunk_state {
44         CHUNK_SIZE,
45         CHUNK_R_BODY,
46         CHUNK_N_BODY,
47         CHUNK_DATA,
48 };
49
50 struct _GWebResult {
51         guint16 status;
52         const guint8 *buffer;
53         gsize length;
54         gboolean use_chunk;
55 };
56
57 struct web_session {
58         GWeb *web;
59
60         char *address;
61         char *host;
62         uint16_t port;
63         unsigned long flags;
64
65         char *content_type;
66
67         GIOChannel *transport_channel;
68         guint transport_watch;
69         guint send_watch;
70
71         guint resolv_action;
72         char *request;
73
74         guint8 *receive_buffer;
75         gsize receive_space;
76         GString *current_header;
77         gboolean header_done;
78         gboolean more_data;
79         gboolean request_started;
80
81         enum chunk_state chunck_state;
82         gsize chunk_size;
83         gsize chunk_left;
84         gsize total_len;
85
86         GWebResult result;
87
88         GWebResultFunc result_func;
89         GWebInputFunc input_func;
90         gpointer user_data;
91 };
92
93 struct _GWeb {
94         gint ref_count;
95
96         guint next_query_id;
97
98         int index;
99         GList *session_list;
100
101         GResolv *resolv;
102         char *proxy;
103         char *accept_option;
104         char *user_agent;
105         gboolean close_connection;
106
107         GWebDebugFunc debug_func;
108         gpointer debug_data;
109 };
110
111 static inline void debug(GWeb *web, const char *format, ...)
112 {
113         char str[256];
114         va_list ap;
115
116         if (web->debug_func == NULL)
117                 return;
118
119         va_start(ap, format);
120
121         if (vsnprintf(str, sizeof(str), format, ap) > 0)
122                 web->debug_func(str, web->debug_data);
123
124         va_end(ap);
125 }
126
127 static void free_session(struct web_session *session)
128 {
129         GWeb *web = session->web;
130
131         if (session == NULL)
132                 return;
133
134         g_free(session->request);
135
136         if (session->resolv_action > 0)
137                 g_resolv_cancel_lookup(web->resolv, session->resolv_action);
138
139         if (session->transport_watch > 0)
140                 g_source_remove(session->transport_watch);
141
142         if (session->send_watch > 0)
143                 g_source_remove(session->send_watch);
144
145         if (session->transport_channel != NULL)
146                 g_io_channel_unref(session->transport_channel);
147
148         g_string_free(session->current_header, TRUE);
149         g_free(session->receive_buffer);
150
151         g_free(session->content_type);
152
153         g_free(session->host);
154         g_free(session->address);
155         g_free(session);
156 }
157
158 static void flush_sessions(GWeb *web)
159 {
160         GList *list;
161
162         for (list = g_list_first(web->session_list);
163                                         list; list = g_list_next(list))
164                 free_session(list->data);
165
166         g_list_free(web->session_list);
167         web->session_list = NULL;
168 }
169
170 GWeb *g_web_new(int index)
171 {
172         GWeb *web;
173
174         if (index < 0)
175                 return NULL;
176
177         web = g_try_new0(GWeb, 1);
178         if (web == NULL)
179                 return NULL;
180
181         web->ref_count = 1;
182
183         web->next_query_id = 1;
184
185         web->index = index;
186         web->session_list = NULL;
187
188         web->resolv = g_resolv_new(index);
189         if (web->resolv == NULL) {
190                 g_free(web);
191                 return NULL;
192         }
193
194         web->accept_option = g_strdup("*/*");
195         web->user_agent = g_strdup_printf("GWeb/%s", VERSION);
196         web->close_connection = FALSE;
197
198         return web;
199 }
200
201 GWeb *g_web_ref(GWeb *web)
202 {
203         if (web == NULL)
204                 return NULL;
205
206         g_atomic_int_inc(&web->ref_count);
207
208         return web;
209 }
210
211 void g_web_unref(GWeb *web)
212 {
213         if (web == NULL)
214                 return;
215
216         if (g_atomic_int_dec_and_test(&web->ref_count) == FALSE)
217                 return;
218
219         flush_sessions(web);
220
221         g_resolv_unref(web->resolv);
222
223         g_free(web->proxy);
224
225         g_free(web->accept_option);
226         g_free(web->user_agent);
227
228         g_free(web);
229 }
230
231 void g_web_set_debug(GWeb *web, GWebDebugFunc func, gpointer user_data)
232 {
233         if (web == NULL)
234                 return;
235
236         web->debug_func = func;
237         web->debug_data = user_data;
238
239         g_resolv_set_debug(web->resolv, func, user_data);
240 }
241
242 gboolean g_web_set_proxy(GWeb *web, const char *proxy)
243 {
244         if (web == NULL)
245                 return FALSE;
246
247         g_free(web->proxy);
248         web->proxy = g_strdup(proxy);
249
250         return TRUE;
251 }
252
253 gboolean g_web_add_nameserver(GWeb *web, const char *address)
254 {
255         if (web == NULL)
256                 return FALSE;
257
258         g_resolv_add_nameserver(web->resolv, address, 53, 0);
259
260         return TRUE;
261 }
262
263 static gboolean set_accept_option(GWeb *web, const char *format, va_list args)
264 {
265         g_free(web->accept_option);
266
267         if (format == NULL) {
268                 web->accept_option = NULL;
269                 debug(web, "clearing accept option");
270         } else {
271                 web->accept_option = g_strdup_vprintf(format, args);
272                 debug(web, "setting accept %s", web->accept_option);
273         }
274
275         return TRUE;
276 }
277
278 gboolean g_web_set_accept(GWeb *web, const char *format, ...)
279 {
280         va_list args;
281         gboolean result;
282
283         if (web == NULL)
284                 return FALSE;
285
286         va_start(args, format);
287         result = set_accept_option(web, format, args);
288         va_end(args);
289
290         return result;
291 }
292
293 static gboolean set_user_agent(GWeb *web, const char *format, va_list args)
294 {
295         g_free(web->user_agent);
296
297         if (format == NULL) {
298                 web->user_agent = NULL;
299                 debug(web, "clearing user agent");
300         } else {
301                 web->user_agent = g_strdup_vprintf(format, args);
302                 debug(web, "setting user agent %s", web->user_agent);
303         }
304
305         return TRUE;
306 }
307
308 gboolean g_web_set_user_agent(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_user_agent(web, format, args);
318         va_end(args);
319
320         return result;
321 }
322
323 void g_web_set_close_connection(GWeb *web, gboolean enabled)
324 {
325         if (web == NULL)
326                 return;
327
328         web->close_connection = enabled;
329 }
330
331 gboolean g_web_get_close_connection(GWeb *web)
332 {
333         if (web == NULL)
334                 return FALSE;
335
336         return web->close_connection;
337 }
338
339 static inline void call_result_func(struct web_session *session, guint16 status)
340 {
341         if (session->result_func == NULL)
342                 return;
343
344         if (status != 0)
345                 session->result.status = status;
346
347         session->result_func(&session->result, session->user_data);
348 }
349
350 static void process_next_chunk(struct web_session *session)
351 {
352         GString *buf;
353         gchar *str;
354         const guint8 *body;
355         gsize length;
356         gsize count, bytes_written;
357         GIOStatus status;
358
359         if (session->input_func == NULL) {
360                 session->more_data = FALSE;
361                 return;
362         }
363
364         session->more_data = session->input_func(&body, &length,
365                                                 session->user_data);
366
367         buf = g_string_new(NULL);
368
369         if (length > 0) {
370                 g_string_append_printf(buf, "%zx\r\n", length);
371                 g_string_append_len(buf, (char *) body, length);
372                 g_string_append(buf, "\r\n");
373         }
374
375         if (session->more_data == FALSE)
376                 g_string_append(buf, "0\r\n\r\n");
377
378         count = buf->len;
379         str = g_string_free(buf, FALSE);
380
381         if (count > 0) {
382                 status = g_io_channel_write_chars(session->transport_channel,
383                                         str, count, &bytes_written, NULL);
384
385                 debug(session->web, "status %u bytes written %zu",
386                                                 status, bytes_written);
387         }
388
389         g_free(str);
390 }
391
392 static void start_request(struct web_session *session)
393 {
394         GString *buf;
395         gchar *str;
396         const guint8 *body;
397         gsize length;
398         gsize count, bytes_written;
399         GIOStatus status;
400
401         debug(session->web, "request %s from %s",
402                                         session->request, session->host);
403
404         buf = g_string_new(NULL);
405
406         if (session->content_type == NULL)
407                 g_string_append_printf(buf, "GET %s HTTP/1.1\r\n",
408                                                         session->request);
409         else
410                 g_string_append_printf(buf, "POST %s HTTP/1.1\r\n",
411                                                         session->request);
412         g_string_append_printf(buf, "Host: %s\r\n", session->host);
413         if (session->web->user_agent != NULL)
414                 g_string_append_printf(buf, "User-Agent: %s\r\n",
415                                                 session->web->user_agent);
416         if (session->web->accept_option != NULL)
417                 g_string_append_printf(buf, "Accept: %s\r\n",
418                                                 session->web->accept_option);
419         if (session->content_type != NULL) {
420                 g_string_append_printf(buf, "Content-Type: %s\r\n",
421                                                         session->content_type);
422                 if (session->input_func == NULL) {
423                         session->more_data = FALSE;
424                         length = 0;
425                 } else
426                         session->more_data = session->input_func(&body, &length,
427                                                         session->user_data);
428                 if (session->more_data == FALSE)
429                         g_string_append_printf(buf, "Content-Length: %zu\r\n",
430                                                                         length);
431                 else
432                         g_string_append(buf, "Transfer-Encoding: chunked\r\n");
433         }
434         if (session->web->close_connection == TRUE)
435                 g_string_append(buf, "Connection: close\r\n");
436         g_string_append(buf, "\r\n");
437
438         if (session->content_type != NULL && length > 0) {
439                 if (session->more_data == TRUE) {
440                         g_string_append_printf(buf, "%zx\r\n", length);
441                         g_string_append_len(buf, (char *) body, length);
442                         g_string_append(buf, "\r\n");
443                 } else
444                         g_string_append_len(buf, (char *) body, length);
445         }
446
447         count = buf->len;
448         str = g_string_free(buf, FALSE);
449
450         debug(session->web, "bytes to write %zu", count);
451
452         status = g_io_channel_write_chars(session->transport_channel,
453                                         str, count, &bytes_written, NULL);
454
455         debug(session->web, "status %u bytes written %zu",
456                                         status, bytes_written);
457
458         //printf("%s", str);
459
460         g_free(str);
461 }
462
463 static gboolean send_data(GIOChannel *channel, GIOCondition cond,
464                                                 gpointer user_data)
465 {
466         struct web_session *session = user_data;
467
468         if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
469                 session->send_watch = 0;
470                 return FALSE;
471         }
472
473         if (session->request_started == FALSE) {
474                 session->request_started = TRUE;
475                 start_request(session);
476         } else if (session->more_data == TRUE)
477                 process_next_chunk(session);
478
479         if (session->more_data == TRUE)
480                 return TRUE;
481
482         session->send_watch = 0;
483
484         return FALSE;
485 }
486
487 static int decode_chunked(struct web_session *session,
488                                         const guint8 *buf, gsize len)
489 {
490         const guint8 *ptr = buf;
491         gsize counter;
492
493         while (len > 0) {
494                 guint8 *pos;
495                 gsize count;
496                 char *str;
497
498                 switch (session->chunck_state) {
499                 case CHUNK_SIZE:
500                         pos = memchr(ptr, '\n', len);
501                         if (pos == NULL) {
502                                 g_string_append_len(session->current_header,
503                                                 (gchar *) ptr, len);
504                                 return 0;
505                         }
506
507                         count = pos - ptr;
508                         if (count < 1 || ptr[count - 1] != '\r')
509                                 return -EILSEQ;
510
511                         g_string_append_len(session->current_header,
512                                                 (gchar *) ptr, count);
513
514                         len -= count + 1;
515                         ptr = pos + 1;
516
517                         str = session->current_header->str;
518
519                         counter = strtoul(str, NULL, 16);
520                         if ((counter == 0 && errno == EINVAL) ||
521                                                 counter == ULONG_MAX)
522                                 return -EILSEQ;
523
524                         session->chunk_size = counter;
525                         session->chunk_left = counter;
526
527                         session->chunck_state = CHUNK_DATA;
528                         break;
529                 case CHUNK_R_BODY:
530                         if (*ptr != '\r')
531                                 return -EILSEQ;
532                         ptr++;
533                         len--;
534                         session->chunck_state = CHUNK_N_BODY;
535                         break;
536                 case CHUNK_N_BODY:
537                         if (*ptr != '\n')
538                                 return -EILSEQ;
539                         ptr++;
540                         len--;
541                         session->chunck_state = CHUNK_SIZE;
542                         break;
543                 case CHUNK_DATA:
544                         if (session->chunk_size == 0) {
545                                 debug(session->web, "Download Done in chunk");
546                                 g_string_truncate(session->current_header, 0);
547                                 return 0;
548                         }
549
550                         if (session->chunk_left <= len) {
551                                 session->result.buffer = ptr;
552                                 session->result.length = session->chunk_left;
553                                 call_result_func(session, 0);
554
555                                 len -= session->chunk_left;
556                                 ptr += session->chunk_left;
557
558                                 session->total_len += session->chunk_left;
559                                 session->chunk_left = 0;
560
561                                 g_string_truncate(session->current_header, 0);
562                                 session->chunck_state = CHUNK_R_BODY;
563                                 break;
564                         }
565                         /* more data */
566                         session->result.buffer = ptr;
567                         session->result.length = len;
568                         call_result_func(session, 0);
569
570                         session->chunk_left -= len;
571                         session->total_len += len;
572
573                         len -= len;
574                         ptr += len;
575                         break;
576                 }
577         }
578
579         return 0;
580 }
581
582 static int handle_body(struct web_session *session,
583                                 const guint8 *buf, gsize len)
584 {
585         int err;
586
587         if (session->result.use_chunk == FALSE) {
588                 session->result.buffer = buf;
589                 session->result.length = len;
590                 call_result_func(session, 0);
591                 return 0;
592         }
593
594         err = decode_chunked(session, buf, len);
595         if (err < 0) {
596                 debug(session->web, "Error in chunk decode %d", err);
597
598                 session->result.buffer = NULL;
599                 session->result.length = 0;
600                 call_result_func(session, 400);
601         }
602
603         return err;
604 }
605
606 static gboolean received_data(GIOChannel *channel, GIOCondition cond,
607                                                         gpointer user_data)
608 {
609         struct web_session *session = user_data;
610         guint8 *ptr = session->receive_buffer;
611         gsize bytes_read;
612         GIOStatus status;
613
614         if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
615                 session->transport_watch = 0;
616                 session->result.buffer = NULL;
617                 session->result.length = 0;
618                 call_result_func(session, 400);
619                 return FALSE;
620         }
621
622         status = g_io_channel_read_chars(channel,
623                                 (gchar *) session->receive_buffer,
624                                 session->receive_space - 1, &bytes_read, NULL);
625
626         debug(session->web, "bytes read %zu", bytes_read);
627
628         if (status != G_IO_STATUS_NORMAL) {
629                 session->transport_watch = 0;
630                 session->result.buffer = NULL;
631                 session->result.length = 0;
632                 call_result_func(session, 0);
633                 return FALSE;
634         }
635
636         session->receive_buffer[bytes_read] = '\0';
637
638         if (session->header_done == TRUE) {
639                 if (handle_body(session,
640                                 session->receive_buffer, bytes_read) < 0) {
641                         session->transport_watch = 0;
642                         return FALSE;
643                 }
644                 return TRUE;
645         }
646
647         while (bytes_read > 0) {
648                 guint8 *pos;
649                 gsize count;
650                 char *str;
651
652                 pos = memchr(ptr, '\n', bytes_read);
653                 if (pos == NULL) {
654                         g_string_append_len(session->current_header,
655                                                 (gchar *) ptr, bytes_read);
656                         return TRUE;
657                 }
658
659                 *pos = '\0';
660                 count = strlen((char *) ptr);
661                 if (count > 0 && ptr[count - 1] == '\r') {
662                         ptr[--count] = '\0';
663                         bytes_read--;
664                 }
665
666                 g_string_append_len(session->current_header,
667                                                 (gchar *) ptr, count);
668
669                 bytes_read -= count + 1;
670                 if (bytes_read > 0)
671                         ptr = pos + 1;
672                 else
673                         ptr = NULL;
674
675                 if (session->current_header->len == 0) {
676                         session->header_done = TRUE;
677                         if (handle_body(session, ptr, bytes_read) < 0) {
678                                 session->transport_watch = 0;
679                                 return FALSE;
680                         }
681                         break;
682                 }
683
684                 str = session->current_header->str;
685
686                 if (session->result.status == 0) {
687                         unsigned int code;
688
689                         if (sscanf(str, "HTTP/%*s %u %*s", &code) == 1)
690                                 session->result.status = code;
691                 } else if (session->result.use_chunk == FALSE &&
692                                 g_ascii_strncasecmp("Transfer-Encoding:",
693                                                                 str, 18) == 0) {
694                         char *val;
695
696                         val = g_strrstr(str + 18, "chunked");
697                         if (val != NULL) {
698                                 session->result.use_chunk = TRUE;
699
700                                 session->chunck_state = CHUNK_SIZE;
701                                 session->chunk_left = 0;
702                                 session->chunk_left = 0;
703                                 session->total_len = 0;
704                         }
705                 }
706
707                 debug(session->web, "[header] %s", str);
708
709                 g_string_truncate(session->current_header, 0);
710         }
711
712         return TRUE;
713 }
714
715 static int connect_session_transport(struct web_session *session)
716 {
717         struct sockaddr_in sin;
718         int sk;
719
720         sk = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
721         if (sk < 0)
722                 return -EIO;
723
724         memset(&sin, 0, sizeof(sin));
725         sin.sin_family = AF_INET;
726         sin.sin_port = htons(session->port);
727         sin.sin_addr.s_addr = inet_addr(session->address);
728
729         if (session->flags & SESSION_FLAG_USE_TLS)
730                 session->transport_channel = g_io_channel_gnutls_new(sk);
731         else
732                 session->transport_channel = g_io_channel_unix_new(sk);
733
734         if (session->transport_channel == NULL) {
735                 close(sk);
736                 return -ENOMEM;
737         }
738
739         g_io_channel_set_flags(session->transport_channel,
740                                         G_IO_FLAG_NONBLOCK, NULL);
741         g_io_channel_set_encoding(session->transport_channel, NULL, NULL);
742         g_io_channel_set_buffered(session->transport_channel, FALSE);
743
744         g_io_channel_set_close_on_unref(session->transport_channel, TRUE);
745
746         if (connect(sk, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
747                 if (errno != EINPROGRESS) {
748                         close(sk);
749                         return -EIO;
750                 }
751         }
752
753         session->transport_watch = g_io_add_watch(session->transport_channel,
754                                 G_IO_IN | G_IO_HUP | G_IO_NVAL | G_IO_ERR,
755                                                 received_data, session);
756
757         session->send_watch = g_io_add_watch(session->transport_channel,
758                                 G_IO_OUT | G_IO_HUP | G_IO_NVAL | G_IO_ERR,
759                                                 send_data, session);
760
761         return 0;
762 }
763
764 static int create_transport(struct web_session *session)
765 {
766         int err;
767
768         err = connect_session_transport(session);
769         if (err < 0)
770                 return err;
771
772         debug(session->web, "creating session %s:%u",
773                                         session->address, session->port);
774
775         return 0;
776 }
777
778 static int parse_url(struct web_session *session, const char *url)
779 {
780         char *scheme, *host, *port, *path;
781
782         scheme = g_strdup(url);
783         if (scheme == NULL)
784                 return -EINVAL;
785
786         host = strstr(scheme, "://");
787         if (host != NULL) {
788                 *host = '\0';
789                 host += 3;
790
791                 if (strcasecmp(scheme, "https") == 0) {
792                         session->port = 443;
793                         session->flags |= SESSION_FLAG_USE_TLS;
794                 } else if (strcasecmp(scheme, "http") == 0) {
795                         session->port = 80;
796                 } else {
797                         g_free(scheme);
798                         return -EINVAL;
799                 }
800         } else {
801                 host = scheme;
802                 session->port = 80;
803         }
804
805         path = strchr(host, '/');
806         if (path != NULL)
807                 *(path++) = '\0';
808
809         session->request = g_strdup_printf("/%s", path ? path : "");
810
811         port = strrchr(host, ':');
812         if (port != NULL) {
813                 char *end;
814                 int tmp = strtol(port + 1, &end, 10);
815
816                 if (*end == '\0') {
817                         *port = '\0';
818                         session->port = tmp;
819                 }
820         }
821
822         session->host = g_strdup(host);
823
824         g_free(scheme);
825
826         return 0;
827 }
828
829 static void resolv_result(GResolvResultStatus status,
830                                         char **results, gpointer user_data)
831 {
832         struct web_session *session = user_data;
833
834         if (results == NULL || results[0] == NULL) {
835                 call_result_func(session, 404);
836                 return;
837         }
838
839         debug(session->web, "address %s", results[0]);
840
841         if (inet_aton(results[0], NULL) == 0) {
842                 call_result_func(session, 400);
843                 return;
844         }
845
846         session->address = g_strdup(results[0]);
847
848         if (create_transport(session) < 0) {
849                 call_result_func(session, 409);
850                 return;
851         }
852 }
853
854 static guint do_request(GWeb *web, const char *url,
855                                 const char *type, GWebInputFunc input,
856                                 GWebResultFunc func, gpointer user_data)
857 {
858         struct web_session *session;
859
860         if (web == NULL || url == NULL)
861                 return 0;
862
863         debug(web, "request %s", url);
864
865         session = g_try_new0(struct web_session, 1);
866         if (session == NULL)
867                 return 0;
868
869         if (parse_url(session, url) < 0) {
870                 free_session(session);
871                 return 0;
872         }
873
874         debug(web, "host %s:%u", session->host, session->port);
875         debug(web, "flags %lu", session->flags);
876
877         if (type != NULL) {
878                 session->content_type = g_strdup(type);
879
880                 debug(web, "content-type %s", session->content_type);
881         }
882
883         session->web = web;
884
885         session->result_func = func;
886         session->input_func = input;
887         session->user_data = user_data;
888
889         session->receive_buffer = g_try_malloc(DEFAULT_BUFFER_SIZE);
890         if (session->receive_buffer == NULL) {
891                 free_session(session);
892                 return 0;
893         }
894
895         session->receive_space = DEFAULT_BUFFER_SIZE;
896         session->current_header = g_string_sized_new(0);
897         session->header_done = FALSE;
898
899         if (inet_aton(session->host, NULL) == 0) {
900                 session->resolv_action = g_resolv_lookup_hostname(web->resolv,
901                                         session->host, resolv_result, session);
902                 if (session->resolv_action == 0) {
903                         free_session(session);
904                         return 0;
905                 }
906         } else {
907                 session->address = g_strdup(session->host);
908
909                 if (create_transport(session) < 0) {
910                         free_session(session);
911                         return 0;
912                 }
913         }
914
915         web->session_list = g_list_append(web->session_list, session);
916
917         return web->next_query_id++;
918 }
919
920 guint g_web_request_get(GWeb *web, const char *url,
921                                 GWebResultFunc func, gpointer user_data)
922 {
923         return do_request(web, url, NULL, NULL, func, user_data);
924 }
925
926 guint g_web_request_post(GWeb *web, const char *url,
927                                 const char *type, GWebInputFunc input,
928                                 GWebResultFunc func, gpointer user_data)
929 {
930         return do_request(web, url, type, input, func, user_data);
931 }
932
933 gboolean g_web_cancel_request(GWeb *web, guint id)
934 {
935         if (web == NULL)
936                 return FALSE;
937
938         return TRUE;
939 }
940
941 guint16 g_web_result_get_status(GWebResult *result)
942 {
943         if (result == NULL)
944                 return 0;
945
946         return result->status;
947 }
948
949 gboolean g_web_result_get_chunk(GWebResult *result,
950                                 const guint8 **chunk, gsize *length)
951 {
952         if (result == NULL)
953                 return FALSE;
954
955         if (chunk == NULL)
956                 return FALSE;
957
958         *chunk = result->buffer;
959
960         if (length != NULL)
961                 *length = result->length;
962
963         return TRUE;
964 }
965
966 struct _GWebParser {
967         gint ref_count;
968         char *begin_token;
969         char *end_token;
970         const char *token_str;
971         size_t token_len;
972         size_t token_pos;
973         gboolean intoken;
974         GString *content;
975         GWebParserFunc func;
976         gpointer user_data;
977 };
978
979 GWebParser *g_web_parser_new(const char *begin, const char *end,
980                                 GWebParserFunc func, gpointer user_data)
981 {
982         GWebParser *parser;
983
984         parser = g_try_new0(GWebParser, 1);
985         if (parser == NULL)
986                 return NULL;
987
988         parser->ref_count = 1;
989
990         parser->begin_token = g_strdup(begin);
991         parser->end_token = g_strdup(end);
992
993         if (parser->begin_token == NULL) {
994                 g_free(parser);
995                 return NULL;
996         }
997
998         parser->func = func;
999         parser->user_data = user_data;
1000
1001         parser->token_str = parser->begin_token;
1002         parser->token_len = strlen(parser->token_str);
1003         parser->token_pos = 0;
1004
1005         parser->intoken = FALSE;
1006         parser->content = g_string_sized_new(0);
1007
1008         return parser;
1009 }
1010
1011 GWebParser *g_web_parser_ref(GWebParser *parser)
1012 {
1013         if (parser == NULL)
1014                 return NULL;
1015
1016         g_atomic_int_inc(&parser->ref_count);
1017
1018         return parser;
1019 }
1020
1021 void g_web_parser_unref(GWebParser *parser)
1022 {
1023         if (parser == NULL)
1024                 return;
1025
1026         if (g_atomic_int_dec_and_test(&parser->ref_count) == FALSE)
1027                 return;
1028
1029         g_string_free(parser->content, TRUE);
1030
1031         g_free(parser->begin_token);
1032         g_free(parser->end_token);
1033         g_free(parser);
1034 }
1035
1036 void g_web_parser_feed_data(GWebParser *parser,
1037                                 const guint8 *data, gsize length)
1038 {
1039         const guint8 *ptr = data;
1040
1041         if (parser == NULL)
1042                 return;
1043
1044         while (length > 0) {
1045                 guint8 chr = parser->token_str[parser->token_pos];
1046
1047                 if (parser->token_pos == 0) {
1048                         guint8 *pos;
1049
1050                         pos = memchr(ptr, chr, length);
1051                         if (pos == NULL) {
1052                                 if (parser->intoken == TRUE)
1053                                         g_string_append_len(parser->content,
1054                                                         (gchar *) ptr, length);
1055                                 break;
1056                         }
1057
1058                         if (parser->intoken == TRUE)
1059                                 g_string_append_len(parser->content,
1060                                                 (gchar *) ptr, (pos - ptr) + 1);
1061
1062                         length -= (pos - ptr) + 1;
1063                         ptr = pos + 1;
1064
1065                         parser->token_pos++;
1066                         continue;
1067                 }
1068
1069                 if (parser->intoken == TRUE)
1070                         g_string_append_c(parser->content, ptr[0]);
1071
1072                 if (ptr[0] != chr) {
1073                         length--;
1074                         ptr++;
1075
1076                         parser->token_pos = 0;
1077                         continue;
1078                 }
1079
1080                 length--;
1081                 ptr++;
1082
1083                 parser->token_pos++;
1084
1085                 if (parser->token_pos == parser->token_len) {
1086                         if (parser->intoken == FALSE) {
1087                                 g_string_append(parser->content,
1088                                                         parser->token_str);
1089
1090                                 parser->intoken = TRUE;
1091                                 parser->token_str = parser->end_token;
1092                                 parser->token_len = strlen(parser->end_token);
1093                                 parser->token_pos = 0;
1094                         } else {
1095                                 char *str;
1096                                 str = g_string_free(parser->content, FALSE);
1097                                 parser->content = g_string_sized_new(0);
1098                                 if (parser->func)
1099                                         parser->func(str, parser->user_data);
1100                                 g_free(str);
1101
1102                                 parser->intoken = FALSE;
1103                                 parser->token_str = parser->begin_token;
1104                                 parser->token_len = strlen(parser->begin_token);
1105                                 parser->token_pos = 0;
1106                         }
1107                 }
1108         }
1109 }
1110
1111 void g_web_parser_end_data(GWebParser *parser)
1112 {
1113         if (parser == NULL)
1114                 return;
1115 }