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