Fix handling of receive watch callback within GWeb
[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
74         GWebDebugFunc debug_func;
75         gpointer debug_data;
76 };
77
78 static inline void debug(GWeb *web, const char *format, ...)
79 {
80         char str[256];
81         va_list ap;
82
83         if (web->debug_func == NULL)
84                 return;
85
86         va_start(ap, format);
87
88         if (vsnprintf(str, sizeof(str), format, ap) > 0)
89                 web->debug_func(str, web->debug_data);
90
91         va_end(ap);
92 }
93
94 static void free_session(struct web_session *session)
95 {
96         GWeb *web = session->web;
97
98         if (session == NULL)
99                 return;
100
101         g_free(session->request);
102
103         if (session->resolv_action > 0)
104                 g_resolv_cancel_lookup(web->resolv, session->resolv_action);
105
106         if (session->transport_watch > 0)
107                 g_source_remove(session->transport_watch);
108
109         if (session->transport_channel != NULL)
110                 g_io_channel_unref(session->transport_channel);
111
112         g_free(session->host);
113         g_free(session->address);
114         g_free(session);
115 }
116
117 static void flush_sessions(GWeb *web)
118 {
119         GList *list;
120
121         for (list = g_list_first(web->session_list);
122                                         list; list = g_list_next(list))
123                 free_session(list->data);
124
125         g_list_free(web->session_list);
126         web->session_list = NULL;
127 }
128
129 GWeb *g_web_new(int index)
130 {
131         GWeb *web;
132
133         if (index < 0)
134                 return NULL;
135
136         web = g_try_new0(GWeb, 1);
137         if (web == NULL)
138                 return NULL;
139
140         web->ref_count = 1;
141
142         web->next_query_id = 1;
143
144         web->index = index;
145         web->session_list = NULL;
146
147         web->resolv = g_resolv_new(index);
148         if (web->resolv == NULL) {
149                 g_free(web);
150                 return NULL;
151         }
152
153         return web;
154 }
155
156 GWeb *g_web_ref(GWeb *web)
157 {
158         if (web == NULL)
159                 return NULL;
160
161         g_atomic_int_inc(&web->ref_count);
162
163         return web;
164 }
165
166 void g_web_unref(GWeb *web)
167 {
168         if (web == NULL)
169                 return;
170
171         if (g_atomic_int_dec_and_test(&web->ref_count) == FALSE)
172                 return;
173
174         flush_sessions(web);
175
176         g_resolv_unref(web->resolv);
177
178         g_free(web);
179 }
180
181 void g_web_set_debug(GWeb *web, GWebDebugFunc func, gpointer user_data)
182 {
183         if (web == NULL)
184                 return;
185
186         web->debug_func = func;
187         web->debug_data = user_data;
188
189         g_resolv_set_debug(web->resolv, func, user_data);
190 }
191
192 gboolean g_web_add_nameserver(GWeb *web, const char *address)
193 {
194         if (web == NULL)
195                 return FALSE;
196
197         g_resolv_add_nameserver(web->resolv, address, 53, 0);
198
199         return TRUE;
200 }
201
202 static gboolean received_data(GIOChannel *channel, GIOCondition cond,
203                                                         gpointer user_data)
204 {
205         struct web_session *session = user_data;
206         gchar buf[4096];
207         gsize bytes_read;
208         GIOStatus status;
209
210         if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
211                 session->transport_watch = 0;
212                 if (session->result_func != NULL)
213                         session->result_func(400, NULL, session->result_data);
214                 return FALSE;
215         }
216
217         memset(buf, 0, sizeof(buf));
218         status = g_io_channel_read_chars(channel, buf, sizeof(buf) - 1,
219                                                 &bytes_read, NULL);
220
221         debug(session->web, "status %u bytes read %zu", status, bytes_read);
222
223         if (status != G_IO_STATUS_NORMAL) {
224                 session->transport_watch = 0;
225                 if (session->result_func != NULL)
226                         session->result_func(200, NULL, session->result_data);
227                 return FALSE;
228         }
229
230         printf("%s", buf);
231
232         return TRUE;
233 }
234
235 static int connect_session_transport(struct web_session *session)
236 {
237         struct sockaddr_in sin;
238         int sk;
239
240         sk = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
241         if (sk < 0)
242                 return -EIO;
243
244         memset(&sin, 0, sizeof(sin));
245         sin.sin_family = AF_INET;
246         sin.sin_port = htons(session->port);
247         sin.sin_addr.s_addr = inet_addr(session->address);
248
249         if (connect(sk, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
250                 close(sk);
251                 return -EIO;
252         }
253
254         debug(session->web, "flags %lu", session->flags);
255
256         if (session->flags & SESSION_FLAG_USE_TLS)
257                 session->transport_channel = g_io_channel_gnutls_new(sk);
258         else
259                 session->transport_channel = g_io_channel_unix_new(sk);
260
261         if (session->transport_channel == NULL) {
262                 close(sk);
263                 return -ENOMEM;
264         }
265
266         g_io_channel_set_close_on_unref(session->transport_channel, TRUE);
267
268         session->transport_watch = g_io_add_watch(session->transport_channel,
269                                 G_IO_IN | G_IO_HUP | G_IO_NVAL | G_IO_ERR,
270                                                 received_data, session);
271
272         return 0;
273 }
274
275 static void start_request(struct web_session *session)
276 {
277         GString *buf;
278         gchar *str;
279         gsize bytes_written;
280         GIOStatus status;
281
282         debug(session->web, "request %s from %s",
283                                         session->request, session->host);
284
285         buf = g_string_new(NULL);
286         g_string_append_printf(buf, "GET %s HTTP/1.1\r\n", session->request);
287         g_string_append_printf(buf, "Host: %s\r\n", session->host);
288         g_string_append_printf(buf, "User-Agent: ConnMan/%s\r\n", VERSION);
289         g_string_append(buf, "Accept: */*\r\n");
290         g_string_append(buf, "\r\n");
291         str = g_string_free(buf, FALSE);
292
293         status = g_io_channel_write_chars(session->transport_channel,
294                                 str, strlen(str), &bytes_written, NULL);
295
296         debug(session->web, "status %u bytes written %zu",
297                                                 status, bytes_written);
298
299         printf("%s", str);
300
301         g_free(str);
302 }
303
304 static int parse_url(struct web_session *session, const char *url)
305 {
306         char *scheme, *host, *port, *path;
307
308         scheme = g_strdup(url);
309         if (scheme == NULL)
310                 return -EINVAL;
311
312         host = strstr(scheme, "://");
313         if (host != NULL) {
314                 *host = '\0';
315                 host += 3;
316
317                 if (strcasecmp(scheme, "https") == 0) {
318                         session->port = 443;
319                         session->flags |= SESSION_FLAG_USE_TLS;
320                 } else if (strcasecmp(scheme, "http") == 0) {
321                         session->port = 80;
322                 } else {
323                         g_free(scheme);
324                         return -EINVAL;
325                 }
326         } else {
327                 host = scheme;
328                 session->port = 80;
329         }
330
331         path = strchr(host, '/');
332         if (path != NULL)
333                 *(path++) = '\0';
334
335         session->request = g_strdup_printf("/%s", path ? path : "");
336
337         port = strrchr(host, ':');
338         if (port != NULL) {
339                 char *end;
340                 int tmp = strtol(port + 1, &end, 10);
341
342                 if (*end == '\0') {
343                         *port = '\0';
344                         session->port = tmp;
345                 }
346         }
347
348         session->host = g_strdup(host);
349
350         g_free(scheme);
351
352         return 0;
353 }
354
355 static void resolv_result(GResolvResultStatus status,
356                                         char **results, gpointer user_data)
357 {
358         struct web_session *session = user_data;
359
360         if (results == NULL || results[0] == NULL) {
361                 if (session->result_func != NULL)
362                         session->result_func(404, NULL, session->result_data);
363                 return;
364         }
365
366         debug(session->web, "address %s", results[0]);
367
368         if (inet_aton(results[0], NULL) == 0) {
369                 if (session->result_func != NULL)
370                         session->result_func(400, NULL, session->result_data);
371                 return;
372         }
373
374         session->address = g_strdup(results[0]);
375
376         if (connect_session_transport(session) < 0) {
377                 if (session->result_func != NULL)
378                         session->result_func(409, NULL, session->result_data);
379                 return;
380         }
381
382         debug(session->web, "creating session %s:%u",
383                                         session->address, session->port);
384
385         start_request(session);
386 }
387
388 guint g_web_request(GWeb *web, GWebMethod method, const char *url,
389                                 GWebResultFunc func, gpointer user_data)
390 {
391         struct web_session *session;
392
393         if (web == NULL || url == NULL)
394                 return 0;
395
396         debug(web, "request %s", url);
397
398         session = g_try_new0(struct web_session, 1);
399         if (session == NULL)
400                 return 0;
401
402         if (parse_url(session, url) < 0) {
403                 free_session(session);
404                 return 0;
405         }
406
407         debug(web, "host %s:%u", session->host, session->port);
408         debug(web, "flags %lu", session->flags);
409
410         session->web = web;
411
412         session->result_func = func;
413         session->result_data = user_data;
414
415         session->resolv_action = g_resolv_lookup_hostname(web->resolv,
416                                         session->host, resolv_result, session);
417         if (session->resolv_action == 0) {
418                 free_session(session);
419                 return 0;
420         }
421
422         web->session_list = g_list_append(web->session_list, session);
423
424         return web->next_query_id++;
425 }