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