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