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