]> git.0d.be Git - empathy.git/blob - libempathy/empathy-utils.c
Initial Voice+Video support Fixes bug #468204 (Elliot Fairweather, Xavier
[empathy.git] / libempathy / empathy-utils.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2003-2007 Imendio AB
4  * Copyright (C) 2007 Collabora Ltd.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (at your option) any later version.
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 GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public
17  * License along with this program; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  *
21  * Authors: Richard Hult <richard@imendio.com>
22  *          Martyn Russell <martyn@imendio.com>
23  *          Xavier Claessens <xclaesse@gmail.com>
24  */
25
26 #include "config.h"
27
28 #include <string.h>
29 #include <time.h>
30 #include <sys/types.h>
31 #include <regex.h>
32
33 #include <glib/gi18n.h>
34
35 #include <libxml/uri.h>
36 #include <libtelepathy/tp-helpers.h>
37
38 #include "empathy-debug.h"
39 #include "empathy-utils.h"
40 #include "empathy-contact-manager.h"
41 #include "empathy-tp-group.h"
42
43 #define DEBUG_DOMAIN "Utils"
44
45 static void regex_init (void);
46
47 gchar *
48 empathy_substring (const gchar *str,
49                   gint         start,
50                   gint         end)
51 {
52         return g_strndup (str + start, end - start);
53 }
54
55 /*
56  * Regular Expression code to match urls.
57  */
58 #define USERCHARS "-A-Za-z0-9"
59 #define PASSCHARS "-A-Za-z0-9,?;.:/!%$^*&~\"#'"
60 #define HOSTCHARS "-A-Za-z0-9"
61 #define PATHCHARS "-A-Za-z0-9_$.+!*(),;:@&=?/~#%"
62 #define SCHEME    "(news:|telnet:|nntp:|file:/|https?:|ftps?:|webcal:)"
63 #define USER      "[" USERCHARS "]+(:["PASSCHARS "]+)?"
64 #define URLPATH   "/[" PATHCHARS "]*[^]'.}>) \t\r\n,\\\"]"
65
66 static regex_t dingus[EMPATHY_REGEX_ALL];
67
68 static void
69 regex_init (void)
70 {
71         static gboolean  inited = FALSE;
72         const gchar     *expression;
73         gint             i;
74
75         if (inited) {
76                 return;
77         }
78
79         for (i = 0; i < EMPATHY_REGEX_ALL; i++) {
80                 switch (i) {
81                 case EMPATHY_REGEX_AS_IS:
82                         expression =
83                                 SCHEME "//(" USER "@)?[" HOSTCHARS ".]+"
84                                 "(:[0-9]+)?(" URLPATH ")?";
85                         break;
86                 case EMPATHY_REGEX_BROWSER:
87                         expression =
88                                 "(www|ftp)[" HOSTCHARS "]*\\.[" HOSTCHARS ".]+"
89                                 "(:[0-9]+)?(" URLPATH ")?";
90                         break;
91                 case EMPATHY_REGEX_EMAIL:
92                         expression =
93                                 "(mailto:)?[a-z0-9][a-z0-9.-]*@[a-z0-9]"
94                                 "[a-z0-9-]*(\\.[a-z0-9][a-z0-9-]*)+";
95                         break;
96                 case EMPATHY_REGEX_OTHER:
97                         expression =
98                                 "news:[-A-Z\\^_a-z{|}~!\"#$%&'()*+,./0-9;:=?`]+"
99                                 "@[" HOSTCHARS ".]+(:[0-9]+)?";
100                         break;
101                 default:
102                         /* Silence the compiler. */
103                         expression = NULL;
104                         continue;
105                 }
106
107                 memset (&dingus[i], 0, sizeof (regex_t));
108                 regcomp (&dingus[i], expression, REG_EXTENDED | REG_ICASE);
109         }
110
111         inited = TRUE;
112 }
113
114 gint
115 empathy_regex_match (EmpathyRegExType  type,
116                     const gchar     *msg,
117                     GArray          *start,
118                     GArray          *end)
119 {
120         regmatch_t matches[1];
121         gint       ret = 0;
122         gint       num_matches = 0;
123         gint       offset = 0;
124         gint       i;
125
126         g_return_val_if_fail (type >= 0 || type <= EMPATHY_REGEX_ALL, 0);
127
128         regex_init ();
129
130         while (!ret && type != EMPATHY_REGEX_ALL) {
131                 ret = regexec (&dingus[type], msg + offset, 1, matches, 0);
132                 if (ret == 0) {
133                         gint s;
134
135                         num_matches++;
136
137                         s = matches[0].rm_so + offset;
138                         offset = matches[0].rm_eo + offset;
139
140                         g_array_append_val (start, s);
141                         g_array_append_val (end, offset);
142                 }
143         }
144
145         if (type != EMPATHY_REGEX_ALL) {
146                 empathy_debug (DEBUG_DOMAIN,
147                               "Found %d matches for regex type:%d",
148                               num_matches, type);
149                 return num_matches;
150         }
151
152         /* If EMPATHY_REGEX_ALL then we run ALL regex's on the string. */
153         for (i = 0; i < EMPATHY_REGEX_ALL; i++, ret = 0) {
154                 while (!ret) {
155                         ret = regexec (&dingus[i], msg + offset, 1, matches, 0);
156                         if (ret == 0) {
157                                 gint s;
158
159                                 num_matches++;
160
161                                 s = matches[0].rm_so + offset;
162                                 offset = matches[0].rm_eo + offset;
163
164                                 g_array_append_val (start, s);
165                                 g_array_append_val (end, offset);
166                         }
167                 }
168         }
169
170         empathy_debug (DEBUG_DOMAIN,
171                       "Found %d matches for ALL regex types",
172                       num_matches);
173
174         return num_matches;
175 }
176
177 gint
178 empathy_strcasecmp (const gchar *s1,
179                    const gchar *s2)
180 {
181         return empathy_strncasecmp (s1, s2, -1);
182 }
183
184 gint
185 empathy_strncasecmp (const gchar *s1,
186                     const gchar *s2,
187                     gsize        n)
188 {
189         gchar *u1, *u2;
190         gint   ret_val;
191
192         u1 = g_utf8_casefold (s1, n);
193         u2 = g_utf8_casefold (s2, n);
194
195         ret_val = g_utf8_collate (u1, u2);
196         g_free (u1);
197         g_free (u2);
198
199         return ret_val;
200 }
201
202 /* Stolen from telepathy-glib */
203 gboolean
204 empathy_strdiff (const gchar *left, const gchar *right)
205 {
206   if ((NULL == left) != (NULL == right))
207     return TRUE;
208
209   else if (left == right)
210     return FALSE;
211
212   else
213     return (0 != strcmp (left, right));
214 }
215
216 /* Stolen from telepathy-glib */
217 static inline gboolean
218 _esc_ident_bad (gchar c, gboolean is_first)
219 {
220   return ((c < 'a' || c > 'z') &&
221           (c < 'A' || c > 'Z') &&
222           (c < '0' || c > '9' || is_first));
223 }
224
225 /* Stolen from telepathy-glib */
226 gchar *
227 empathy_escape_as_identifier (const gchar *name)
228 {
229   gboolean bad = FALSE;
230   size_t len = 0;
231   GString *op;
232   const gchar *ptr, *first_ok;
233
234   g_return_val_if_fail (name != NULL, NULL);
235
236   for (ptr = name; *ptr; ptr++)
237     {
238       if (_esc_ident_bad (*ptr, ptr == name))
239         {
240           bad = TRUE;
241           len += 3;
242         }
243       else
244         len++;
245     }
246
247   /* fast path if it's clean */
248   if (!bad)
249     return g_strdup (name);
250
251   /* If strictly less than ptr, first_ok is the first uncopied safe character.
252    */
253   first_ok = name;
254   op = g_string_sized_new (len);
255   for (ptr = name; *ptr; ptr++)
256     {
257       if (_esc_ident_bad (*ptr, ptr == name))
258         {
259           /* copy preceding safe characters if any */
260           if (first_ok < ptr)
261             {
262               g_string_append_len (op, first_ok, ptr - first_ok);
263             }
264           /* escape the unsafe character */
265           g_string_append_printf (op, "_%02x", (unsigned char)(*ptr));
266           /* restart after it */
267           first_ok = ptr + 1;
268         }
269     }
270   /* copy trailing safe characters if any */
271   if (first_ok < ptr)
272     {
273       g_string_append_len (op, first_ok, ptr - first_ok);
274     }
275   return g_string_free (op, FALSE);
276 }
277
278 gboolean
279 empathy_xml_validate (xmlDoc      *doc,
280                      const gchar *dtd_filename)
281 {
282         gchar        *path, *escaped;
283         xmlValidCtxt  cvp;
284         xmlDtd       *dtd;
285         gboolean      ret;
286
287         path = g_build_filename (DATADIR, "empathy", dtd_filename, NULL);
288
289         /* The list of valid chars is taken from libxml. */
290         escaped = xmlURIEscapeStr (path, ":@&=+$,/?;");
291
292         g_free (path);
293
294         memset (&cvp, 0, sizeof (cvp));
295         dtd = xmlParseDTD (NULL, escaped);
296         ret = xmlValidateDtd (&cvp, doc, dtd);
297
298         xmlFree (escaped);
299         xmlFreeDtd (dtd);
300
301         return ret;
302 }
303
304 xmlNodePtr
305 empathy_xml_node_get_child (xmlNodePtr   node, 
306                            const gchar *child_name)
307 {
308         xmlNodePtr l;
309
310         g_return_val_if_fail (node != NULL, NULL);
311         g_return_val_if_fail (child_name != NULL, NULL);
312
313         for (l = node->children; l; l = l->next) {
314                 if (l->name && strcmp (l->name, child_name) == 0) {
315                         return l;
316                 }
317         }
318
319         return NULL;
320 }
321
322 xmlChar *
323 empathy_xml_node_get_child_content (xmlNodePtr   node, 
324                                    const gchar *child_name)
325 {
326         xmlNodePtr l;
327
328         g_return_val_if_fail (node != NULL, NULL);
329         g_return_val_if_fail (child_name != NULL, NULL);
330
331         l = empathy_xml_node_get_child (node, child_name);
332         if (l) {
333                 return xmlNodeGetContent (l);
334         }
335                 
336         return NULL;
337 }
338
339 xmlNodePtr
340 empathy_xml_node_find_child_prop_value (xmlNodePtr   node, 
341                                        const gchar *prop_name,
342                                        const gchar *prop_value)
343 {
344         xmlNodePtr l;
345         xmlNodePtr found = NULL;
346
347         g_return_val_if_fail (node != NULL, NULL);
348         g_return_val_if_fail (prop_name != NULL, NULL);
349         g_return_val_if_fail (prop_value != NULL, NULL);
350
351         for (l = node->children; l && !found; l = l->next) {
352                 xmlChar *prop;
353
354                 if (!xmlHasProp (l, prop_name)) {
355                         continue;
356                 }
357
358                 prop = xmlGetProp (l, prop_name);
359                 if (prop && strcmp (prop, prop_value) == 0) {
360                         found = l;
361                 }
362                 
363                 xmlFree (prop);
364         }
365                 
366         return found;
367 }
368
369 guint
370 empathy_account_hash (gconstpointer key)
371 {
372         return g_str_hash (mc_account_get_unique_name (MC_ACCOUNT (key)));
373 }
374
375 gboolean
376 empathy_account_equal (gconstpointer a,
377                       gconstpointer b)
378 {
379         const gchar *name_a;
380         const gchar *name_b;
381
382         name_a = mc_account_get_unique_name (MC_ACCOUNT (a));
383         name_b = mc_account_get_unique_name (MC_ACCOUNT (b));
384
385         return g_str_equal (name_a, name_b);
386 }
387
388 MissionControl *
389 empathy_mission_control_new (void)
390 {
391         static MissionControl *mc = NULL;
392
393         if (!mc) {
394                 mc = mission_control_new (tp_get_bus ());
395                 g_object_add_weak_pointer (G_OBJECT (mc), (gpointer) &mc);
396         } else {
397                 g_object_ref (mc);
398         }
399
400         return mc;
401 }
402
403 gchar *
404 empathy_inspect_channel (McAccount *account,
405                          TpChan    *tp_chan)
406 {
407         g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
408         g_return_val_if_fail (TELEPATHY_IS_CHAN (tp_chan), NULL);
409
410         return empathy_inspect_handle (account,
411                                        tp_chan->handle,
412                                        tp_chan->handle_type);
413 }
414
415 gchar *
416 empathy_inspect_handle (McAccount *account,
417                         guint      handle,
418                         guint      handle_type)
419 {
420         MissionControl  *mc;
421         TpConn          *tp_conn;
422         GArray          *handles;
423         gchar          **names;
424         gchar           *name;
425         GError          *error;
426
427         g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
428         g_return_val_if_fail (handle != 0, NULL);
429         g_return_val_if_fail (handle_type != 0, NULL);
430
431         mc = empathy_mission_control_new ();
432         tp_conn = mission_control_get_connection (mc, account, NULL);
433         g_object_unref (mc);
434
435         if (!tp_conn) {
436                 return NULL;
437         }
438
439         /* Get the handle's name */
440         handles = g_array_new (FALSE, FALSE, sizeof (guint));
441         g_array_append_val (handles, handle);
442         if (!tp_conn_inspect_handles (DBUS_G_PROXY (tp_conn),
443                                       handle_type,
444                                       handles,
445                                       &names,
446                                       &error)) {
447                 empathy_debug (DEBUG_DOMAIN, 
448                               "Couldn't get id: %s",
449                               error ? error->message : "No error given");
450
451                 g_clear_error (&error);
452                 g_array_free (handles, TRUE);
453                 g_object_unref (tp_conn);
454                 
455                 return NULL;
456         }
457
458         g_array_free (handles, TRUE);
459         name = *names;
460         g_free (names);
461         g_object_unref (tp_conn);
462
463         return name;
464 }
465
466 void
467 empathy_call_contact (EmpathyContact *contact)
468 {
469 #ifdef HAVE_VOIP
470         MissionControl *mc;
471         McAccount      *account;
472         TpConn         *tp_conn;
473         gchar          *object_path;
474         const gchar    *bus_name;
475         TpChan         *new_chan;
476         EmpathyTpGroup *group;
477         GError         *error;
478
479         /* StreamedMedia channels must have handle=0 and handle_type=none.
480          * To call a contact we have to add him in the group interface of the
481          * channel. MissionControl will detect the channel creation and 
482          * dispatch it to the VoIP chandler automatically. */
483
484         mc = empathy_mission_control_new ();
485         account = empathy_contact_get_account (contact);
486         tp_conn = mission_control_get_connection (mc, account, NULL);
487         /* FIXME: Should be async */
488         if (!tp_conn_request_channel (DBUS_G_PROXY (tp_conn),
489                                       TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA,
490                                       TP_HANDLE_TYPE_NONE,
491                                       0,
492                                       FALSE,
493                                       &object_path,
494                                       &error)) {
495                 empathy_debug (DEBUG_DOMAIN, 
496                               "Couldn't request channel: %s",
497                               error ? error->message : "No error given");
498                 g_clear_error (&error);
499                 g_object_unref (mc);
500                 g_object_unref (tp_conn);
501                 return;
502         }
503
504         bus_name = dbus_g_proxy_get_bus_name (DBUS_G_PROXY (tp_conn));
505         new_chan = tp_chan_new (tp_get_bus (),
506                                 bus_name,
507                                 object_path,
508                                 TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA,
509                                 TP_HANDLE_TYPE_NONE,
510                                 0);
511
512         /* FIXME: group is leaked, we can't unref it directly because
513          * _add_member is async so we have to wait for it to return before
514          * finalizing the group. I think EmpathyTpGroup should ref itself
515          * when it does async calls to avoid finalizing when there is calls
516          * in fligth like that we could unref it here. */
517         group = empathy_tp_group_new (account, new_chan);
518         empathy_tp_group_add_member (group, contact, "");
519
520         g_object_unref (mc);
521         g_object_unref (tp_conn);
522         g_object_unref (new_chan);
523         g_free (object_path);
524 #endif
525 }
526