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