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