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 <%s>", 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