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