1 /* GData plugin for Claws Mail
2 * Copyright (C) 2011 Holger Berndt
3 * Copyright (C) 2011-2019 the Claws Mail team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 # include "claws-features.h"
25 #include <glib/gi18n.h>
29 #include "cm_gdata_contacts.h"
30 #include "cm_gdata_prefs.h"
32 #include "addr_compl.h"
34 #include "passwordstore.h"
35 #include "prefs_common.h"
36 #include "mainwindow.h"
37 #include "common/log.h"
38 #include "common/xml.h"
39 #include "common/utils.h"
40 #include "common/passcrypt.h"
41 #include "gtk/gtkutils.h"
43 #include <gdata/gdata.h>
45 #define GDATA_CONTACTS_FILENAME "gdata_cache.xml"
47 #define GDATA_C1 "EOnuQt4fxED3WrO//iub3KLQMScIxXiT0VBD8RZUeKjkcm1zEBVMboeWDLYyqjJKZaL4oaZ24umWygbj19T2oJR6ZpjbCw=="
48 #define GDATA_C2 "QYjIgZblg/4RMCnEqNQypcHZba9ePqAN"
49 #define GDATA_C3 "XHEZEgO06YbWfQWOyYhE/ny5Q10aNOZlkQ=="
51 #define REFRESH_TIMEOUT_MINUTES 45.0
56 const gchar
*family_name
;
57 const gchar
*given_name
;
58 const gchar
*full_name
;
65 } CmGDataContactsCache
;
68 static CmGDataContactsCache contacts_cache
;
69 static gboolean cm_gdata_contacts_query_running
= FALSE
;
70 static gchar
*contacts_group_id
= NULL
;
71 static GDataOAuth2Authorizer
*authorizer
= NULL
;
72 static GDataContactsService
*service
= NULL
;
73 static GTimer
*refresh_timer
= NULL
;
76 static void protect_fields_against_NULL(Contact
*contact
)
78 g_return_if_fail(contact
!= NULL
);
80 /* protect fields against being NULL */
81 if(contact
->full_name
== NULL
)
82 contact
->full_name
= g_strdup("");
83 if(contact
->given_name
== NULL
)
84 contact
->given_name
= g_strdup("");
85 if(contact
->family_name
== NULL
)
86 contact
->family_name
= g_strdup("");
91 const gchar
*auth_uri
;
93 } AuthCodeQueryButtonData
;
96 static void auth_uri_link_button_clicked_cb(GtkButton
*button
, gpointer data
)
98 AuthCodeQueryButtonData
*auth_code_query_data
= data
;
99 open_uri(auth_code_query_data
->auth_uri
, prefs_common_get_uri_cmd());
100 gtk_widget_grab_focus(auth_code_query_data
->entry
);
103 static void auth_code_entry_changed_cb(GtkEditable
*entry
, gpointer data
)
105 gtk_widget_set_sensitive(GTK_WIDGET(data
), gtk_entry_get_text_length(GTK_ENTRY(entry
)) > 0);
109 /* Returns the authorization code as newly allocated string, or NULL */
110 gchar
* ask_user_for_auth_code(const gchar
*auth_uri
)
115 GtkWidget
*link_button
;
119 gchar
*retval
= NULL
;
123 AuthCodeQueryButtonData
*auth_code_query_data
;
125 mainwin
= mainwindow_get_mainwindow();
126 dialog
= gtk_message_dialog_new_with_markup(mainwin
? GTK_WINDOW(mainwin
->window
) : NULL
,
127 GTK_DIALOG_DESTROY_WITH_PARENT
,
130 "<span weight=\"bold\" size=\"larger\">%s</span>", _("GData plugin: Authorization required"));
131 gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog
),
132 _("You need to authorize Claws Mail to access your Google contact list to use the GData plugin."
133 "\n\nVisit Google's authorization page by pressing the button below. After you "
134 "confirmed the authorization, you will get an authorization code. Enter that code "
135 "in the field below to grant Claws Mail access to your Google contact list."));
136 gtk_dialog_add_button(GTK_DIALOG(dialog
), _("_Cancel"), GTK_RESPONSE_CANCEL
);
137 btn_ok
= gtk_dialog_add_button(GTK_DIALOG(dialog
), _("_OK"), GTK_RESPONSE_OK
);
138 gtk_window_set_resizable(GTK_WINDOW(dialog
), TRUE
);
139 gtk_window_set_position(GTK_WINDOW(dialog
), GTK_WIN_POS_CENTER
);
141 gtk_widget_set_sensitive(btn_ok
, FALSE
);
143 table
= gtk_grid_new();
144 gtk_grid_set_row_spacing(GTK_GRID(table
), 8);
145 gtk_grid_set_column_spacing(GTK_GRID(table
), 8);
147 str
= g_strconcat("<b>", _("Step 1:"), "</b>", NULL
);
148 label
= gtk_label_new(str
);
150 gtk_label_set_use_markup(GTK_LABEL(label
), TRUE
);
151 gtk_grid_attach(GTK_GRID(table
), label
, 0, 0, 1, 1);
153 link_button
= gtk_button_new_with_label(_("Click here to open the Google authorization page in a browser"));
154 auth_code_query_data
= g_new0(AuthCodeQueryButtonData
,1);
155 gtk_grid_attach(GTK_GRID(table
), link_button
, 1, 0, 1, 1);
157 str
= g_strconcat("<b>", _("Step 2:"), "</b>", NULL
);
158 label
= gtk_label_new(str
);
160 gtk_label_set_use_markup(GTK_LABEL(label
), TRUE
);
161 gtk_grid_attach(GTK_GRID(table
), label
, 0, 1, 1, 1);
163 gtk_grid_attach(GTK_GRID(table
), gtk_label_new(_("Enter code:")), 1, 1, 1, 1);
165 entry
= gtk_entry_new();
166 g_signal_connect(G_OBJECT(entry
), "changed", (GCallback
)auth_code_entry_changed_cb
, (gpointer
)btn_ok
);
167 gtk_grid_attach(GTK_GRID(table
), entry
, 2, 1, 1, 1);
168 gtk_widget_set_hexpand(entry
, TRUE
);
169 gtk_widget_set_halign(entry
, GTK_ALIGN_FILL
);
171 auth_code_query_data
->auth_uri
= auth_uri
;
172 auth_code_query_data
->entry
= entry
;
173 g_signal_connect(G_OBJECT(link_button
), "clicked", (GCallback
)auth_uri_link_button_clicked_cb
, (gpointer
)auth_code_query_data
);
175 vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 4);
176 gtk_box_pack_start(GTK_BOX(vbox
), table
, FALSE
, FALSE
, 0);
178 gtk_box_pack_start(GTK_BOX(gtk_message_dialog_get_message_area(GTK_MESSAGE_DIALOG(dialog
))), vbox
, FALSE
, FALSE
, 0);
180 gtk_widget_show_all(dialog
);
182 dlg_res
= gtk_dialog_run(GTK_DIALOG(dialog
));
185 case GTK_RESPONSE_DELETE_EVENT
:
186 case GTK_RESPONSE_CANCEL
:
188 case GTK_RESPONSE_OK
:
189 retval
= g_strdup(gtk_entry_get_text(GTK_ENTRY(entry
)));
193 g_free(auth_code_query_data
);
194 gtk_widget_destroy(dialog
);
200 static void write_cache_to_file(void)
210 path
= g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S
, GDATA_CONTACTS_FILENAME
, NULL
);
211 pfile
= prefs_write_open(path
);
214 debug_print("GData plugin error: Cannot open file " GDATA_CONTACTS_FILENAME
" for writing\n");
218 /* XML declarations */
219 xml_file_put_xml_decl(pfile
->fp
);
221 /* Build up XML tree */
224 tag
= xml_tag_new("gdata");
225 xmlnode
= xml_node_new(tag
, NULL
);
226 rootnode
= g_node_new(xmlnode
);
229 tag
= xml_tag_new("contacts");
230 xmlnode
= xml_node_new(tag
, NULL
);
231 contactsnode
= g_node_new(xmlnode
);
232 g_node_append(rootnode
, contactsnode
);
234 /* walk contacts cache */
235 for(walk
= contacts_cache
.contacts
; walk
; walk
= walk
->next
)
238 Contact
*contact
= walk
->data
;
239 tag
= xml_tag_new("contact");
240 xml_tag_add_attr(tag
, xml_attr_new("family_name",contact
->family_name
));
241 xml_tag_add_attr(tag
, xml_attr_new("given_name",contact
->given_name
));
242 xml_tag_add_attr(tag
, xml_attr_new("full_name",contact
->full_name
));
243 xml_tag_add_attr(tag
, xml_attr_new("address",contact
->address
));
244 xmlnode
= xml_node_new(tag
, NULL
);
245 contactnode
= g_node_new(xmlnode
);
246 g_node_append(contactsnode
, contactnode
);
249 /* Actual writing and cleanup */
250 xml_write_tree(rootnode
, pfile
->fp
);
251 if (prefs_file_close(pfile
) < 0)
252 debug_print("GData plugin error: Failed to write file " GDATA_CONTACTS_FILENAME
"\n");
254 debug_print("GData plugin: Wrote cache to file " GDATA_CONTACTS_FILENAME
"\n");
257 xml_free_tree(rootnode
);
260 static int add_gdata_contact_to_cache(GDataContactsContact
*contact
)
266 for(walk
= gdata_contacts_contact_get_email_addresses(contact
); walk
; walk
= walk
->next
) {
267 const gchar
*email_address
;
268 GDataGDEmailAddress
*address
= GDATA_GD_EMAIL_ADDRESS(walk
->data
);
270 email_address
= gdata_gd_email_address_get_address(address
);
271 if(email_address
&& (*email_address
!= '\0')) {
273 Contact
*cached_contact
;
275 name
= gdata_contacts_contact_get_name(contact
);
277 cached_contact
= g_new0(Contact
, 1);
278 cached_contact
->full_name
= g_strdup(gdata_gd_name_get_full_name(name
));
279 cached_contact
->given_name
= g_strdup(gdata_gd_name_get_given_name(name
));
280 cached_contact
->family_name
= g_strdup(gdata_gd_name_get_family_name(name
));
281 cached_contact
->address
= g_strdup(email_address
);
283 protect_fields_against_NULL(cached_contact
);
285 contacts_cache
.contacts
= g_slist_prepend(contacts_cache
.contacts
, cached_contact
);
287 debug_print("GData plugin: Added %s <%s>\n", cached_contact
->full_name
, cached_contact
->address
);
293 debug_print("GData plugin: Skipped received contact \"%s\" because it doesn't have an email address\n",
294 gdata_gd_name_get_full_name(gdata_contacts_contact_get_name(contact
)));
299 static void free_contact(Contact
*contact
)
301 g_free((gpointer
)contact
->full_name
);
302 g_free((gpointer
)contact
->family_name
);
303 g_free((gpointer
)contact
->given_name
);
304 g_free((gpointer
)contact
->address
);
308 static void clear_contacts_cache(void)
311 for(walk
= contacts_cache
.contacts
; walk
; walk
= walk
->next
)
312 free_contact(walk
->data
);
313 g_slist_free(contacts_cache
.contacts
);
314 contacts_cache
.contacts
= NULL
;
317 static void cm_gdata_query_contacts_ready(GDataContactsService
*service
, GAsyncResult
*res
, gpointer data
)
321 GError
*error
= NULL
;
322 guint num_contacts
= 0;
323 guint num_contacts_added
= 0;
324 gchar
*tmpstr1
, *tmpstr2
;
326 feed
= gdata_service_query_finish(GDATA_SERVICE(service
), res
, &error
);
327 cm_gdata_contacts_query_running
= FALSE
;
330 g_object_unref(feed
);
331 log_error(LOG_PROTOCOL
, _("GData plugin: Error querying for contacts: %s\n"), error
->message
);
337 clear_contacts_cache();
339 /* Iterate through the returned contacts and fill the cache */
340 for(walk
= gdata_feed_get_entries(feed
); walk
; walk
= walk
->next
) {
341 num_contacts_added
+= add_gdata_contact_to_cache(GDATA_CONTACTS_CONTACT(walk
->data
));
344 g_object_unref(feed
);
345 contacts_cache
.contacts
= g_slist_reverse(contacts_cache
.contacts
);
346 /* TRANSLATORS: First part of "Added X of Y contacts to cache" */
347 tmpstr1
= g_strdup_printf(ngettext("Added %d of", "Added %d of", num_contacts_added
), num_contacts_added
);
348 /* TRANSLATORS: Second part of "Added X of Y contacts to cache" */
349 tmpstr2
= g_strdup_printf(ngettext("1 contact to the cache", "%d contacts to the cache", num_contacts
), num_contacts
);
350 log_message(LOG_PROTOCOL
, "%s %s\n", tmpstr1
, tmpstr2
);
355 static void query_contacts(GDataContactsService
*service
)
357 GDataContactsQuery
*query
;
359 log_message(LOG_PROTOCOL
, _("GData plugin: Starting async contacts query\n"));
361 query
= gdata_contacts_query_new(NULL
);
362 gdata_contacts_query_set_group(query
, contacts_group_id
);
363 gdata_query_set_max_results(GDATA_QUERY(query
), cm_gdata_config
.max_num_results
);
364 gdata_contacts_service_query_contacts_async(service
, GDATA_QUERY(query
), NULL
, NULL
, NULL
,
365 NULL
, (GAsyncReadyCallback
)cm_gdata_query_contacts_ready
, NULL
);
367 g_object_unref(query
);
370 static void cm_gdata_query_groups_ready(GDataContactsService
*service
, GAsyncResult
*res
, gpointer data
)
374 GError
*error
= NULL
;
376 feed
= gdata_service_query_finish(GDATA_SERVICE(service
), res
, &error
);
379 g_object_unref(feed
);
380 log_error(LOG_PROTOCOL
, _("GData plugin: Error querying for groups: %s\n"), error
->message
);
385 /* Iterate through the returned groups and search for Contacts group id */
386 for(walk
= gdata_feed_get_entries(feed
); walk
; walk
= walk
->next
) {
387 const gchar
*system_group_id
;
388 GDataContactsGroup
*group
= GDATA_CONTACTS_GROUP(walk
->data
);
390 system_group_id
= gdata_contacts_group_get_system_group_id(group
);
391 if(system_group_id
&& !strcmp(system_group_id
, GDATA_CONTACTS_GROUP_CONTACTS
)) {
395 id
= gdata_entry_get_id(GDATA_ENTRY(group
));
397 /* possibly replace projection "full" by "base" */
398 pos
= g_strrstr(id
, "/full/");
400 GString
*str
= g_string_new("\0");
403 g_string_append_len(str
, id
, off
);
404 g_string_append(str
, "/base/");
405 g_string_append(str
, id
+off
+strlen("/full/"));
406 g_string_append_c(str
, '\0');
407 contacts_group_id
= g_string_free(str
, FALSE
);
410 contacts_group_id
= g_strdup(id
);
414 g_object_unref(feed
);
416 log_message(LOG_PROTOCOL
, _("GData plugin: Groups received\n"));
418 query_contacts(service
);
421 static void query_for_contacts_group_id(GDataAuthorizer
*authorizer
)
423 log_message(LOG_PROTOCOL
, _("GData plugin: Starting async groups query\n"));
425 gdata_contacts_service_query_groups_async(service
, NULL
, NULL
, NULL
, NULL
, NULL
,
426 (GAsyncReadyCallback
)cm_gdata_query_groups_ready
, NULL
);
430 static void query_after_auth()
432 if(!contacts_group_id
)
433 query_for_contacts_group_id(GDATA_AUTHORIZER(authorizer
));
435 query_contacts(service
);
439 static void cm_gdata_auth_ready(GDataOAuth2Authorizer
*auth
, GAsyncResult
*res
, gpointer data
)
441 GError
*error
= NULL
;
443 if(gdata_oauth2_authorizer_request_authorization_finish(auth
, res
, &error
) == FALSE
)
445 /* Notify the user of all errors except cancellation errors */
446 if(!g_error_matches(error
, G_IO_ERROR
, G_IO_ERROR_CANCELLED
))
448 log_error(LOG_PROTOCOL
, _("GData plugin: Authorization error: %s\n"), error
->message
);
451 cm_gdata_contacts_query_running
= FALSE
;
455 log_message(LOG_PROTOCOL
, _("GData plugin: Authorization successful\n"));
460 static void cm_gdata_interactive_auth()
462 static gboolean interactive_auth_running
= FALSE
;
466 auth_uri
= gdata_oauth2_authorizer_build_authentication_uri(authorizer
, cm_gdata_config
.username
, FALSE
);
467 g_return_if_fail(auth_uri
);
469 if(!interactive_auth_running
)
473 interactive_auth_running
= TRUE
;
475 log_message(LOG_PROTOCOL
, _("GData plugin: Starting interactive authorization\n"));
477 auth_code
= ask_user_for_auth_code(auth_uri
);
481 cm_gdata_contacts_query_running
= TRUE
;
482 log_message(LOG_PROTOCOL
, _("GData plugin: Got authorization code, requesting authorization\n"));
483 gdata_oauth2_authorizer_request_authorization_async(authorizer
, auth_code
, NULL
, (GAsyncReadyCallback
)cm_gdata_auth_ready
, NULL
);
484 memset(auth_code
, 0, strlen(auth_code
));
489 log_warning(LOG_PROTOCOL
, _("GData plugin: No authorization code received, authorization request cancelled\n"));
491 interactive_auth_running
= FALSE
;
495 log_message(LOG_PROTOCOL
, _("GData plugin: Interactive authorization still running, no additional session started\n"));
502 static void cm_gdata_refresh_ready(GDataOAuth2Authorizer
*auth
, GAsyncResult
*res
, gpointer data
)
504 GError
*error
= NULL
;
505 gboolean start_interactive_auth
= FALSE
;
507 if(gdata_authorizer_refresh_authorization_finish(GDATA_AUTHORIZER(auth
), res
, &error
) == FALSE
)
509 /* Notify the user of all errors except cancellation errors */
510 if(!g_error_matches(error
, G_IO_ERROR
, G_IO_ERROR_CANCELLED
))
512 log_error(LOG_PROTOCOL
, _("GData plugin: Authorization refresh error: %s\n"), error
->message
);
514 if(mainwindow_get_mainwindow())
516 mainwindow_show_error();
520 /* Only start an interactive auth session in case of authorization issues, but not
521 * for e.g. sporadic network issues or other non-authorization-related problems. */
522 start_interactive_auth
=
523 g_error_matches(error
, GDATA_SERVICE_ERROR
, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED
) ||
524 g_error_matches(error
, GDATA_SERVICE_ERROR
, GDATA_SERVICE_ERROR_FORBIDDEN
);
528 if(start_interactive_auth
)
530 cm_gdata_interactive_auth();
536 log_message(LOG_PROTOCOL
, _("GData plugin: Authorization refresh successful\n"));
538 g_timer_start(refresh_timer
);
544 /* returns allocated string which must be freed */
545 static guchar
* decode(const gchar
*in
)
550 tmp
= g_base64_decode(in
, &len
);
551 passcrypt_decrypt(tmp
, len
);
559 int elapsed_time_min
;
561 if(cm_gdata_contacts_query_running
)
563 debug_print("GData plugin: Network query already in progress\n");
569 gchar
*c1
= decode(GDATA_C1
);
570 gchar
*c2
= decode(GDATA_C2
);
571 gchar
*c3
= decode(GDATA_C3
);
573 authorizer
= gdata_oauth2_authorizer_new(c1
, c2
, c3
, GDATA_TYPE_CONTACTS_SERVICE
);
579 g_return_if_fail(authorizer
);
583 service
= gdata_contacts_service_new(GDATA_AUTHORIZER(authorizer
));
585 g_return_if_fail(service
);
589 refresh_timer
= g_timer_new();
591 g_return_if_fail(refresh_timer
);
593 elapsed_time_min
= (int)((g_timer_elapsed(refresh_timer
, NULL
)/60.0)+0.5);
594 if(elapsed_time_min
> REFRESH_TIMEOUT_MINUTES
)
596 log_message(LOG_PROTOCOL
, _("GData plugin: Elapsed time since last refresh: %d minutes, refreshing now\n"), elapsed_time_min
);
597 gdata_authorizer_refresh_authorization_async(GDATA_AUTHORIZER(authorizer
), NULL
, (GAsyncReadyCallback
)cm_gdata_refresh_ready
, NULL
);
599 else if(!gdata_service_is_authorized(GDATA_SERVICE(service
)))
601 /* Try to restore from saved refresh token.*/
602 if((token
= passwd_store_get(PWS_PLUGIN
, "GData", GDATA_TOKEN_PWD_STRING
)) != NULL
)
604 log_message(LOG_PROTOCOL
, _("GData plugin: Trying to refresh authorization\n"));
605 gdata_oauth2_authorizer_set_refresh_token(authorizer
, token
);
606 memset(token
, 0, strlen(token
));
608 gdata_authorizer_refresh_authorization_async(GDATA_AUTHORIZER(authorizer
), NULL
, (GAsyncReadyCallback
)cm_gdata_refresh_ready
, NULL
);
612 cm_gdata_interactive_auth();
622 static void add_contacts_to_list(GList
**address_list
, GSList
*contacts
)
626 for(walk
= contacts
; walk
; walk
= walk
->next
)
629 Contact
*contact
= walk
->data
;
631 ae
= g_new0(address_entry
, 1);
632 ae
->name
= g_strdup(contact
->full_name
);
633 ae
->address
= g_strdup(contact
->address
);
634 ae
->grp_emails
= NULL
;
636 *address_list
= g_list_prepend(*address_list
, ae
);
637 addr_compl_add_address1(ae
->address
, ae
);
639 if(contact
->given_name
&& *(contact
->given_name
) != '\0')
640 addr_compl_add_address1(contact
->given_name
, ae
);
642 if(contact
->family_name
&& *(contact
->family_name
) != '\0')
643 addr_compl_add_address1(contact
->family_name
, ae
);
647 void cm_gdata_add_contacts(GList
**address_list
)
649 add_contacts_to_list(address_list
, contacts_cache
.contacts
);
652 gboolean
cm_gdata_update_contacts_cache(void)
654 if(prefs_common_get_prefs()->work_offline
)
656 debug_print("GData plugin: Offline mode\n");
660 debug_print("GData plugin: Querying contacts\n");
666 void cm_gdata_contacts_done(void)
670 g_free(contacts_group_id
);
671 contacts_group_id
= NULL
;
673 write_cache_to_file();
674 if(contacts_cache
.contacts
&& !claws_is_exiting())
675 clear_contacts_cache();
679 /* store refresh token */
680 pass
= gdata_oauth2_authorizer_dup_refresh_token(authorizer
);
681 passwd_store_set(PWS_PLUGIN
, "GData", GDATA_TOKEN_PWD_STRING
, pass
, FALSE
);
683 memset(pass
, 0, strlen(pass
));
686 passwd_store_write_config();
688 g_object_unref(G_OBJECT(authorizer
));
694 g_object_unref(G_OBJECT(service
));
700 g_timer_destroy(refresh_timer
);
701 refresh_timer
= NULL
;
705 void cm_gdata_load_contacts_cache_from_file(void)
708 GNode
*rootnode
, *childnode
, *contactnode
;
711 path
= g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S
, GDATA_CONTACTS_FILENAME
, NULL
);
712 if(!is_file_exist(path
)) {
717 /* no merging; make sure the cache is empty (this should be a noop, but just to be safe...) */
718 clear_contacts_cache();
720 rootnode
= xml_parse_file(path
);
724 xmlnode
= rootnode
->data
;
726 /* Check that root entry is "gdata" */
727 if(g_strcmp0(xmlnode
->tag
->tag
, "gdata") != 0) {
728 g_warning("wrong gdata cache file");
729 xml_free_tree(rootnode
);
733 for(childnode
= rootnode
->children
; childnode
; childnode
= childnode
->next
) {
735 xmlnode
= childnode
->data
;
737 if(g_strcmp0(xmlnode
->tag
->tag
, "contacts") != 0)
740 for(contactnode
= childnode
->children
; contactnode
; contactnode
= contactnode
->next
)
742 Contact
*cached_contact
;
744 xmlnode
= contactnode
->data
;
746 cached_contact
= g_new0(Contact
, 1);
747 /* Attributes of the branch nodes */
748 for(attributes
= xmlnode
->tag
->attr
; attributes
; attributes
= attributes
->next
) {
749 XMLAttr
*attr
= attributes
->data
;
751 if(attr
&& attr
->name
&& attr
->value
) {
752 if(!g_strcmp0(attr
->name
, "full_name"))
753 cached_contact
->full_name
= g_strdup(attr
->value
);
754 else if(!g_strcmp0(attr
->name
, "given_name"))
755 cached_contact
->given_name
= g_strdup(attr
->value
);
756 else if(!g_strcmp0(attr
->name
, "family_name"))
757 cached_contact
->family_name
= g_strdup(attr
->value
);
758 else if(!g_strcmp0(attr
->name
, "address"))
759 cached_contact
->address
= g_strdup(attr
->value
);
763 if(cached_contact
->address
)
765 protect_fields_against_NULL(cached_contact
);
767 contacts_cache
.contacts
= g_slist_prepend(contacts_cache
.contacts
, cached_contact
);
768 debug_print("Read contact from cache: %s\n", cached_contact
->full_name
);
771 debug_print("Ignored contact without email address: %s\n", cached_contact
->full_name
? cached_contact
->full_name
: "(null)");
772 /* Not added to list: return allocated memory */
773 if (cached_contact
->full_name
)
774 g_free((gchar
*) cached_contact
->full_name
);
775 if (cached_contact
->given_name
)
776 g_free((gchar
*) cached_contact
->given_name
);
777 if (cached_contact
->family_name
)
778 g_free((gchar
*) cached_contact
->family_name
);
779 if (cached_contact
->address
)
780 g_free((gchar
*) cached_contact
->address
);
781 g_free(cached_contact
);
787 xml_free_tree(rootnode
);
789 contacts_cache
.contacts
= g_slist_reverse(contacts_cache
.contacts
);