1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2
3 /*
4 * e-destination.c
5 *
6 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
7 *
8 * This library is free software: you can redistribute it and/or modify it
9 * under the terms of the GNU Lesser General Public License as published by
10 * the Free Software Foundation.
11 *
12 * This library is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
15 * for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with this library. If not, see <http://www.gnu.org/licenses/>.
19 *
20 * Authors: Jon Trowbridge <trow@ximian.com>
21 * Chris Toshok <toshok@ximian.com>
22 */
23
24 /*
25 * We should probably make most of the functions in this file a little
26 * stupider.. all the work extracting useful info from the
27 * EContact/raw text/etc should happen in e_destination_set_contact
28 * (and the other setters), not in a bunch of if's in the respective
29 * _get_*() functions.
30 */
31
32 #include "evolution-data-server-config.h"
33
34 #include "e-destination.h"
35
36 #include <stdlib.h>
37 #include <ctype.h>
38 #include <string.h>
39 #include <libebook/e-book.h>
40
41 #include <libxml/xmlmemory.h>
42 #include <glib/gi18n-lib.h>
43 #include <camel/camel.h>
44
45 #define d(x)
46
47 struct _EDestinationPrivate {
48 gchar *raw;
49
50 gchar *source_uid;
51
52 EContact *contact;
53 gchar *contact_uid;
54
55 gint email_num;
56
57 gchar *name;
58 gchar *email;
59 gchar *addr;
60 gchar *textrep;
61 gboolean ignored;
62
63 GList *list_dests;
64 GList *list_alldests;
65
66 guint html_mail_override : 1;
67 guint wants_html_mail : 1;
68
69 guint show_addresses : 1;
70
71 guint auto_recipient : 1;
72 };
73
74 G_DEFINE_TYPE_WITH_PRIVATE (EDestination, e_destination, G_TYPE_OBJECT)
75
76 static gboolean e_destination_from_contact (const EDestination *);
77 static xmlNodePtr e_destination_xml_encode (const EDestination *dest);
78 static gboolean e_destination_xml_decode (EDestination *dest, xmlNodePtr node);
79 static void e_destination_clear (EDestination *dest);
80
81 /* Signals */
82
83 enum {
84 CHANGED,
85 LAST_SIGNAL
86 };
87
88 enum CONTACT_TYPE {
89 NONE,
90 CONTACT,
91 CONTACT_LIST
92 };
93
94 static guint signals[LAST_SIGNAL] = { 0 };
95
96 /* Copied from eab-book-util.c. The name selector also keeps its own copy... */
97 static gint
utf8_casefold_collate_len(const gchar * str1,const gchar * str2,gint len)98 utf8_casefold_collate_len (const gchar *str1,
99 const gchar *str2,
100 gint len)
101 {
102 gchar *s1 = g_utf8_casefold (str1, len);
103 gchar *s2 = g_utf8_casefold (str2, len);
104 gint rv;
105
106 rv = g_utf8_collate (s1, s2);
107
108 g_free (s1);
109 g_free (s2);
110
111 return rv;
112 }
113
114 /* Copied from eab-book-util.c. The name selector also keeps its own copy... */
115 static gint
utf8_casefold_collate(const gchar * str1,const gchar * str2)116 utf8_casefold_collate (const gchar *str1,
117 const gchar *str2)
118 {
119 return utf8_casefold_collate_len (str1, str2, -1);
120 }
121
122 static void
destination_finalize(GObject * object)123 destination_finalize (GObject *object)
124 {
125 EDestinationPrivate *priv;
126
127 priv = E_DESTINATION (object)->priv;
128
129 e_destination_clear (E_DESTINATION (object));
130
131 g_free (priv->source_uid);
132
133 /* Chain up to parent's finalize() method. */
134 G_OBJECT_CLASS (e_destination_parent_class)->finalize (object);
135 }
136
137 static void
e_destination_class_init(EDestinationClass * class)138 e_destination_class_init (EDestinationClass *class)
139 {
140 GObjectClass *object_class;
141
142 object_class = G_OBJECT_CLASS (class);
143 object_class->finalize = destination_finalize;
144
145 signals[CHANGED] = g_signal_new (
146 "changed",
147 G_OBJECT_CLASS_TYPE (object_class),
148 G_SIGNAL_RUN_LAST,
149 G_STRUCT_OFFSET (EDestinationClass, changed),
150 NULL, NULL, NULL,
151 G_TYPE_NONE, 0);
152 }
153
154 static void
e_destination_init(EDestination * dest)155 e_destination_init (EDestination *dest)
156 {
157 dest->priv = e_destination_get_instance_private (dest);
158 }
159
160 /**
161 * e_destination_new:
162 *
163 * Creates a new #EDestination with blank values.
164 *
165 * Returns: A newly created #EDestination.
166 **/
167 EDestination *
e_destination_new(void)168 e_destination_new (void)
169 {
170 return g_object_new (E_TYPE_DESTINATION, NULL);
171 }
172
173 /**
174 * e_destination_copy:
175 * @dest: an #EDestination
176 *
177 * Creates a new #EDestination identical to @dest.
178 *
179 * Returns: (transfer full): A newly created #EDestination, identical to @dest.
180 */
181 EDestination *
e_destination_copy(const EDestination * dest)182 e_destination_copy (const EDestination *dest)
183 {
184 EDestination *new_dest;
185 GList *iter;
186
187 g_return_val_if_fail (dest && E_IS_DESTINATION (dest), NULL);
188
189 new_dest = e_destination_new ();
190
191 new_dest->priv->source_uid = g_strdup (dest->priv->source_uid);
192 new_dest->priv->contact_uid = g_strdup (dest->priv->contact_uid);
193 new_dest->priv->name = g_strdup (dest->priv->name);
194 new_dest->priv->email = g_strdup (dest->priv->email);
195 new_dest->priv->addr = g_strdup (dest->priv->addr);
196 new_dest->priv->email_num = dest->priv->email_num;
197 new_dest->priv->ignored = dest->priv->ignored;
198
199 if (dest->priv->contact)
200 new_dest->priv->contact = g_object_ref (dest->priv->contact);
201
202 new_dest->priv->html_mail_override = dest->priv->html_mail_override;
203 new_dest->priv->wants_html_mail = dest->priv->wants_html_mail;
204
205 /* deep copy, recursively copy our children */
206 for (iter = dest->priv->list_dests; iter != NULL; iter = g_list_next (iter)) {
207 new_dest->priv->list_dests = g_list_append (
208 new_dest->priv->list_dests,
209 e_destination_copy (E_DESTINATION (iter->data)));
210 }
211
212 /* XXX other settings? */
213 new_dest->priv->raw = g_strdup (dest->priv->raw);
214
215 return new_dest;
216 }
217
218 static void
e_destination_clear(EDestination * dest)219 e_destination_clear (EDestination *dest)
220 {
221 g_free (dest->priv->contact_uid);
222 dest->priv->contact_uid = NULL;
223
224 g_free (dest->priv->raw);
225 dest->priv->raw = NULL;
226
227 g_free (dest->priv->name);
228 dest->priv->name = NULL;
229
230 g_free (dest->priv->email);
231 dest->priv->email = NULL;
232
233 g_free (dest->priv->addr);
234 dest->priv->addr = NULL;
235
236 g_free (dest->priv->textrep);
237 dest->priv->textrep = NULL;
238
239 g_clear_object (&dest->priv->contact);
240 dest->priv->email_num = -1;
241
242 g_list_foreach (dest->priv->list_dests, (GFunc) g_object_unref, NULL);
243 g_list_free (dest->priv->list_dests);
244 dest->priv->list_dests = NULL;
245 g_list_free (dest->priv->list_alldests);
246 dest->priv->list_alldests = NULL;
247 }
248
249 static gboolean
nonempty(const gchar * s)250 nonempty (const gchar *s)
251 {
252 gunichar c;
253 if (s == NULL)
254 return FALSE;
255 while (*s) {
256 c = g_utf8_get_char (s);
257 if (!g_unichar_isspace (c))
258 return TRUE;
259 s = g_utf8_next_char (s);
260 }
261 return FALSE;
262 }
263
264 /**
265 * e_destination_empty:
266 * @dest: an #EDestination
267 *
268 * Checks if @dest is blank.
269 *
270 * Returns: %TRUE if @dest is empty, %FALSE otherwise.
271 */
272 gboolean
e_destination_empty(const EDestination * dest)273 e_destination_empty (const EDestination *dest)
274
275 {
276 EDestinationPrivate *p;
277
278 g_return_val_if_fail (E_IS_DESTINATION (dest), TRUE);
279
280 p = dest->priv;
281
282 return !(p->contact != NULL
283 || (p->source_uid && *p->source_uid)
284 || (p->contact_uid && *p->contact_uid)
285 || (nonempty (p->raw))
286 || (nonempty (p->name))
287 || (nonempty (p->email))
288 || (nonempty (p->addr))
289 || (p->list_dests != NULL));
290 }
291
292 /**
293 * e_destination_equal:
294 * @a: an #EDestination
295 * @b: an #EDestination
296 *
297 * Checks if @a and @b are equal.
298 *
299 * Returns: %TRUE if the destinations are equal, %FALSE otherwise.
300 **/
301 gboolean
e_destination_equal(const EDestination * a,const EDestination * b)302 e_destination_equal (const EDestination *a,
303 const EDestination *b)
304 {
305 const EDestinationPrivate *pa, *pb;
306 const gchar *na, *nb;
307
308 g_return_val_if_fail (E_IS_DESTINATION (a), FALSE);
309 g_return_val_if_fail (E_IS_DESTINATION (b), FALSE);
310
311 if (a == b)
312 return TRUE;
313
314 pa = a->priv;
315 pb = b->priv;
316
317 /* Check equality of contacts. */
318 if (pa->contact || pb->contact) {
319 if (!(pa->contact && pb->contact))
320 return FALSE;
321
322 if (pa->contact == pb->contact || !strcmp (e_contact_get_const (pa->contact, E_CONTACT_UID),
323 e_contact_get_const (pb->contact, E_CONTACT_UID)))
324 return TRUE;
325
326 return FALSE;
327 }
328
329 /* Just in case name returns NULL */
330 na = e_destination_get_name (a);
331 nb = e_destination_get_name (b);
332 if ((na || nb) && !(na && nb && !utf8_casefold_collate (na, nb)))
333 return FALSE;
334
335 if (!g_ascii_strcasecmp (e_destination_get_email (a), e_destination_get_email (b)))
336 return TRUE;
337 else
338 return FALSE;
339 }
340
341 static void
remove_empty_subgroups(EDestination * dest,GHashTable * lists_hash)342 remove_empty_subgroups (EDestination *dest,
343 GHashTable *lists_hash)
344 {
345 EDestination *s_dest;
346 GSList *to_remove = NULL, *siter;
347 GList *iter;
348
349 if (!dest)
350 return;
351
352 for (iter = dest->priv->list_dests; iter; iter = g_list_next (iter)) {
353 s_dest = iter->data;
354
355 remove_empty_subgroups (s_dest, lists_hash);
356
357 if (g_hash_table_lookup (lists_hash, s_dest) &&
358 !s_dest->priv->list_dests)
359 to_remove = g_slist_prepend (to_remove, s_dest);
360 }
361
362 for (siter = to_remove; siter; siter = g_slist_next (siter)) {
363 s_dest = siter->data;
364
365 dest->priv->list_dests = g_list_remove (dest->priv->list_dests, s_dest);
366 dest->priv->list_alldests = g_list_remove (dest->priv->list_alldests, s_dest);
367 }
368
369 g_slist_free_full (to_remove, g_object_unref);
370 }
371
372 /**
373 * e_destination_set_contact:
374 * @dest: an #EDestination
375 * @contact: an #EContact
376 * @email_num: an email index
377 *
378 * Sets @dest to point to one of @contact's e-mail addresses
379 * indicated by @email_num.
380 **/
381 void
e_destination_set_contact(EDestination * dest,EContact * contact,gint email_num)382 e_destination_set_contact (EDestination *dest,
383 EContact *contact,
384 gint email_num)
385 {
386 g_return_if_fail (dest && E_IS_DESTINATION (dest));
387 g_return_if_fail (contact && E_IS_CONTACT (contact));
388
389 if (dest->priv->contact != contact ) {
390
391 e_destination_clear (dest);
392
393 dest->priv->contact = e_contact_duplicate (contact);
394
395 dest->priv->contact_uid = e_contact_get (dest->priv->contact, E_CONTACT_UID);
396
397 dest->priv->email_num = email_num;
398
399 dest->priv->ignored = FALSE;
400
401 /* handle the mailing list case */
402 if (e_contact_get (dest->priv->contact, E_CONTACT_IS_LIST)) {
403 gint list_length;
404 GList *attr, *attrs;
405 GHashTable *hash_table, *lists_hash;
406 gint list_iterations = 0;
407 gint lists_count = 0;
408
409 hash_table = g_hash_table_new_full (
410 g_str_hash, g_str_equal,
411 (GDestroyNotify) g_free, NULL);
412 lists_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
413
414 g_hash_table_insert (hash_table, g_strdup ("0"), dest);
415
416 e_destination_set_name (
417 dest,
418 e_contact_get_const (
419 dest->priv->contact,
420 E_CONTACT_FILE_AS));
421
422 attrs = g_list_copy (e_vcard_get_attributes (E_VCARD (dest->priv->contact)));
423 list_length = g_list_length (attrs);
424
425 attr = attrs;
426 while (list_length) {
427 EDestination *parent_dest;
428 gint type;
429 gboolean remove = FALSE; /* Can item be removed from attrs list? */
430
431 GList *params, *param, *value;
432 const gchar *parent_id;
433
434 param = e_vcard_attribute_get_param (attr->data, EVC_PARENT_CL);
435 if (param)
436 parent_id = param->data;
437 else
438 parent_id = "0";
439
440 /* This is so just that we don't have to call g_ascii_strcasecmp more times */
441 if (g_ascii_strcasecmp (EVC_CONTACT_LIST, e_vcard_attribute_get_name (attr->data)) == 0) {
442 lists_count++;
443 type = CONTACT_LIST;
444 } else if (g_ascii_strcasecmp (EVC_EMAIL, e_vcard_attribute_get_name (attr->data)) == 0) {
445 type = CONTACT;
446 } else {
447 type = NONE;
448 remove = TRUE;
449 }
450
451 /* Is parent of current attribute already in the tree? */
452 parent_dest = g_hash_table_lookup (hash_table, parent_id);
453 /* Make sure that when parent with parent_id does not exist the item will be appended to root
454 * destination. */
455 if (parent_dest == NULL && lists_count == 0 && list_iterations > 0) {
456 parent_id = "0";
457 parent_dest = dest;
458 }
459 if (type != NONE && parent_dest) {
460 gchar *id = NULL;
461 gint set_email_num = 0;
462 EDestination *s_dest;
463
464 s_dest = e_destination_new ();
465 s_dest->priv->ignored = FALSE;
466
467 params = e_vcard_attribute_get_params (attr->data);
468 for (param = params; param; param = param->next) {
469 const gchar *param_name = e_vcard_attribute_param_get_name (param->data);
470 if ((g_ascii_strcasecmp (param_name, EVC_CL_UID) == 0) ||
471 (g_ascii_strcasecmp (param_name, EVC_X_DEST_CONTACT_UID) == 0)) {
472 value = e_vcard_attribute_param_get_values (param->data);
473 id = value ? g_strdup (value->data) : NULL;
474 } else if (g_ascii_strcasecmp (param_name, EVC_X_DEST_EMAIL_NUM) == 0) {
475 value = e_vcard_attribute_param_get_values (param->data);
476 set_email_num = value ? atoi (value->data) : -1;
477 } else if (!g_ascii_strcasecmp (param_name, EVC_X_DEST_HTML_MAIL)) {
478 value = e_vcard_attribute_param_get_values (param->data);
479 e_destination_set_html_mail_pref (s_dest, value ? !g_ascii_strcasecmp (value->data, "true") : FALSE);
480 }
481 }
482
483 if (type == CONTACT) {
484 CamelInternetAddress *addr;
485 const gchar *name, *email;
486 gchar *raw;
487
488 raw = e_vcard_attribute_get_value (attr->data);
489 addr = camel_internet_address_new ();
490 if (camel_address_unformat (CAMEL_ADDRESS (addr), raw) > 0) {
491 camel_internet_address_sanitize_ascii_domain (addr);
492 if (camel_internet_address_get (addr, 0, &name, &email)) {
493 e_destination_set_name (s_dest, name);
494 e_destination_set_email (s_dest, email);
495
496 dest->priv->list_alldests = g_list_append (dest->priv->list_alldests, s_dest);
497 }
498 }
499
500 g_object_unref (addr);
501 g_free (raw);
502 } else {
503 gchar *name = e_vcard_attribute_get_value (attr->data);
504 e_destination_set_name (s_dest, name);
505 g_free (name);
506
507 if (id)
508 g_hash_table_insert (hash_table, g_strdup (id), s_dest);
509 lists_count--;
510
511 g_hash_table_insert (lists_hash, s_dest, GINT_TO_POINTER (1));
512 }
513
514 if (id) {
515 e_destination_set_contact_uid (s_dest, id, set_email_num);
516 g_free (id);
517 }
518
519 parent_dest->priv->list_dests = g_list_append (parent_dest->priv->list_dests, s_dest);
520
521 remove = TRUE;
522 }
523
524 /* Go to next attribute */
525 if (attr->next) {
526 attr = attr->next;
527 if (remove) {
528 attrs = g_list_delete_link (attrs, attr->prev);
529 list_length--;
530 }
531 continue;
532 /* Or return to first attribute */
533 } else if (attrs) {
534 if (remove) {
535 attrs = g_list_delete_link (attrs, attr);
536 list_length--;
537 }
538 attr = attrs;
539 list_iterations++;
540 continue;
541 /* When all attribute are processed, leave. */
542 } else {
543 break;
544 }
545 }
546
547 remove_empty_subgroups (dest, lists_hash);
548
549 g_hash_table_unref (lists_hash);
550 g_hash_table_unref (hash_table);
551 g_list_free (attrs);
552 } else {
553 /* handle the normal contact case */
554 /* is there anything to do here? */
555 }
556
557 g_signal_emit (dest, signals[CHANGED], 0);
558 } else if (dest->priv->email_num != email_num) {
559 /* Splitting here would help the contact lists not rebuiding, so that it remembers ignored values */
560
561 /* increase ref counter, because e_destination_clear calls g_object_unref, but we want to keep the contact */
562 g_object_ref (contact);
563
564 e_destination_clear (dest);
565
566 dest->priv->contact = contact;
567
568 dest->priv->contact_uid = e_contact_get (dest->priv->contact, E_CONTACT_UID);
569
570 dest->priv->email_num = email_num;
571
572 g_signal_emit (dest, signals[CHANGED], 0);
573 }
574 }
575
576 /**
577 * e_destination_set_book:
578 * @dest: an #EDestination
579 * @book: an #EBook
580 *
581 * Specify the source @dest's contact comes from. This is useful
582 * if you need to update the contact later.
583 *
584 * Deprecated: 3.2: Use e_destination_set_client() instead.
585 **/
586 void
e_destination_set_book(EDestination * dest,EBook * book)587 e_destination_set_book (EDestination *dest,
588 EBook *book)
589 {
590 ESource *source;
591 const gchar *uid;
592
593 g_return_if_fail (dest && E_IS_DESTINATION (dest));
594 g_return_if_fail (book && E_IS_BOOK (book));
595
596 source = e_book_get_source (book);
597 uid = e_source_get_uid (source);
598 g_return_if_fail (uid != NULL);
599
600 if (!dest->priv->source_uid || strcmp (uid, dest->priv->source_uid)) {
601 g_free (dest->priv->source_uid);
602 dest->priv->source_uid = g_strdup (uid);
603
604 g_signal_emit (dest, signals[CHANGED], 0);
605 }
606 }
607
608 /**
609 * e_destination_set_client:
610 * @dest: an #EDestination
611 * @client: an #EBookClient
612 *
613 * Specify the source @dest's contact comes from. This is useful
614 * if you need to update the contact later.
615 *
616 * Since: 3.2
617 **/
618 void
e_destination_set_client(EDestination * dest,EBookClient * client)619 e_destination_set_client (EDestination *dest,
620 EBookClient *client)
621 {
622 ESource *source;
623 const gchar *uid;
624
625 g_return_if_fail (dest && E_IS_DESTINATION (dest));
626 g_return_if_fail (client && E_IS_BOOK_CLIENT (client));
627
628 source = e_client_get_source (E_CLIENT (client));
629 uid = e_source_get_uid (source);
630 g_return_if_fail (uid != NULL);
631
632 if (!dest->priv->source_uid || strcmp (uid, dest->priv->source_uid)) {
633 g_free (dest->priv->source_uid);
634 dest->priv->source_uid = g_strdup (uid);
635
636 g_signal_emit (dest, signals[CHANGED], 0);
637 }
638 }
639
640 /**
641 * e_destination_set_contact_uid:
642 * @dest: an #EDestination
643 * @uid: a unique contact ID
644 * @email_num: an email index
645 *
646 * Sets @dest to point to one of the contact specified by @uid's e-mail
647 * addresses indicated by @email_num.
648 **/
649 void
e_destination_set_contact_uid(EDestination * dest,const gchar * uid,gint email_num)650 e_destination_set_contact_uid (EDestination *dest,
651 const gchar *uid,
652 gint email_num)
653 {
654 g_return_if_fail (dest && E_IS_DESTINATION (dest));
655 g_return_if_fail (uid != NULL);
656
657 if (dest->priv->contact_uid == NULL
658 || strcmp (dest->priv->contact_uid, uid)
659 || dest->priv->email_num != email_num) {
660
661 g_free (dest->priv->contact_uid);
662 dest->priv->contact_uid = g_strdup (uid);
663 dest->priv->email_num = email_num;
664
665 /* If we already have a contact, remove it unless its uid matches the one
666 * we just set. */
667 if (dest->priv->contact && strcmp (uid,
668 e_contact_get_const (dest->priv->contact, E_CONTACT_UID))) {
669 g_object_unref (dest->priv->contact);
670 dest->priv->contact = NULL;
671 }
672
673 g_signal_emit (dest, signals[CHANGED], 0);
674 }
675 }
676
677 static void
e_destination_set_source_uid(EDestination * dest,const gchar * uid)678 e_destination_set_source_uid (EDestination *dest,
679 const gchar *uid)
680 {
681 g_return_if_fail (dest && E_IS_DESTINATION (dest));
682 g_return_if_fail (uid != NULL);
683
684 if (dest->priv->source_uid == NULL
685 || strcmp (dest->priv->source_uid, uid)) {
686
687 g_free (dest->priv->source_uid);
688 dest->priv->source_uid = g_strdup (uid);
689
690 g_signal_emit (dest, signals[CHANGED], 0);
691 }
692 }
693
694 /**
695 * e_destination_set_name:
696 * @dest: an #EDestination
697 * @name: the destination's full name
698 *
699 * Sets the full name of @dest's addressee.
700 **/
701 void
e_destination_set_name(EDestination * dest,const gchar * name)702 e_destination_set_name (EDestination *dest,
703 const gchar *name)
704 {
705 gboolean changed = FALSE;
706
707 g_return_if_fail (E_IS_DESTINATION (dest));
708
709 if (name == NULL) {
710 if (dest->priv->name != NULL) {
711 g_free (dest->priv->name);
712 dest->priv->name = NULL;
713 changed = TRUE;
714 }
715 } else if (dest->priv->name == NULL || strcmp (dest->priv->name, name)) {
716 g_free (dest->priv->name);
717 dest->priv->name = g_strdup (name);
718 changed = TRUE;
719 }
720
721 if (changed) {
722 g_free (dest->priv->addr);
723 dest->priv->addr = NULL;
724 g_free (dest->priv->textrep);
725 dest->priv->textrep = NULL;
726
727 g_signal_emit (dest, signals[CHANGED], 0);
728 }
729 }
730
731 /**
732 * e_destination_set_email:
733 * @dest: an #EDestination
734 * @email: the destination's e-mail address
735 *
736 * Sets the e-mail address of @dest's addressee.
737 **/
738 void
e_destination_set_email(EDestination * dest,const gchar * email)739 e_destination_set_email (EDestination *dest,
740 const gchar *email)
741 {
742 gboolean changed = FALSE;
743
744 g_return_if_fail (E_IS_DESTINATION (dest));
745
746 if (email == NULL) {
747 if (dest->priv->email != NULL) {
748 g_free (dest->priv->email);
749 dest->priv->email = NULL;
750 changed = TRUE;
751 }
752 } else if (dest->priv->email == NULL || strcmp (dest->priv->email, email)) {
753 g_free (dest->priv->email);
754 dest->priv->email = camel_utils_sanitize_ascii_domain_in_address (email, TRUE);
755 if (!dest->priv->email)
756 dest->priv->email = g_strdup (email);
757 changed = TRUE;
758 }
759
760 if (changed) {
761 g_free (dest->priv->addr);
762 dest->priv->addr = NULL;
763 g_free (dest->priv->textrep);
764 dest->priv->textrep = NULL;
765
766 g_signal_emit (dest, signals[CHANGED], 0);
767 }
768 }
769
770 static gboolean
e_destination_from_contact(const EDestination * dest)771 e_destination_from_contact (const EDestination *dest)
772 {
773 g_return_val_if_fail (dest && E_IS_DESTINATION (dest), FALSE);
774 return dest->priv->contact != NULL || dest->priv->source_uid != NULL || dest->priv->contact_uid != NULL;
775 }
776
777 /**
778 * e_destination_is_auto_recipient:
779 * @dest: an #EDestination
780 *
781 * Checks if @dest is flagged as an automatic recipient, meaning
782 * it was not explicitly specified by the user. This can be used
783 * to hide it from some UI elements.
784 *
785 * Returns: %TRUE if destination is an auto recipient, %FALSE otherwise.
786 **/
787 gboolean
e_destination_is_auto_recipient(const EDestination * dest)788 e_destination_is_auto_recipient (const EDestination *dest)
789 {
790 g_return_val_if_fail (dest && E_IS_DESTINATION (dest), FALSE);
791
792 return dest->priv->auto_recipient;
793 }
794
795 /**
796 * e_destination_set_auto_recipient:
797 * @dest: an #EDestination
798 * @value: the auto recipient flag
799 *
800 * Sets the flag indicating if @dest is an automatic recipient, meaning
801 * it was not explicitly specified by the user. This can be used
802 * to hide it from some UI elements.
803 **/
804 void
e_destination_set_auto_recipient(EDestination * dest,gboolean value)805 e_destination_set_auto_recipient (EDestination *dest,
806 gboolean value)
807 {
808 g_return_if_fail (dest && E_IS_DESTINATION (dest));
809
810 dest->priv->auto_recipient = value;
811
812 g_signal_emit (dest, signals[CHANGED], 0);
813 }
814
815 /**
816 * e_destination_get_contact:
817 * @dest: an #EDestination
818 *
819 * Gets the contact @dest is pointing to, if any.
820 *
821 * Returns: (transfer none) (nullable): An #EContact, or %NULL if none was set.
822 **/
823 EContact *
e_destination_get_contact(const EDestination * dest)824 e_destination_get_contact (const EDestination *dest)
825 {
826 g_return_val_if_fail (dest && E_IS_DESTINATION (dest), NULL);
827
828 return dest->priv->contact;
829 }
830
831 /**
832 * e_destination_get_contact_uid:
833 * @dest: an #EDestination
834 *
835 * Gets the unique contact ID @dest is pointing to, if any.
836 *
837 * Returns: (nullable): A unique contact ID, or %NULL if none was set.
838 */
839 const gchar *
e_destination_get_contact_uid(const EDestination * dest)840 e_destination_get_contact_uid (const EDestination *dest)
841 {
842 g_return_val_if_fail (dest && E_IS_DESTINATION (dest), NULL);
843
844 return dest->priv->contact_uid;
845 }
846
847 /**
848 * e_destination_get_source_uid:
849 * @dest: an #EDestination
850 *
851 * Gets the unique source ID @dest is pointing to, if any. The source
852 * ID specifies which address book @dest's contact came from.
853 *
854 * Returns: (nullable): A unique source ID, or %NULL if none was set.
855 */
856 const gchar *
e_destination_get_source_uid(const EDestination * dest)857 e_destination_get_source_uid (const EDestination *dest)
858 {
859 g_return_val_if_fail (dest && E_IS_DESTINATION (dest), NULL);
860
861 return dest->priv->source_uid;
862 }
863
864 /**
865 * e_destination_get_email_num:
866 * @dest: an #EDestination
867 *
868 * Gets the index of the e-mail address of the contact that
869 * @dest is pointing to, if any.
870 *
871 * Returns: The e-mail index, or -1 if none was set.
872 **/
873 gint
e_destination_get_email_num(const EDestination * dest)874 e_destination_get_email_num (const EDestination *dest)
875 {
876 g_return_val_if_fail (dest && E_IS_DESTINATION (dest), -1);
877
878 if (dest->priv->contact == NULL && (dest->priv->source_uid == NULL || dest->priv->contact_uid == NULL))
879 return -1;
880
881 return dest->priv->email_num;
882 }
883
884 /**
885 * e_destination_get_name:
886 * @dest: an #EDestination
887 *
888 * Gets the full name of @dest's addressee, or if the addressee is
889 * a contact list, the name the list was filed under. The name can
890 * be encoded in quoted printable.
891 *
892 * Returns: (nullable): The full name of the addressee, or %NULL if none was set.
893 **/
894 const gchar *
e_destination_get_name(const EDestination * dest)895 e_destination_get_name (const EDestination *dest)
896 {
897 EDestinationPrivate *priv;
898
899 g_return_val_if_fail (dest && E_IS_DESTINATION (dest), NULL);
900
901 priv = (EDestinationPrivate *) dest->priv; /* cast out const */
902
903 if (priv->name == NULL) {
904 if (priv->contact != NULL) {
905 priv->name = e_contact_get (priv->contact, E_CONTACT_FULL_NAME);
906
907 if (priv->name == NULL || *priv->name == '\0') {
908 g_free (priv->name);
909 priv->name = e_contact_get (priv->contact, E_CONTACT_FILE_AS);
910 }
911
912 if (priv->name == NULL || *priv->name == '\0') {
913 g_free (priv->name);
914 if (e_contact_get (priv->contact, E_CONTACT_IS_LIST))
915 priv->name = g_strdup (_("Unnamed List"));
916 else
917 priv->name = g_strdup (e_destination_get_email (dest));
918 }
919 }
920 else if (priv->raw != NULL) {
921 CamelInternetAddress *addr = camel_internet_address_new ();
922
923 if (camel_address_unformat (CAMEL_ADDRESS (addr), priv->raw)) {
924 const gchar *camel_name = NULL;
925
926 if (camel_internet_address_get (addr, 0, &camel_name, NULL))
927 priv->name = g_strdup (camel_name);
928 }
929
930 g_object_unref (addr);
931 }
932 }
933
934 return priv->name;
935 }
936
937 /**
938 * e_destination_is_ignored:
939 * @dest: an #EDestination
940 *
941 * Check if @dest is to be ignored.
942 *
943 * Returns: %TRUE if this destination should be ignored, else %FALSE.
944 */
945 gboolean
e_destination_is_ignored(const EDestination * dest)946 e_destination_is_ignored (const EDestination *dest)
947 {
948 return dest->priv->ignored;
949 }
950
951 /**
952 * e_destination_set_ignored:
953 * @dest: an #EDestination
954 * @ignored: %TRUE if this #EDestination should be ignored.
955 *
956 * Set the ignore flag on an #EDestination.
957 */
958 void
e_destination_set_ignored(EDestination * dest,gboolean ignored)959 e_destination_set_ignored (EDestination *dest,
960 gboolean ignored)
961 {
962 dest->priv->ignored = ignored;
963 }
964
965 /**
966 * e_destination_get_email:
967 * @dest: an #EDestination
968 *
969 * Gets the e-mail address of @dest's addressee.
970 *
971 * Returns: An e-mail address, or an empty string if none was set.
972 **/
973 const gchar *
e_destination_get_email(const EDestination * dest)974 e_destination_get_email (const EDestination *dest)
975 {
976 EDestinationPrivate *priv;
977
978 g_return_val_if_fail (dest && E_IS_DESTINATION (dest), NULL);
979
980 priv = (EDestinationPrivate *) dest->priv; /* cast out const */
981
982 if (priv->email == NULL) {
983 if (priv->contact != NULL) {
984 /* Pull the address out of the card. */
985 GList *email = e_contact_get (priv->contact, E_CONTACT_EMAIL);
986 if (email) {
987 gchar *e = g_list_nth_data (email, priv->email_num);
988
989 if (e)
990 priv->email = g_strdup (e);
991 }
992 if (email) {
993 g_list_foreach (email, (GFunc) g_free, NULL);
994 g_list_free (email);
995 }
996
997 } else if (priv->raw != NULL) {
998 CamelInternetAddress *addr = camel_internet_address_new ();
999
1000 if (camel_address_unformat (CAMEL_ADDRESS (addr), priv->raw)) {
1001 const gchar *camel_email = NULL;
1002 camel_internet_address_sanitize_ascii_domain (addr);
1003 if (camel_internet_address_get (addr, 0, NULL, &camel_email))
1004 priv->email = g_strdup (camel_email);
1005 }
1006
1007 g_object_unref (addr);
1008 }
1009
1010 /* Force e-mail to be non-null... */
1011 if (priv->email == NULL) {
1012 priv->email = g_strdup ("");
1013 }
1014 }
1015
1016 return priv->email;
1017 }
1018
1019 /* Helper function to e_destination_get_address capable of recursively
1020 * iterating through structured destinations list */
1021 static void
destination_get_address(const EDestination * dest,CamelInternetAddress * addr)1022 destination_get_address (const EDestination *dest,
1023 CamelInternetAddress *addr)
1024 {
1025 const GList *iter;
1026
1027 if (e_destination_is_evolution_list (dest)) {
1028
1029 for (iter = dest->priv->list_dests; iter; iter = iter->next) {
1030 EDestination *list_dest = E_DESTINATION (iter->data);
1031
1032 destination_get_address (list_dest, addr);
1033 }
1034
1035 } else if (!dest->priv->ignored) {
1036 const gchar *name, *email;
1037 name = e_destination_get_name (dest);
1038 email = e_destination_get_email (dest);
1039
1040 if (nonempty (name) && nonempty (email))
1041 camel_internet_address_add (addr, name, email);
1042 else if (nonempty (email))
1043 camel_address_decode (CAMEL_ADDRESS (addr), email);
1044 else /* this case loses i suppose, but there's
1045 nothing we can do here */
1046 camel_address_decode (CAMEL_ADDRESS (addr), name);
1047 }
1048 }
1049
1050 /**
1051 * e_destination_get_address:
1052 * @dest: an #EDestination
1053 *
1054 * Gets the encoded name and email address, or in the case of lists, the
1055 * encoded list of email addresses, from @dest. The returned string is
1056 * suitable for use in an email header, but not for displaying to users.
1057 *
1058 * Returns: (nullable): an encoded destination string suitable for use in an
1059 * email header, or %NULL if the destination was empty
1060 **/
1061 const gchar *
e_destination_get_address(const EDestination * dest)1062 e_destination_get_address (const EDestination *dest)
1063 {
1064 EDestinationPrivate *priv;
1065 CamelInternetAddress *addr = camel_internet_address_new ();
1066
1067 g_return_val_if_fail (dest && E_IS_DESTINATION (dest), NULL);
1068
1069 priv = (EDestinationPrivate *) dest->priv; /* cast out const */
1070 g_clear_pointer (&priv->addr, g_free);
1071
1072 if (e_destination_is_evolution_list (dest)) {
1073 destination_get_address (dest, addr);
1074 camel_internet_address_sanitize_ascii_domain (addr);
1075 priv->addr = camel_address_encode (CAMEL_ADDRESS (addr));
1076 } else if (priv->raw) {
1077 if (camel_address_unformat (CAMEL_ADDRESS (addr), priv->raw)) {
1078 camel_internet_address_sanitize_ascii_domain (addr);
1079 priv->addr = camel_address_encode (CAMEL_ADDRESS (addr));
1080 }
1081 } else {
1082 destination_get_address (dest, addr);
1083 camel_internet_address_sanitize_ascii_domain (addr);
1084 priv->addr = camel_address_encode (CAMEL_ADDRESS (addr));
1085 }
1086
1087 g_object_unref (addr);
1088
1089 return priv->addr;
1090 }
1091
1092 /**
1093 * e_destination_set_raw:
1094 * @dest: an #EDestination
1095 * @raw: an unparsed string
1096 *
1097 * Sets @dest to point to the name and e-mail address resulting from
1098 * parsing the supplied string. Useful for user input.
1099 **/
1100 void
e_destination_set_raw(EDestination * dest,const gchar * raw)1101 e_destination_set_raw (EDestination *dest,
1102 const gchar *raw)
1103 {
1104 g_return_if_fail (E_IS_DESTINATION (dest));
1105 g_return_if_fail (raw != NULL);
1106
1107 if (dest->priv->raw == NULL || strcmp (dest->priv->raw, raw)) {
1108 CamelInternetAddress *addr = camel_internet_address_new ();
1109
1110 e_destination_clear (dest);
1111
1112 if (camel_address_unformat (CAMEL_ADDRESS (addr), raw) > 0 &&
1113 camel_internet_address_sanitize_ascii_domain (addr))
1114 dest->priv->raw = camel_address_format (CAMEL_ADDRESS (addr));
1115 else
1116 dest->priv->raw = g_strdup (raw);
1117
1118 g_object_unref (addr);
1119
1120 g_signal_emit (dest, signals[CHANGED], 0);
1121 }
1122 }
1123
1124 /**
1125 * e_destination_get_textrep:
1126 * @dest: an #EDestination
1127 * @include_email: whether to include the e-mail address
1128 *
1129 * Generates a textual representation of @dest, suitable for referring
1130 * to the destination during user interaction. The name can be encoded
1131 * in quoted printable.
1132 *
1133 * Returns: A textual representation of the destination.
1134 **/
1135 const gchar *
e_destination_get_textrep(const EDestination * dest,gboolean include_email)1136 e_destination_get_textrep (const EDestination *dest,
1137 gboolean include_email)
1138 {
1139 const gchar *name, *email;
1140
1141 g_return_val_if_fail (dest && E_IS_DESTINATION (dest), NULL);
1142
1143 if (dest->priv->raw)
1144 return dest->priv->raw;
1145
1146 name = e_destination_get_name (dest);
1147 email = e_destination_get_email (dest);
1148
1149 if (e_destination_from_contact (dest) && name != NULL && (!include_email || !email || !*email))
1150 return name;
1151
1152 /* Make sure that our address gets quoted properly */
1153 if (email && dest->priv->textrep == NULL) {
1154 CamelInternetAddress *addr = camel_internet_address_new ();
1155
1156 camel_internet_address_add (addr, name, email);
1157 g_free (dest->priv->textrep);
1158 camel_internet_address_sanitize_ascii_domain (addr);
1159 dest->priv->textrep = camel_address_format (CAMEL_ADDRESS (addr));
1160 g_object_unref (addr);
1161 }
1162
1163 if (dest->priv->textrep != NULL)
1164 return dest->priv->textrep;
1165
1166 if (email)
1167 return email;
1168
1169 return "";
1170 }
1171
1172 /**
1173 * e_destination_is_evolution_list:
1174 * @dest: an #EDestination
1175 *
1176 * Checks if @dest is a list of addresses.
1177 *
1178 * Returns: %TRUE if destination is a list, %FALSE if it is an individual.
1179 **/
1180 gboolean
e_destination_is_evolution_list(const EDestination * dest)1181 e_destination_is_evolution_list (const EDestination *dest)
1182 {
1183 g_return_val_if_fail (dest && E_IS_DESTINATION (dest), FALSE);
1184
1185 return dest->priv->list_dests != NULL;
1186 }
1187
1188 /**
1189 * e_destination_list_show_addresses:
1190 * @dest: an #EDestination
1191 *
1192 * If @dest is a list, checks if the addresses in the list
1193 * should be presented to the user during interaction.
1194 *
1195 * Returns: %TRUE if addresses should be shown, %FALSE otherwise.
1196 **/
1197 gboolean
e_destination_list_show_addresses(const EDestination * dest)1198 e_destination_list_show_addresses (const EDestination *dest)
1199 {
1200 g_return_val_if_fail (E_IS_DESTINATION (dest), FALSE);
1201
1202 if (dest->priv->contact != NULL)
1203 return GPOINTER_TO_UINT (e_contact_get (dest->priv->contact, E_CONTACT_LIST_SHOW_ADDRESSES));
1204
1205 return dest->priv->show_addresses;
1206 }
1207
1208 /**
1209 * e_destination_list_get_root_dests:
1210 * @dest: an #EDestination
1211 *
1212 * If @dest is a list, gets the list of EDestinations assigned directly
1213 * to @dest.
1214 * The list and its elements belong to @dest, and should not be freed.
1215 *
1216 * Returns: (element-type EDestination) (transfer none) (nullable): A list of elements of type
1217 * #EDestination, or %NULL.
1218 *
1219 * Since: 3.2
1220 **/
1221 const GList *
e_destination_list_get_root_dests(const EDestination * dest)1222 e_destination_list_get_root_dests (const EDestination *dest)
1223 {
1224 g_return_val_if_fail (dest && E_IS_DESTINATION (dest), NULL);
1225
1226 if (!e_destination_is_evolution_list (dest))
1227 return NULL;
1228
1229 return dest->priv->list_dests;
1230 }
1231
1232 /**
1233 * e_destination_list_get_dests:
1234 * @dest: an #EDestination
1235 *
1236 * If @dest is a list, gets recursively list of all destinations.
1237 * Everything returned from this function belongs to @dest and
1238 * thus should not be freed.
1239 *
1240 * Returns: (element-type EDestination) (transfer none) (nullable): A list of elements of type
1241 * #EDestination, or %NULL.
1242 *
1243 * Since: 3.2
1244 **/
1245 const GList *
e_destination_list_get_dests(const EDestination * dest)1246 e_destination_list_get_dests (const EDestination *dest)
1247 {
1248 g_return_val_if_fail (dest && E_IS_DESTINATION (dest), NULL);
1249
1250 if (!e_destination_is_evolution_list (dest))
1251 return NULL;
1252
1253 if (!dest->priv->list_alldests) {
1254 GList *iter;
1255 for (iter = dest->priv->list_dests; iter; iter = iter->next) {
1256 if (e_destination_is_evolution_list (iter->data)) {
1257 GList *l = g_list_copy ((GList *) e_destination_list_get_dests (iter->data));
1258 dest->priv->list_alldests = g_list_concat (dest->priv->list_alldests, l);
1259 } else {
1260 dest->priv->list_alldests = g_list_append (dest->priv->list_alldests, iter->data);
1261 }
1262 }
1263 }
1264
1265 return dest->priv->list_alldests;
1266 }
1267
1268 /**
1269 * e_destination_get_html_mail_pref:
1270 * @dest: an #EDestination
1271 *
1272 * Check if @dest wants to get mail formatted as HTML.
1273 *
1274 * Returns: %TRUE if destination wants HTML, %FALSE if not.
1275 **/
1276 gboolean
e_destination_get_html_mail_pref(const EDestination * dest)1277 e_destination_get_html_mail_pref (const EDestination *dest)
1278 {
1279 g_return_val_if_fail (dest && E_IS_DESTINATION (dest), FALSE);
1280
1281 if (dest->priv->html_mail_override || dest->priv->contact == NULL)
1282 return dest->priv->wants_html_mail;
1283
1284 return e_contact_get (dest->priv->contact, E_CONTACT_WANTS_HTML) ? TRUE : FALSE;
1285 }
1286
1287 /**
1288 * e_destination_set_html_mail_pref:
1289 * @dest: an #EDestination
1290 * @flag: whether the destination wants HTML mail
1291 *
1292 * Specifies whether @dest wants to get mail formatted as HTML.
1293 **/
1294 void
e_destination_set_html_mail_pref(EDestination * dest,gboolean flag)1295 e_destination_set_html_mail_pref (EDestination *dest,
1296 gboolean flag)
1297 {
1298 g_return_if_fail (dest && E_IS_DESTINATION (dest));
1299
1300 dest->priv->html_mail_override = TRUE;
1301 if (dest->priv->wants_html_mail != flag) {
1302 dest->priv->wants_html_mail = flag;
1303
1304 g_signal_emit (dest, signals[CHANGED], 0);
1305 }
1306 }
1307
1308 /*
1309 * Destination import/export
1310 */
1311
1312 /**
1313 * e_destination_get_textrepv:
1314 * @destv: (array zero-terminated=1): %NULL-terminated array of pointers to #EDestination
1315 *
1316 * Generates a joint text representation of all the #EDestination
1317 * elements in @destv.
1318 *
1319 * Returns: The text representation of @destv.
1320 **/
1321 gchar *
e_destination_get_textrepv(EDestination ** destv)1322 e_destination_get_textrepv (EDestination **destv)
1323 {
1324 gint i, j, len = 0;
1325 gchar **strv;
1326 gchar *str;
1327
1328 g_return_val_if_fail (destv, NULL);
1329
1330 /* Q: Please tell me this is only for assertion
1331 * reasons. If this is considered to be ok behavior then you
1332 * shouldn't use g_return's. Just a reminder;-)
1333 *
1334 * A: Yes, this is just an assertion. (Though it does find the
1335 * length of the vector in the process...)
1336 */
1337 while (destv[len]) {
1338 g_return_val_if_fail (E_IS_DESTINATION (destv[len]), NULL);
1339 len++;
1340 }
1341
1342 strv = g_new0 (gchar *, len + 1);
1343 for (i = 0, j = 0; destv[i]; i++) {
1344 if (!e_destination_empty (destv[i])) {
1345 const gchar *addr = e_destination_get_address (destv[i]);
1346 strv[j++] = addr ? (gchar *) addr : (gchar *) "";
1347 }
1348 }
1349
1350 str = g_strjoinv (", ", strv);
1351
1352 g_free (strv);
1353
1354 return str;
1355 }
1356
1357 /**
1358 * e_destination_xml_encode:
1359 * @dest: an #EDestination
1360 *
1361 * Generates an XML tree from @dest.
1362 *
1363 * Returns: Pointer to the root node of the XML tree.
1364 **/
1365 static xmlNodePtr
e_destination_xml_encode(const EDestination * dest)1366 e_destination_xml_encode (const EDestination *dest)
1367 {
1368 xmlNodePtr dest_node;
1369 const gchar *str;
1370
1371 g_return_val_if_fail (dest && E_IS_DESTINATION (dest), NULL);
1372
1373 dest_node = xmlNewNode (NULL, (xmlChar *)"destination");
1374
1375 str = e_destination_get_name (dest);
1376 if (str)
1377 xmlNewTextChild (dest_node, NULL, (xmlChar *)"name", (xmlChar *) str);
1378
1379 if (!e_destination_is_evolution_list (dest)) {
1380 str = e_destination_get_email (dest);
1381 if (str)
1382 xmlNewTextChild (dest_node, NULL, (xmlChar *)"email", (xmlChar *) str);
1383 } else {
1384 GList *iter = dest->priv->list_dests;
1385
1386 while (iter) {
1387 EDestination *list_dest = E_DESTINATION (iter->data);
1388 xmlNodePtr list_node = xmlNewNode (NULL, (xmlChar *)"list_entry");
1389
1390 str = e_destination_get_name (list_dest);
1391 if (str) {
1392 xmlChar *escaped = xmlEncodeEntitiesReentrant (NULL, (xmlChar *) str);
1393 xmlNewTextChild (list_node, NULL, (xmlChar *)"name", escaped);
1394 xmlFree (escaped);
1395 }
1396
1397 str = e_destination_get_email (list_dest);
1398 if (str) {
1399 xmlChar *escaped = xmlEncodeEntitiesReentrant (NULL, (xmlChar *) str);
1400 xmlNewTextChild (list_node, NULL, (xmlChar *)"email", escaped);
1401 xmlFree (escaped);
1402 }
1403
1404 xmlAddChild (dest_node, list_node);
1405
1406 iter = g_list_next (iter);
1407 }
1408
1409 xmlNewProp (dest_node, (xmlChar *)"is_list", (xmlChar *)"yes");
1410 xmlNewProp (
1411 dest_node, (xmlChar *)"show_addresses",
1412 e_destination_list_show_addresses (dest) ?
1413 (xmlChar *)"yes" : (xmlChar *)"no");
1414 }
1415
1416 str = e_destination_get_source_uid (dest);
1417 if (str) {
1418 xmlChar *escaped = xmlEncodeEntitiesReentrant (NULL, (xmlChar *) str);
1419 xmlNewTextChild (dest_node, NULL, (xmlChar *)"source_uid", escaped);
1420 xmlFree (escaped);
1421 }
1422
1423 str = e_destination_get_contact_uid (dest);
1424 if (str) {
1425 gchar buf[16];
1426
1427 xmlNodePtr uri_node = xmlNewTextChild (dest_node, NULL, (xmlChar *)"card_uid", (xmlChar *) str);
1428 g_snprintf (buf, 16, "%d", e_destination_get_email_num (dest));
1429 xmlNewProp (uri_node, (xmlChar *)"email_num", (xmlChar *) buf);
1430 }
1431
1432 xmlNewProp (
1433 dest_node, (xmlChar *)"html_mail",
1434 e_destination_get_html_mail_pref (dest) ?
1435 (xmlChar *)"yes" : (xmlChar *)"no");
1436
1437 xmlNewProp (
1438 dest_node, (xmlChar *)"auto_recipient",
1439 e_destination_is_auto_recipient (dest) ?
1440 (xmlChar *)"yes" : (xmlChar *)"no");
1441
1442 return dest_node;
1443 }
1444
1445 /**
1446 * e_destination_xml_decode:
1447 * @dest: an #EDestination
1448 * @node: the root node of an XML tree
1449 *
1450 * Initializes @dest based on the information encoded in the
1451 * XML tree under @node.
1452 *
1453 * Returns: %TRUE if the XML tree was well-formed, %FALSE otherwise.
1454 **/
1455 static gboolean
e_destination_xml_decode(EDestination * dest,xmlNodePtr node)1456 e_destination_xml_decode (EDestination *dest,
1457 xmlNodePtr node)
1458 {
1459 gchar *name = NULL, *email = NULL, *source_uid = NULL, *card_uid = NULL;
1460 gboolean is_list = FALSE, show_addr = FALSE, auto_recip = FALSE;
1461 gboolean html_mail = FALSE;
1462 GList *list_dests = NULL;
1463 gint email_num = -1;
1464 gchar *tmp;
1465
1466 g_return_val_if_fail (dest && E_IS_DESTINATION (dest), FALSE);
1467 g_return_val_if_fail (node != NULL, FALSE);
1468
1469 if (strcmp ((gchar *) node->name, "destination"))
1470 return FALSE;
1471
1472 tmp = (gchar *) xmlGetProp (node, (xmlChar *)"html_mail");
1473 if (tmp) {
1474 html_mail = !strcmp (tmp, "yes");
1475 xmlFree (tmp);
1476 }
1477
1478 tmp = (gchar *) xmlGetProp (node, (xmlChar *)"is_list");
1479 if (tmp) {
1480 is_list = !strcmp (tmp, "yes");
1481 xmlFree (tmp);
1482 }
1483
1484 tmp = (gchar *) xmlGetProp (node, (xmlChar *)"show_addresses");
1485 if (tmp) {
1486 show_addr = !strcmp (tmp, "yes");
1487 xmlFree (tmp);
1488 }
1489
1490 tmp = (gchar *) xmlGetProp (node, (xmlChar *)"auto_recipient");
1491 if (tmp) {
1492 auto_recip = !strcmp (tmp, "yes");
1493 xmlFree (tmp);
1494 }
1495
1496 node = node->xmlChildrenNode;
1497 while (node) {
1498 if (!strcmp ((gchar *) node->name, "name")) {
1499 tmp = (gchar *) xmlNodeGetContent (node);
1500 g_free (name);
1501 name = g_strdup (tmp);
1502 xmlFree (tmp);
1503 } else if (!is_list && !strcmp ((gchar *) node->name, "email")) {
1504 tmp = (gchar *) xmlNodeGetContent (node);
1505 g_free (email);
1506 email = g_strdup (tmp);
1507 xmlFree (tmp);
1508 } else if (is_list && !strcmp ((gchar *) node->name, "list_entry")) {
1509 xmlNodePtr subnode = node->xmlChildrenNode;
1510 gchar *list_name = NULL, *list_email = NULL;
1511
1512 while (subnode) {
1513 if (!strcmp ((gchar *) subnode->name, "name")) {
1514 tmp = (gchar *) xmlNodeGetContent (subnode);
1515 g_free (list_name);
1516 list_name = g_strdup (tmp);
1517 xmlFree (tmp);
1518 } else if (!strcmp ((gchar *) subnode->name, "email")) {
1519 tmp = (gchar *) xmlNodeGetContent (subnode);
1520 g_free (list_email);
1521 list_email = g_strdup (tmp);
1522 xmlFree (tmp);
1523 }
1524
1525 subnode = subnode->next;
1526 }
1527
1528 if (list_name || list_email) {
1529 EDestination *list_dest = e_destination_new ();
1530
1531 if (list_name)
1532 e_destination_set_name (list_dest, list_name);
1533 if (list_email)
1534 e_destination_set_email (list_dest, list_email);
1535
1536 g_free (list_name);
1537 g_free (list_email);
1538
1539 list_dests = g_list_append (list_dests, list_dest);
1540 }
1541 } else if (!strcmp ((gchar *) node->name, "source_uid")) {
1542 tmp = (gchar *) xmlNodeGetContent (node);
1543 g_free (source_uid);
1544 source_uid = g_strdup (tmp);
1545 xmlFree (tmp);
1546 } else if (!strcmp ((gchar *) node->name, "card_uid")) {
1547 tmp = (gchar *) xmlNodeGetContent (node);
1548 g_free (card_uid);
1549 card_uid = g_strdup (tmp);
1550 xmlFree (tmp);
1551
1552 tmp = (gchar *) xmlGetProp (node, (xmlChar *)"email_num");
1553 email_num = atoi (tmp);
1554 xmlFree (tmp);
1555 }
1556
1557 node = node->next;
1558 }
1559
1560 e_destination_clear (dest);
1561
1562 if (name) {
1563 e_destination_set_name (dest, name);
1564 g_free (name);
1565 }
1566 if (email) {
1567 e_destination_set_email (dest, email);
1568 g_free (email);
1569 }
1570 if (source_uid) {
1571 e_destination_set_source_uid (dest, source_uid);
1572 g_free (source_uid);
1573 }
1574 if (card_uid) {
1575 e_destination_set_contact_uid (dest, card_uid, email_num);
1576 g_free (card_uid);
1577 }
1578 if (list_dests)
1579 dest->priv->list_dests = list_dests;
1580
1581 dest->priv->html_mail_override = TRUE;
1582 dest->priv->wants_html_mail = html_mail;
1583
1584 dest->priv->show_addresses = show_addr;
1585
1586 dest->priv->auto_recipient = auto_recip;
1587
1588 return TRUE;
1589 }
1590
1591 static gchar *
null_terminate_and_remove_extra_whitespace(xmlChar * xml_in,gint size)1592 null_terminate_and_remove_extra_whitespace (xmlChar *xml_in,
1593 gint size)
1594 {
1595 gboolean skip_white = FALSE;
1596 gchar *xml, *r, *w;
1597
1598 if (xml_in == NULL || size <= 0)
1599 return NULL;
1600
1601 xml = g_strndup ((gchar *) xml_in, size);
1602 r = w = xml;
1603
1604 while (*r) {
1605 if (*r == '\n' || *r == '\r') {
1606 skip_white = TRUE;
1607 } else {
1608 gunichar c = g_utf8_get_char (r);
1609 gboolean is_space = g_unichar_isspace (c);
1610
1611 *w = *r;
1612
1613 if (!(skip_white && is_space))
1614 w++;
1615 if (!is_space)
1616 skip_white = FALSE;
1617 }
1618 r = g_utf8_next_char (r);
1619 }
1620
1621 *w = '\0';
1622
1623 return xml;
1624 }
1625
1626 /**
1627 * e_destination_export:
1628 * @dest: an #EDestination
1629 *
1630 * Exports a destination to an XML document.
1631 *
1632 * Returns: An XML string, allocated with g_malloc.
1633 **/
1634 gchar *
e_destination_export(const EDestination * dest)1635 e_destination_export (const EDestination *dest)
1636 {
1637 xmlNodePtr dest_node;
1638 xmlDocPtr dest_doc;
1639 xmlChar *buffer = NULL;
1640 gint size = -1;
1641 gchar *str;
1642
1643 g_return_val_if_fail (dest && E_IS_DESTINATION (dest), NULL);
1644
1645 dest_node = e_destination_xml_encode (dest);
1646 if (dest_node == NULL)
1647 return NULL;
1648
1649 dest_doc = xmlNewDoc ((xmlChar *) XML_DEFAULT_VERSION);
1650 xmlDocSetRootElement (dest_doc, dest_node);
1651
1652 xmlDocDumpMemory (dest_doc, &buffer, &size);
1653 xmlFreeDoc (dest_doc);
1654
1655 str = null_terminate_and_remove_extra_whitespace (buffer, size);
1656 xmlFree (buffer);
1657
1658 return str;
1659 }
1660
1661 /**
1662 * e_destination_import:
1663 * @str: an XML string
1664 *
1665 * Creates an #EDestination from an XML document.
1666 *
1667 * Returns: (transfer full) (nullable): An #EDestination, or %NULL if the document was not
1668 * well-formed.
1669 **/
1670 EDestination *
e_destination_import(const gchar * str)1671 e_destination_import (const gchar *str)
1672 {
1673 EDestination *dest = NULL;
1674 xmlDocPtr dest_doc;
1675
1676 if (!(str && *str))
1677 return NULL;
1678
1679 dest_doc = xmlParseMemory ((gchar *) str, strlen (str));
1680 if (dest_doc && dest_doc->xmlRootNode) {
1681 dest = e_destination_new ();
1682 if (!e_destination_xml_decode (dest, dest_doc->xmlRootNode)) {
1683 g_object_unref (dest);
1684 dest = NULL;
1685 }
1686 }
1687 xmlFreeDoc (dest_doc);
1688
1689 return dest;
1690 }
1691
1692 /**
1693 * e_destination_exportv:
1694 * @destv: (array zero-terminated=1): a %NULL-terminated array of pointers to #EDestination
1695 *
1696 * Exports multiple #EDestination elements to a single XML document.
1697 *
1698 * Returns: An XML string, allocated with g_malloc.
1699 **/
1700 gchar *
e_destination_exportv(EDestination ** destv)1701 e_destination_exportv (EDestination **destv)
1702 {
1703 xmlDocPtr destv_doc;
1704 xmlNodePtr destv_node;
1705 xmlChar *buffer = NULL;
1706 gint i, size = -1;
1707 gchar *str;
1708
1709 if (destv == NULL || *destv == NULL)
1710 return NULL;
1711
1712 destv_doc = xmlNewDoc ((xmlChar *) XML_DEFAULT_VERSION);
1713 destv_node = xmlNewNode (NULL, (xmlChar *)"destinations");
1714 xmlDocSetRootElement (destv_doc, destv_node);
1715
1716 for (i = 0; destv[i]; i++) {
1717 if (!e_destination_empty (destv[i])) {
1718 xmlNodePtr dest_node = e_destination_xml_encode (destv[i]);
1719 if (dest_node)
1720 xmlAddChild (destv_node, dest_node);
1721 }
1722 }
1723
1724 xmlDocDumpMemory (destv_doc, &buffer, &size);
1725 xmlFreeDoc (destv_doc);
1726
1727 str = null_terminate_and_remove_extra_whitespace (buffer, size);
1728 xmlFree (buffer);
1729
1730 return str;
1731 }
1732
1733 /**
1734 * e_destination_importv:
1735 * @str: an XML string
1736 *
1737 * Creates an array of pointers to #EDestination elements
1738 * from an XML document.
1739 *
1740 * Returns: (transfer full) (array zero-terminated=1): A %NULL-terminated
1741 * array of pointers to #EDestination elements.
1742 **/
1743 EDestination **
e_destination_importv(const gchar * str)1744 e_destination_importv (const gchar *str)
1745 {
1746 GPtrArray *dest_array = NULL;
1747 xmlDocPtr destv_doc;
1748 xmlNodePtr node;
1749 EDestination **destv = NULL;
1750
1751 if (!(str && *str))
1752 return NULL;
1753
1754 destv_doc = xmlParseMemory ((gchar *) str, strlen (str));
1755 if (destv_doc == NULL)
1756 return NULL;
1757
1758 node = destv_doc->xmlRootNode;
1759
1760 if (strcmp ((gchar *) node->name, "destinations"))
1761 goto finished;
1762
1763 node = node->xmlChildrenNode;
1764
1765 dest_array = g_ptr_array_new ();
1766
1767 while (node) {
1768 EDestination *dest;
1769
1770 dest = e_destination_new ();
1771 if (e_destination_xml_decode (dest, node) && !e_destination_empty (dest)) {
1772 g_ptr_array_add (dest_array, dest);
1773 } else {
1774 g_object_unref (dest);
1775 }
1776
1777 node = node->next;
1778 }
1779
1780 /* we need destv to be NULL terminated */
1781 g_ptr_array_add (dest_array, NULL);
1782
1783 destv = (EDestination **) dest_array->pdata;
1784 g_ptr_array_free (dest_array, FALSE);
1785
1786 finished:
1787 xmlFreeDoc (destv_doc);
1788
1789 return destv;
1790 }
1791
1792 /**
1793 * e_destination_freev:
1794 * @destv: (array zero-terminated=1): a %NULL-terminated array of pointers to #EDestination
1795 *
1796 * Unrefs the elements of @destv and frees @destv itself.
1797 **/
1798 void
e_destination_freev(EDestination ** destv)1799 e_destination_freev (EDestination **destv)
1800 {
1801 gint i;
1802
1803 if (destv) {
1804 for (i = 0; destv[i] != NULL; ++i) {
1805 g_object_unref (destv[i]);
1806 }
1807 g_free (destv);
1808 }
1809
1810 }
1811
1812 /**
1813 * e_destination_export_to_vcard_attribute:
1814 * @dest: an #EDestination
1815 * @attr: an #EVCardAttribute
1816 *
1817 * Exports the contact information from @dest to parameters
1818 * and values in @attr, suitable for an address book.
1819 **/
1820 void
e_destination_export_to_vcard_attribute(EDestination * dest,EVCardAttribute * attr)1821 e_destination_export_to_vcard_attribute (EDestination *dest,
1822 EVCardAttribute *attr)
1823 {
1824 e_vcard_attribute_remove_values (attr);
1825 e_vcard_attribute_remove_params (attr);
1826
1827 if (e_destination_get_contact_uid (dest))
1828 e_vcard_attribute_add_param_with_value (
1829 attr,
1830 e_vcard_attribute_param_new (EVC_X_DEST_CONTACT_UID),
1831 e_destination_get_contact_uid (dest));
1832 if (e_destination_get_source_uid (dest))
1833 e_vcard_attribute_add_param_with_value (
1834 attr,
1835 e_vcard_attribute_param_new (EVC_X_DEST_SOURCE_UID),
1836 e_destination_get_source_uid (dest));
1837 if (-1 != e_destination_get_email_num (dest)) {
1838 gchar buf[10];
1839 g_snprintf (buf, sizeof (buf), "%d", e_destination_get_email_num (dest));
1840 e_vcard_attribute_add_param_with_value (
1841 attr,
1842 e_vcard_attribute_param_new (EVC_X_DEST_EMAIL_NUM),
1843 buf);
1844 }
1845 e_vcard_attribute_add_param_with_value (
1846 attr,
1847 e_vcard_attribute_param_new (EVC_X_DEST_HTML_MAIL),
1848 e_destination_get_html_mail_pref (dest) ? "TRUE" : "FALSE");
1849
1850 if (e_destination_get_address (dest))
1851 e_vcard_attribute_add_value (attr, e_destination_get_address (dest));
1852 }
1853