Treat storing empty password same as storing NULL password.
[claws.git] / src / plugins / gdata / cm_gdata_contacts.c
1 /* GData plugin for Claws-Mail
2  * Copyright (C) 2011 Holger Berndt
3  * Copyright (C) 2011-2015 the Claws Mail team
4  *
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.
9  *
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.
14  *
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/>.
17  */
18
19 #ifdef HAVE_CONFIG_H
20 #  include "config.h"
21 #  include "claws-features.h"
22 #endif
23
24 #include <glib.h>
25 #include <glib/gi18n.h>
26
27 #include <gtk/gtk.h>
28
29 #include "cm_gdata_contacts.h"
30 #include "cm_gdata_prefs.h"
31
32 #include "addr_compl.h"
33 #include "main.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"
42
43 #include <gdata/gdata.h>
44
45 #define GDATA_CONTACTS_FILENAME "gdata_cache.xml"
46
47 #define GDATA_C1 "EOnuQt4fxED3WrO//iub3KLQMScIxXiT0VBD8RZUeKjkcm1zEBVMboeWDLYyqjJKZaL4oaZ24umWygbj19T2oJR6ZpjbCw=="
48 #define GDATA_C2 "QYjIgZblg/4RMCnEqNQypcHZba9ePqAN"
49 #define GDATA_C3 "XHEZEgO06YbWfQWOyYhE/ny5Q10aNOZlkQ=="
50
51
52 typedef struct
53 {
54   const gchar *family_name;
55   const gchar *given_name;
56   const gchar *full_name;
57   const gchar *address;
58 } Contact;
59
60 typedef struct
61 {
62   GSList *contacts;
63 } CmGDataContactsCache;
64
65
66 CmGDataContactsCache contacts_cache;
67 gboolean cm_gdata_contacts_query_running = FALSE;
68 gchar *contacts_group_id = NULL;
69 GDataOAuth2Authorizer *authorizer = NULL;
70 GDataContactsService *service = NULL;
71
72
73 static void protect_fields_against_NULL(Contact *contact)
74 {
75   g_return_if_fail(contact != NULL);
76
77   /* protect fields against being NULL */
78   if(contact->full_name == NULL)
79     contact->full_name = g_strdup("");
80   if(contact->given_name == NULL)
81     contact->given_name = g_strdup("");
82   if(contact->family_name == NULL)
83     contact->family_name = g_strdup("");
84 }
85
86 typedef struct
87 {
88   const gchar *auth_uri;
89   GtkWidget *entry;
90 } AuthCodeQueryButtonData;
91
92
93 static void auth_uri_link_button_clicked_cb(GtkButton *button, gpointer data)
94 {
95   AuthCodeQueryButtonData *auth_code_query_data = data;
96   open_uri(auth_code_query_data->auth_uri, prefs_common_get_uri_cmd());
97   gtk_widget_grab_focus(auth_code_query_data->entry);
98 }
99
100 static void auth_code_entry_changed_cb(GtkEditable *entry, gpointer data)
101 {
102   gtk_widget_set_sensitive(GTK_WIDGET(data), gtk_entry_get_text_length(GTK_ENTRY(entry)) > 0);
103 }
104
105
106 /* Returns the authorization code as newly allocated string, or NULL */
107 gchar* ask_user_for_auth_code(const gchar *auth_uri)
108 {
109   GtkWidget *dialog;
110   GtkWidget *vbox;
111   GtkWidget *table;
112   GtkWidget *link_button;
113   GtkWidget *label;
114   GtkWidget *entry;
115   gchar *str;
116   gchar *retval = NULL;
117   MainWindow *mainwin;
118   gint dlg_res;
119   GtkWidget *btn_ok;
120   AuthCodeQueryButtonData *auth_code_query_data;
121
122   mainwin = mainwindow_get_mainwindow();
123   dialog = gtk_message_dialog_new_with_markup(mainwin ? GTK_WINDOW(mainwin->window) : NULL,
124       GTK_DIALOG_DESTROY_WITH_PARENT,
125       GTK_MESSAGE_INFO,
126       GTK_BUTTONS_NONE,
127       "<span weight=\"bold\" size=\"larger\">%s</span>", _("GData plugin: Authorization required"));
128   gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
129       _("You need to authorize Claws Mail to access your Google contact list to use the GData plugin."
130       "\n\nVisit Google's authorization page by pressing the button below. After you "
131       "confirmed the authorization, you will get an authorization code. Enter that code "
132       "in the field below to grant Claws Mail access to your Google contact list."));
133   gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
134   btn_ok = gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_OK, GTK_RESPONSE_OK);
135   gtk_window_set_resizable(GTK_WINDOW(dialog), TRUE);
136   gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
137
138   gtk_widget_set_sensitive(btn_ok, FALSE);
139
140   table = gtk_table_new(2, 3, FALSE);
141   gtk_table_set_row_spacings(GTK_TABLE(table), 8);
142   gtk_table_set_col_spacings(GTK_TABLE(table), 8);
143
144   str = g_strconcat("<b>", _("Step 1:"), "</b>", NULL);
145   label = gtk_label_new(str);
146   g_free(str);
147   gtk_label_set_use_markup(GTK_LABEL(label), TRUE);
148   gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, 0, 0, 0, 0);
149
150   link_button = gtk_button_new_with_label(_("Click here to open the Google authorization page in a browser"));
151   auth_code_query_data = g_new0(AuthCodeQueryButtonData,1);
152   gtk_table_attach(GTK_TABLE(table), link_button, 1, 3, 0, 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
153
154   str = g_strconcat("<b>", _("Step 2:"), "</b>", NULL);
155   label = gtk_label_new(str);
156   g_free(str);
157   gtk_label_set_use_markup(GTK_LABEL(label), TRUE);
158   gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2, 0, 0, 0, 0);
159
160   gtk_table_attach(GTK_TABLE(table), gtk_label_new(_("Enter code:")), 1, 2, 1, 2, 0, 0, 0, 0);
161
162   entry = gtk_entry_new();
163   g_signal_connect(G_OBJECT(entry), "changed", (GCallback)auth_code_entry_changed_cb, (gpointer)btn_ok);
164   gtk_table_attach(GTK_TABLE(table), entry, 2, 3, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
165
166   auth_code_query_data->auth_uri = auth_uri;
167   auth_code_query_data->entry = entry;
168   g_signal_connect(G_OBJECT(link_button), "clicked", (GCallback)auth_uri_link_button_clicked_cb, (gpointer)auth_code_query_data);
169
170   vbox = gtk_vbox_new(FALSE, 4);
171   gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
172
173   gtk_box_pack_start(GTK_BOX(gtk_message_dialog_get_message_area(GTK_MESSAGE_DIALOG(dialog))), vbox, FALSE, FALSE, 0);
174
175   gtk_widget_show_all(dialog);
176
177   dlg_res = gtk_dialog_run(GTK_DIALOG(dialog));
178   switch(dlg_res)
179   {
180   case GTK_RESPONSE_DELETE_EVENT:
181   case GTK_RESPONSE_CANCEL:
182     break;
183   case GTK_RESPONSE_OK:
184     retval = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
185     break;
186   }
187
188   g_free(auth_code_query_data);
189   gtk_widget_destroy(dialog);
190
191   return retval;
192 }
193
194
195 static void write_cache_to_file(void)
196 {
197   gchar *path;
198   PrefFile *pfile;
199   XMLTag *tag;
200   XMLNode *xmlnode;
201   GNode *rootnode;
202   GNode *contactsnode;
203   GSList *walk;
204
205   path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, GDATA_CONTACTS_FILENAME, NULL);
206   pfile = prefs_write_open(path);
207   g_free(path);
208   if(pfile == NULL) {
209     debug_print("GData plugin error: Cannot open file " GDATA_CONTACTS_FILENAME " for writing\n");
210     return;
211   }
212
213   /* XML declarations */
214   xml_file_put_xml_decl(pfile->fp);
215
216   /* Build up XML tree */
217
218   /* root node */
219   tag = xml_tag_new("gdata");
220   xmlnode = xml_node_new(tag, NULL);
221   rootnode = g_node_new(xmlnode);
222
223   /* contacts node */
224   tag = xml_tag_new("contacts");
225   xmlnode = xml_node_new(tag, NULL);
226   contactsnode = g_node_new(xmlnode);
227   g_node_append(rootnode, contactsnode);
228
229   /* walk contacts cache */
230   for(walk = contacts_cache.contacts; walk; walk = walk->next)
231   {
232     GNode *contactnode;
233     Contact *contact = walk->data;
234     tag = xml_tag_new("contact");
235     xml_tag_add_attr(tag, xml_attr_new("family_name",contact->family_name));
236     xml_tag_add_attr(tag, xml_attr_new("given_name",contact->given_name));
237     xml_tag_add_attr(tag, xml_attr_new("full_name",contact->full_name));
238     xml_tag_add_attr(tag, xml_attr_new("address",contact->address));
239     xmlnode = xml_node_new(tag, NULL);
240     contactnode = g_node_new(xmlnode);
241     g_node_append(contactsnode, contactnode);
242   }
243
244         /* Actual writing and cleanup */
245         xml_write_tree(rootnode, pfile->fp);
246         if (prefs_file_close(pfile) < 0)
247                 debug_print("GData plugin error: Failed to write file " GDATA_CONTACTS_FILENAME "\n");
248         else
249                 debug_print("GData plugin: Wrote cache to file " GDATA_CONTACTS_FILENAME "\n");
250
251   /* Free XML tree */
252   xml_free_tree(rootnode);
253 }
254
255 static int add_gdata_contact_to_cache(GDataContactsContact *contact)
256 {
257   GList *walk;
258   int retval;
259
260   retval = 0;
261   for(walk = gdata_contacts_contact_get_email_addresses(contact); walk; walk = walk->next) {
262     const gchar *email_address;
263     GDataGDEmailAddress *address = GDATA_GD_EMAIL_ADDRESS(walk->data);
264
265     email_address = gdata_gd_email_address_get_address(address);
266     if(email_address && (*email_address != '\0')) {
267       GDataGDName *name;
268       Contact *cached_contact;
269
270       name = gdata_contacts_contact_get_name(contact);
271
272       cached_contact = g_new0(Contact, 1);
273       cached_contact->full_name = g_strdup(gdata_gd_name_get_full_name(name));
274       cached_contact->given_name = g_strdup(gdata_gd_name_get_given_name(name));
275       cached_contact->family_name = g_strdup(gdata_gd_name_get_family_name(name));
276       cached_contact->address = g_strdup(email_address);
277
278       protect_fields_against_NULL(cached_contact);
279
280       contacts_cache.contacts = g_slist_prepend(contacts_cache.contacts, cached_contact);
281
282       debug_print("GData plugin: Added %s <%s>\n", cached_contact->full_name, cached_contact->address);
283       retval = 1;
284     }
285   }
286   if(retval == 0)
287   {
288     debug_print("GData plugin: Skipped received contact \"%s\" because it doesn't have an email address\n",
289         gdata_gd_name_get_full_name(gdata_contacts_contact_get_name(contact)));
290   }
291   return retval;
292 }
293
294 static void free_contact(Contact *contact)
295 {
296   g_free((gpointer)contact->full_name);
297   g_free((gpointer)contact->family_name);
298   g_free((gpointer)contact->given_name);
299   g_free((gpointer)contact->address);
300   g_free(contact);
301 }
302
303 static void clear_contacts_cache(void)
304 {
305   GSList *walk;
306   for(walk = contacts_cache.contacts; walk; walk = walk->next)
307     free_contact(walk->data);
308   g_slist_free(contacts_cache.contacts);
309   contacts_cache.contacts = NULL;
310 }
311
312 static void cm_gdata_query_contacts_ready(GDataContactsService *service, GAsyncResult *res, gpointer data)
313 {
314   GDataFeed *feed;
315   GList *walk;
316   GError *error = NULL;
317   guint num_contacts = 0;
318   guint num_contacts_added = 0;
319         gchar *tmpstr1, *tmpstr2;
320
321   feed = gdata_service_query_finish(GDATA_SERVICE(service), res, &error);
322   cm_gdata_contacts_query_running = FALSE;
323   if(error)
324   {
325     g_object_unref(feed);
326     log_error(LOG_PROTOCOL, _("GData plugin: Error querying for contacts: %s\n"), error->message);
327     g_error_free(error);
328     return;
329   }
330
331   /* clear cache */
332   clear_contacts_cache();
333
334   /* Iterate through the returned contacts and fill the cache */
335   for(walk = gdata_feed_get_entries(feed); walk; walk = walk->next) {
336     num_contacts_added += add_gdata_contact_to_cache(GDATA_CONTACTS_CONTACT(walk->data));
337     num_contacts++;
338   }
339   g_object_unref(feed);
340   contacts_cache.contacts = g_slist_reverse(contacts_cache.contacts);
341         /* TRANSLATORS: First part of "Added X of Y contacts to cache" */
342   tmpstr1 = g_strdup_printf(ngettext("Added %d of", "Added %d of", num_contacts_added), num_contacts_added);
343         /* TRANSLATORS: Second part of "Added X of Y contacts to cache" */
344   tmpstr2 = g_strdup_printf(ngettext("1 contact to the cache", "%d contacts to the cache", num_contacts), num_contacts);
345   log_message(LOG_PROTOCOL, "%s %s\n", tmpstr1, tmpstr2);
346         g_free(tmpstr1);
347         g_free(tmpstr2);
348 }
349
350 static void query_contacts(GDataContactsService *service)
351 {
352   GDataContactsQuery *query;
353
354   log_message(LOG_PROTOCOL, _("GData plugin: Starting async contacts query\n"));
355
356   query = gdata_contacts_query_new(NULL);
357   gdata_contacts_query_set_group(query, contacts_group_id);
358   gdata_query_set_max_results(GDATA_QUERY(query), cm_gdata_config.max_num_results);
359   gdata_contacts_service_query_contacts_async(service, GDATA_QUERY(query), NULL, NULL, NULL,
360   NULL, (GAsyncReadyCallback)cm_gdata_query_contacts_ready, NULL);
361
362   g_object_unref(query);
363 }
364
365 static void cm_gdata_query_groups_ready(GDataContactsService *service, GAsyncResult *res, gpointer data)
366 {
367   GDataFeed *feed;
368   GList *walk;
369   GError *error = NULL;
370
371   feed = gdata_service_query_finish(GDATA_SERVICE(service), res, &error);
372   if(error)
373   {
374     g_object_unref(feed);
375     log_error(LOG_PROTOCOL, _("GData plugin: Error querying for groups: %s\n"), error->message);
376     g_error_free(error);
377     return;
378   }
379
380   /* Iterate through the returned groups and search for Contacts group id */
381   for(walk = gdata_feed_get_entries(feed); walk; walk = walk->next) {
382     const gchar *system_group_id;
383     GDataContactsGroup *group = GDATA_CONTACTS_GROUP(walk->data);
384
385     system_group_id = gdata_contacts_group_get_system_group_id(group);
386     if(system_group_id && !strcmp(system_group_id, GDATA_CONTACTS_GROUP_CONTACTS)) {
387       gchar *pos;
388       const gchar *id;
389
390       id = gdata_entry_get_id(GDATA_ENTRY(group));
391
392       /* possibly replace projection "full" by "base" */
393       pos = g_strrstr(id, "/full/");
394       if(pos) {
395         GString *str = g_string_new("\0");
396         int off = pos-id;
397
398         g_string_append_len(str, id, off);
399         g_string_append(str, "/base/");
400         g_string_append(str, id+off+strlen("/full/"));
401         g_string_append_c(str, '\0');
402         contacts_group_id = str->str;
403         g_string_free(str, FALSE);
404       }
405       else
406         contacts_group_id = g_strdup(id);
407       break;
408     }
409   }
410   g_object_unref(feed);
411
412   log_message(LOG_PROTOCOL, _("GData plugin: Groups received\n"));
413
414   query_contacts(service);
415 }
416
417 static void query_for_contacts_group_id(GDataAuthorizer *authorizer)
418 {
419   log_message(LOG_PROTOCOL, _("GData plugin: Starting async groups query\n"));
420
421   gdata_contacts_service_query_groups_async(service, NULL, NULL, NULL, NULL, NULL,
422       (GAsyncReadyCallback)cm_gdata_query_groups_ready, NULL);
423 }
424
425
426 static void query_after_auth()
427 {
428   if(!contacts_group_id)
429     query_for_contacts_group_id(GDATA_AUTHORIZER(authorizer));
430   else
431     query_contacts(service);
432 }
433
434
435 static void cm_gdata_auth_ready(GDataOAuth2Authorizer *auth, GAsyncResult *res, gpointer data)
436 {
437   GError *error = NULL;
438
439   if(gdata_oauth2_authorizer_request_authorization_finish(auth, res, &error) == FALSE)
440   {
441     /* Notify the user of all errors except cancellation errors */
442     if(!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
443     {
444       log_error(LOG_PROTOCOL, _("GData plugin: Authorization error: %s\n"), error->message);
445     }
446     g_error_free(error);
447     cm_gdata_contacts_query_running = FALSE;
448     return;
449   }
450
451   log_message(LOG_PROTOCOL, _("GData plugin: Authorization successful\n"));
452
453   query_after_auth();
454 }
455
456 static void cm_gdata_interactive_auth()
457 {
458   gchar *auth_uri;
459   gchar *auth_code;
460
461   log_message(LOG_PROTOCOL, _("GData plugin: Starting interactive authorization\n"));
462
463   auth_uri = gdata_oauth2_authorizer_build_authentication_uri(authorizer, cm_gdata_config.username, FALSE);
464   g_return_if_fail(auth_uri);
465
466   auth_code = ask_user_for_auth_code(auth_uri);
467
468   if(auth_code)
469   {
470     cm_gdata_contacts_query_running = TRUE;
471     log_message(LOG_PROTOCOL, _("GData plugin: Got authorization code, requesting authorization\n"));
472     gdata_oauth2_authorizer_request_authorization_async(authorizer, auth_code, NULL, (GAsyncReadyCallback)cm_gdata_auth_ready, NULL);
473     memset(auth_code, 0, strlen(auth_code));
474     g_free(auth_code);
475   }
476   else
477   {
478     log_warning(LOG_PROTOCOL, _("GData plugin: No authorization code received, authorization request cancelled\n"));
479   }
480
481   g_free(auth_uri);
482 }
483
484
485 #if GDATA_CHECK_VERSION(0,17,2)
486 static void cm_gdata_refresh_ready(GDataOAuth2Authorizer *auth, GAsyncResult *res, gpointer data)
487 {
488   GError *error = NULL;
489
490   if(gdata_authorizer_refresh_authorization_finish(GDATA_AUTHORIZER(auth), res, &error) == FALSE)
491   {
492     /* Notify the user of all errors except cancellation errors */
493     if(!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
494     {
495       log_error(LOG_PROTOCOL, _("GData plugin: Authorization refresh error: %s\n"), error->message);
496     }
497     g_error_free(error);
498
499     cm_gdata_interactive_auth();
500
501     return;
502   }
503
504   log_message(LOG_PROTOCOL, _("GData plugin: Authorization refresh successful\n"));
505
506   query_after_auth();
507 }
508 #endif
509
510
511 /* returns allocated string which must be freed */
512 static guchar* decode(const gchar *in)
513 {
514   guchar *tmp;
515   gsize len;
516
517   tmp = g_base64_decode(in, &len);
518   passcrypt_decrypt(tmp, len);
519   return tmp;
520 }
521
522
523 static void query()
524 {
525         gchar *token;
526
527   if(cm_gdata_contacts_query_running)
528   {
529     debug_print("GData plugin: Network query already in progress");
530     return;
531   }
532
533   if(!authorizer)
534   {
535     gchar *c1 = decode(GDATA_C1);
536     gchar *c2 = decode(GDATA_C2);
537     gchar *c3 = decode(GDATA_C3);
538
539     authorizer = gdata_oauth2_authorizer_new(c1, c2, c3, GDATA_TYPE_CONTACTS_SERVICE);
540
541     g_free(c1);
542     g_free(c2);
543     g_free(c3);
544   }
545   g_return_if_fail(authorizer);
546
547   if(!service)
548   {
549     service = gdata_contacts_service_new(GDATA_AUTHORIZER(authorizer));
550   }
551   g_return_if_fail(service);
552
553   if(!gdata_service_is_authorized(GDATA_SERVICE(service)))
554   {
555 #if GDATA_CHECK_VERSION(0,17,2)
556     /* Try to restore from saved refresh token.*/
557                 if((token = passwd_store_get(PWS_PLUGIN,
558                                                 "GData", GDATA_TOKEN_PWD_STRING)) != NULL)
559     {
560       log_message(LOG_PROTOCOL, _("GData plugin: Trying to refresh authorization\n"));
561       gdata_oauth2_authorizer_set_refresh_token(authorizer, token);
562       memset(token, 0, strlen(token));
563       g_free(token);
564       gdata_authorizer_refresh_authorization_async(GDATA_AUTHORIZER(authorizer), NULL, (GAsyncReadyCallback)cm_gdata_refresh_ready, NULL);
565     }
566     else
567     {
568       cm_gdata_interactive_auth();
569     }
570 #else
571     cm_gdata_interactive_auth();
572 #endif
573   }
574   else
575   {
576     query_after_auth();
577   }
578 }
579
580
581 static void add_contacts_to_list(GList **address_list, GSList *contacts)
582 {
583   GSList *walk;
584
585   for(walk = contacts; walk; walk = walk->next)
586   {
587     address_entry *ae;
588     Contact *contact = walk->data;
589
590     ae = g_new0(address_entry, 1);
591     ae->name = g_strdup(contact->full_name);
592     ae->address = g_strdup(contact->address);
593     ae->grp_emails = NULL;
594
595     *address_list = g_list_prepend(*address_list, ae);
596     addr_compl_add_address1(ae->address, ae);
597
598     if(contact->given_name && *(contact->given_name) != '\0')
599       addr_compl_add_address1(contact->given_name, ae);
600
601     if(contact->family_name && *(contact->family_name) != '\0')
602       addr_compl_add_address1(contact->family_name, ae);
603   }
604 }
605
606 void cm_gdata_add_contacts(GList **address_list)
607 {
608   add_contacts_to_list(address_list, contacts_cache.contacts);
609 }
610
611 gboolean cm_gdata_update_contacts_cache(void)
612 {
613   if(prefs_common_get_prefs()->work_offline)
614   {
615     debug_print("GData plugin: Offline mode\n");
616   }
617   else
618   {
619     debug_print("GData plugin: Querying contacts\n");
620     query();
621   }
622   return TRUE;
623 }
624
625 void cm_gdata_contacts_done(void)
626 {
627   gchar *pass;
628
629   g_free(contacts_group_id);
630   contacts_group_id = NULL;
631
632   write_cache_to_file();
633   if(contacts_cache.contacts && !claws_is_exiting())
634     clear_contacts_cache();
635
636   if(authorizer)
637   {
638 #if GDATA_CHECK_VERSION(0,17,2)
639     /* store refresh token */
640     pass = gdata_oauth2_authorizer_dup_refresh_token(authorizer);
641                 passwd_store_set(PWS_PLUGIN, "GData", GDATA_TOKEN_PWD_STRING,
642                                 pass, FALSE);
643                 if (pass != NULL) {
644             memset(pass, 0, strlen(pass));
645             g_free(pass);
646                 }
647                 passwd_store_write_config();
648 #endif
649
650     g_object_unref(G_OBJECT(authorizer));
651     authorizer = NULL;
652   }
653
654   if(service)
655   {
656     g_object_unref(G_OBJECT(service));
657     service = NULL;
658   }
659 }
660
661 void cm_gdata_load_contacts_cache_from_file(void)
662 {
663   gchar *path;
664   GNode *rootnode, *childnode, *contactnode;
665   XMLNode *xmlnode;
666
667   path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, GDATA_CONTACTS_FILENAME, NULL);
668   if(!is_file_exist(path)) {
669     g_free(path);
670     return;
671   }
672
673   /* no merging; make sure the cache is empty (this should be a noop, but just to be safe...) */
674   clear_contacts_cache();
675
676   rootnode = xml_parse_file(path);
677   g_free(path);
678   if(!rootnode)
679     return;
680   xmlnode = rootnode->data;
681
682   /* Check that root entry is "gdata" */
683   if(strcmp2(xmlnode->tag->tag, "gdata") != 0) {
684     g_warning("wrong gdata cache file");
685     xml_free_tree(rootnode);
686     return;
687   }
688
689   for(childnode = rootnode->children; childnode; childnode = childnode->next) {
690     GList *attributes;
691     xmlnode = childnode->data;
692
693     if(strcmp2(xmlnode->tag->tag, "contacts") != 0)
694       continue;
695
696     for(contactnode = childnode->children; contactnode; contactnode = contactnode->next)
697     {
698       Contact *cached_contact;
699
700       xmlnode = contactnode->data;
701
702       cached_contact = g_new0(Contact, 1);
703       /* Attributes of the branch nodes */
704       for(attributes = xmlnode->tag->attr; attributes; attributes = attributes->next) {
705         XMLAttr *attr = attributes->data;
706
707         if(attr && attr->name && attr->value) {
708           if(!strcmp2(attr->name, "full_name"))
709             cached_contact->full_name = g_strdup(attr->value);
710           else if(!strcmp2(attr->name, "given_name"))
711             cached_contact->given_name = g_strdup(attr->value);
712           else if(!strcmp2(attr->name, "family_name"))
713             cached_contact->family_name = g_strdup(attr->value);
714           else if(!strcmp2(attr->name, "address"))
715             cached_contact->address = g_strdup(attr->value);
716         }
717       }
718
719       if(cached_contact->address)
720       {
721         protect_fields_against_NULL(cached_contact);
722
723         contacts_cache.contacts = g_slist_prepend(contacts_cache.contacts, cached_contact);
724         debug_print("Read contact from cache: %s\n", cached_contact->full_name);
725       }
726       else {
727         debug_print("Ignored contact without email address: %s\n", cached_contact->full_name ? cached_contact->full_name : "(null)");
728       }
729     }
730   }
731
732   /* Free XML tree */
733   xml_free_tree(rootnode);
734
735   contacts_cache.contacts = g_slist_reverse(contacts_cache.contacts);
736 }