]> git.0d.be Git - empathy.git/blob - libempathy/empathy-utils.c
Add missing telepathy-glib-dbus.h includes
[empathy.git] / libempathy / empathy-utils.c
1 /*
2  * Copyright (C) 2003-2007 Imendio AB
3  * Copyright (C) 2007-2011 Collabora Ltd.
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of the
8  * License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public
16  * License along with this program; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA  02110-1301  USA
19  *
20  * Authors: Richard Hult <richard@imendio.com>
21  *          Martyn Russell <martyn@imendio.com>
22  *          Xavier Claessens <xclaesse@gmail.com>
23  *
24  * Some snippets are taken from GnuTLS 2.8.6, which is distributed under the
25  * same GNU Lesser General Public License 2.1 (or later) version. See
26  * empathy_get_x509_certified_hostname ().
27  */
28
29 #include "config.h"
30 #include "empathy-utils.h"
31
32 #include <glib/gi18n-lib.h>
33 #include <dbus/dbus-protocol.h>
34 #include <math.h>
35 #include <telepathy-glib/telepathy-glib-dbus.h>
36
37 #include "empathy-client-factory.h"
38 #include "extensions.h"
39
40 #include <math.h>
41
42 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
43 #include "empathy-debug.h"
44
45 /* Translation between presence types and string */
46 static struct {
47   const gchar *name;
48   TpConnectionPresenceType type;
49 } presence_types[] = {
50   { "available", TP_CONNECTION_PRESENCE_TYPE_AVAILABLE },
51   { "busy",      TP_CONNECTION_PRESENCE_TYPE_BUSY },
52   { "away",      TP_CONNECTION_PRESENCE_TYPE_AWAY },
53   { "ext_away",  TP_CONNECTION_PRESENCE_TYPE_EXTENDED_AWAY },
54   { "hidden",    TP_CONNECTION_PRESENCE_TYPE_HIDDEN },
55   { "offline",   TP_CONNECTION_PRESENCE_TYPE_OFFLINE },
56   { "unset",     TP_CONNECTION_PRESENCE_TYPE_UNSET },
57   { "unknown",   TP_CONNECTION_PRESENCE_TYPE_UNKNOWN },
58   { "error",     TP_CONNECTION_PRESENCE_TYPE_ERROR },
59   /* alternative names */
60   { "dnd",      TP_CONNECTION_PRESENCE_TYPE_BUSY },
61   { "brb",      TP_CONNECTION_PRESENCE_TYPE_AWAY },
62   { "xa",       TP_CONNECTION_PRESENCE_TYPE_EXTENDED_AWAY },
63   { NULL, },
64 };
65
66 static gboolean
67 properties_contains (gchar **list,
68                      gint length,
69                      const gchar *property);
70
71 static gboolean
72 check_writeable_property (TpConnection *connection,
73                           FolksIndividual *individual,
74                           gchar *property);
75
76 void
77 empathy_init (void)
78 {
79   static gboolean initialized = FALSE;
80   TpAccountManager *am;
81   EmpathyClientFactory *factory;
82
83   if (initialized)
84     return;
85
86   g_type_init ();
87
88   /* Setup gettext */
89   bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
90   bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
91
92   /* Setup debug output for empathy and telepathy-glib */
93   if (g_getenv ("EMPATHY_TIMING") != NULL)
94     g_log_set_default_handler (tp_debug_timestamped_log_handler, NULL);
95
96   empathy_debug_set_flags (g_getenv ("EMPATHY_DEBUG"));
97   tp_debug_divert_messages (g_getenv ("EMPATHY_LOGFILE"));
98
99   emp_cli_init ();
100
101   initialized = TRUE;
102
103   factory = empathy_client_factory_dup ();
104   am = tp_account_manager_new_with_factory (TP_SIMPLE_CLIENT_FACTORY (factory));
105   tp_account_manager_set_default (am);
106
107   g_object_unref (factory);
108   g_object_unref (am);
109 }
110
111 xmlNodePtr
112 empathy_xml_node_get_child (xmlNodePtr   node,
113     const gchar *child_name)
114 {
115   xmlNodePtr l;
116
117   g_return_val_if_fail (node != NULL, NULL);
118   g_return_val_if_fail (child_name != NULL, NULL);
119
120   for (l = node->children; l; l = l->next)
121     {
122       if (l->name && strcmp ((const gchar *) l->name, child_name) == 0)
123         return l;
124     }
125
126   return NULL;
127 }
128
129 xmlChar *
130 empathy_xml_node_get_child_content (xmlNodePtr   node,
131     const gchar *child_name)
132 {
133   xmlNodePtr l;
134
135   g_return_val_if_fail (node != NULL, NULL);
136   g_return_val_if_fail (child_name != NULL, NULL);
137
138   l = empathy_xml_node_get_child (node, child_name);
139   if (l != NULL)
140     return xmlNodeGetContent (l);
141
142   return NULL;
143 }
144
145 xmlNodePtr
146 empathy_xml_node_find_child_prop_value (xmlNodePtr   node,
147     const gchar *prop_name,
148     const gchar *prop_value)
149 {
150   xmlNodePtr l;
151   xmlNodePtr found = NULL;
152
153   g_return_val_if_fail (node != NULL, NULL);
154   g_return_val_if_fail (prop_name != NULL, NULL);
155   g_return_val_if_fail (prop_value != NULL, NULL);
156
157   for (l = node->children; l && !found; l = l->next)
158     {
159       xmlChar *prop;
160
161       if (!xmlHasProp (l, (const xmlChar *) prop_name))
162         continue;
163
164       prop = xmlGetProp (l, (const xmlChar *) prop_name);
165       if (prop && strcmp ((const gchar *) prop, prop_value) == 0)
166         found = l;
167
168       xmlFree (prop);
169     }
170
171   return found;
172 }
173
174 const gchar *
175 empathy_presence_get_default_message (TpConnectionPresenceType presence)
176 {
177   switch (presence)
178     {
179       case TP_CONNECTION_PRESENCE_TYPE_AVAILABLE:
180         return _("Available");
181       case TP_CONNECTION_PRESENCE_TYPE_BUSY:
182         return _("Busy");
183       case TP_CONNECTION_PRESENCE_TYPE_AWAY:
184       case TP_CONNECTION_PRESENCE_TYPE_EXTENDED_AWAY:
185         return _("Away");
186       case TP_CONNECTION_PRESENCE_TYPE_HIDDEN:
187         return _("Invisible");
188       case TP_CONNECTION_PRESENCE_TYPE_OFFLINE:
189         return _("Offline");
190       case TP_CONNECTION_PRESENCE_TYPE_UNKNOWN:
191         /* translators: presence type is unknown */
192         return C_("presence", "Unknown");
193       case TP_CONNECTION_PRESENCE_TYPE_UNSET:
194       case TP_CONNECTION_PRESENCE_TYPE_ERROR:
195       default:
196         return NULL;
197     }
198
199   return NULL;
200 }
201
202 const gchar *
203 empathy_presence_to_str (TpConnectionPresenceType presence)
204 {
205   int i;
206
207   for (i = 0 ; presence_types[i].name != NULL; i++)
208     if (presence == presence_types[i].type)
209       return presence_types[i].name;
210
211   return NULL;
212 }
213
214 TpConnectionPresenceType
215 empathy_presence_from_str (const gchar *str)
216 {
217   int i;
218
219   for (i = 0 ; presence_types[i].name != NULL; i++)
220     if (!tp_strdiff (str, presence_types[i].name))
221       return presence_types[i].type;
222
223   return TP_CONNECTION_PRESENCE_TYPE_UNSET;
224 }
225
226 static const gchar *
227 empathy_status_reason_get_default_message (TpConnectionStatusReason reason)
228 {
229   switch (reason)
230     {
231       case TP_CONNECTION_STATUS_REASON_NONE_SPECIFIED:
232         return _("No reason specified");
233       case TP_CONNECTION_STATUS_REASON_REQUESTED:
234         return _("Status is set to offline");
235       case TP_CONNECTION_STATUS_REASON_NETWORK_ERROR:
236         return _("Network error");
237       case TP_CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED:
238         return _("Authentication failed");
239       case TP_CONNECTION_STATUS_REASON_ENCRYPTION_ERROR:
240         return _("Encryption error");
241       case TP_CONNECTION_STATUS_REASON_NAME_IN_USE:
242         return _("Name in use");
243       case TP_CONNECTION_STATUS_REASON_CERT_NOT_PROVIDED:
244         return _("Certificate not provided");
245       case TP_CONNECTION_STATUS_REASON_CERT_UNTRUSTED:
246         return _("Certificate untrusted");
247       case TP_CONNECTION_STATUS_REASON_CERT_EXPIRED:
248         return _("Certificate expired");
249       case TP_CONNECTION_STATUS_REASON_CERT_NOT_ACTIVATED:
250         return _("Certificate not activated");
251       case TP_CONNECTION_STATUS_REASON_CERT_HOSTNAME_MISMATCH:
252         return _("Certificate hostname mismatch");
253       case TP_CONNECTION_STATUS_REASON_CERT_FINGERPRINT_MISMATCH:
254         return _("Certificate fingerprint mismatch");
255       case TP_CONNECTION_STATUS_REASON_CERT_SELF_SIGNED:
256         return _("Certificate self-signed");
257       case TP_CONNECTION_STATUS_REASON_CERT_OTHER_ERROR:
258         return _("Certificate error");
259       default:
260         return _("Unknown reason");
261     }
262 }
263
264 static GHashTable *
265 create_errors_to_message_hash (void)
266 {
267   GHashTable *errors;
268
269   errors = g_hash_table_new (g_str_hash, g_str_equal);
270   g_hash_table_insert (errors, TP_ERROR_STR_NETWORK_ERROR, _("Network error"));
271   g_hash_table_insert (errors, TP_ERROR_STR_AUTHENTICATION_FAILED,
272     _("Authentication failed"));
273   g_hash_table_insert (errors, TP_ERROR_STR_ENCRYPTION_ERROR,
274     _("Encryption error"));
275   g_hash_table_insert (errors, TP_ERROR_STR_CERT_NOT_PROVIDED,
276     _("Certificate not provided"));
277   g_hash_table_insert (errors, TP_ERROR_STR_CERT_UNTRUSTED,
278     _("Certificate untrusted"));
279   g_hash_table_insert (errors, TP_ERROR_STR_CERT_EXPIRED,
280     _("Certificate expired"));
281   g_hash_table_insert (errors, TP_ERROR_STR_CERT_NOT_ACTIVATED,
282     _("Certificate not activated"));
283   g_hash_table_insert (errors, TP_ERROR_STR_CERT_HOSTNAME_MISMATCH,
284     _("Certificate hostname mismatch"));
285   g_hash_table_insert (errors, TP_ERROR_STR_CERT_FINGERPRINT_MISMATCH,
286     _("Certificate fingerprint mismatch"));
287   g_hash_table_insert (errors, TP_ERROR_STR_CERT_SELF_SIGNED,
288     _("Certificate self-signed"));
289   g_hash_table_insert (errors, TP_ERROR_STR_CANCELLED,
290     _("Status is set to offline"));
291   g_hash_table_insert (errors, TP_ERROR_STR_ENCRYPTION_NOT_AVAILABLE,
292     _("Encryption is not available"));
293   g_hash_table_insert (errors, TP_ERROR_STR_CERT_INVALID,
294     _("Certificate is invalid"));
295   g_hash_table_insert (errors, TP_ERROR_STR_CONNECTION_REFUSED,
296     _("Connection has been refused"));
297   g_hash_table_insert (errors, TP_ERROR_STR_CONNECTION_FAILED,
298     _("Connection can't be established"));
299   g_hash_table_insert (errors, TP_ERROR_STR_CONNECTION_LOST,
300     _("Connection has been lost"));
301   g_hash_table_insert (errors, TP_ERROR_STR_ALREADY_CONNECTED,
302     _("This account is already connected to the server"));
303   g_hash_table_insert (errors, TP_ERROR_STR_CONNECTION_REPLACED,
304     _("Connection has been replaced by a new connection using the "
305     "same resource"));
306   g_hash_table_insert (errors, TP_ERROR_STR_REGISTRATION_EXISTS,
307     _("The account already exists on the server"));
308   g_hash_table_insert (errors, TP_ERROR_STR_SERVICE_BUSY,
309     _("Server is currently too busy to handle the connection"));
310   g_hash_table_insert (errors, TP_ERROR_STR_CERT_REVOKED,
311     _("Certificate has been revoked"));
312   g_hash_table_insert (errors, TP_ERROR_STR_CERT_INSECURE,
313     _("Certificate uses an insecure cipher algorithm or is "
314     "cryptographically weak"));
315   g_hash_table_insert (errors, TP_ERROR_STR_CERT_LIMIT_EXCEEDED,
316     _("The length of the server certificate, or the depth of the "
317     "server certificate chain, exceed the limits imposed by the "
318     "cryptography library"));
319   g_hash_table_insert (errors, TP_ERROR_STR_SOFTWARE_UPGRADE_REQUIRED,
320     _("Your software is too old"));
321   g_hash_table_insert (errors, DBUS_ERROR_NO_REPLY,
322     _("Internal error"));
323
324   return errors;
325 }
326
327 static const gchar *
328 empathy_dbus_error_name_get_default_message  (const gchar *error)
329 {
330   static GHashTable *errors_to_message = NULL;
331
332   if (error == NULL)
333     return NULL;
334
335   if (G_UNLIKELY (errors_to_message == NULL))
336     errors_to_message = create_errors_to_message_hash ();
337
338   return g_hash_table_lookup (errors_to_message, error);
339 }
340
341 const gchar *
342 empathy_account_get_error_message (TpAccount *account,
343     gboolean *user_requested)
344 {
345   const gchar *dbus_error;
346   const gchar *message;
347   const GHashTable *details = NULL;
348   TpConnectionStatusReason reason;
349
350   dbus_error = tp_account_get_detailed_error (account, &details);
351
352   if (user_requested != NULL)
353     {
354       if (tp_asv_get_boolean (details, "user-requested", NULL))
355         *user_requested = TRUE;
356       else
357         *user_requested = FALSE;
358     }
359
360   message = empathy_dbus_error_name_get_default_message (dbus_error);
361   if (message != NULL)
362     return message;
363
364   tp_account_get_connection_status (account, &reason);
365
366   DEBUG ("Don't understand error '%s'; fallback to the status reason (%u)",
367     dbus_error, reason);
368
369   return empathy_status_reason_get_default_message (reason);
370 }
371
372 gchar *
373 empathy_file_lookup (const gchar *filename, const gchar *subdir)
374 {
375   gchar *path;
376
377   if (subdir == NULL)
378     subdir = ".";
379
380   path = g_build_filename (g_getenv ("EMPATHY_SRCDIR"), subdir, filename, NULL);
381   if (!g_file_test (path, G_FILE_TEST_EXISTS))
382     {
383       g_free (path);
384       path = g_build_filename (DATADIR, "empathy", filename, NULL);
385     }
386
387   return path;
388 }
389
390 gint
391 empathy_uint_compare (gconstpointer a,
392     gconstpointer b)
393 {
394   return *(guint *) a - *(guint *) b;
395 }
396
397 GType
398 empathy_type_dbus_ao (void)
399 {
400   static GType t = 0;
401
402   if (G_UNLIKELY (t == 0))
403      t = dbus_g_type_get_collection ("GPtrArray", DBUS_TYPE_G_OBJECT_PATH);
404
405   return t;
406 }
407
408 gboolean
409 empathy_account_manager_get_accounts_connected (gboolean *connecting)
410 {
411   TpAccountManager *manager;
412   GList *accounts, *l;
413   gboolean out_connecting = FALSE;
414   gboolean out_connected = FALSE;
415
416   manager = tp_account_manager_dup ();
417
418   if (G_UNLIKELY (!tp_account_manager_is_prepared (manager,
419           TP_ACCOUNT_MANAGER_FEATURE_CORE)))
420     g_critical (G_STRLOC ": %s called before AccountManager ready", G_STRFUNC);
421
422   accounts = tp_account_manager_dup_valid_accounts (manager);
423
424   for (l = accounts; l != NULL; l = l->next)
425     {
426       TpConnectionStatus s = tp_account_get_connection_status (
427           TP_ACCOUNT (l->data), NULL);
428
429       if (s == TP_CONNECTION_STATUS_CONNECTING)
430         out_connecting = TRUE;
431       else if (s == TP_CONNECTION_STATUS_CONNECTED)
432         out_connected = TRUE;
433
434       if (out_connecting && out_connected)
435         break;
436     }
437
438   g_list_free_full (accounts, g_object_unref);
439   g_object_unref (manager);
440
441   if (connecting != NULL)
442     *connecting = out_connecting;
443
444   return out_connected;
445 }
446
447 /* Translate Folks' general presence type to the Tp presence type */
448 TpConnectionPresenceType
449 empathy_folks_presence_type_to_tp (FolksPresenceType type)
450 {
451   return (TpConnectionPresenceType) type;
452 }
453
454 /* Returns TRUE if the given Individual contains a TpContact */
455 gboolean
456 empathy_folks_individual_contains_contact (FolksIndividual *individual)
457 {
458   GeeSet *personas;
459   GeeIterator *iter;
460   gboolean retval = FALSE;
461
462   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), FALSE);
463
464   personas = folks_individual_get_personas (individual);
465   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
466   while (!retval && gee_iterator_next (iter))
467     {
468       FolksPersona *persona = gee_iterator_get (iter);
469       TpContact *contact = NULL;
470
471       if (empathy_folks_persona_is_interesting (persona))
472         contact = tpf_persona_get_contact (TPF_PERSONA (persona));
473
474       g_clear_object (&persona);
475
476       if (contact != NULL)
477         retval = TRUE;
478     }
479   g_clear_object (&iter);
480
481   return retval;
482 }
483
484 /* TODO: this needs to be eliminated (and replaced in some cases with user
485  * prompts) when we break the assumption that FolksIndividuals are 1:1 with
486  * TpContacts */
487
488 /* Retrieve the EmpathyContact corresponding to the first TpContact contained
489  * within the given Individual. Note that this is a temporary convenience. See
490  * the TODO above. */
491 EmpathyContact *
492 empathy_contact_dup_from_folks_individual (FolksIndividual *individual)
493 {
494   GeeSet *personas;
495   GeeIterator *iter;
496   EmpathyContact *contact = NULL;
497
498   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
499
500   personas = folks_individual_get_personas (individual);
501   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
502   while (gee_iterator_next (iter) && (contact == NULL))
503     {
504       TpfPersona *persona = gee_iterator_get (iter);
505
506       if (empathy_folks_persona_is_interesting (FOLKS_PERSONA (persona)))
507         {
508           TpContact *tp_contact;
509
510           tp_contact = tpf_persona_get_contact (persona);
511           if (tp_contact != NULL)
512             {
513               contact = empathy_contact_dup_from_tp_contact (tp_contact);
514               empathy_contact_set_persona (contact, FOLKS_PERSONA (persona));
515             }
516         }
517       g_clear_object (&persona);
518     }
519   g_clear_object (&iter);
520
521   if (contact == NULL)
522     {
523       DEBUG ("Can't create an EmpathyContact for Individual %s",
524           folks_individual_get_id (individual));
525     }
526
527   return contact;
528 }
529
530 TpChannelGroupChangeReason
531 tp_channel_group_change_reason_from_folks_groups_change_reason (
532     FolksGroupDetailsChangeReason reason)
533 {
534   return (TpChannelGroupChangeReason) reason;
535 }
536
537 TpfPersonaStore *
538 empathy_dup_persona_store_for_connection (TpConnection *connection)
539 {
540   FolksBackendStore *backend_store;
541   FolksBackend *backend;
542   TpfPersonaStore *result = NULL;
543
544   backend_store = folks_backend_store_dup ();
545   backend = folks_backend_store_dup_backend_by_name (backend_store,
546       "telepathy");
547   if (backend != NULL)
548     {
549       GeeMap *stores_map;
550       GeeMapIterator *iter;
551
552       stores_map = folks_backend_get_persona_stores (backend);
553       iter = gee_map_map_iterator (stores_map);
554       while (gee_map_iterator_next (iter))
555         {
556           TpfPersonaStore *persona_store = gee_map_iterator_get_value (iter);
557           TpAccount *account;
558           TpConnection *conn_cur;
559
560           account = tpf_persona_store_get_account (persona_store);
561           conn_cur = tp_account_get_connection (account);
562           if (conn_cur == connection)
563             result = persona_store;
564         }
565       g_clear_object (&iter);
566     }
567
568   g_object_unref (backend);
569   g_object_unref (backend_store);
570
571   return result;
572 }
573
574 gboolean
575 empathy_connection_can_add_personas (TpConnection *connection)
576 {
577   gboolean retval;
578   FolksPersonaStore *persona_store;
579
580   g_return_val_if_fail (TP_IS_CONNECTION (connection), FALSE);
581
582   if (tp_connection_get_status (connection, NULL) !=
583           TP_CONNECTION_STATUS_CONNECTED)
584       return FALSE;
585
586   persona_store = FOLKS_PERSONA_STORE (
587       empathy_dup_persona_store_for_connection (connection));
588
589   retval = (folks_persona_store_get_can_add_personas (persona_store) ==
590       FOLKS_MAYBE_BOOL_TRUE);
591
592   g_clear_object (&persona_store);
593
594   return retval;
595 }
596
597 gboolean
598 empathy_connection_can_alias_personas (TpConnection *connection,
599                                        FolksIndividual *individual)
600 {
601   gboolean retval;
602
603   g_return_val_if_fail (TP_IS_CONNECTION (connection), FALSE);
604
605   if (tp_connection_get_status (connection, NULL) !=
606           TP_CONNECTION_STATUS_CONNECTED)
607       return FALSE;
608
609   retval = check_writeable_property (connection, individual, "alias");
610
611   return retval;
612 }
613
614 gboolean
615 empathy_connection_can_group_personas (TpConnection *connection,
616                                        FolksIndividual *individual)
617 {
618   gboolean retval;
619
620   g_return_val_if_fail (TP_IS_CONNECTION (connection), FALSE);
621
622   if (tp_connection_get_status (connection, NULL) !=
623           TP_CONNECTION_STATUS_CONNECTED)
624       return FALSE;
625
626   retval = check_writeable_property (connection, individual, "groups");
627
628   return retval;
629 }
630
631 gboolean
632 empathy_folks_persona_is_interesting (FolksPersona *persona)
633 {
634   /* We're not interested in non-Telepathy personas */
635   if (!TPF_IS_PERSONA (persona))
636     return FALSE;
637
638   /* We're not interested in user personas which haven't been added to the
639    * contact list (see bgo#637151). */
640   if (folks_persona_get_is_user (persona) &&
641       !tpf_persona_get_is_in_contact_list (TPF_PERSONA (persona)))
642     {
643       return FALSE;
644     }
645
646   return TRUE;
647 }
648
649 gchar *
650 empathy_get_x509_certificate_hostname (gnutls_x509_crt_t cert)
651 {
652   gchar dns_name[256];
653   gsize dns_name_size;
654   gint idx;
655   gint res = 0;
656
657   /* this snippet is taken from GnuTLS.
658    * see gnutls/lib/x509/rfc2818_hostname.c
659    */
660   for (idx = 0; res >= 0; idx++)
661     {
662       dns_name_size = sizeof (dns_name);
663       res = gnutls_x509_crt_get_subject_alt_name (cert, idx,
664           dns_name, &dns_name_size, NULL);
665
666       if (res == GNUTLS_SAN_DNSNAME || res == GNUTLS_SAN_IPADDRESS)
667         return g_strndup (dns_name, dns_name_size);
668     }
669
670   dns_name_size = sizeof (dns_name);
671   res = gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_X520_COMMON_NAME,
672       0, 0, dns_name, &dns_name_size);
673
674   if (res >= 0)
675     return g_strndup (dns_name, dns_name_size);
676
677   return NULL;
678 }
679
680 gchar *
681 empathy_format_currency (gint amount,
682     guint scale,
683     const gchar *currency)
684 {
685 #define MINUS "\342\210\222"
686 #define EURO "\342\202\254"
687 #define YEN "\302\245"
688 #define POUND "\302\243"
689
690   /* localised representations of currency */
691   /* FIXME: check these, especially negatives and decimals */
692   static const struct {
693     const char *currency;
694     const char *positive;
695     const char *negative;
696     const char *decimal;
697   } currencies[] = {
698     /* sym   positive    negative          decimal */
699     { "EUR", EURO "%s",  MINUS EURO "%s",  "." },
700     { "USD", "$%s",      MINUS "$%s",      "." },
701     { "JPY", YEN "%s"    MINUS YEN "%s",   "." },
702     { "GBP", POUND "%s", MINUS POUND "%s", "." },
703     { "PLN", "%s zl",    MINUS "%s zl",    "." },
704     { "BRL", "R$%s",     MINUS "R$%s",     "." },
705     { "SEK", "%s kr",    MINUS "%s kr",    "." },
706     { "DKK", "kr %s",    "kr " MINUS "%s", "." },
707     { "HKD", "$%s",      MINUS "$%s",      "." },
708     { "CHF", "%s Fr.",   MINUS "%s Fr.",   "." },
709     { "NOK", "kr %s",    "kr" MINUS "%s",  "," },
710     { "CAD", "$%s",      MINUS "$%s",      "." },
711     { "TWD", "$%s",      MINUS "$%s",      "." },
712     { "AUD", "$%s",      MINUS "$%s",      "." },
713   };
714
715   const char *positive = "%s";
716   const char *negative = MINUS "%s";
717   const char *decimal = ".";
718   char *fmt_amount, *money;
719   guint i;
720
721   /* get the localised currency format */
722   for (i = 0; i < G_N_ELEMENTS (currencies); i++)
723     {
724       if (!tp_strdiff (currency, currencies[i].currency))
725         {
726           positive = currencies[i].positive;
727           negative = currencies[i].negative;
728           decimal = currencies[i].decimal;
729           break;
730         }
731     }
732
733   /* format the amount using the scale */
734   if (scale == 0)
735     {
736       /* no decimal point required */
737       fmt_amount = g_strdup_printf ("%d", amount);
738     }
739   else
740     {
741       /* don't use floating point arithmatic, it's noisy;
742        * we take the absolute values, because we want the minus
743        * sign to appear before the $ */
744       int divisor = pow (10, scale);
745       int dollars = abs (amount / divisor);
746       int cents = abs (amount % divisor);
747
748       fmt_amount = g_strdup_printf ("%d%s%0*d",
749         dollars, decimal, scale, cents);
750     }
751
752   money = g_strdup_printf (amount < 0 ? negative : positive, fmt_amount);
753   g_free (fmt_amount);
754
755   return money;
756 }
757
758 /* Return the TpContact on @conn associated with @individual, if any */
759 TpContact *
760 empathy_get_tp_contact_for_individual (FolksIndividual *individual,
761     TpConnection *conn)
762 {
763   TpContact *contact = NULL;
764   GeeSet *personas;
765   GeeIterator *iter;
766
767   personas = folks_individual_get_personas (individual);
768   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
769   while (contact == NULL && gee_iterator_next (iter))
770     {
771       TpfPersona *persona = gee_iterator_get (iter);
772       TpConnection *contact_conn;
773       TpContact *contact_cur = NULL;
774
775       if (TPF_IS_PERSONA (persona))
776         {
777           contact_cur = tpf_persona_get_contact (persona);
778           if (contact_cur != NULL)
779             {
780               contact_conn = tp_contact_get_connection (contact_cur);
781
782               if (!tp_strdiff (tp_proxy_get_object_path (contact_conn),
783                     tp_proxy_get_object_path (conn)))
784                 contact = contact_cur;
785             }
786         }
787
788       g_clear_object (&persona);
789     }
790   g_clear_object (&iter);
791
792   return contact;
793 }
794
795 static gboolean
796 properties_contains (gchar **list,
797                      gint length,
798                      const gchar *property)
799 {
800   gint i;
801
802   for (i = 0; i < length; i++)
803     {
804       if (!tp_strdiff (list[i], property))
805         return TRUE;
806     }
807
808   return FALSE;
809 }
810
811 static gboolean
812 check_writeable_property (TpConnection *connection,
813                           FolksIndividual *individual,
814                           gchar *property)
815 {
816   gchar **properties;
817   gint prop_len;
818   gboolean retval = FALSE;
819   GeeSet *personas;
820   GeeIterator *iter;
821   FolksPersonaStore *persona_store;
822
823   persona_store = FOLKS_PERSONA_STORE (
824       empathy_dup_persona_store_for_connection (connection));
825
826   properties =
827     folks_persona_store_get_always_writeable_properties (persona_store,
828                                                          &prop_len);
829   retval = properties_contains (properties, prop_len, property);
830   if (retval == TRUE)
831     goto out;
832
833   /* Lets see if the Individual contains a Persona with the given property */
834   personas = folks_individual_get_personas (individual);
835   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
836   while (!retval && gee_iterator_next (iter))
837     {
838       FolksPersona *persona = gee_iterator_get (iter);
839
840       properties =
841         folks_persona_get_writeable_properties (persona, &prop_len);
842       retval = properties_contains (properties, prop_len, property);
843
844       g_clear_object (&persona);
845
846       if (retval == TRUE)
847         break;
848     }
849   g_clear_object (&iter);
850
851 out:
852   g_clear_object (&persona_store);
853   return retval;
854 }
855
856 /* Calculate whether the Individual can do audio or video calls.
857  * FIXME: We can remove this once libfolks has grown capabilities support
858  * again: bgo#626179. */
859 void
860 empathy_individual_can_audio_video_call (FolksIndividual *individual,
861     gboolean *can_audio_call,
862     gboolean *can_video_call,
863     EmpathyContact **out_contact)
864 {
865   GeeSet *personas;
866   GeeIterator *iter;
867   gboolean can_audio = FALSE, can_video = FALSE;
868
869   personas = folks_individual_get_personas (individual);
870   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
871   while (gee_iterator_next (iter))
872     {
873       FolksPersona *persona = gee_iterator_get (iter);
874       TpContact *tp_contact;
875
876       if (!empathy_folks_persona_is_interesting (persona))
877         goto while_finish;
878
879       tp_contact = tpf_persona_get_contact (TPF_PERSONA (persona));
880       if (tp_contact != NULL)
881         {
882           EmpathyContact *contact;
883
884           contact = empathy_contact_dup_from_tp_contact (tp_contact);
885           empathy_contact_set_persona (contact, persona);
886
887           can_audio = can_audio || empathy_contact_get_capabilities (contact) &
888               EMPATHY_CAPABILITIES_AUDIO;
889           can_video = can_video || empathy_contact_get_capabilities (contact) &
890               EMPATHY_CAPABILITIES_VIDEO;
891
892           if (out_contact != NULL)
893             *out_contact = g_object_ref (contact);
894
895           g_object_unref (contact);
896         }
897
898 while_finish:
899       g_clear_object (&persona);
900
901       if (can_audio && can_video)
902         break;
903     }
904
905   g_clear_object (&iter);
906
907   if (can_audio_call != NULL)
908     *can_audio_call = can_audio;
909
910   if (can_video_call != NULL)
911     *can_video_call = can_video;
912 }
913
914 gboolean
915 empathy_client_types_contains_mobile_device (const GStrv types) {
916   int i;
917
918   if (types == NULL)
919     return FALSE;
920
921   for (i = 0; types[i] != NULL; i++)
922     if (!tp_strdiff (types[i], "phone") || !tp_strdiff (types[i], "handheld"))
923         return TRUE;
924
925   return FALSE;
926 }
927
928 static FolksIndividual *
929 create_individual_from_persona (FolksPersona *persona)
930 {
931   GeeSet *personas;
932   FolksIndividual *individual;
933
934   personas = GEE_SET (
935       gee_hash_set_new (FOLKS_TYPE_PERSONA, g_object_ref, g_object_unref,
936       NULL, NULL, NULL, NULL, NULL, NULL));
937
938   gee_collection_add (GEE_COLLECTION (personas), persona);
939
940   individual = folks_individual_new (personas);
941
942   g_clear_object (&personas);
943
944   return individual;
945 }
946
947 FolksIndividual *
948 empathy_create_individual_from_tp_contact (TpContact *contact)
949 {
950   TpfPersona *persona;
951   FolksIndividual *individual;
952
953   persona = tpf_persona_dup_for_contact (contact);
954   if (persona == NULL)
955     {
956       DEBUG ("Failed to get a persona for %s",
957           tp_contact_get_identifier (contact));
958       return NULL;
959     }
960
961   individual = create_individual_from_persona (FOLKS_PERSONA (persona));
962
963   g_object_unref (persona);
964   return individual;
965 }
966
967 /* Look for a FolksIndividual containing @contact as one of his persona
968  * and create one if needed */
969 FolksIndividual *
970 empathy_ensure_individual_from_tp_contact (TpContact *contact)
971 {
972   TpfPersona *persona;
973   FolksIndividual *individual;
974
975   persona = tpf_persona_dup_for_contact (contact);
976   if (persona == NULL)
977     {
978       DEBUG ("Failed to get a persona for %s",
979           tp_contact_get_identifier (contact));
980       return NULL;
981     }
982
983   individual = folks_persona_get_individual (FOLKS_PERSONA (persona));
984
985   if (individual != NULL)
986     g_object_ref (individual);
987   else
988     individual = create_individual_from_persona (FOLKS_PERSONA (persona));
989
990   g_object_unref (persona);
991   return individual;
992 }
993
994 const gchar * const *
995 empathy_individual_get_client_types (FolksIndividual *individual)
996 {
997   GeeSet *personas;
998   GeeIterator *iter;
999   const gchar * const *types = NULL;
1000   FolksPresenceType presence_type = FOLKS_PRESENCE_TYPE_UNSET;
1001
1002   personas = folks_individual_get_personas (individual);
1003   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1004   while (gee_iterator_next (iter))
1005     {
1006       FolksPresenceDetails *presence;
1007       FolksPersona *persona = gee_iterator_get (iter);
1008
1009       /* We only want personas which have presence and a TpContact */
1010       if (!empathy_folks_persona_is_interesting (persona))
1011         goto while_finish;
1012
1013       presence = FOLKS_PRESENCE_DETAILS (persona);
1014
1015       if (folks_presence_details_typecmp (
1016               folks_presence_details_get_presence_type (presence),
1017               presence_type) > 0)
1018         {
1019           TpContact *tp_contact;
1020
1021           presence_type = folks_presence_details_get_presence_type (presence);
1022
1023           tp_contact = tpf_persona_get_contact (TPF_PERSONA (persona));
1024           if (tp_contact != NULL)
1025             types = tp_contact_get_client_types (tp_contact);
1026         }
1027
1028 while_finish:
1029       g_clear_object (&persona);
1030     }
1031   g_clear_object (&iter);
1032
1033   return types;
1034 }
1035
1036 GVariant *
1037 empathy_asv_to_vardict (const GHashTable *asv)
1038 {
1039   return empathy_boxed_to_variant (TP_HASH_TYPE_STRING_VARIANT_MAP, "a{sv}",
1040       (gpointer) asv);
1041 }
1042
1043 GVariant *
1044 empathy_boxed_to_variant (GType gtype,
1045     const gchar *variant_type,
1046     gpointer boxed)
1047 {
1048   GValue v = G_VALUE_INIT;
1049   GVariant *ret;
1050
1051   g_return_val_if_fail (boxed != NULL, NULL);
1052
1053   g_value_init (&v, gtype);
1054   g_value_set_boxed (&v, boxed);
1055
1056   ret = dbus_g_value_build_g_variant (&v);
1057   g_return_val_if_fail (!tp_strdiff (g_variant_get_type_string (ret),
1058         variant_type), NULL);
1059
1060   g_value_unset (&v);
1061
1062   return g_variant_ref_sink (ret);
1063 }