Try to load dtd and glade files from the srcdir first to aboid having to install...
[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 gboolean
203 empathy_xml_validate (xmlDoc      *doc,
204                      const gchar *dtd_filename)
205 {
206         gchar        *path, *escaped;
207         xmlValidCtxt  cvp;
208         xmlDtd       *dtd;
209         gboolean      ret;
210
211         path = g_build_filename (UNINSTALLED_DTD_DIR, dtd_filename, NULL);
212         if (!g_file_test (path, G_FILE_TEST_EXISTS)) {
213                 g_free (path);
214                 path = g_build_filename (DATADIR, "empathy", dtd_filename, NULL);
215         }
216         empathy_debug (DEBUG_DOMAIN, "Loading dtd file %s", path);
217
218         /* The list of valid chars is taken from libxml. */
219         escaped = xmlURIEscapeStr (path, ":@&=+$,/?;");
220         g_free (path);
221
222         memset (&cvp, 0, sizeof (cvp));
223         dtd = xmlParseDTD (NULL, escaped);
224         ret = xmlValidateDtd (&cvp, doc, dtd);
225
226         xmlFree (escaped);
227         xmlFreeDtd (dtd);
228
229         return ret;
230 }
231
232 xmlNodePtr
233 empathy_xml_node_get_child (xmlNodePtr   node, 
234                            const gchar *child_name)
235 {
236         xmlNodePtr l;
237
238         g_return_val_if_fail (node != NULL, NULL);
239         g_return_val_if_fail (child_name != NULL, NULL);
240
241         for (l = node->children; l; l = l->next) {
242                 if (l->name && strcmp (l->name, child_name) == 0) {
243                         return l;
244                 }
245         }
246
247         return NULL;
248 }
249
250 xmlChar *
251 empathy_xml_node_get_child_content (xmlNodePtr   node, 
252                                    const gchar *child_name)
253 {
254         xmlNodePtr l;
255
256         g_return_val_if_fail (node != NULL, NULL);
257         g_return_val_if_fail (child_name != NULL, NULL);
258
259         l = empathy_xml_node_get_child (node, child_name);
260         if (l) {
261                 return xmlNodeGetContent (l);
262         }
263                 
264         return NULL;
265 }
266
267 xmlNodePtr
268 empathy_xml_node_find_child_prop_value (xmlNodePtr   node, 
269                                        const gchar *prop_name,
270                                        const gchar *prop_value)
271 {
272         xmlNodePtr l;
273         xmlNodePtr found = NULL;
274
275         g_return_val_if_fail (node != NULL, NULL);
276         g_return_val_if_fail (prop_name != NULL, NULL);
277         g_return_val_if_fail (prop_value != NULL, NULL);
278
279         for (l = node->children; l && !found; l = l->next) {
280                 xmlChar *prop;
281
282                 if (!xmlHasProp (l, prop_name)) {
283                         continue;
284                 }
285
286                 prop = xmlGetProp (l, prop_name);
287                 if (prop && strcmp (prop, prop_value) == 0) {
288                         found = l;
289                 }
290                 
291                 xmlFree (prop);
292         }
293                 
294         return found;
295 }
296
297 guint
298 empathy_account_hash (gconstpointer key)
299 {
300         g_return_val_if_fail (MC_IS_ACCOUNT (key), 0);
301
302         return g_str_hash (mc_account_get_unique_name (MC_ACCOUNT (key)));
303 }
304
305 gboolean
306 empathy_account_equal (gconstpointer a,
307                        gconstpointer b)
308 {
309         const gchar *name_a;
310         const gchar *name_b;
311
312         g_return_val_if_fail (MC_IS_ACCOUNT (a), FALSE);
313         g_return_val_if_fail (MC_IS_ACCOUNT (b), FALSE);
314
315         name_a = mc_account_get_unique_name (MC_ACCOUNT (a));
316         name_b = mc_account_get_unique_name (MC_ACCOUNT (b));
317
318         return g_str_equal (name_a, name_b);
319 }
320
321 MissionControl *
322 empathy_mission_control_new (void)
323 {
324         static MissionControl *mc = NULL;
325
326         if (!mc) {
327                 mc = mission_control_new (tp_get_bus ());
328                 g_object_add_weak_pointer (G_OBJECT (mc), (gpointer) &mc);
329         } else {
330                 g_object_ref (mc);
331         }
332
333         return mc;
334 }
335
336 gchar *
337 empathy_inspect_channel (McAccount *account,
338                          TpChan    *tp_chan)
339 {
340         g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
341         g_return_val_if_fail (TELEPATHY_IS_CHAN (tp_chan), NULL);
342
343         return empathy_inspect_handle (account,
344                                        tp_chan->handle,
345                                        tp_chan->handle_type);
346 }
347
348 gchar *
349 empathy_inspect_handle (McAccount *account,
350                         guint      handle,
351                         guint      handle_type)
352 {
353         MissionControl  *mc;
354         TpConn          *tp_conn;
355         GArray          *handles;
356         gchar          **names;
357         gchar           *name;
358         GError          *error = NULL;
359
360         g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
361         g_return_val_if_fail (handle != 0, NULL);
362         g_return_val_if_fail (handle_type != 0, NULL);
363
364         mc = empathy_mission_control_new ();
365         tp_conn = mission_control_get_connection (mc, account, NULL);
366         g_object_unref (mc);
367
368         if (!tp_conn) {
369                 return NULL;
370         }
371
372         /* Get the handle's name */
373         handles = g_array_new (FALSE, FALSE, sizeof (guint));
374         g_array_append_val (handles, handle);
375         if (!tp_conn_inspect_handles (DBUS_G_PROXY (tp_conn),
376                                       handle_type,
377                                       handles,
378                                       &names,
379                                       &error)) {
380                 empathy_debug (DEBUG_DOMAIN, 
381                               "Couldn't get id: %s",
382                               error ? error->message : "No error given");
383
384                 g_clear_error (&error);
385                 g_array_free (handles, TRUE);
386                 g_object_unref (tp_conn);
387                 
388                 return NULL;
389         }
390
391         g_array_free (handles, TRUE);
392         name = *names;
393         g_free (names);
394         g_object_unref (tp_conn);
395
396         return name;
397 }
398
399 void
400 empathy_call_contact (EmpathyContact *contact)
401 {
402 #ifdef HAVE_VOIP
403         MissionControl *mc;
404         McAccount      *account;
405         TpConn         *tp_conn;
406         gchar          *object_path;
407         const gchar    *bus_name;
408         TpChan         *new_chan;
409         EmpathyTpGroup *group;
410         GError         *error = NULL;
411
412         g_return_if_fail (EMPATHY_IS_CONTACT (contact));
413
414         /* StreamedMedia channels must have handle=0 and handle_type=none.
415          * To call a contact we have to add him in the group interface of the
416          * channel. MissionControl will detect the channel creation and 
417          * dispatch it to the VoIP chandler automatically. */
418
419         mc = empathy_mission_control_new ();
420         account = empathy_contact_get_account (contact);
421         tp_conn = mission_control_get_connection (mc, account, NULL);
422         /* FIXME: Should be async */
423         if (!tp_conn_request_channel (DBUS_G_PROXY (tp_conn),
424                                       TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA,
425                                       TP_HANDLE_TYPE_NONE,
426                                       0,
427                                       FALSE,
428                                       &object_path,
429                                       &error)) {
430                 empathy_debug (DEBUG_DOMAIN, 
431                               "Couldn't request channel: %s",
432                               error ? error->message : "No error given");
433                 g_clear_error (&error);
434                 g_object_unref (mc);
435                 g_object_unref (tp_conn);
436                 return;
437         }
438
439         bus_name = dbus_g_proxy_get_bus_name (DBUS_G_PROXY (tp_conn));
440         new_chan = tp_chan_new (tp_get_bus (),
441                                 bus_name,
442                                 object_path,
443                                 TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA,
444                                 TP_HANDLE_TYPE_NONE,
445                                 0);
446
447         group = empathy_tp_group_new (account, new_chan);
448         empathy_tp_group_add_member (group, contact, "");
449
450         g_object_unref (group);
451         g_object_unref (mc);
452         g_object_unref (tp_conn);
453         g_object_unref (new_chan);
454         g_free (object_path);
455 #endif
456 }
457
458 void
459 empathy_chat_with_contact (EmpathyContact  *contact)
460 {
461         MissionControl *mc;
462
463         mc = empathy_mission_control_new ();
464         mission_control_request_channel (mc,
465                                          empathy_contact_get_account (contact),
466                                          TP_IFACE_CHANNEL_TYPE_TEXT,
467                                          empathy_contact_get_handle (contact),
468                                          TP_HANDLE_TYPE_CONTACT,
469                                          NULL, NULL);
470         g_object_unref (mc);
471 }
472
473 void
474 empathy_chat_with_contact_id (McAccount *account, const gchar *contact_id)
475 {
476         MissionControl *mc;
477
478         mc = empathy_mission_control_new ();
479         mission_control_request_channel_with_string_handle (mc,
480                                                             account,
481                                                             TP_IFACE_CHANNEL_TYPE_TEXT,
482                                                             contact_id,
483                                                             TP_HANDLE_TYPE_CONTACT,
484                                                             NULL, NULL);
485         g_object_unref (mc);
486 }
487