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  *		Chris Toshok <toshok@ximian.com>
17  *		Dan Vratil <dvratil@redhat.com>
18  *
19  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
20  *
21  */
22 
23 #include "evolution-config.h"
24 
25 #include <unistd.h>
26 #include <fcntl.h>
27 #include <errno.h>
28 #include <string.h>
29 #include <locale.h>
30 
31 #include <gtk/gtk.h>
32 #include <glib/gi18n.h>
33 
34 #include "shell/e-shell.h"
35 
36 #include "eab-gui-util.h"
37 #include "util/eab-book-util.h"
38 #include "eab-contact-merging.h"
39 
40 /* we link to camel for decoding quoted printable email addresses */
41 #include <camel/camel.h>
42 
43 /* Template tags for address format localization */
44 #define ADDRESS_REALNAME   			"%n" /* this is not used intentionally */
45 #define ADDRESS_REALNAME_UPPER			"%N" /* this is not used intentionally */
46 #define ADDRESS_COMPANY				"%m"
47 #define ADDRESS_COMPANY_UPPER			"%M"
48 #define ADDRESS_POBOX				"%p"
49 #define ADDRESS_STREET				"%s"
50 #define ADDRESS_STREET_UPPER			"%S"
51 #define ADDRESS_ZIPCODE				"%z"
52 #define ADDRESS_LOCATION			"%l"
53 #define ADDRESS_LOCATION_UPPER			"%L"
54 #define ADDRESS_REGION				"%r"
55 #define ADDRESS_REGION_UPPER			"%R"
56 #define ADDRESS_CONDCOMMA			"%,"	/* Conditional comma is removed when a surrounding tag is evaluated to zero */
57 #define ADDRESS_CONDWHITE			"%w"	/* Conditional whitespace is removed when a surrounding tag is evaluated to zero */
58 #define ADDRESS_COND_PURGEEMPTY			"%0"	/* Purge empty has following syntax: %0(...) and is removed when no tag within () is evaluated non-zero */
59 
60 /* Fallback formats */
61 #define ADDRESS_DEFAULT_FORMAT 			"%0(%n\n)%0(%m\n)%0(%s\n)%0(PO BOX %p\n)%0(%l%w%r)%,%z"
62 #define ADDRESS_DEFAULT_COUNTRY_POSITION	"below"
63 
64 enum {
65 	LOCALES_LANGUAGE = 0,
66 	LOCALES_COUNTRY = 1
67 };
68 
69 typedef enum {
70 	ADDRESS_FORMAT_HOME = 0,
71 	ADDRESS_FORMAT_BUSINESS = 1
72 } AddressFormat;
73 
74 void
eab_error_dialog(EAlertSink * alert_sink,GtkWindow * parent,const gchar * msg,const GError * error)75 eab_error_dialog (EAlertSink *alert_sink,
76                   GtkWindow *parent,
77                   const gchar *msg,
78                   const GError *error)
79 {
80 	if (error && error->message) {
81 		if (alert_sink)
82 			e_alert_submit (
83 				alert_sink,
84 				"addressbook:generic-error",
85 				msg, error->message, NULL);
86 		else {
87 			if (!parent)
88 				parent = e_shell_get_active_window (NULL);
89 
90 			e_alert_run_dialog_for_args (
91 				parent,
92 				"addressbook:generic-error",
93 				msg, error->message, NULL);
94 		}
95 	}
96 }
97 
98 void
eab_load_error_dialog(GtkWidget * parent,EAlertSink * alert_sink,ESource * source,const GError * error)99 eab_load_error_dialog (GtkWidget *parent,
100                        EAlertSink *alert_sink,
101                        ESource *source,
102                        const GError *error)
103 {
104 	ESourceBackend *extension;
105 	gchar *label_string, *label = NULL;
106 	gboolean can_detail_error = TRUE;
107 	const gchar *backend_name;
108 	const gchar *extension_name;
109 
110 	g_return_if_fail (source != NULL);
111 
112 	extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
113 	extension = e_source_get_extension (source, extension_name);
114 	backend_name = e_source_backend_get_backend_name (extension);
115 
116 	if (g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_OFFLINE_UNAVAILABLE)) {
117 		can_detail_error = FALSE;
118 		label_string =
119 			_("This address book cannot be opened. This either "
120 			  "means this book is not marked for offline usage "
121 			  "or not yet downloaded for offline usage. Please "
122 			  "load the address book once in online mode to "
123 			  "download its contents.");
124 	}
125 
126 	else if (g_strcmp0 (backend_name, "local") == 0) {
127 		const gchar *user_data_dir;
128 		const gchar *uid;
129 		gchar *path;
130 
131 		uid = e_source_get_uid (source);
132 		user_data_dir = e_get_user_data_dir ();
133 
134 		path = g_build_filename (
135 			user_data_dir, "addressbook", uid, NULL);
136 
137 		label = g_strdup_printf (
138 			_("This address book cannot be opened.  Please check that the "
139 			"path %s exists and that permissions are set to access it."), path);
140 
141 		g_free (path);
142 		label_string = label;
143 	}
144 
145 #ifndef HAVE_LDAP
146 	else if (g_strcmp0 (backend_name, "ldap") == 0) {
147 		/* special case for ldap: contact folders so we can tell the user about openldap */
148 
149 		can_detail_error = FALSE;
150 		label_string =
151 			_("This version of Evolution does not have LDAP support "
152 			  "compiled in to it.  To use LDAP in Evolution "
153 			  "an LDAP-enabled Evolution package must be installed.");
154 
155 	}
156 #endif
157 	 else {
158 		/* other network folders (or if ldap is enabled and server is unreachable) */
159 		label_string =
160 			_("This address book cannot be opened.  This either "
161 			  "means that an incorrect URI was entered, or the server "
162 			  "is unreachable.");
163 	}
164 
165 	if (g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_REPOSITORY_OFFLINE)) {
166 		/* Do not show a detailed message; too generic. */
167 	} else if (can_detail_error && error != NULL) {
168 		label = g_strconcat (
169 			label_string, "\n\n",
170 			_("Detailed error message:"),
171 			" ", error->message, NULL);
172 		label_string = label;
173 	}
174 
175 	if (alert_sink) {
176 		e_alert_submit (
177 			alert_sink, "addressbook:load-error",
178 			e_source_get_display_name (source),
179 			label_string, NULL);
180 	} else {
181 		GtkWidget *dialog;
182 
183 		dialog = e_alert_dialog_new_for_args (
184 			(GtkWindow *) parent,
185 			"addressbook:load-error",
186 			e_source_get_display_name (source),
187 			label_string, NULL);
188 		g_signal_connect (
189 			dialog, "response",
190 			G_CALLBACK (gtk_widget_destroy), NULL);
191 		gtk_widget_show (dialog);
192 	}
193 
194 	g_free (label);
195 }
196 
197 void
eab_search_result_dialog(EAlertSink * alert_sink,const GError * error)198 eab_search_result_dialog (EAlertSink *alert_sink,
199                           const GError *error)
200 {
201 	gchar *str = NULL;
202 
203 	if (!error)
204 		return;
205 
206 	if (error->domain == E_CLIENT_ERROR) {
207 		switch (error->code) {
208 		case E_CLIENT_ERROR_SEARCH_SIZE_LIMIT_EXCEEDED:
209 			str = _("More cards matched this query than either the server is \n"
210 				"configured to return or Evolution is configured to display.\n"
211 				"Please make your search more specific or raise the result limit in\n"
212 				"the directory server preferences for this address book.");
213 			str = g_strdup (str);
214 			break;
215 		case E_CLIENT_ERROR_SEARCH_TIME_LIMIT_EXCEEDED:
216 			str = _("The time to execute this query exceeded the server limit or the limit\n"
217 				"configured for this address book.  Please make your search\n"
218 				"more specific or raise the time limit in the directory server\n"
219 				"preferences for this address book.");
220 			str = g_strdup (str);
221 			break;
222 		case E_CLIENT_ERROR_INVALID_QUERY:
223 			/* Translators: %s is replaced with a detailed error message, or an empty string, if not provided */
224 			str = _("The backend for this address book was unable to parse this query. %s");
225 			str = g_strdup_printf (str, error->message);
226 			break;
227 		case E_CLIENT_ERROR_QUERY_REFUSED:
228 			/* Translators: %s is replaced with a detailed error message, or an empty string, if not provided */
229 			str = _("The backend for this address book refused to perform this query. %s");
230 			str = g_strdup_printf (str, error->message);
231 			break;
232 		case E_CLIENT_ERROR_OTHER_ERROR:
233 		default:
234 			/* Translators: %s is replaced with a detailed error message, or an empty string, if not provided */
235 			str = _("This query did not complete successfully. %s");
236 			str = g_strdup_printf (str, error->message);
237 			break;
238 		}
239 	} else {
240 		/* Translators: %s is replaced with a detailed error message, or an empty string, if not provided */
241 		str = _("This query did not complete successfully. %s");
242 		str = g_strdup_printf (str, error->message);
243 	}
244 
245 	e_alert_submit (alert_sink, "addressbook:search-error", str, NULL);
246 
247 	g_free (str);
248 }
249 
250 gint
eab_prompt_save_dialog(GtkWindow * parent)251 eab_prompt_save_dialog (GtkWindow *parent)
252 {
253 	return e_alert_run_dialog_for_args (parent, "addressbook:prompt-save", NULL);
254 }
255 
256 static gchar *
make_safe_filename(gchar * name)257 make_safe_filename (gchar *name)
258 {
259 	gchar *safe;
260 
261 	if (!name) {
262 		/* This is a filename. Translators take note. */
263 		name = _("card.vcf");
264 	}
265 
266 	if (!g_strrstr (name, ".vcf"))
267 		safe = g_strdup_printf ("%s%s", name, ".vcf");
268 	else
269 		safe = g_strdup (name);
270 
271 	e_util_make_safe_filename (safe);
272 
273 	return safe;
274 }
275 
276 static void
source_selection_changed_cb(ESourceSelector * selector,GtkWidget * ok_button)277 source_selection_changed_cb (ESourceSelector *selector,
278                              GtkWidget *ok_button)
279 {
280 	ESource *except_source = NULL, *selected;
281 	gboolean sensitive;
282 
283 	except_source = g_object_get_data (G_OBJECT (ok_button), "except-source");
284 	selected = e_source_selector_ref_primary_selection (selector);
285 
286 	sensitive = (selected != NULL && selected != except_source);
287 	gtk_widget_set_sensitive (ok_button, sensitive);
288 
289 	if (selected != NULL)
290 		g_object_unref (selected);
291 }
292 
293 ESource *
eab_select_source(ESourceRegistry * registry,ESource * except_source,const gchar * title,const gchar * message,const gchar * select_uid,GtkWindow * parent)294 eab_select_source (ESourceRegistry *registry,
295                    ESource *except_source,
296                    const gchar *title,
297                    const gchar *message,
298                    const gchar *select_uid,
299                    GtkWindow *parent)
300 {
301 	ESource *source;
302 	GtkWidget *content_area;
303 	GtkWidget *dialog;
304 	GtkWidget *ok_button;
305 	/* GtkWidget *label; */
306 	GtkWidget *selector;
307 	GtkWidget *scrolled_window;
308 	const gchar *extension_name;
309 	gint response;
310 
311 	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
312 
313 	dialog = gtk_dialog_new_with_buttons (
314 		_("Select Address Book"), parent,
315 		GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
316 		_("_Cancel"), GTK_RESPONSE_CANCEL,
317 		_("_OK"), GTK_RESPONSE_ACCEPT,
318 		NULL);
319 	gtk_window_set_default_size (GTK_WINDOW (dialog), 350, 300);
320 
321 	gtk_dialog_set_response_sensitive (
322 		GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT, FALSE);
323 
324 	/* label = gtk_label_new (message); */
325 
326 	extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
327 	selector = e_source_selector_new (registry, extension_name);
328 	e_source_selector_set_show_toggles (
329 		E_SOURCE_SELECTOR (selector), FALSE);
330 
331 	ok_button = gtk_dialog_get_widget_for_response (
332 		GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
333 
334 	if (except_source)
335 		g_object_set_data (
336 			G_OBJECT (ok_button), "except-source", except_source);
337 
338 	g_signal_connect (
339 		selector, "primary_selection_changed",
340 		G_CALLBACK (source_selection_changed_cb), ok_button);
341 
342 	if (select_uid) {
343 		source = e_source_registry_ref_source (registry, select_uid);
344 		if (source != NULL) {
345 			e_source_selector_set_primary_selection (
346 				E_SOURCE_SELECTOR (selector), source);
347 			g_object_unref (source);
348 		}
349 	}
350 
351 	scrolled_window = gtk_scrolled_window_new (NULL, NULL);
352 	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window), GTK_SHADOW_IN);
353 	gtk_container_add (GTK_CONTAINER (scrolled_window), selector);
354 
355 	content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
356 	gtk_box_pack_start (GTK_BOX (content_area), scrolled_window, TRUE, TRUE, 4);
357 
358 	gtk_widget_show_all (dialog);
359 	response = gtk_dialog_run (GTK_DIALOG (dialog));
360 
361 	if (response == GTK_RESPONSE_ACCEPT)
362 		source = e_source_selector_ref_primary_selection (
363 			E_SOURCE_SELECTOR (selector));
364 	else
365 		source = NULL;
366 
367 	gtk_widget_destroy (dialog);
368 
369 	/* XXX Return a borrowed reference for backward-compatibility. */
370 	if (source != NULL)
371 		g_object_unref (source);
372 
373 	return source;
374 }
375 
376 gchar *
eab_suggest_filename(const GSList * contact_list)377 eab_suggest_filename (const GSList *contact_list)
378 {
379 	gchar *res = NULL;
380 
381 	g_return_val_if_fail (contact_list != NULL, NULL);
382 
383 	if (!contact_list->next) {
384 		EContact *contact = E_CONTACT (contact_list->data);
385 		gchar *string;
386 
387 		string = e_contact_get (contact, E_CONTACT_FILE_AS);
388 		if (string == NULL)
389 			string = e_contact_get (contact, E_CONTACT_FULL_NAME);
390 		if (string != NULL)
391 			res = make_safe_filename (string);
392 		g_free (string);
393 	}
394 
395 	if (res == NULL)
396 		res = make_safe_filename (_("list"));
397 
398 	return res;
399 }
400 
401 typedef struct ContactCopyProcess_ ContactCopyProcess;
402 
403 struct ContactCopyProcess_ {
404 	gint count;
405 	gboolean book_status;
406 	GSList *contacts;
407 	EBookClient *source;
408 	EBookClient *destination;
409 	ESourceRegistry *registry;
410 	gboolean delete_from_source;
411 	EAlertSink *alert_sink;
412 };
413 
414 static void process_unref (ContactCopyProcess *process);
415 
416 static void
remove_contact_ready_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)417 remove_contact_ready_cb (GObject *source_object,
418                          GAsyncResult *result,
419                          gpointer user_data)
420 {
421 	EBookClient *book_client = E_BOOK_CLIENT (source_object);
422 	ContactCopyProcess *process = user_data;
423 	GError *error = NULL;
424 
425 	e_book_client_remove_contact_by_uid_finish (book_client, result, &error);
426 
427 	if (error != NULL) {
428 		g_warning (
429 			"%s: Remove contact by uid failed: %s",
430 			G_STRFUNC, error->message);
431 		g_error_free (error);
432 	}
433 
434 	process_unref (process);
435 }
436 
437 static void
do_delete_from_source(gpointer data,gpointer user_data)438 do_delete_from_source (gpointer data,
439                        gpointer user_data)
440 {
441 	ContactCopyProcess *process = user_data;
442 	EContact *contact = data;
443 	const gchar *id;
444 	EBookClient *book_client = process->source;
445 
446 	id = e_contact_get_const (contact, E_CONTACT_UID);
447 	g_return_if_fail (id != NULL);
448 	g_return_if_fail (book_client != NULL);
449 
450 	process->count++;
451 	e_book_client_remove_contact_by_uid (book_client, id, E_BOOK_OPERATION_FLAG_NONE, NULL, remove_contact_ready_cb, process);
452 }
453 
454 static void
delete_contacts(ContactCopyProcess * process)455 delete_contacts (ContactCopyProcess *process)
456 {
457 	if (process->book_status == TRUE) {
458 		g_slist_foreach (process->contacts,
459 				do_delete_from_source,
460 				process);
461 	}
462 }
463 
464 static void
process_unref(ContactCopyProcess * process)465 process_unref (ContactCopyProcess *process)
466 {
467 	process->count--;
468 	if (process->count == 0) {
469 		if (process->delete_from_source) {
470 			delete_contacts (process);
471 			/* to not repeate this again */
472 			process->delete_from_source = FALSE;
473 
474 			if (process->count > 0)
475 				return;
476 		}
477 		g_slist_free_full (
478 			process->contacts,
479 			(GDestroyNotify) g_object_unref);
480 		g_object_unref (process->source);
481 		g_object_unref (process->destination);
482 		g_object_unref (process->registry);
483 		g_slice_free (ContactCopyProcess, process);
484 	}
485 }
486 
487 static void
contact_added_cb(EBookClient * book_client,const GError * error,const gchar * id,gpointer user_data)488 contact_added_cb (EBookClient *book_client,
489                   const GError *error,
490                   const gchar *id,
491                   gpointer user_data)
492 {
493 	ContactCopyProcess *process = user_data;
494 
495 	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
496 		process->book_status = FALSE;
497 	} else if (error != NULL) {
498 		process->book_status = FALSE;
499 		eab_error_dialog (
500 			process->alert_sink, NULL,
501 			_("Error adding contact"), error);
502 	} else {
503 		/* success */
504 		process->book_status = TRUE;
505 	}
506 
507 	process_unref (process);
508 }
509 
510 static void
do_copy(gpointer data,gpointer user_data)511 do_copy (gpointer data,
512          gpointer user_data)
513 {
514 	EBookClient *book_client;
515 	EContact *contact;
516 	ContactCopyProcess *process;
517 
518 	process = user_data;
519 	contact = data;
520 
521 	book_client = process->destination;
522 	e_contact_inline_local_photos (contact, NULL);
523 
524 	process->count++;
525 	eab_merging_book_add_contact (
526 		process->registry, book_client,
527 		contact, contact_added_cb, process);
528 }
529 
530 static void
book_client_connect_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)531 book_client_connect_cb (GObject *source_object,
532                         GAsyncResult *result,
533                         gpointer user_data)
534 {
535 	ContactCopyProcess *process = user_data;
536 	EClient *client;
537 	GError *error = NULL;
538 
539 	client = e_book_client_connect_finish (result, &error);
540 
541 	/* Sanity check. */
542 	g_return_if_fail (
543 		((client != NULL) && (error == NULL)) ||
544 		((client == NULL) && (error != NULL)));
545 
546 	if (error != NULL) {
547 		g_warning ("%s: %s", G_STRFUNC, error->message);
548 		g_error_free (error);
549 		goto exit;
550 	}
551 
552 	process->destination = E_BOOK_CLIENT (client);
553 	process->book_status = TRUE;
554 	g_slist_foreach (process->contacts, do_copy, process);
555 
556 exit:
557 	process_unref (process);
558 }
559 
560 void
eab_transfer_contacts(ESourceRegistry * registry,EBookClient * source_client,GSList * contacts,gboolean delete_from_source,EAlertSink * alert_sink)561 eab_transfer_contacts (ESourceRegistry *registry,
562                        EBookClient *source_client,
563                        GSList *contacts /* adopted */,
564                        gboolean delete_from_source,
565                        EAlertSink *alert_sink)
566 {
567 	ESource *source;
568 	ESource *destination;
569 	static gchar *last_uid = NULL;
570 	ContactCopyProcess *process;
571 	gchar *desc;
572 	GtkWindow *window = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (alert_sink)));
573 
574 	g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
575 	g_return_if_fail (E_IS_BOOK_CLIENT (source_client));
576 
577 	if (contacts == NULL)
578 		return;
579 
580 	if (last_uid == NULL)
581 		last_uid = g_strdup ("");
582 
583 	if (contacts->next == NULL) {
584 		if (delete_from_source)
585 			desc = _("Move contact to");
586 		else
587 			desc = _("Copy contact to");
588 	} else {
589 		if (delete_from_source)
590 			desc = _("Move contacts to");
591 		else
592 			desc = _("Copy contacts to");
593 	}
594 
595 	source = e_client_get_source (E_CLIENT (source_client));
596 
597 	destination = eab_select_source (
598 		registry, source, desc, NULL, last_uid, window);
599 
600 	if (!destination)
601 		return;
602 
603 	if (strcmp (last_uid, e_source_get_uid (destination)) != 0) {
604 		g_free (last_uid);
605 		last_uid = g_strdup (e_source_get_uid (destination));
606 	}
607 
608 	process = g_slice_new0 (ContactCopyProcess);
609 	process->count = 1;
610 	process->book_status = FALSE;
611 	process->source = g_object_ref (source_client);
612 	process->contacts = contacts;
613 	process->destination = NULL;
614 	process->registry = g_object_ref (registry);
615 	process->alert_sink = alert_sink;
616 	process->delete_from_source = delete_from_source;
617 
618 	e_book_client_connect (
619 		destination, 30, NULL, book_client_connect_cb, process);
620 }
621 
622 /*
623  * eab_format_address helper function
624  *
625  * Splits locales from en_US to array "en","us",NULL. When
626  * locales don't have the second part (for example "C"),
627  * the output array is "c",NULL
628  */
629 static gchar **
get_locales(void)630 get_locales (void)
631 {
632 	gchar *locale, *l_locale;
633 	gchar *dot;
634 	gchar **split;
635 
636 #ifdef LC_ADDRESS
637 	locale = g_strdup (setlocale (LC_ADDRESS, NULL));
638 #else
639 	locale = NULL;
640 #endif
641 	if (!locale)
642 		return NULL;
643 
644 	l_locale = g_utf8_strdown (locale, -1);
645 	g_free (locale);
646 
647 	dot = strchr (l_locale, '.');
648 	if (dot != NULL) {
649 		gchar *p = l_locale;
650 		l_locale = g_strndup (l_locale, dot - l_locale);
651 		g_free (p);
652 	}
653 
654 	split = g_strsplit (l_locale, "_", 2);
655 
656 	g_free (l_locale);
657 	return split;
658 
659 }
660 
661 static gchar *
get_locales_str(void)662 get_locales_str (void)
663 {
664 	gchar *ret = NULL;
665 	gchar **loc = get_locales ();
666 
667 	if (!loc)
668 		return g_strdup ("C");
669 
670 	if (!loc[0] ||
671 	    (loc[0] && !loc[1])) /* We don't care about language now, we need a country at first! */
672 		ret = g_strdup ("C");
673 	else if (loc[0] && loc[1]) {
674 		if (*loc[0])
675 			ret = g_strconcat (loc[LOCALES_COUNTRY], "_", loc[LOCALES_LANGUAGE], NULL);
676 		else
677 			ret = g_strdup (loc[LOCALES_COUNTRY]);
678 	}
679 
680 	g_strfreev (loc);
681 	return ret;
682 }
683 
684 /*
685  * Reads countrytransl.map file, which contains map of localized
686  * country names and their ISO codes and tries to find matching record
687  * for given country. The search is case insensitive.
688  * When no record is found (country is probably in untranslated language), returns
689  * code of local computer country (from locales)
690  */
691 static gchar *
country_to_ISO(const gchar * country)692 country_to_ISO (const gchar *country)
693 {
694 	FILE *file = fopen (EVOLUTION_PRIVDATADIR "/countrytransl.map", "r");
695 	gchar buffer[100];
696 	gint length = 100;
697 	gchar **pair;
698 	gchar *res;
699 	gchar *l_country = g_utf8_strdown (country, -1);
700 
701 	if (!file) {
702 		gchar **loc;
703 		g_warning ("%s: Failed to open countrytransl.map. Check your installation.", G_STRFUNC);
704 		loc = get_locales ();
705 		res = g_strdup (loc ? loc[LOCALES_COUNTRY] : NULL);
706 		g_free (l_country);
707 		g_strfreev (loc);
708 		return res;
709 	}
710 
711 	while (fgets (buffer, length, file) != NULL) {
712 		gchar *low = NULL;
713 		pair = g_strsplit (buffer, "\t", 2);
714 
715 		if (pair[0]) {
716 			low = g_utf8_strdown (pair[0], -1);
717 			if (g_utf8_collate (low, l_country) == 0) {
718 				gchar *ret = g_strdup (pair[1]);
719 				gchar *pos;
720 				/* Remove trailing newline character */
721 				if ((pos = g_strrstr (ret, "\n")) != NULL)
722 					pos[0] = '\0';
723 				fclose (file);
724 				g_strfreev (pair);
725 				g_free (low);
726 				g_free (l_country);
727 				return ret;
728 			}
729 		}
730 
731 		g_strfreev (pair);
732 		g_free (low);
733 	}
734 
735 	/* If we get here, then no match was found in the map file and we
736 	 * fallback to local system locales */
737 	fclose (file);
738 
739 	pair = get_locales ();
740 	res = g_strdup (pair ? pair[LOCALES_COUNTRY] : NULL);
741 	g_strfreev (pair);
742 	g_free (l_country);
743 	return res;
744 }
745 
746 /*
747  * Tries to find given key in "country_LANGUAGE" group. When fails to find
748  * such group, then fallbacks to "country" group. When such group does not
749  * exist either, NULL is returned
750  */
751 static gchar *
get_key_file_locale_string(GKeyFile * key_file,const gchar * key,const gchar * locale)752 get_key_file_locale_string (GKeyFile *key_file,
753                             const gchar *key,
754                             const gchar *locale)
755 {
756 	gchar *result;
757 	gchar *group;
758 
759 	g_return_val_if_fail (locale, NULL);
760 
761 	/* Default locale is in "country_lang", but such group may not exist. In such case use group "country" */
762 	if (g_key_file_has_group (key_file, locale))
763 		group = g_strdup (locale);
764 	else {
765 		gchar **locales = g_strsplit (locale, "_", 0);
766 		group = g_strdup (locales[LOCALES_COUNTRY]);
767 		g_strfreev (locales);
768 	}
769 
770 	/* When group or key does not exist, returns NULL and fallback string will be used */
771 	result = g_key_file_get_string (key_file, group, key, NULL);
772 	g_free (group);
773 	return result;
774 }
775 
776 static void
get_address_format(AddressFormat address_format,const gchar * locale,gchar ** format,gchar ** country_position)777 get_address_format (AddressFormat address_format,
778                     const gchar *locale,
779                     gchar **format,
780                     gchar **country_position)
781 {
782 	GKeyFile *key_file;
783 	GError *error;
784 	gchar *loc;
785 	const gchar *addr_key, *country_key;
786 
787 	if (address_format == ADDRESS_FORMAT_HOME) {
788 		addr_key = "AddressFormat";
789 		country_key = "CountryPosition";
790 	} else if (address_format == ADDRESS_FORMAT_BUSINESS) {
791 		addr_key = "BusinessAddressFormat";
792 		country_key = "BusinessCountryPosition";
793 	} else {
794 		return;
795 	}
796 
797 	if (locale == NULL)
798 		loc = get_locales_str ();
799 	else
800 		loc = g_strdup (locale);
801 
802 	error = NULL;
803 	key_file = g_key_file_new ();
804 	g_key_file_load_from_file (key_file, EVOLUTION_PRIVDATADIR "/address_formats.dat", 0, &error);
805 	if (error != NULL) {
806 		g_warning ("%s: Failed to load address_formats.dat file: %s", G_STRFUNC, error->message);
807 		if (format)
808 			*format = g_strdup (ADDRESS_DEFAULT_FORMAT);
809 		if (country_position)
810 			*country_position = g_strdup (ADDRESS_DEFAULT_COUNTRY_POSITION);
811 		g_key_file_free (key_file);
812 		g_free (loc);
813 		g_error_free (error);
814 		return;
815 	}
816 
817 	if (format) {
818 		g_free (*format);
819 		*format = get_key_file_locale_string (key_file, addr_key, loc);
820 		if (!*format && address_format == ADDRESS_FORMAT_HOME) {
821 			*format = g_strdup (ADDRESS_DEFAULT_FORMAT);
822 		} else if (!*format && address_format == ADDRESS_FORMAT_BUSINESS)
823 			get_address_format (ADDRESS_FORMAT_HOME, loc, format, NULL);
824 	}
825 
826 	if (country_position) {
827 		g_free (*country_position);
828 		*country_position = get_key_file_locale_string (key_file, country_key, loc);
829 		if (!*country_position && address_format == ADDRESS_FORMAT_HOME)
830 			*country_position = g_strdup (ADDRESS_DEFAULT_COUNTRY_POSITION);
831 		else if (!*country_position && address_format == ADDRESS_FORMAT_BUSINESS)
832 			get_address_format (ADDRESS_FORMAT_HOME, loc, NULL, country_position);
833 	}
834 
835 	g_free (loc);
836 	g_key_file_free (key_file);
837 }
838 
839 static const gchar *
find_balanced_bracket(const gchar * str)840 find_balanced_bracket (const gchar *str)
841 {
842 	gint balance_counter = 0;
843 	gint i = 0;
844 
845 	do {
846 		if (str[i] == '(')
847 			balance_counter++;
848 
849 		if (str[i] == ')')
850 			balance_counter--;
851 
852 		i++;
853 
854 	} while ((balance_counter > 0) && (str[i]));
855 
856 	if (balance_counter > 0)
857 		return str;
858 
859 	return str + i;
860 }
861 
862 static GString *
string_append_upper(GString * str,const gchar * c)863 string_append_upper (GString *str,
864                      const gchar *c)
865 {
866 	gchar *up_c;
867 
868 	g_return_val_if_fail (str, NULL);
869 
870 	if (!c || !*c)
871 		return str;
872 
873 	up_c = g_utf8_strup (c, -1);
874 	g_string_append (str, up_c);
875 	g_free (up_c);
876 
877 	return str;
878 }
879 
880 static gboolean
parse_address_template_section(const gchar * format,const gchar * realname,const gchar * org_name,EContactAddress * address,gchar ** result)881 parse_address_template_section (const gchar *format,
882                                 const gchar *realname,
883                                 const gchar *org_name,
884                                 EContactAddress *address,
885                                 gchar **result)
886 
887 {
888 	const gchar *pos, *old_pos;
889 	gboolean ret = FALSE; /* Indicates, wheter at least something was replaced */
890 
891 	GString *res = g_string_new ("");
892 
893 	pos = format;
894 	old_pos = pos;
895 	while ((pos = strchr (pos, '%')) != NULL) {
896 
897 		if (old_pos != pos)
898 			g_string_append_len (res, old_pos, pos - old_pos);
899 
900 		switch (pos[1]) {
901 			case 'n':
902 				if (realname && *realname) {
903 					g_string_append (res, realname);
904 					ret = TRUE;
905 				}
906 				pos += 2; /* Jump behind the modifier, see what's next */
907 				break;
908 			case 'N':
909 				if (realname && *realname) {
910 					string_append_upper (res, realname);
911 					ret = TRUE;
912 				}
913 				pos += 2;
914 				break;
915 			case 'm':
916 				if (org_name && *org_name) {
917 					g_string_append (res, org_name);
918 					ret = TRUE;
919 				}
920 				pos += 2;
921 				break;
922 			case 'M':
923 				if (org_name && *org_name) {
924 					string_append_upper (res, org_name);
925 					ret = TRUE;
926 				}
927 				pos += 2;
928 				break;
929 			case 'p':
930 				if (address->po && *(address->po)) {
931 					g_string_append (res, address->po);
932 					ret = TRUE;
933 				}
934 				pos += 2;
935 				break;
936 			case 's':
937 				if (address->street && *(address->street)) {
938 					g_string_append (res, address->street);
939 					if (address->ext && *(address->ext))
940 						g_string_append_printf (
941 							res, "\n%s",
942 							address->ext);
943 					ret = TRUE;
944 				}
945 				pos += 2;
946 				break;
947 			case 'S':
948 				if (address->street && *(address->street)) {
949 					string_append_upper (res, address->street);
950 					if (address->ext && *(address->ext)) {
951 						g_string_append_c (res, '\n');
952 						string_append_upper (res, address->ext);
953 					}
954 					ret = TRUE;
955 				}
956 				pos += 2;
957 				break;
958 			case 'z':
959 				if (address->code && *(address->code)) {
960 					g_string_append (res, address->code);
961 					ret = TRUE;
962 				}
963 				pos += 2;
964 				break;
965 			case 'l':
966 				if (address->locality && *(address->locality)) {
967 					g_string_append (res, address->locality);
968 					ret = TRUE;
969 				}
970 				pos += 2;
971 				break;
972 			case 'L':
973 				if (address->locality && *(address->locality)) {
974 					string_append_upper (res, address->locality);
975 					ret = TRUE;
976 				}
977 				pos += 2;
978 				break;
979 			case 'r':
980 				if (address->region && *(address->region)) {
981 					g_string_append (res, address->region);
982 					ret = TRUE;
983 				}
984 				pos += 2;
985 				break;
986 			case 'R':
987 				if (address->region && *(address->region)) {
988 					string_append_upper (res, address->region);
989 					ret = TRUE;
990 				}
991 				pos += 2;
992 				break;
993 			case ',':
994 				if (ret && (pos >= format + 2) &&		/* If there's something before %, */
995 				    (g_ascii_strcasecmp (pos - 2, "\n") != 0) &&  /* And if it is not a newline */
996 				    (g_ascii_strcasecmp (pos - 2, "%w") != 0))    /* Nor whitespace */
997 					g_string_append (res, ", ");
998 				pos += 2;
999 				break;
1000 			case 'w':
1001 				if (ret && (pos >= format + 2) &&
1002 				    (g_ascii_strcasecmp (pos - 2, "\n") != 0) &&
1003 				    (g_ascii_strcasecmp (pos - 1, " ") != 0))
1004 					g_string_append_c (res, ' ');
1005 				pos += 2;
1006 				break;
1007 			case '0': {
1008 				const gchar *bpos1, *bpos2;
1009 				gchar *inner;
1010 				gchar *ires;
1011 				gboolean replaced;
1012 
1013 				bpos1 = pos + 2;
1014 				bpos2 = find_balanced_bracket (bpos1);
1015 
1016 				inner = g_strndup (bpos1 + 1, bpos2 - bpos1 - 2); /* Get inner content of the %0 (...) */
1017 				replaced = parse_address_template_section (inner, realname, org_name, address, &ires);
1018 				if (replaced)
1019 					g_string_append (res, ires);
1020 
1021 				g_free (ires);
1022 				g_free (inner);
1023 
1024 				ret = replaced;
1025 				pos += (bpos2 - bpos1 + 2);
1026 			} break;
1027 		}
1028 
1029 		old_pos = pos;
1030 	}
1031 	g_string_append (res, old_pos);
1032 
1033 	*result = g_string_free (res, FALSE);
1034 
1035 	return ret;
1036 }
1037 
1038 gchar *
eab_format_address(EContact * contact,EContactField address_type)1039 eab_format_address (EContact *contact,
1040                     EContactField address_type)
1041 {
1042 	gchar *result;
1043 	gchar *format = NULL;
1044 	gchar *country_position = NULL;
1045 	gchar *locale;
1046 	EContactAddress *addr = e_contact_get (contact, address_type);
1047 
1048 	if (!addr)
1049 		return NULL;
1050 
1051 	if (!addr->po && !addr->ext && !addr->street && !addr->locality && !addr->region &&
1052 	    !addr->code && !addr->country) {
1053 		e_contact_address_free (addr);
1054 		return NULL;
1055 	}
1056 
1057 	if (addr->country) {
1058 		gchar *cntry = country_to_ISO (addr->country);
1059 		gchar **loc = get_locales ();
1060 		locale = g_strconcat (loc ? loc[LOCALES_LANGUAGE] : "C", "_", cntry, NULL);
1061 		g_strfreev (loc);
1062 		g_free (cntry);
1063 	} else
1064 		locale = get_locales_str ();
1065 
1066 	if (address_type == E_CONTACT_ADDRESS_HOME)
1067 		get_address_format (ADDRESS_FORMAT_HOME, locale, &format, &country_position);
1068 	else if (address_type == E_CONTACT_ADDRESS_WORK)
1069 		get_address_format (ADDRESS_FORMAT_BUSINESS, locale, &format, &country_position);
1070 	else {
1071 		e_contact_address_free (addr);
1072 		g_free (locale);
1073 		return NULL;
1074 	}
1075 
1076 	/* Expand all the variables in format.
1077 	 * Don't display organization in home address;
1078 	 * and skip full names, as it's part of the EContact itself,
1079 	 * check this bug for reason: https://bugzilla.gnome.org/show_bug.cgi?id=667912
1080 	 */
1081 	parse_address_template_section (
1082 		format,
1083 		NULL,
1084 		(address_type == E_CONTACT_ADDRESS_WORK) ?
1085 			e_contact_get_const (contact, E_CONTACT_ORG) : NULL,
1086 		addr,
1087 		&result);
1088 
1089 	/* Add the country line. In some countries, the address can be located above the
1090 	 * rest of the address */
1091 	if (addr->country && country_position) {
1092 		gchar *country_upper = g_utf8_strup (addr->country, -1);
1093 		gchar *p = result;
1094 		if (g_strcmp0 (country_position, "BELOW") == 0) {
1095 			result = g_strconcat (p, "\n\n", country_upper, NULL);
1096 			g_free (p);
1097 		} else if (g_strcmp0 (country_position, "below") == 0) {
1098 			result = g_strconcat (p, "\n\n", addr->country, NULL);
1099 			g_free (p);
1100 		} else if (g_strcmp0 (country_position, "ABOVE") == 0) {
1101 			result = g_strconcat (country_upper, "\n\n", p, NULL);
1102 			g_free (p);
1103 		} else if (g_strcmp0 (country_position, "above") == 0) {
1104 			result = g_strconcat (addr->country, "\n\n", p, NULL);
1105 			g_free (p);
1106 		}
1107 		g_free (country_upper);
1108 	}
1109 
1110 	e_contact_address_free (addr);
1111 	g_free (locale);
1112 	g_free (format);
1113 	g_free (country_position);
1114 
1115 	return result;
1116 }
1117 
1118 gboolean
eab_fullname_matches_nickname(EContact * contact)1119 eab_fullname_matches_nickname (EContact *contact)
1120 {
1121 	gchar *nickname, *fullname;
1122 	gboolean same;
1123 
1124 	g_return_val_if_fail (E_IS_CONTACT (contact), FALSE);
1125 
1126 	nickname = e_contact_get (contact, E_CONTACT_NICKNAME);
1127 	fullname = e_contact_get (contact, E_CONTACT_FULL_NAME);
1128 	same = g_strcmp0 (nickname && *nickname ? nickname : NULL,
1129 			  fullname && *fullname ? fullname : NULL) == 0;
1130 
1131 	g_free (nickname);
1132 	g_free (fullname);
1133 
1134 	return same;
1135 }
1136