Add proper support for HTTP close connection option
[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 SESSION_FLAG_USE_TLS    (1 << 0)
40
41 struct _GWebResult {
42 };
43
44 struct web_session {
45         GWeb *web;
46
47         char *address;
48         char *host;
49         uint16_t port;
50         unsigned long flags;
51
52         GIOChannel *transport_channel;
53         guint transport_watch;
54
55         guint resolv_action;
56         char *request;
57
58         GWebResult *result;
59
60         GWebResultFunc result_func;
61         gpointer result_data;
62 };
63
64 struct _GWeb {
65         gint ref_count;
66
67         guint next_query_id;
68
69         int index;
70         GList *session_list;
71
72         GResolv *resolv;
73         char *accept_option;
74         char *user_agent;
75         gboolean close_connection;
76
77         GWebDebugFunc debug_func;
78         gpointer debug_data;
79 };
80
81 static inline void debug(GWeb *web, const char *format, ...)
82 {
83         char str[256];
84         va_list ap;
85
86         if (web->debug_func == NULL)
87                 return;
88
89         va_start(ap, format);
90
91         if (vsnprintf(str, sizeof(str), format, ap) > 0)
92                 web->debug_func(str, web->debug_data);
93
94         va_end(ap);
95 }
96
97 static void free_session(struct web_session *session)
98 {
99         GWeb *web = session->web;
100
101         if (session == NULL)
102                 return;
103
104         g_free(session->request);
105
106         if (session->resolv_action > 0)
107                 g_resolv_cancel_lookup(web->resolv, session->resolv_action);
108
109         if (session->transport_watch > 0)
110                 g_source_remove(session->transport_watch);
111
112         if (session->transport_channel != NULL)
113                 g_io_channel_unref(session->transport_channel);
114
115         g_free(session->host);
116         g_free(session->address);
117         g_free(session);
118 }
119
120 static void flush_sessions(GWeb *web)
121 {
122         GList *list;
123
124         for (list = g_list_first(web->session_list);
125                                         list; list = g_list_next(list))
126                 free_session(list->data);
127
128         g_list_free(web->session_list);
129         web->session_list = NULL;
130 }
131
132 GWeb *g_web_new(int index)
133 {
134         GWeb *web;
135
136         if (index < 0)
137                 return NULL;
138
139         web = g_try_new0(GWeb, 1);
140         if (web == NULL)
141                 return NULL;
142
143         web->ref_count = 1;
144
145         web->next_query_id = 1;
146
147         web->index = index;
148         web->session_list = NULL;
149
150         web->resolv = g_resolv_new(index);
151         if (web->resolv == NULL) {
152                 g_free(web);
153                 return NULL;
154         }
155
156         web->accept_option = g_strdup("*/*");
157         web->user_agent = g_strdup_printf("GWeb/%s", VERSION);
158         web->close_connection = FALSE;
159
160         return web;
161 }
162
163 GWeb *g_web_ref(GWeb *web)
164 {
165         if (web == NULL)
166                 return NULL;
167
168         g_atomic_int_inc(&web->ref_count);
169
170         return web;
171 }
172
173 void g_web_unref(GWeb *web)
174 {
175         if (web == NULL)
176                 return;
177
178         if (g_atomic_int_dec_and_test(&web->ref_count) == FALSE)
179                 return;
180
181         flush_sessions(web);
182
183         g_resolv_unref(web->resolv);
184
185         g_free(web->accept_option);
186         g_free(web->user_agent);
187         g_free(web);
188 }
189
190 void g_web_set_debug(GWeb *web, GWebDebugFunc func, gpointer user_data)
191 {
192         if (web == NULL)
193                 return;
194
195         web->debug_func = func;
196         web->debug_data = user_data;
197
198         g_resolv_set_debug(web->resolv, func, user_data);
199 }
200
201 gboolean g_web_add_nameserver(GWeb *web, const char *address)
202 {
203         if (web == NULL)
204                 return FALSE;
205
206         g_resolv_add_nameserver(web->resolv, address, 53, 0);
207
208         return TRUE;
209 }
210
211 static gboolean set_accept_option(GWeb *web, const char *format, va_list args)
212 {
213         g_free(web->accept_option);
214
215         if (format == NULL) {
216                 web->accept_option = NULL;
217                 debug(web, "clearing accept option");
218         } else {
219                 web->accept_option = g_strdup_vprintf(format, args);
220                 debug(web, "setting accept %s", web->accept_option);
221         }
222
223         return TRUE;
224 }
225
226 gboolean g_web_set_accept(GWeb *web, const char *format, ...)
227 {
228         va_list args;
229         gboolean result;
230
231         if (web == NULL)
232                 return FALSE;
233
234         va_start(args, format);
235         result = set_accept_option(web, format, args);
236         va_end(args);
237
238         return result;
239 }
240
241 static gboolean set_user_agent(GWeb *web, const char *format, va_list args)
242 {
243         g_free(web->user_agent);
244
245         if (format == NULL) {
246                 web->user_agent = NULL;
247                 debug(web, "clearing user agent");
248         } else {
249                 web->user_agent = g_strdup_vprintf(format, args);
250                 debug(web, "setting user agent %s", web->user_agent);
251         }
252
253         return TRUE;
254 }
255
256 gboolean g_web_set_user_agent(GWeb *web, const char *format, ...)
257 {
258         va_list args;
259         gboolean result;
260
261         if (web == NULL)
262                 return FALSE;
263
264         va_start(args, format);
265         result = set_user_agent(web, format, args);
266         va_end(args);
267
268         return result;
269 }
270
271 void g_web_set_close_connection(GWeb *web, gboolean enabled)
272 {
273         if (web == NULL)
274                 return;
275
276         web->close_connection = enabled;
277 }
278
279 gboolean g_web_get_close_connection(GWeb *web)
280 {
281         if (web == NULL)
282                 return FALSE;
283
284         return web->close_connection;
285 }
286
287 static gboolean received_data(GIOChannel *channel, GIOCondition cond,
288                                                         gpointer user_data)
289 {
290         struct web_session *session = user_data;
291         gchar buf[4096];
292         gsize bytes_read;
293         GIOStatus status;
294
295         if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
296                 session->transport_watch = 0;
297                 if (session->result_func != NULL)
298                         session->result_func(400, NULL, session->result_data);
299                 return FALSE;
300         }
301
302         memset(buf, 0, sizeof(buf));
303         status = g_io_channel_read_chars(channel, buf, sizeof(buf) - 1,
304                                                 &bytes_read, NULL);
305
306         debug(session->web, "status %u bytes read %zu", status, bytes_read);
307
308         if (status != G_IO_STATUS_NORMAL) {
309                 session->transport_watch = 0;
310                 if (session->result_func != NULL)
311                         session->result_func(200, NULL, session->result_data);
312                 return FALSE;
313         }
314
315         printf("%s", buf);
316
317         return TRUE;
318 }
319
320 static int connect_session_transport(struct web_session *session)
321 {
322         struct sockaddr_in sin;
323         int sk;
324
325         sk = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
326         if (sk < 0)
327                 return -EIO;
328
329         memset(&sin, 0, sizeof(sin));
330         sin.sin_family = AF_INET;
331         sin.sin_port = htons(session->port);
332         sin.sin_addr.s_addr = inet_addr(session->address);
333
334         if (connect(sk, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
335                 close(sk);
336                 return -EIO;
337         }
338
339         debug(session->web, "flags %lu", session->flags);
340
341         if (session->flags & SESSION_FLAG_USE_TLS)
342                 session->transport_channel = g_io_channel_gnutls_new(sk);
343         else
344                 session->transport_channel = g_io_channel_unix_new(sk);
345
346         if (session->transport_channel == NULL) {
347                 close(sk);
348                 return -ENOMEM;
349         }
350
351         g_io_channel_set_encoding(session->transport_channel, NULL, NULL);
352         g_io_channel_set_buffered(session->transport_channel, FALSE);
353
354         g_io_channel_set_close_on_unref(session->transport_channel, TRUE);
355
356         session->transport_watch = g_io_add_watch(session->transport_channel,
357                                 G_IO_IN | G_IO_HUP | G_IO_NVAL | G_IO_ERR,
358                                                 received_data, session);
359
360         return 0;
361 }
362
363 static int create_transport(struct web_session *session)
364 {
365         int err;
366
367         err = connect_session_transport(session);
368         if (err < 0)
369                 return err;
370
371         debug(session->web, "creating session %s:%u",
372                                         session->address, session->port);
373
374         return 0;
375 }
376
377 static void start_request(struct web_session *session)
378 {
379         GString *buf;
380         gchar *str;
381         gsize count, bytes_written;
382         GIOStatus status;
383
384         debug(session->web, "request %s from %s",
385                                         session->request, session->host);
386
387         buf = g_string_new(NULL);
388         g_string_append_printf(buf, "GET %s HTTP/1.1\r\n", session->request);
389         g_string_append_printf(buf, "Host: %s\r\n", session->host);
390         if (session->web->user_agent != NULL)
391                 g_string_append_printf(buf, "User-Agent: %s\r\n",
392                                                 session->web->user_agent);
393         if (session->web->accept_option != NULL)
394                 g_string_append_printf(buf, "Accept: %s\r\n",
395                                                 session->web->accept_option);
396         if (session->web->close_connection == TRUE)
397                 g_string_append(buf, "Connection: close\r\n");
398         g_string_append(buf, "\r\n");
399         str = g_string_free(buf, FALSE);
400
401         count = strlen(str);
402
403         debug(session->web, "bytes to write %zu", count);
404
405         status = g_io_channel_write_chars(session->transport_channel,
406                                         str, count, &bytes_written, NULL);
407
408         debug(session->web, "status %u bytes written %zu",
409                                                 status, bytes_written);
410
411         printf("%s", str);
412
413         g_free(str);
414 }
415
416 static int parse_url(struct web_session *session, const char *url)
417 {
418         char *scheme, *host, *port, *path;
419
420         scheme = g_strdup(url);
421         if (scheme == NULL)
422                 return -EINVAL;
423
424         host = strstr(scheme, "://");
425         if (host != NULL) {
426                 *host = '\0';
427                 host += 3;
428
429                 if (strcasecmp(scheme, "https") == 0) {
430                         session->port = 443;
431                         session->flags |= SESSION_FLAG_USE_TLS;
432                 } else if (strcasecmp(scheme, "http") == 0) {
433                         session->port = 80;
434                 } else {
435                         g_free(scheme);
436                         return -EINVAL;
437                 }
438         } else {
439                 host = scheme;
440                 session->port = 80;
441         }
442
443         path = strchr(host, '/');
444         if (path != NULL)
445                 *(path++) = '\0';
446
447         session->request = g_strdup_printf("/%s", path ? path : "");
448
449         port = strrchr(host, ':');
450         if (port != NULL) {
451                 char *end;
452                 int tmp = strtol(port + 1, &end, 10);
453
454                 if (*end == '\0') {
455                         *port = '\0';
456                         session->port = tmp;
457                 }
458         }
459
460         session->host = g_strdup(host);
461
462         g_free(scheme);
463
464         return 0;
465 }
466
467 static void resolv_result(GResolvResultStatus status,
468                                         char **results, gpointer user_data)
469 {
470         struct web_session *session = user_data;
471
472         if (results == NULL || results[0] == NULL) {
473                 if (session->result_func != NULL)
474                         session->result_func(404, NULL, session->result_data);
475                 return;
476         }
477
478         debug(session->web, "address %s", results[0]);
479
480         if (inet_aton(results[0], NULL) == 0) {
481                 if (session->result_func != NULL)
482                         session->result_func(400, NULL, session->result_data);
483                 return;
484         }
485
486         session->address = g_strdup(results[0]);
487
488         if (create_transport(session) < 0) {
489                 if (session->result_func != NULL)
490                         session->result_func(409, NULL, session->result_data);
491                 return;
492         }
493
494         start_request(session);
495 }
496
497 guint g_web_request(GWeb *web, GWebMethod method, const char *url,
498                                 GWebResultFunc func, gpointer user_data)
499 {
500         struct web_session *session;
501
502         if (web == NULL || url == NULL)
503                 return 0;
504
505         debug(web, "request %s", url);
506
507         session = g_try_new0(struct web_session, 1);
508         if (session == NULL)
509                 return 0;
510
511         if (parse_url(session, url) < 0) {
512                 free_session(session);
513                 return 0;
514         }
515
516         debug(web, "host %s:%u", session->host, session->port);
517         debug(web, "flags %lu", session->flags);
518
519         session->web = web;
520
521         session->result_func = func;
522         session->result_data = user_data;
523
524         if (inet_aton(session->host, NULL) == 0) {
525                 session->resolv_action = g_resolv_lookup_hostname(web->resolv,
526                                         session->host, resolv_result, session);
527                 if (session->resolv_action == 0) {
528                         free_session(session);
529                         return 0;
530                 }
531         } else {
532                 session->address = g_strdup(session->host);
533
534                 if (create_transport(session) < 0) {
535                         free_session(session);
536                         return 0;
537                 }
538
539                 start_request(session);
540         }
541
542         web->session_list = g_list_append(web->session_list, session);
543
544         return web->next_query_id++;
545 }