1 /*
2  * This program is free software; you can redistribute it and/or modify it
3  * under the terms of the GNU Lesser General Public License as published by
4  * the Free Software Foundation.
5  *
6  * This program is distributed in the hope that it will be useful, but
7  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
8  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
9  * for more details.
10  *
11  * You should have received a copy of the GNU Lesser General Public License
12  * along with this program; if not, see <http://www.gnu.org/licenses/>.
13  *
14  *
15  * Authors:
16  *		Jon Trowbridge <trow@ximian.com>
17  *      Chris Toshok <toshok@ximian.com>
18  *
19  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
20  *
21  */
22 
23 #include "evolution-config.h"
24 
25 #include <glib/gi18n.h>
26 #include <string.h>
27 
28 #include "e-util/e-util.h"
29 #include "eab-book-util.h"
30 
31 static EABTypeLabel
32 email_types[] =
33 {
34 	{ -1, "WORK",  NULL, NC_ ("addressbook-label", "Work Email")  },
35 	{ -1, "HOME",  NULL, NC_ ("addressbook-label", "Home Email")  },
36 	{ -1, "OTHER", NULL, NC_ ("addressbook-label", "Other Email") }
37 };
38 
39 static EABTypeLabel
40 sip_types[] =
41 {
42 	{ E_CONTACT_SIP, "WORK",  NULL, NC_ ("addressbook-label", "Work SIP")  },
43 	{ E_CONTACT_SIP, "HOME",  NULL, NC_ ("addressbook-label", "Home SIP")  },
44 	{ E_CONTACT_SIP, "OTHER", NULL, NC_ ("addressbook-label", "Other SIP") }
45 };
46 
47 static EABTypeLabel
48 eab_phone_types[] = {
49 	{ E_CONTACT_PHONE_ASSISTANT,    EVC_X_ASSISTANT,       NULL,    NULL   },
50 	{ E_CONTACT_PHONE_BUSINESS,     "WORK",                "VOICE", NULL   },
51 	{ E_CONTACT_PHONE_BUSINESS_FAX, "WORK",                "FAX",   NULL   },
52 	{ E_CONTACT_PHONE_CALLBACK,     EVC_X_CALLBACK,        NULL,    NULL   },
53 	{ E_CONTACT_PHONE_CAR,          "CAR",                 NULL,    NULL   },
54 	{ E_CONTACT_PHONE_COMPANY,      "X-EVOLUTION-COMPANY", NULL,    NULL   },
55 	{ E_CONTACT_PHONE_HOME,         "HOME",                "VOICE", NULL   },
56 	{ E_CONTACT_PHONE_HOME_FAX,     "HOME",                "FAX",   NULL   },
57 	{ E_CONTACT_PHONE_ISDN,         "ISDN",                NULL,    NULL   },
58 	{ E_CONTACT_PHONE_MOBILE,       "CELL",                NULL,    NULL   },
59 	{ E_CONTACT_PHONE_OTHER,        "VOICE",               NULL,    NULL   },
60 	{ E_CONTACT_PHONE_OTHER_FAX,    "FAX",                 NULL,    NULL   },
61 	{ E_CONTACT_PHONE_PAGER,        "PAGER",               NULL,    NULL   },
62 	{ E_CONTACT_PHONE_PRIMARY,      "PREF",                NULL,    NULL   },
63 	{ E_CONTACT_PHONE_RADIO,        EVC_X_RADIO,           NULL,    NULL   },
64 	{ E_CONTACT_PHONE_TELEX,        EVC_X_TELEX,           NULL,    NULL   },
65 	{ E_CONTACT_PHONE_TTYTDD,       EVC_X_TTYTDD,          NULL,    NULL   }
66 };
67 static gboolean eab_phone_types_init = TRUE;
68 
69 static EABTypeLabel
70 eab_im_service[] =
71 {
72 	{ E_CONTACT_IM_AIM,         NULL, NULL, NC_ ("addressbook-label", "AIM")       },
73 	{ E_CONTACT_IM_JABBER,      NULL, NULL, NC_ ("addressbook-label", "Jabber")    },
74 	{ E_CONTACT_IM_YAHOO,       NULL, NULL, NC_ ("addressbook-label", "Yahoo")     },
75 	{ E_CONTACT_IM_GADUGADU,    NULL, NULL, NC_ ("addressbook-label", "Gadu-Gadu") },
76 	{ E_CONTACT_IM_MSN,         NULL, NULL, NC_ ("addressbook-label", "MSN")       },
77 	{ E_CONTACT_IM_ICQ,         NULL, NULL, NC_ ("addressbook-label", "ICQ")       },
78 	{ E_CONTACT_IM_GROUPWISE,   NULL, NULL, NC_ ("addressbook-label", "GroupWise") },
79 	{ E_CONTACT_IM_SKYPE,       NULL, NULL, NC_ ("addressbook-label", "Skype")     },
80 	{ E_CONTACT_IM_TWITTER,     NULL, NULL, NC_ ("addressbook-label", "Twitter")   },
81 	{ E_CONTACT_IM_GOOGLE_TALK, NULL, NULL, NC_ ("addressbook-label", "Google Talk")},
82 	{ E_CONTACT_IM_MATRIX,      NULL, NULL, NC_ ("addressbook-label", "Matrix")    }
83 };
84 
85 const EABTypeLabel*
eab_get_email_type_labels(gint * n_elements)86 eab_get_email_type_labels (gint *n_elements)
87 {
88 	*n_elements = G_N_ELEMENTS (email_types);
89 	return email_types;
90 }
91 
92 gint
eab_get_email_type_index(EVCardAttribute * attr)93 eab_get_email_type_index (EVCardAttribute *attr)
94 {
95 	gint ii;
96 
97 	for (ii = 0; ii < G_N_ELEMENTS (email_types); ii++) {
98 		if (e_vcard_attribute_has_type (attr, email_types[ii].type_1))
99 			return ii;
100 	}
101 
102 	return -1;
103 }
104 
105 void
eab_email_index_to_type(gint index,const gchar ** type_1)106 eab_email_index_to_type (gint index, const gchar **type_1)
107 {
108 	*type_1 = email_types[index].type_1;
109 }
110 
111 const gchar*
eab_get_email_label_text(EVCardAttribute * attr)112 eab_get_email_label_text (EVCardAttribute *attr)
113 {
114 	const gchar *result;
115 	gint n_elements;
116 	gint index = eab_get_email_type_index (attr);
117 
118 	if (index >= 0) {
119 		result = _(eab_get_email_type_labels (&n_elements) [index].text);
120 	} else {
121 		/* To Translators:
122 		 * if an email address type is not one of the predefined types,
123 		 * this generic label is used instead of one of the predefined labels.
124 		 */
125 		result = C_("addressbook-label", "Email");
126 	}
127 
128 	return result;
129 }
130 
131 const EABTypeLabel*
eab_get_sip_type_labels(gint * n_elements)132 eab_get_sip_type_labels (gint *n_elements)
133 {
134 	*n_elements = G_N_ELEMENTS (sip_types);
135 	return sip_types;
136 }
137 
138 gint
eab_get_sip_type_index(EVCardAttribute * attr)139 eab_get_sip_type_index (EVCardAttribute *attr)
140 {
141 	gint ii;
142 
143 	for (ii = 0; ii < G_N_ELEMENTS (sip_types); ii++) {
144 		if (e_vcard_attribute_has_type (attr, sip_types[ii].type_1))
145 			return ii;
146 	}
147 
148 	return -1;
149 }
150 
151 void
eab_sip_index_to_type(gint index,const gchar ** type_1)152 eab_sip_index_to_type (gint index, const gchar **type_1)
153 {
154 	*type_1 = sip_types[index].type_1;
155 }
156 
157 const gchar*
eab_get_sip_label_text(EVCardAttribute * attr)158 eab_get_sip_label_text (EVCardAttribute *attr)
159 {
160 	const gchar *result;
161 	gint n_elements;
162 	gint index = eab_get_sip_type_index (attr);
163 
164 	if (index >= 0) {
165 		result = _(eab_get_sip_type_labels (&n_elements) [index].text);
166 	} else {
167 		/* To Translators:
168 		 * if a SIP address type is not one of the predefined types,
169 		 * this generic label is used instead of one of the predefined labels.
170 		 * SIP=Session Initiation Protocol, used for voice over IP
171 		 */
172 		result = C_("addressbook-label", "SIP");
173 	}
174 
175 	return result;
176 }
177 
178 const EABTypeLabel*
eab_get_im_type_labels(gint * n_elements)179 eab_get_im_type_labels (gint *n_elements)
180 {
181 	*n_elements = G_N_ELEMENTS (eab_im_service);
182 	return eab_im_service;
183 }
184 
185 gint
eab_get_im_type_index(EVCardAttribute * attr)186 eab_get_im_type_index (EVCardAttribute *attr)
187 {
188 	gint ii;
189 	const gchar *name;
190 	EContactField field;
191 
192 	for (ii = 0; ii < G_N_ELEMENTS (eab_im_service); ii++) {
193 		name = e_vcard_attribute_get_name (attr);
194 		field = e_contact_field_id_from_vcard (name);
195 		if (field == eab_im_service[ii].field_id)
196 			return ii;
197 	}
198 	return -1;
199 }
200 
201 const gchar *
eab_get_im_label_text(EVCardAttribute * attr)202 eab_get_im_label_text (EVCardAttribute *attr)
203 {
204 	const gchar *result;
205 	gint index = eab_get_im_type_index (attr);
206 
207 	if (index >= 0) {
208 		result = _(eab_im_service [index].text);
209 	} else {
210 		/* To Translators:
211 		 * if an IM address type is not one of the predefined types,
212 		 * this generic label is used instead of one of the predefined labels.
213 		 * IM=Instant Messaging
214 		 */
215 		result = C_("addressbook-label", "IM");
216 	}
217 
218 	return result;
219 }
220 
221 const EABTypeLabel*
eab_get_phone_type_labels(gint * n_elements)222 eab_get_phone_type_labels (gint *n_elements)
223 {
224 	*n_elements = G_N_ELEMENTS (eab_phone_types);
225 
226 	if (eab_phone_types_init) {
227 		gint i;
228 		eab_phone_types_init = FALSE;
229 		for (i = 0; i < *n_elements; i++) {
230 			eab_phone_types[i].text = e_contact_pretty_name (eab_phone_types[i].field_id);
231 		}
232 	}
233 
234 	return eab_phone_types;
235 }
236 
237 /*
238  * return the index within eab_phone_types[]
239  */
240 gint
eab_get_phone_type_index(EVCardAttribute * attr)241 eab_get_phone_type_index (EVCardAttribute *attr)
242 {
243 	gint i;
244 
245 	for (i = 0; i < G_N_ELEMENTS (eab_phone_types); i++) {
246 		if (e_vcard_attribute_has_type (attr, eab_phone_types[i].type_1) &&
247 		    (eab_phone_types[i].type_2 == NULL || e_vcard_attribute_has_type (attr, eab_phone_types[i].type_2) ||
248 		    (g_ascii_strcasecmp (eab_phone_types[i].type_2, "VOICE") == 0 &&
249 		     g_list_length (e_vcard_attribute_get_param (attr, EVC_TYPE)) == 1)))
250 			return i;
251 	}
252 
253 	return -1;
254 }
255 
256 const gchar*
eab_get_phone_label_text(EVCardAttribute * attr)257 eab_get_phone_label_text (EVCardAttribute *attr)
258 {
259 	const gchar *result;
260 	gint n_elements;
261 	gint index = eab_get_phone_type_index (attr);
262 
263 	if (index >= 0) {
264 		result = _(eab_get_phone_type_labels (&n_elements) [index].text);
265 	} else {
266 		/* To Translators:
267 		 * if a phone number type is not one of the predefined types,
268 		 * this generic label is used instead of one of the predefined labels.
269 		 */
270 		result = C_("addressbook-label", "Phone");
271 	}
272 
273 	return result;
274 }
275 
276 void
eab_phone_index_to_type(gint index,const gchar ** type_1,const gchar ** type_2)277 eab_phone_index_to_type (gint index,
278                          const gchar **type_1,
279                          const gchar **type_2)
280 {
281 	*type_1 = eab_phone_types [index].type_1;
282 	*type_2 = eab_phone_types [index].type_2;
283 }
284 
285 /* Copied from camel_strstrcase */
286 static gchar *
eab_strstrcase(const gchar * haystack,const gchar * needle)287 eab_strstrcase (const gchar *haystack,
288                 const gchar *needle)
289 {
290 	/* find the needle in the haystack neglecting case */
291 	const gchar *ptr;
292 	guint len;
293 
294 	g_return_val_if_fail (haystack != NULL, NULL);
295 	g_return_val_if_fail (needle != NULL, NULL);
296 
297 	len = strlen (needle);
298 	if (len > strlen (haystack))
299 		return NULL;
300 
301 	if (len == 0)
302 		return (gchar *) haystack;
303 
304 	for (ptr = haystack; *(ptr + len - 1) != '\0'; ptr++)
305 		if (!g_ascii_strncasecmp (ptr, needle, len))
306 			return (gchar *) ptr;
307 
308 	return NULL;
309 }
310 
311 GSList *
eab_contact_list_from_string(const gchar * str)312 eab_contact_list_from_string (const gchar *str)
313 {
314 	GSList *contacts = NULL;
315 	GString *gstr = g_string_new (NULL);
316 	gchar *str_stripped;
317 	gchar *p = (gchar *) str;
318 	gchar *q;
319 
320 	if (!p)
321 		return NULL;
322 
323 	if (!strncmp (p, "Book: ", 6)) {
324 		p = strchr (p, '\n');
325 		if (!p) {
326 			g_warning (G_STRLOC ": Got book but no newline!");
327 			return NULL;
328 		}
329 		p++;
330 	}
331 
332 	while (*p) {
333 		if (*p != '\r') g_string_append_c (gstr, *p);
334 
335 		p++;
336 	}
337 
338 	p = str_stripped = g_string_free (gstr, FALSE);
339 
340 	/* Note: The vCard standard says
341 	 *
342 	 * vcard = "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF
343 	 *         items *CRLF "END" [ws] ":" [ws] "VCARD"
344 	 *
345 	 * which means we can have whitespace (e.g. "BEGIN : VCARD"). So we're not being
346 	 * fully compliant here, although I'm not sure it matters. The ideal solution
347 	 * would be to have a vcard parsing function that returned the end of the vcard
348 	 * parsed. Arguably, contact list parsing should all be in libebook's e-vcard.c,
349 	 * where we can do proper parsing and validation without code duplication. */
350 
351 	for (p = eab_strstrcase (p, "BEGIN:VCARD"); p; p = eab_strstrcase (q, "\nBEGIN:VCARD")) {
352 		gchar *card_str;
353 
354 		if (*p == '\n')
355 			p++;
356 
357 		for (q = eab_strstrcase (p, "END:VCARD"); q; q = eab_strstrcase (q, "END:VCARD")) {
358 			gchar *temp;
359 
360 			q += 9;
361 			temp = q;
362 			if (*temp)
363 				temp += strspn (temp, "\r\n\t ");
364 
365 			if (*temp == '\0' || !g_ascii_strncasecmp (temp, "BEGIN:VCARD", 11))
366 				break;  /* Found the outer END:VCARD */
367 		}
368 
369 		if (!q)
370 			break;
371 
372 		card_str = g_strndup (p, q - p);
373 		contacts = g_slist_prepend (contacts, e_contact_new_from_vcard (card_str));
374 		g_free (card_str);
375 	}
376 
377 	g_free (str_stripped);
378 
379 	return g_slist_reverse (contacts);
380 }
381 
382 gchar *
eab_contact_list_to_string(const GSList * contacts)383 eab_contact_list_to_string (const GSList *contacts)
384 {
385 	GString *str = g_string_new ("");
386 	const GSList *l;
387 
388 	for (l = contacts; l; l = l->next) {
389 		EContact *contact = l->data;
390 		gchar *vcard_str;
391 
392 		e_contact_inline_local_photos (contact, NULL);
393 		vcard_str = e_vcard_to_string (
394 			E_VCARD (contact), EVC_FORMAT_VCARD_30);
395 
396 		g_string_append (str, vcard_str);
397 		if (l->next)
398 			g_string_append (str, "\r\n\r\n");
399 	}
400 
401 	return g_string_free (str, FALSE);
402 }
403 
404 gboolean
eab_source_and_contact_list_from_string(ESourceRegistry * registry,const gchar * str,ESource ** out_source,GSList ** out_contacts)405 eab_source_and_contact_list_from_string (ESourceRegistry *registry,
406                                          const gchar *str,
407                                          ESource **out_source,
408                                          GSList **out_contacts)
409 {
410 	ESource *source;
411 	const gchar *s0, *s1;
412 	gchar *uid;
413 	gboolean success = FALSE;
414 
415 	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), FALSE);
416 	g_return_val_if_fail (str != NULL, FALSE);
417 
418 	if (out_source != NULL)
419 		*out_source = NULL;  /* in case we fail */
420 
421 	if (out_contacts != NULL)
422 		*out_contacts = NULL;  /* in case we fail */
423 
424 	if (!strncmp (str, "Book: ", 6)) {
425 		s0 = str + 6;
426 		s1 = strchr (str, '\r');
427 
428 		if (!s1)
429 			s1 = strchr (str, '\n');
430 	} else {
431 		s0 = NULL;
432 		s1 = NULL;
433 	}
434 
435 	if (!s0 || !s1)
436 		return FALSE;
437 
438 	uid = g_strndup (s0, s1 - s0);
439 	source = e_source_registry_ref_source (registry, uid);
440 	if (source != NULL) {
441 		if (out_source != NULL)
442 			*out_source = g_object_ref (source);
443 		g_object_unref (source);
444 		success = TRUE;
445 	}
446 	g_free (uid);
447 
448 	if (success && out_contacts != NULL)
449 		*out_contacts = eab_contact_list_from_string (str);
450 
451 	return success;
452 }
453 
454 gchar *
eab_book_and_contact_list_to_string(EBookClient * book_client,const GSList * contacts)455 eab_book_and_contact_list_to_string (EBookClient *book_client,
456                                      const GSList *contacts)
457 {
458 	gchar *s0, *s1;
459 
460 	s0 = eab_contact_list_to_string (contacts);
461 	if (!s0)
462 		s0 = g_strdup ("");
463 
464 	if (book_client != NULL) {
465 		EClient *client;
466 		ESource *source;
467 		const gchar *uid;
468 
469 		client = E_CLIENT (book_client);
470 		source = e_client_get_source (client);
471 		uid = e_source_get_uid (source);
472 		s1 = g_strconcat ("Book: ", uid, "\r\n", s0, NULL);
473 	} else
474 		s1 = g_strdup (s0);
475 
476 	g_free (s0);
477 	return s1;
478 }
479 
480 /* bad place for this i know. */
481 gint
e_utf8_casefold_collate_len(const gchar * str1,const gchar * str2,gint len)482 e_utf8_casefold_collate_len (const gchar *str1,
483                              const gchar *str2,
484                              gint len)
485 {
486 	gchar *s1 = g_utf8_casefold (str1, len);
487 	gchar *s2 = g_utf8_casefold (str2, len);
488 	gint rv;
489 
490 	rv = g_utf8_collate (s1, s2);
491 
492 	g_free (s1);
493 	g_free (s2);
494 
495 	return rv;
496 }
497 
498 gint
e_utf8_casefold_collate(const gchar * str1,const gchar * str2)499 e_utf8_casefold_collate (const gchar *str1,
500                          const gchar *str2)
501 {
502 	return e_utf8_casefold_collate_len (str1, str2, -1);
503 }
504 
505 /* To parse something like...
506  * =?UTF-8?Q?=E0=A4=95=E0=A4=95=E0=A4=AC=E0=A5=82=E0=A5=8B=E0=A5=87?=\t\n=?UTF-8?Q?=E0=A4=B0?=\t\n<aa@aa.ccom>
507  * and return the decoded representation of name & email parts. */
508 gboolean
eab_parse_qp_email(const gchar * string,gchar ** name,gchar ** email)509 eab_parse_qp_email (const gchar *string,
510                     gchar **name,
511                     gchar **email)
512 {
513 	struct _camel_header_address *address;
514 	gboolean res = FALSE;
515 
516 	address = camel_header_address_decode (string, "UTF-8");
517 
518 	if (address) {
519 		/* report success only when we have filled both name and email address */
520 		if (address->type == CAMEL_HEADER_ADDRESS_NAME && address->name && *address->name && address->v.addr && *address->v.addr) {
521 			*name = g_strdup (address->name);
522 			*email = g_strdup (address->v.addr);
523 			res = TRUE;
524 		}
525 
526 		camel_header_address_unref (address);
527 	}
528 
529 	if (!res) {
530 		CamelInternetAddress *addr = camel_internet_address_new ();
531 		const gchar *const_name = NULL, *const_email = NULL;
532 
533 		if (camel_address_unformat (CAMEL_ADDRESS (addr), string) == 1 &&
534 		    camel_internet_address_get (addr, 0, &const_name, &const_email) &&
535 		    const_name && *const_name && const_email && *const_email) {
536 			*name = g_strdup (const_name);
537 			*email = g_strdup (const_email);
538 			res = TRUE;
539 		}
540 
541 		g_clear_object (&addr);
542 	}
543 
544 	return res;
545 }
546 
547 /* This is only wrapper to parse_qp_mail, it decodes string and if returned TRUE,
548  * then makes one string and returns it, otherwise returns NULL.
549  * Returned string is usable to place directly into GtkHtml stream.
550  * Returned value should be freed with g_free. */
551 gchar *
eab_parse_qp_email_to_html(const gchar * string)552 eab_parse_qp_email_to_html (const gchar *string)
553 {
554 	gchar *name = NULL, *mail = NULL;
555 	gchar *html_name, *html_mail;
556 	gchar *value;
557 
558 	if (!eab_parse_qp_email (string, &name, &mail))
559 		return NULL;
560 
561 	html_name = e_text_to_html (name, 0);
562 	html_mail = e_text_to_html (mail, E_TEXT_TO_HTML_CONVERT_ADDRESSES);
563 
564 	value = g_strdup_printf ("%s &lt;%s&gt;", html_name, html_mail);
565 
566 	g_free (html_name);
567 	g_free (html_mail);
568 	g_free (name);
569 	g_free (mail);
570 
571 	return value;
572 }
573