1 /* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
2 /* Balsa E-Mail Client
3  *
4  * Copyright (C) 1997-2013 Stuart Parmenter and others,
5  *                         See the file AUTHORS for a list.
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2, or (at your option)
10  * any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
20  * 02111-1307, USA.
21  */
22 
23 #if defined(HAVE_CONFIG_H) && HAVE_CONFIG_H
24 # include "config.h"
25 #endif                          /* HAVE_CONFIG_H */
26 #include "address.h"
27 
28 #include <string.h>
29 #include <gmime/gmime.h>
30 
31 #include "misc.h"
32 #include <glib/gi18n.h>
33 
34 static GObjectClass *parent_class;
35 
36 static void libbalsa_address_class_init(LibBalsaAddressClass * klass);
37 static void libbalsa_address_init(LibBalsaAddress * ab);
38 static void libbalsa_address_finalize(GObject * object);
39 
40 static gchar ** vcard_strsplit(const gchar * string);
41 
libbalsa_address_get_type(void)42 GType libbalsa_address_get_type(void)
43 {
44     static GType address_type = 0;
45 
46     if (!address_type) {
47 	static const GTypeInfo address_info = {
48 	    sizeof(LibBalsaAddressClass),
49             NULL,               /* base_init */
50             NULL,               /* base_finalize */
51 	    (GClassInitFunc) libbalsa_address_class_init,
52             NULL,               /* class_finalize */
53             NULL,               /* class_data */
54 	    sizeof(LibBalsaAddress),
55             0,                  /* n_preallocs */
56 	    (GInstanceInitFunc) libbalsa_address_init
57 	};
58 
59 	address_type =
60 	    g_type_register_static(G_TYPE_OBJECT,
61 	                           "LibBalsaAddress",
62                                    &address_info, 0);
63     }
64 
65     return address_type;
66 }
67 
68 static void
libbalsa_address_class_init(LibBalsaAddressClass * klass)69 libbalsa_address_class_init(LibBalsaAddressClass * klass)
70 {
71     GObjectClass *object_class;
72 
73     parent_class = g_type_class_peek_parent(klass);
74 
75     object_class = G_OBJECT_CLASS(klass);
76     object_class->finalize = libbalsa_address_finalize;
77 }
78 
79 static void
libbalsa_address_init(LibBalsaAddress * addr)80 libbalsa_address_init(LibBalsaAddress * addr)
81 {
82     addr->nick_name = NULL;
83     addr->full_name = NULL;
84     addr->first_name = NULL;
85     addr->last_name = NULL;
86     addr->organization = NULL;
87     addr->address_list = NULL;
88 }
89 
90 static void
libbalsa_address_finalize(GObject * object)91 libbalsa_address_finalize(GObject * object)
92 {
93     LibBalsaAddress *addr;
94 
95     g_return_if_fail(object != NULL);
96 
97     addr = LIBBALSA_ADDRESS(object);
98 
99     g_free(addr->nick_name);    addr->nick_name = NULL;
100     g_free(addr->full_name);    addr->full_name = NULL;
101     g_free(addr->first_name);   addr->first_name = NULL;
102     g_free(addr->last_name);    addr->last_name = NULL;
103     g_free(addr->organization); addr->organization = NULL;
104 
105     g_list_foreach(addr->address_list, (GFunc) g_free, NULL);
106     g_list_free(addr->address_list);
107     addr->address_list = NULL;
108 
109     G_OBJECT_CLASS(parent_class)->finalize(object);
110 }
111 
112 LibBalsaAddress *
libbalsa_address_new(void)113 libbalsa_address_new(void)
114 {
115     return g_object_new(LIBBALSA_TYPE_ADDRESS, NULL);
116 }
117 
118 /** Extract full name in order from <string> that has GnomeCard format
119    and returns the pointer to the allocated memory chunk.
120 
121    VCARD code attempts to obey published documentation:
122 
123    [1] VCARD 1.2 specs: http://www.imc.org/pdi/vcard-21.txt
124    [2] VCARD 3.0 specs, RFC 2426 (http://www.ietf.org/rfc/rfc2426.txt)
125 */
126 gchar*
libbalsa_address_extract_name(const gchar * string,gchar ** last_name,gchar ** first_name)127 libbalsa_address_extract_name(const gchar * string, gchar ** last_name,
128                               gchar ** first_name)
129 {
130     enum GCardFieldOrder { LAST = 0, FIRST, MIDDLE, PREFIX, SUFFIX };
131     gint cpt, j;
132     gchar **fld, **name_arr;
133     gchar *res = NULL;
134 
135     fld = vcard_strsplit(string);
136 
137     cpt = 0;
138     while (fld[cpt] != NULL)
139 	cpt++;
140 
141     if (cpt == 0) {
142         /* insane empty name */
143         g_strfreev(fld);
144         return NULL;
145     }
146 
147     if (fld[LAST] && *fld[LAST])
148         *last_name = g_strdup(fld[LAST]);
149 
150     if (fld[FIRST] && *fld[FIRST])
151         *first_name = fld[MIDDLE] && *fld[MIDDLE] ?
152             g_strconcat(fld[FIRST], " ", fld[MIDDLE], NULL) :
153             g_strdup(fld[FIRST]);
154 
155     name_arr = g_malloc((cpt + 1) * sizeof(gchar *));
156 
157     j = 0;
158     if (cpt > PREFIX && *fld[PREFIX] != '\0')
159 	name_arr[j++] = g_strdup(fld[PREFIX]);
160 
161     if (cpt > FIRST && *fld[FIRST] != '\0')
162 	name_arr[j++] = g_strdup(fld[FIRST]);
163 
164     if (cpt > MIDDLE && *fld[MIDDLE] != '\0')
165 	name_arr[j++] = g_strdup(fld[MIDDLE]);
166 
167     if (cpt > LAST && *fld[LAST] != '\0')
168 	name_arr[j++] = g_strdup(fld[LAST]);
169 
170     if (cpt > SUFFIX && *fld[SUFFIX] != '\0')
171 	name_arr[j++] = g_strdup(fld[SUFFIX]);
172 
173     name_arr[j] = NULL;
174 
175     g_strfreev(fld);
176 
177     /* collect the data to one string */
178     res = g_strjoinv(" ", name_arr);
179     g_strfreev(name_arr);
180 
181     return res;
182 }
183 
184 /* Duplicates some code from address-book-vcard.c:
185  */
186 static gchar*
validate_vcard_string(gchar * vcstr,const gchar * charset)187 validate_vcard_string(gchar * vcstr, const gchar * charset)
188 {
189     gchar * utf8res;
190     gsize b_written;
191 
192     /* check if it's a utf8 clean string and return it in this case */
193     if (!vcstr || g_utf8_validate(vcstr, -1, NULL))
194 	return vcstr;
195 
196     /* convert from the passed charset or as fallback from the locale setting */
197     if (charset && g_ascii_strcasecmp(charset, "utf-8")) {
198 	utf8res = g_convert(vcstr, -1, "utf-8", charset, NULL, &b_written, NULL);
199     } else
200 	utf8res = g_locale_to_utf8(vcstr, -1, NULL, &b_written, NULL);
201     if (!utf8res)
202 	return vcstr;
203 
204     g_free(vcstr);
205     return utf8res;
206 }
207 
208 
209 static inline gchar *
vcard_qp_decode(gchar * str)210 vcard_qp_decode(gchar * str)
211 {
212     gint len = strlen(str);
213     gchar * newstr = g_malloc0(len + 1);
214     int state = 0;
215     guint32 save;
216 
217     /* qp decode the input string */
218     g_mime_encoding_quoted_decode_step((unsigned char *) str, len,
219 				    (unsigned char *) newstr, &state, &save);
220 
221     /* free input and return new string */
222     g_free(str);
223     return newstr;
224 }
225 
226 
227 static inline gchar *
vcard_b64_decode(gchar * str)228 vcard_b64_decode(gchar * str)
229 {
230     gint len = strlen(str);
231     gchar * newstr = g_malloc0(len + 1);
232     int state = 0;
233     guint32 save;
234 
235     /* base64 decode the input string */
236     g_mime_encoding_base64_decode_step((unsigned char *) str, len,
237 				    (unsigned char *) newstr, &state, &save);
238 
239     /* free input and return new string */
240     g_free(str);
241     return newstr;
242 }
243 
244 
245 static inline gchar *
vcard_charset_to_utf8(gchar * str,const gchar * charset)246 vcard_charset_to_utf8(gchar * str, const gchar * charset)
247 {
248     gsize bytes_written;
249     gchar * convstr;
250 
251     /* convert only if the source is known and not utf-8 */
252     if (!charset || !g_ascii_strcasecmp(charset, "utf-8"))
253 	return str;
254 
255     convstr = g_convert(str, -1, "utf-8", charset, NULL, &bytes_written, NULL);
256     g_free(str);
257     return convstr ? convstr : g_strdup("");
258 }
259 
260 
261 /* mainly copied from g_strsplit, but (a) with the fixed delimiter ';'
262  * (b) ignoring '\;' sequences (c) always returning as many elements as
263  * possible and (d) unescape '\;' sequences in the resulting array */
264 static gchar **
vcard_strsplit(const gchar * string)265 vcard_strsplit(const gchar * string)
266 {
267     GSList *string_list = NULL, *slist;
268     gchar **str_array, *s;
269     guint n = 0;
270     const gchar *remainder;
271 
272     g_return_val_if_fail(string != NULL, NULL);
273 
274     remainder = string;
275     s = strchr(remainder, ';');
276     while (s && s > remainder && s[-1] == '\\')
277 	s = strchr(s + 1, ';');
278 
279     while (s) {
280 	gsize len;
281 
282 	len = s - remainder;
283         /* skip empty fields: */
284         if (len > 0) {
285             string_list =
286                 g_slist_prepend(string_list, g_strndup(remainder, len));
287             n++;
288         }
289 	remainder = s + 1;
290 	s = strchr(remainder, ';');
291 	while (s && s > remainder && s[-1] == '\\')
292 	    s = strchr(s + 1, ';');
293     }
294 
295     if (*string) {
296 	n++;
297 	string_list = g_slist_prepend(string_list, g_strdup(remainder));
298     }
299 
300     str_array = g_new(gchar*, n + 1);
301 
302     str_array[n--] = NULL;
303     for (slist = string_list; slist; slist = slist->next) {
304 	gchar * str = (gchar *) slist->data;
305 	gchar * p;
306 
307 	while ((p = strstr(str, "\\;"))) {
308 	    gchar * newstr = g_malloc(strlen(str));
309 
310 	    strncpy(newstr, str, p - str);
311 	    strcpy(newstr + (p - str), p + 1);
312 	    g_free(str);
313 	    str = newstr;
314 	}
315 	str_array[n--] = str;
316     }
317 
318     g_slist_free(string_list);
319 
320     return str_array;
321 }
322 
323 /*
324  * Create a LibBalsaAddress from a vCard; return NULL if the string does
325  * not contain a complete vCard with at least one email address.
326  */
327 
328 LibBalsaAddress*
libbalsa_address_new_from_vcard(const gchar * str,const gchar * charset)329 libbalsa_address_new_from_vcard(const gchar *str, const gchar *charset)
330 {
331     gchar *name = NULL, *nick_name = NULL, *org = NULL;
332     gchar *full_name = NULL, *last_name = NULL, *first_name = NULL;
333     gint in_vcard = FALSE;
334     GList *address_list = NULL;
335     const gchar *string, *next_line;
336     gchar * vcard;
337 
338     g_return_val_if_fail(str, NULL);
339 
340     /* rfc 2425 unfold the string */
341     vcard = g_strdup(str);
342     while ((string = strstr(vcard, "\r\n ")) ||
343 	   (string = strstr(vcard, "\r\n\t"))) {
344 	gchar * newstr = g_malloc0(strlen(vcard) - 2);
345 
346 	strncpy(newstr, vcard, string - vcard);
347 	strcpy(newstr + (string - vcard), string + 3);
348 	g_free(vcard);
349 	vcard = newstr;
350     }
351     while ((string = strstr(vcard, "\n ")) ||
352 	   (string = strstr(vcard, "\n\t"))) {
353 	gchar * newstr = g_malloc(strlen(vcard) - 1);
354 
355 	strncpy(newstr, vcard, string - vcard);
356 	strcpy(newstr + (string - vcard), string + 2);
357 	g_free(vcard);
358 	vcard = newstr;
359     }
360 
361     /* may contain \r's when decoded from base64... */
362     while ((string = strstr(vcard, "\r\n ")) ||
363 	   (string = strstr(vcard, "\r\n\t"))) {
364 	gchar * newstr = g_malloc(strlen(vcard) - 2);
365 
366 	strncpy(newstr, vcard, string - vcard);
367 	strcpy(newstr + (string - vcard), string + 3);
368 	g_free(vcard);
369 	vcard = newstr;
370     }
371 
372     /* process */
373     for(string = vcard; *string; string = next_line) {
374         gchar *line;
375 
376         next_line = strchr(string, '\n');
377         if (next_line)
378             ++next_line;
379         else
380             next_line = string + strlen(string);
381 	/*
382 	 * Check if it is a card.
383 	 */
384 	if (g_ascii_strncasecmp(string, "BEGIN:VCARD", 11) == 0)
385 	    in_vcard = TRUE;
386 	else if (in_vcard) {
387 	    if (g_ascii_strncasecmp(string, "END:VCARD", 9) == 0) {
388 		/*
389 		 * We are done loading a card.
390 		 */
391 		LibBalsaAddress *address;
392 
393 		if (!address_list)
394                     break;
395 
396 		address = g_object_new(LIBBALSA_TYPE_ADDRESS, NULL);
397 
398 		if (full_name) {
399 		    address->full_name = full_name;
400 		    g_free(name);
401 		} else if (name)
402 		    address->full_name = name;
403 		else if (nick_name)
404 		    address->full_name = g_strdup(nick_name);
405 		else
406 		    address->full_name = g_strdup(_("No-Name"));
407 
408                 address->last_name = last_name;
409                 address->first_name = first_name;
410                 address->nick_name = nick_name;
411                 address->organization = org;
412                 address->address_list = g_list_reverse(address_list);
413 
414                 return address;
415             }
416 
417             line = g_strndup(string, next_line - string);
418             g_strchomp(line);
419 
420 	    /* Encoding of national characters:
421 	     * - vcard 2.1 allows charset=xxx and encoding=(base64|quoted-printable|8bit)
422 	     * - Thunderbird claims to use vcard 2.1, but uses only "quoted-printable",
423 	     *   and the charset from part MIME header
424 	     * - vcard 3.0 (rfc 2426) allows "encoding=b", the charset must be taken
425 	     *   from the content-type MIME header */
426 	    if (strchr(line, ':')) {
427 		gchar ** parts = g_strsplit(line, ":", 2);
428 		gchar ** tokens;
429 		gint n;
430 
431 		/* split control stuff into tokens */
432 		tokens = g_strsplit(parts[0], ";", -1);
433 
434 		/* find encoding= */
435 		for (n = 0; tokens[n]; n++)
436 		    if (!g_ascii_strncasecmp(tokens[n], "encoding=", 9)) {
437 			if (!g_ascii_strcasecmp(tokens[n] + 9, "base64")) {
438 			    /* vcard 2.1: use the charset parameter (below) */
439 			    parts[1] = vcard_b64_decode(parts[1]);
440 			} else if (!g_ascii_strcasecmp(tokens[n] + 9, "b")) {
441 			    /* rfc 2426: charset from MIME part */
442 			    parts[1] = vcard_b64_decode(parts[1]);
443 			    parts[1] = vcard_charset_to_utf8(parts[1], charset);
444 			} else if (!g_ascii_strcasecmp(tokens[n] + 9, "quoted-printable")) {
445 			    /* vcard 2.1: use the charset parameter (below) */
446 			    parts[1] = vcard_qp_decode(parts[1]);
447 			}
448 		    }
449 
450 		/* find quoted-printable */
451 		for (n = 0; tokens[n]; n++)
452 		    if (!g_ascii_strcasecmp(tokens[n], "quoted-printable")) {
453 			/* Thunderbird: broken vcard 2.1, charset from MIME part */
454 			parts[1] = vcard_qp_decode(parts[1]);
455 			parts[1] = vcard_charset_to_utf8(parts[1], charset);
456 		    }
457 
458 		/* find charset= (vcard 2.1 only) */
459 		for (n = 0; tokens[n]; n++)
460 		    if (!g_ascii_strncasecmp(tokens[n], "charset=", 8))
461 			parts[1] = vcard_charset_to_utf8(parts[1], tokens[n] + 8);
462 
463 		/* construct the result */
464 		g_free(line);
465 		line = g_strdup_printf("%s:%s", tokens[0], parts[1]);
466 
467 		/* clean up */
468 		g_strfreev(tokens);
469 		g_strfreev(parts);
470 	    }
471 
472             if (g_ascii_strncasecmp(line, "FN:", 3) == 0) {
473 
474                 g_free(full_name);
475                 full_name = g_strdup(line + 3);
476                 full_name = validate_vcard_string(full_name, charset);
477 
478             } else if (g_ascii_strncasecmp(line, "N:", 2) == 0) {
479 
480                 g_free(name);
481                 g_free(last_name);
482                 g_free(first_name);
483                 name = libbalsa_address_extract_name(line + 2,
484                                                      &last_name, &first_name);
485                 name = validate_vcard_string(name, charset);
486                 last_name = validate_vcard_string(last_name, charset);
487                 first_name = validate_vcard_string(first_name, charset);
488 
489             } else if (g_ascii_strncasecmp(line, "NICKNAME:", 9) == 0) {
490 
491                 g_free(nick_name);
492                 nick_name = g_strdup(line + 9);
493                 nick_name = validate_vcard_string(nick_name, charset);
494 
495             } else if (g_ascii_strncasecmp(line, "ORG:", 4) == 0) {
496 		gchar ** org_strs = vcard_strsplit(line + 4);
497 
498                 g_free(org);
499                 org = g_strjoinv(", ", org_strs);
500 		g_strfreev(org_strs);
501                 org = validate_vcard_string(org, charset);
502 
503             } else if (g_ascii_strncasecmp(line, "EMAIL:", 6) == 0) {
504 
505 		address_list =
506 		    g_list_prepend(address_list, g_strdup(line + 6));
507             }
508             g_free(line);
509         }
510     }
511 
512     g_free(full_name);
513     g_free(name);
514     g_free(last_name);
515     g_free(first_name);
516     g_free(nick_name);
517     g_free(org);
518     g_list_foreach(address_list, (GFunc) g_free, NULL);
519     g_list_free(address_list);
520 
521     return NULL;
522 }
523 
524 void
libbalsa_address_set_copy(LibBalsaAddress * dest,LibBalsaAddress * src)525 libbalsa_address_set_copy(LibBalsaAddress * dest, LibBalsaAddress * src)
526 {
527     GList *src_al, *dst_al;
528 
529     if (dest == src)            /* safety check */
530         return;
531 
532     g_free(dest->nick_name);
533     dest->nick_name = g_strdup(src->nick_name);
534     g_free(dest->full_name);
535     dest->full_name = g_strdup(src->full_name);
536     g_free(dest->first_name);
537     dest->first_name = g_strdup(src->first_name);
538     g_free(dest->last_name);
539     dest->last_name = g_strdup(src->last_name);
540     g_free(dest->organization);
541     dest->organization = g_strdup(src->organization);
542     g_list_foreach(dest->address_list, (GFunc) g_free, NULL);
543     g_list_free(dest->address_list);
544 
545     dst_al = NULL;
546     for (src_al = src->address_list; src_al; src_al = src_al->next)
547         dst_al = g_list_prepend(dst_al, g_strdup(src_al->data));
548     dest->address_list = g_list_reverse(dst_al);
549 }
550 
551 static gchar *
rfc2822_mailbox(const gchar * full_name,const gchar * address)552 rfc2822_mailbox(const gchar * full_name, const gchar * address)
553 {
554     InternetAddress *ia;
555     gchar *new_str;
556 
557     ia = internet_address_mailbox_new(full_name, address);
558     new_str = internet_address_to_string(ia, FALSE);
559     g_object_unref(ia);
560 
561     return new_str;
562 }
563 
564 static gchar*
rfc2822_group(const gchar * full_name,GList * addr_list)565 rfc2822_group(const gchar *full_name, GList *addr_list)
566 {
567     InternetAddress *ia;
568     gchar *res;
569 
570     ia = internet_address_group_new(full_name);
571     for (; addr_list; addr_list = addr_list->next) {
572 	InternetAddress *member;
573 
574 	member = internet_address_mailbox_new(NULL, addr_list->data);
575 	internet_address_group_add_member(INTERNET_ADDRESS_GROUP(ia), member);
576 	g_object_unref(member);
577     }
578     res = internet_address_to_string(ia, FALSE);
579     g_object_unref(ia);
580 
581     return res;
582 }
583 
584 /*
585    Get a string version of this address.
586 
587    If n == -1 then return all addresses, else return the n'th one.
588    If n > the number of addresses, will cause an error.
589 */
590 gchar *
libbalsa_address_to_gchar(LibBalsaAddress * address,gint n)591 libbalsa_address_to_gchar(LibBalsaAddress * address, gint n)
592 {
593     gchar *retc = NULL;
594 
595     g_return_val_if_fail(LIBBALSA_IS_ADDRESS(address), NULL);
596 
597     if(!address->address_list)
598         return NULL;
599     if(n==-1) {
600         if(address->address_list->next)
601             retc = rfc2822_group(address->full_name, address->address_list);
602         else
603             retc = rfc2822_mailbox(address->full_name,
604                                    address->address_list->data);
605     } else {
606 	const gchar *mailbox = g_list_nth_data(address->address_list, n);
607 	g_return_val_if_fail(mailbox != NULL, NULL);
608 
609 	retc = rfc2822_mailbox(address->full_name, mailbox);
610     }
611 
612     return retc;
613 }
614 
615 /* Helper */
616 static const gchar *
lba_get_name_or_mailbox(InternetAddressList * address_list,gboolean get_name,gboolean in_group)617 lba_get_name_or_mailbox(InternetAddressList * address_list,
618                         gboolean get_name, gboolean in_group)
619 {
620     const gchar *retval = NULL;
621     InternetAddress *ia;
622     gint i, len;
623 
624     if (address_list == NULL)
625 	return NULL;
626 
627     len = internet_address_list_length(address_list);
628     for (i = 0; i < len; i++) {
629         ia = internet_address_list_get_address (address_list, i);
630 
631         if (get_name && ia->name && *ia->name)
632             return ia->name;
633 
634         if (INTERNET_ADDRESS_IS_MAILBOX (ia))
635             retval = INTERNET_ADDRESS_MAILBOX (ia)->addr;
636         else {
637             if (in_group)
638                 g_message("Ignoring nested group address");
639             else
640                 retval = lba_get_name_or_mailbox(INTERNET_ADDRESS_GROUP(ia)->members,
641 			get_name, TRUE);
642         }
643         if (retval)
644             break;
645     }
646 
647     return retval;
648 }
649 
650 /* Get either a name or a mailbox from an InternetAddressList. */
651 const gchar *
libbalsa_address_get_name_from_list(InternetAddressList * address_list)652 libbalsa_address_get_name_from_list(InternetAddressList *address_list)
653 {
654     return lba_get_name_or_mailbox(address_list, TRUE, FALSE);
655 }
656 
657 /* Get a mailbox from an InternetAddressList. */
658 const gchar *
libbalsa_address_get_mailbox_from_list(InternetAddressList * address_list)659 libbalsa_address_get_mailbox_from_list(InternetAddressList *address_list)
660 {
661     return lba_get_name_or_mailbox(address_list, FALSE, FALSE);
662 }
663 
664 /* Number of individual mailboxes in an InternetAddressList. */
665 gint
libbalsa_address_n_mailboxes_in_list(InternetAddressList * address_list)666 libbalsa_address_n_mailboxes_in_list(InternetAddressList * address_list)
667 {
668     gint i, len, n_mailboxes = 0;
669 
670     g_return_val_if_fail(IS_INTERNET_ADDRESS_LIST(address_list), -1);
671 
672     len = internet_address_list_length(address_list);
673     for (i = 0; i < len; i++) {
674         const InternetAddress *ia =
675             internet_address_list_get_address(address_list, i);
676 
677         if (INTERNET_ADDRESS_IS_MAILBOX(ia))
678             ++n_mailboxes;
679         else
680             n_mailboxes +=
681                 libbalsa_address_n_mailboxes_in_list(INTERNET_ADDRESS_GROUP
682                                                      (ia)->members);
683     }
684 
685     return n_mailboxes;
686 }
687 
688 /* =================================================================== */
689 /*                                UI PART                              */
690 /* =================================================================== */
691 
692 /** libbalsa_address_set_edit_entries() initializes the GtkEntry widgets
693     in entries with values from address
694 */
695 void
libbalsa_address_set_edit_entries(const LibBalsaAddress * address,GtkWidget ** entries)696 libbalsa_address_set_edit_entries(const LibBalsaAddress * address,
697                                   GtkWidget **entries)
698 {
699     gchar *new_name = NULL;
700     gchar *new_email = NULL;
701     gchar *new_organization = NULL;
702     gchar *first_name = NULL;
703     gchar *last_name = NULL;
704     gchar *nick_name = NULL;
705     gint cnt;
706     GtkListStore *store;
707     GtkTreeIter iter;
708 
709     new_email = g_strdup(address
710                          && address->address_list
711                          && address->address_list->data ?
712                          address->address_list->data : "");
713     /* initialize the organization... */
714     if (!address || address->organization == NULL)
715 	new_organization = g_strdup("");
716     else
717 	new_organization = g_strdup(address->organization);
718 
719     /* if the message only contains an e-mail address */
720     if (!address || address->full_name == NULL)
721 	new_name = g_strdup(new_email);
722     else {
723         gchar **names;
724         g_assert(address);
725 	/* make sure address->personal is not all whitespace */
726 	new_name = g_strstrip(g_strdup(address->full_name));
727 
728 	/* guess the first name and last name */
729 	if (*new_name != '\0') {
730 	    names = g_strsplit(new_name, " ", 0);
731 
732 	    for (cnt=0; names[cnt]; cnt++)
733 		;
734 
735 	    /* get first name */
736 	    first_name = g_strdup(address->first_name
737                                   ? address->first_name : names[0]);
738 
739 	    /* get last name */
740             if(address->last_name)
741                 last_name = g_strdup(address->last_name);
742             else {
743                 if (cnt == 1)
744                     last_name = g_strdup("");
745                 else
746                     last_name = g_strdup(names[cnt - 1]);
747             }
748 	    g_strfreev(names);
749 	}
750     }
751 
752     if (first_name == NULL)
753 	first_name = g_strdup("");
754     if (last_name == NULL)
755 	last_name = g_strdup("");
756     if (!address || address->nick_name == NULL)
757 	nick_name = g_strdup("");
758     else
759 	nick_name = g_strdup(address->nick_name);
760 
761     /* Full name must be set after first and last names. */
762     gtk_entry_set_text(GTK_ENTRY(entries[FIRST_NAME]), first_name);
763     gtk_entry_set_text(GTK_ENTRY(entries[LAST_NAME]), last_name);
764     gtk_entry_set_text(GTK_ENTRY(entries[FULL_NAME]), new_name);
765     gtk_entry_set_text(GTK_ENTRY(entries[NICK_NAME]), nick_name);
766     gtk_entry_set_text(GTK_ENTRY(entries[ORGANIZATION]), new_organization);
767 
768     store = GTK_LIST_STORE(gtk_tree_view_get_model
769                            (GTK_TREE_VIEW(entries[EMAIL_ADDRESS])));
770     gtk_list_store_clear(store);
771     if (address) {
772         GList *list;
773 
774         for (list = address->address_list; list; list = list->next) {
775             gtk_list_store_append(store, &iter);
776             gtk_list_store_set(store, &iter, 0, list->data, -1);
777         }
778     } else {
779         gtk_list_store_append(store, &iter);
780         gtk_list_store_set(store, &iter, 0, "", -1);
781     }
782 
783     gtk_editable_select_region(GTK_EDITABLE(entries[FULL_NAME]), 0, -1);
784 
785     for (cnt = FULL_NAME + 1; cnt < NUM_FIELDS; cnt++)
786         if (GTK_IS_EDITABLE(entries[cnt]))
787             gtk_editable_set_position(GTK_EDITABLE(entries[cnt]), 0);
788 
789     g_free(new_name);
790     g_free(first_name);
791     g_free(last_name);
792     g_free(nick_name);
793     g_free(new_email);
794     g_free(new_organization);
795     gtk_widget_grab_focus(entries[FULL_NAME]);
796 }
797 
798 /** libbalsa_address_get_edit_widget() returns an widget adapted
799     for a LibBalsaAddress edition, with initial values set if address
800     is provided. The edit entries are set in entries array
801     and enumerated with LibBalsaAddressField constants
802 */
803 static void
lba_entry_changed(GtkEntry * entry,GtkEntry ** entries)804 lba_entry_changed(GtkEntry * entry, GtkEntry ** entries)
805 {
806     gchar *full_name =
807         g_strconcat(gtk_entry_get_text(entries[FIRST_NAME]), " ",
808                     gtk_entry_get_text(entries[LAST_NAME]), NULL);
809     gtk_entry_set_text(entries[FULL_NAME], full_name);
810     g_free(full_name);
811 }
812 
813 static void
lba_cell_edited(GtkCellRendererText * cell,const gchar * path_string,const gchar * new_text,GtkListStore * store)814 lba_cell_edited(GtkCellRendererText * cell, const gchar * path_string,
815                 const gchar * new_text, GtkListStore * store)
816 {
817     GtkTreeIter iter;
818 
819     if (gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(store),
820                                             &iter, path_string))
821         gtk_list_store_set(store, &iter, 0, new_text, -1);
822 }
823 
824 static GtkWidget *
lba_address_list_widget(GCallback changed_cb,gpointer changed_data)825 lba_address_list_widget(GCallback changed_cb, gpointer changed_data)
826 {
827     GtkListStore *store;
828     GtkWidget *tree_view;
829     GtkCellRenderer *renderer;
830     GtkTreeViewColumn *column;
831 
832     store = gtk_list_store_new(1, G_TYPE_STRING);
833     tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
834     g_object_unref(store);
835     gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree_view), FALSE);
836 
837     renderer = gtk_cell_renderer_text_new();
838     g_object_set(renderer, "editable", TRUE, NULL);
839     g_signal_connect(renderer, "edited", G_CALLBACK(lba_cell_edited),
840                      store);
841     if (changed_cb)
842         g_signal_connect_swapped(renderer, "edited",
843                                  changed_cb, changed_data);
844 
845     column = gtk_tree_view_column_new_with_attributes(NULL, renderer,
846                                                       "text", 0, NULL);
847     gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), column);
848 
849     return tree_view;
850 }
851 
852 static void
add_row(GtkWidget * button,gpointer data)853 add_row(GtkWidget*button, gpointer data)
854 {
855     GtkTreeView *tv = GTK_TREE_VIEW(data);
856     GtkListStore *store = GTK_LIST_STORE(gtk_tree_view_get_model(tv));
857     GtkTreeIter iter;
858     GtkTreePath *path;
859     gtk_list_store_insert_with_values(store, &iter, 99999, 0, "", -1);
860     gtk_widget_grab_focus(GTK_WIDGET(tv));
861     path = gtk_tree_model_get_path(GTK_TREE_MODEL(store), &iter);
862     gtk_tree_view_set_cursor(tv, path, NULL, TRUE);
863     gtk_tree_path_free(path);
864 }
865 
866 GtkTargetEntry libbalsa_address_target_list[2] = {
867     {"text/plain",           0, LIBBALSA_ADDRESS_TRG_STRING },
868     {"x-application/x-addr", GTK_TARGET_SAME_APP,LIBBALSA_ADDRESS_TRG_ADDRESS}
869 };
870 
871 static void
addrlist_drag_received_cb(GtkWidget * widget,GdkDragContext * context,gint x,gint y,GtkSelectionData * selection_data,guint target_type,guint32 time,gpointer data)872 addrlist_drag_received_cb(GtkWidget * widget, GdkDragContext * context,
873                           gint x, gint y, GtkSelectionData * selection_data,
874                           guint target_type, guint32 time, gpointer data)
875 {
876     GtkTreeView *tree_view = GTK_TREE_VIEW(widget);
877     GtkTreeModel *model = gtk_tree_view_get_model(tree_view);
878     GtkTreeIter iter;
879     gboolean dnd_success = FALSE;
880     LibBalsaAddress *addr;
881 
882     printf("drag_received:\n");
883     /* Deal with what we are given from source */
884     if(selection_data
885        && gtk_selection_data_get_length(selection_data) >= 0) {
886         switch (target_type) {
887         case LIBBALSA_ADDRESS_TRG_ADDRESS:
888             addr = *(LibBalsaAddress **)
889                 gtk_selection_data_get_data(selection_data);
890             if(addr && addr->address_list) {
891                 g_print ("string: %s\n", (gchar*)addr->address_list->data);
892                 gtk_list_store_insert_with_values(GTK_LIST_STORE(model),
893                                                   &iter, 99999,
894                                                   0,
895                                                   addr->address_list->data,
896                                                   -1);
897                 dnd_success = TRUE;
898             }
899             break;
900         case LIBBALSA_ADDRESS_TRG_STRING:
901             g_print("text/plain target not implemented.\n");
902             break;
903         default: g_print ("nothing good");
904         }
905     }
906 
907     if (!dnd_success)
908         g_print ("DnD data transfer failed!\n");
909 
910     gtk_drag_finish(context, dnd_success, FALSE, time);
911 }
912 
913 static gboolean
addrlist_drag_drop_cb(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time,gpointer user_data)914 addrlist_drag_drop_cb(GtkWidget *widget, GdkDragContext *context,
915                       gint x, gint y, guint time, gpointer user_data)
916 {
917   gboolean        is_valid_drop_site;
918   GdkAtom         target_type;
919   GList          *targets;
920 
921   /* Check to see if (x,y) is a valid drop site within widget */
922   is_valid_drop_site = TRUE;
923 
924   /* If the source offers a target */
925   targets = gdk_drag_context_list_targets(context);
926   if (targets) {
927       /* Choose the best target type */
928       target_type = GDK_POINTER_TO_ATOM
929         (g_list_nth_data (targets, LIBBALSA_ADDRESS_TRG_ADDRESS));
930 
931       /* Request the data from the source. */
932       printf("drag_drop requests target=%p\n", target_type);
933       gtk_drag_get_data
934         (
935          widget,         /* will receive 'drag-data-received' signal */
936          context,        /* represents the current state of the DnD */
937          target_type,    /* the target type we want */
938          time            /* time stamp */
939          );
940   } else {
941       is_valid_drop_site = FALSE;
942   }
943 
944   return  is_valid_drop_site;
945 }
946 
947 
948 GtkWidget*
libbalsa_address_get_edit_widget(const LibBalsaAddress * address,GtkWidget ** entries,GCallback changed_cb,gpointer changed_data)949 libbalsa_address_get_edit_widget(const LibBalsaAddress *address,
950                                  GtkWidget **entries,
951                                  GCallback changed_cb, gpointer changed_data)
952 {
953     const static gchar *labels[NUM_FIELDS] = {
954 	N_("D_isplayed Name:"),
955 	N_("_First Name:"),
956 	N_("_Last Name:"),
957 	N_("_Nickname:"),
958 	N_("O_rganization:"),
959         N_("_Email Address:")
960     };
961 
962     GtkWidget *grid, *label, *lhs;
963     gint cnt;
964 
965     grid = gtk_grid_new();
966 #define HIG_PADDING 6
967     gtk_grid_set_row_spacing(GTK_GRID(grid), HIG_PADDING);
968     gtk_grid_set_column_spacing(GTK_GRID(grid), HIG_PADDING);
969     gtk_container_set_border_width(GTK_CONTAINER(grid), HIG_PADDING);
970 
971     for (cnt = 0; cnt < NUM_FIELDS; cnt++) {
972         if (!labels[cnt])
973             continue;
974 	label = gtk_label_new_with_mnemonic(_(labels[cnt]));
975 	gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.0);
976         if (cnt == EMAIL_ADDRESS) {
977             GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
978             GtkWidget *but = gtk_button_new_with_mnemonic(_("A_dd"));
979             entries[cnt] = lba_address_list_widget(changed_cb,
980                                                    changed_data);
981             gtk_box_pack_start(GTK_BOX(box), label, FALSE, FALSE, 1);
982             gtk_box_pack_start(GTK_BOX(box), but,   FALSE, FALSE, 1);
983             lhs = box;
984             g_signal_connect(but, "clicked", G_CALLBACK(add_row),
985                              entries[cnt]);
986              gtk_drag_dest_set(entries[cnt],
987                                GTK_DEST_DEFAULT_MOTION |
988                                GTK_DEST_DEFAULT_HIGHLIGHT,
989                                libbalsa_address_target_list,
990                                2,              /* size of list */
991                                GDK_ACTION_COPY);
992             g_signal_connect(G_OBJECT(entries[cnt]), "drag-data-received",
993                              G_CALLBACK(addrlist_drag_received_cb), NULL);
994             g_signal_connect (G_OBJECT(entries[cnt]), "drag-drop",
995                               G_CALLBACK (addrlist_drag_drop_cb), NULL);
996         } else {
997             entries[cnt] = gtk_entry_new();
998             if (changed_cb)
999                 g_signal_connect_swapped(entries[cnt], "changed",
1000                                          changed_cb, changed_data);
1001             lhs = label;
1002         }
1003 	gtk_label_set_mnemonic_widget(GTK_LABEL(label), entries[cnt]);
1004 
1005 	gtk_grid_attach(GTK_GRID(grid), lhs, 0, cnt + 1, 1, 1);
1006 
1007 	gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
1008 
1009         gtk_widget_set_hexpand(entries[cnt], TRUE);
1010         gtk_widget_set_vexpand(entries[cnt], TRUE);
1011 	gtk_grid_attach(GTK_GRID(grid), entries[cnt], 1, cnt + 1, 1, 1);
1012     }
1013     g_signal_connect(entries[FIRST_NAME], "changed",
1014                      G_CALLBACK(lba_entry_changed), entries);
1015     g_signal_connect(entries[LAST_NAME], "changed",
1016                      G_CALLBACK(lba_entry_changed), entries);
1017 
1018     libbalsa_address_set_edit_entries(address, entries);
1019 
1020     if (changed_cb) {
1021         GtkTreeModel *model =
1022             gtk_tree_view_get_model(GTK_TREE_VIEW(entries[EMAIL_ADDRESS]));
1023         g_signal_connect_swapped(G_OBJECT(model), "row-inserted",
1024                                  changed_cb, changed_data);
1025         g_signal_connect_swapped(G_OBJECT(model), "row-changed",
1026                                  changed_cb, changed_data);
1027         g_signal_connect_swapped(G_OBJECT(model), "row-deleted",
1028                                  changed_cb, changed_data);
1029     }
1030     return grid;
1031 }
1032 
1033 LibBalsaAddress *
libbalsa_address_new_from_edit_entries(GtkWidget ** entries)1034 libbalsa_address_new_from_edit_entries(GtkWidget ** entries)
1035 {
1036 #define SET_FIELD(f,e)\
1037   do{ (f) = g_strstrip(gtk_editable_get_chars(GTK_EDITABLE(e), 0, -1));\
1038       if( !(f) || !*(f)) { g_free(f); (f) = NULL; }                    \
1039  else { while( (p=strchr(address->full_name,';'))) *p = ','; }  } while(0)
1040 
1041     LibBalsaAddress *address;
1042     char *p;
1043     GList *list = NULL;
1044     GtkTreeModel *model;
1045     gboolean valid;
1046     GtkTreeIter iter;
1047 
1048     /* make sure gtk_tree_model looses focus, otherwise the list does
1049      * not get updated (gtk2-2.8.20) */
1050     gtk_widget_grab_focus(entries[FULL_NAME]);
1051     /* FIXME: This problem should be solved in the VCard
1052        implementation in libbalsa: semicolons mess up how GnomeCard
1053        processes the fields, so disallow them and replace them
1054        by commas. */
1055 
1056     address = libbalsa_address_new();
1057     SET_FIELD(address->full_name,   entries[FULL_NAME]);
1058     if (!address->full_name) {
1059         g_object_unref(address);
1060         return NULL;
1061     }
1062     SET_FIELD(address->first_name,  entries[FIRST_NAME]);
1063     SET_FIELD(address->last_name,   entries[LAST_NAME]);
1064     SET_FIELD(address->nick_name,   entries[NICK_NAME]);
1065     SET_FIELD(address->organization,entries[ORGANIZATION]);
1066 
1067     model = gtk_tree_view_get_model(GTK_TREE_VIEW(entries[EMAIL_ADDRESS]));
1068     for (valid = gtk_tree_model_get_iter_first(model, &iter); valid;
1069          valid = gtk_tree_model_iter_next(model, &iter)) {
1070         gchar *email;
1071 
1072         gtk_tree_model_get(model, &iter, 0, &email, -1);
1073         if (email && *email)
1074             list = g_list_prepend(list, email);
1075     }
1076     address->address_list = g_list_reverse(list);
1077 
1078     return address;
1079 }
1080