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