1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
4  * SPDX-License-Identifier: LGPL-2.1-or-later
5  */
6 
7 #include "evolution-ews-config.h"
8 
9 #include <string.h>
10 #include <time.h>
11 
12 #include <glib.h>
13 #include <glib/gi18n-lib.h>
14 
15 #include <libedata-book/libedata-book.h>
16 
17 #include "common/camel-m365-settings.h"
18 #include "common/e-m365-connection.h"
19 #include "common/e-source-m365-folder.h"
20 
21 #include "e-book-backend-m365.h"
22 
23 #ifdef G_OS_WIN32
24 #ifdef gmtime_r
25 #undef gmtime_r
26 #endif
27 
28 /* The gmtime() in Microsoft's C library is MT-safe */
29 #define gmtime_r(tp,tmp) (gmtime(tp)?(*(tmp)=*gmtime(tp),(tmp)):0)
30 #endif
31 
32 #define EC_ERROR_EX(_code,_msg) e_client_error_create (_code, _msg)
33 #define EBC_ERROR_EX(_code,_msg) e_book_client_error_create (_code, _msg)
34 
35 #define LOCK(_bb) g_rec_mutex_lock (&_bb->priv->property_lock)
36 #define UNLOCK(_bb) g_rec_mutex_unlock (&_bb->priv->property_lock)
37 
38 struct _EBookBackendM365Private {
39 	GRecMutex property_lock;
40 	EM365Connection *cnc;
41 	gchar *folder_id;
42 };
43 
G_DEFINE_TYPE_WITH_PRIVATE(EBookBackendM365,e_book_backend_m365,E_TYPE_BOOK_META_BACKEND)44 G_DEFINE_TYPE_WITH_PRIVATE (EBookBackendM365, e_book_backend_m365, E_TYPE_BOOK_META_BACKEND)
45 
46 static void
47 ebb_m365_contact_get_string_attribute (EM365Contact *m365_contact,
48 				       EContact *inout_contact,
49 				       EContactField field_id,
50 				       const gchar * (*m365_get_func) (EM365Contact *contact))
51 {
52 	e_contact_set (inout_contact, field_id, m365_get_func (m365_contact));
53 }
54 
55 static void
ebb_m365_contact_add_string_attribute(EContact * new_contact,EContact * old_contact,EContactField field_id,JsonBuilder * builder,void (* m365_add_func)(JsonBuilder * builder,const gchar * value))56 ebb_m365_contact_add_string_attribute (EContact *new_contact,
57 				       EContact *old_contact,
58 				       EContactField field_id,
59 				       JsonBuilder *builder,
60 				       void (* m365_add_func) (JsonBuilder *builder,
61 							       const gchar *value))
62 {
63 	const gchar *new_value, *old_value;
64 
65 	g_return_if_fail (m365_add_func != NULL);
66 
67 	new_value = e_contact_get_const (new_contact, field_id);
68 	old_value = old_contact ? e_contact_get_const (old_contact, field_id) : NULL;
69 
70 	if (g_strcmp0 (new_value, old_value) != 0)
71 		m365_add_func (builder, new_value);
72 }
73 
74 static gboolean
ebb_m365_contact_get_rev(EBookBackendM365 * bbm365,EM365Contact * m365_contact,EContact * inout_contact,EContactField field_id,EM365Connection * cnc,GCancellable * cancellable,GError ** error)75 ebb_m365_contact_get_rev (EBookBackendM365 *bbm365,
76 			  EM365Contact *m365_contact,
77 			  EContact *inout_contact,
78 			  EContactField field_id,
79 			  EM365Connection *cnc,
80 			  GCancellable *cancellable,
81 			  GError **error)
82 {
83 	gchar time_string[100] = { 0 };
84 	struct tm stm;
85 	time_t value;
86 
87 	value = e_m365_contact_get_last_modified_date_time (m365_contact);
88 
89 	if (value <= (time_t) 0)
90 		value = time (NULL);
91 
92 	gmtime_r (&value, &stm);
93 	strftime (time_string, 100, "%Y-%m-%dT%H:%M:%SZ", &stm);
94 
95 	e_contact_set (inout_contact, field_id, time_string);
96 
97 	return TRUE;
98 }
99 
100 static gboolean
ebb_m365_contact_get_birthday(EBookBackendM365 * bbm365,EM365Contact * m365_contact,EContact * inout_contact,EContactField field_id,EM365Connection * cnc,GCancellable * cancellable,GError ** error)101 ebb_m365_contact_get_birthday (EBookBackendM365 *bbm365,
102 			       EM365Contact *m365_contact,
103 			       EContact *inout_contact,
104 			       EContactField field_id,
105 			       EM365Connection *cnc,
106 			       GCancellable *cancellable,
107 			       GError **error)
108 {
109 	time_t value;
110 	gboolean exists = FALSE;
111 
112 	value = e_m365_contact_get_birthday (m365_contact, &exists);
113 
114 	if (exists) {
115 		EContactDate dt;
116 		struct tm stm;
117 
118 		gmtime_r (&value, &stm);
119 
120 		dt.year = stm.tm_year + 1900;
121 		dt.month = stm.tm_mon + 1;
122 		dt.day = stm.tm_mday;
123 
124 		e_contact_set (inout_contact, field_id, &dt);
125 	}
126 
127 	return TRUE;
128 }
129 
130 static gboolean
ebb_m365_contact_add_birthday(EBookBackendM365 * bbm365,EContact * new_contact,EContact * old_contact,EContactField field_id,const gchar * m365_id,JsonBuilder * builder,GCancellable * cancellable,GError ** error)131 ebb_m365_contact_add_birthday (EBookBackendM365 *bbm365,
132 			       EContact *new_contact,
133 			       EContact *old_contact,
134 			       EContactField field_id,
135 			       const gchar *m365_id,
136 			       JsonBuilder *builder,
137 			       GCancellable *cancellable,
138 			       GError **error)
139 {
140 	EContactDate *old_dt = NULL;
141 	EContactDate *new_dt = NULL;
142 
143 	new_dt = e_contact_get (new_contact, field_id);
144 	old_dt = old_contact ? e_contact_get (old_contact, field_id) : NULL;
145 
146 	if (!e_contact_date_equal (new_dt, old_dt)) {
147 		if (new_dt) {
148 			GDateTime *gdt;
149 			time_t value = (time_t) 0;
150 
151 			gdt = g_date_time_new_local (new_dt->year, new_dt->month, new_dt->day, 11, 59, 0.0);
152 
153 			if (gdt) {
154 				value = g_date_time_to_unix (gdt);
155 				value = value - (value % (24 * 60 * 60));
156 				value = value + (((12 * 60) - 1) * 60);
157 
158 				g_date_time_unref (gdt);
159 			}
160 
161 			e_m365_contact_add_birthday (builder, value);
162 		} else {
163 			e_m365_contact_add_birthday (builder, (time_t) 0);
164 		}
165 	}
166 
167 	e_contact_date_free (new_dt);
168 	e_contact_date_free (old_dt);
169 
170 	return TRUE;
171 }
172 
173 static gboolean
ebb_m365_contact_get_address(EBookBackendM365 * bbm365,EM365Contact * m365_contact,EContact * inout_contact,EContactField field_id,EM365Connection * cnc,GCancellable * cancellable,GError ** error)174 ebb_m365_contact_get_address (EBookBackendM365 *bbm365,
175 			      EM365Contact *m365_contact,
176 			      EContact *inout_contact,
177 			      EContactField field_id,
178 			      EM365Connection *cnc,
179 			      GCancellable *cancellable,
180 			      GError **error)
181 {
182 	EM365PhysicalAddress *phys_address = NULL;
183 
184 	if (field_id == E_CONTACT_ADDRESS_WORK)
185 		phys_address = e_m365_contact_get_business_address (m365_contact);
186 	else if (field_id == E_CONTACT_ADDRESS_HOME)
187 		phys_address = e_m365_contact_get_home_address (m365_contact);
188 	else if (field_id == E_CONTACT_ADDRESS_OTHER)
189 		phys_address = e_m365_contact_get_other_address (m365_contact);
190 	else
191 		g_warning ("%s: Uncaught field '%s'", G_STRFUNC, e_contact_vcard_attribute (field_id));
192 
193 	if (phys_address) {
194 		EContactAddress addr;
195 
196 		memset (&addr, 0, sizeof (EContactAddress));
197 
198 		addr.locality = (gchar *) e_m365_physical_address_get_city (phys_address);
199 		addr.country = (gchar *) e_m365_physical_address_get_country_or_region (phys_address);
200 		addr.code = (gchar *) e_m365_physical_address_get_postal_code (phys_address);
201 		addr.region = (gchar *) e_m365_physical_address_get_state (phys_address);
202 		addr.street = (gchar *) e_m365_physical_address_get_street (phys_address);
203 
204 		if (addr.locality || addr.country || addr.code || addr.region || addr.street)
205 			e_contact_set (inout_contact, field_id, &addr);
206 		else
207 			e_contact_set (inout_contact, field_id, NULL);
208 	}
209 
210 	return TRUE;
211 }
212 
213 static gboolean
ebb_m365_contact_address_equal(const EContactAddress * addr1,const EContactAddress * addr2)214 ebb_m365_contact_address_equal (const EContactAddress *addr1,
215 				const EContactAddress *addr2)
216 {
217 	if (!addr1 && !addr2)
218 		return TRUE;
219 
220 	if ((addr1 && !addr2) || (!addr1 && addr2))
221 		return FALSE;
222 
223 	return /* g_strcmp0 (addr1->address_format, addr2->address_format) == 0 && */
224 		g_strcmp0 (addr1->po, addr2->po) == 0 &&
225 		g_strcmp0 (addr1->ext, addr2->ext) == 0 &&
226 		g_strcmp0 (addr1->street, addr2->street) == 0 &&
227 		g_strcmp0 (addr1->locality, addr2->locality) == 0 &&
228 		g_strcmp0 (addr1->region, addr2->region) == 0 &&
229 		g_strcmp0 (addr1->code, addr2->code) == 0 &&
230 		g_strcmp0 (addr1->country, addr2->country) == 0;
231 }
232 
233 static gboolean
ebb_m365_contact_add_address(EBookBackendM365 * bbm365,EContact * new_contact,EContact * old_contact,EContactField field_id,const gchar * m365_id,JsonBuilder * builder,GCancellable * cancellable,GError ** error)234 ebb_m365_contact_add_address (EBookBackendM365 *bbm365,
235 			      EContact *new_contact,
236 			      EContact *old_contact,
237 			      EContactField field_id,
238 			      const gchar *m365_id,
239 			      JsonBuilder *builder,
240 			      GCancellable *cancellable,
241 			      GError **error)
242 {
243 	EContactAddress *new_addr, *old_addr;
244 
245 	new_addr = e_contact_get (new_contact, field_id);
246 	old_addr = old_contact ? e_contact_get (old_contact, field_id) : NULL;
247 
248 	if (!ebb_m365_contact_address_equal (new_addr, old_addr)) {
249 		void (* add_func) (JsonBuilder *builder,
250 				   const gchar *city,
251 				   const gchar *country_or_region,
252 				   const gchar *postal_code,
253 				   const gchar *state,
254 				   const gchar *street) = NULL;
255 
256 		if (field_id == E_CONTACT_ADDRESS_WORK)
257 			add_func = e_m365_contact_add_business_address;
258 		else if (field_id == E_CONTACT_ADDRESS_HOME)
259 			add_func = e_m365_contact_add_home_address;
260 		else if (field_id == E_CONTACT_ADDRESS_OTHER)
261 			add_func = e_m365_contact_add_other_address;
262 		else
263 			g_warning ("%s: Uncaught field '%s'", G_STRFUNC, e_contact_vcard_attribute (field_id));
264 
265 		if (add_func) {
266 			if (new_addr) {
267 				add_func (builder, new_addr->locality, new_addr->country, new_addr->code, new_addr->region, new_addr->street);
268 			} else {
269 				add_func (builder, NULL, NULL, NULL, NULL, NULL);
270 			}
271 		}
272 	}
273 
274 	e_contact_address_free (new_addr);
275 	e_contact_address_free (old_addr);
276 
277 	return TRUE;
278 }
279 
280 static gboolean
ebb_m365_string_values_equal(GSList * new_values,GSList * old_values)281 ebb_m365_string_values_equal (GSList *new_values, /* const gchar * */
282 			      GSList *old_values) /* const gchar * */
283 {
284 	GHashTable *values;
285 	GSList *link;
286 	gboolean equal = TRUE;
287 
288 	if (g_slist_length (new_values) != g_slist_length (old_values))
289 		return FALSE;
290 
291 	values = g_hash_table_new (g_str_hash, g_str_equal);
292 
293 	for (link = new_values; link; link = g_slist_next (link)) {
294 		gchar *value = link->data;
295 
296 		if (value)
297 			g_hash_table_add (values, value);
298 	}
299 
300 	for (link = old_values; link && equal; link = g_slist_next (link)) {
301 		const gchar *value = link->data;
302 
303 		if (value)
304 			equal = g_hash_table_remove (values, value);
305 	}
306 
307 	equal = equal && !g_hash_table_size (values);
308 
309 	g_hash_table_destroy (values);
310 
311 	return equal;
312 }
313 
314 static gboolean
ebb_m365_string_list_values_equal(GList * new_values,GList * old_values)315 ebb_m365_string_list_values_equal (GList *new_values, /* const gchar * */
316 				   GList *old_values) /* const gchar * */
317 {
318 	GHashTable *values;
319 	GList *link;
320 	gboolean equal = TRUE;
321 
322 	if (g_list_length (new_values) != g_list_length (old_values))
323 		return FALSE;
324 
325 	values = g_hash_table_new (g_str_hash, g_str_equal);
326 
327 	for (link = new_values; link; link = g_list_next (link)) {
328 		gchar *value = link->data;
329 
330 		if (value)
331 			g_hash_table_add (values, value);
332 	}
333 
334 	for (link = old_values; link && equal; link = g_list_next (link)) {
335 		const gchar *value = link->data;
336 
337 		if (value)
338 			equal = g_hash_table_remove (values, value);
339 	}
340 
341 	equal = equal && !g_hash_table_size (values);
342 
343 	g_hash_table_destroy (values);
344 
345 	return equal;
346 }
347 
348 static gboolean
ebb_m365_contact_get_phone(EBookBackendM365 * bbm365,EM365Contact * m365_contact,EContact * inout_contact,EContactField field_id,EM365Connection * cnc,GCancellable * cancellable,GError ** error)349 ebb_m365_contact_get_phone (EBookBackendM365 *bbm365,
350 			    EM365Contact *m365_contact,
351 			    EContact *inout_contact,
352 			    EContactField field_id,
353 			    EM365Connection *cnc,
354 			    GCancellable *cancellable,
355 			    GError **error)
356 {
357 	JsonArray *values = NULL;
358 	const gchar *type_val = NULL;
359 
360 	if (field_id == E_CONTACT_PHONE_BUSINESS) {
361 		values = e_m365_contact_get_business_phones (m365_contact);
362 		type_val = "WORK";
363 	} else if (field_id == E_CONTACT_PHONE_HOME) {
364 		values = e_m365_contact_get_home_phones (m365_contact);
365 		type_val = "HOME";
366 	} else {
367 		g_warning ("%s: Uncaught field '%s'", G_STRFUNC, e_contact_vcard_attribute (field_id));
368 	}
369 
370 	if (values) {
371 		EVCard *vcard = E_VCARD (inout_contact);
372 		guint ii, len;
373 
374 		len = json_array_get_length (values);
375 
376 		for (ii = 0; ii < len; ii++) {
377 			const gchar *str = json_array_get_string_element (values, len - ii - 1);
378 
379 			if (str && *str) {
380 				EVCardAttributeParam *param;
381 				EVCardAttribute *attr;
382 
383 				attr = e_vcard_attribute_new (NULL, EVC_TEL);
384 				param = e_vcard_attribute_param_new (EVC_TYPE);
385 
386 				e_vcard_attribute_add_param_with_value (attr, param, type_val);
387 				e_vcard_add_attribute_with_value (vcard, attr, str);
388 			}
389 		}
390 	}
391 
392 	return TRUE;
393 }
394 
395 static GSList * /* gchar * */
ebb_m365_extract_phones(EContact * contact,const gchar * only_type)396 ebb_m365_extract_phones (EContact *contact,
397 			 const gchar *only_type) /* NULL for anything but known types */
398 {
399 	GSList *phones = NULL;
400 	GList *attrs, *link;
401 
402 	if (!contact)
403 		return NULL;
404 
405 	attrs = e_vcard_get_attributes (E_VCARD (contact));
406 
407 	for (link = attrs; link; link = g_list_next (link)) {
408 		EVCardAttribute *attr = link->data;
409 		gboolean use_it = FALSE;
410 
411 		if (!attr || !e_vcard_attribute_get_name (attr) ||
412 		    g_ascii_strcasecmp (e_vcard_attribute_get_name (attr), EVC_TEL) != 0)
413 			continue;
414 
415 		if (only_type) {
416 			use_it = e_vcard_attribute_has_type (attr, only_type);
417 		} else {
418 			use_it = !e_vcard_attribute_has_type (attr, "WORK") &&
419 				 !e_vcard_attribute_has_type (attr, "CELL");
420 		}
421 
422 		if (use_it)
423 			phones = g_slist_prepend (phones, e_vcard_attribute_get_value (attr));
424 	}
425 
426 	return g_slist_reverse (phones);
427 }
428 
429 static gboolean
ebb_m365_contact_add_phone(EBookBackendM365 * bbm365,EContact * new_contact,EContact * old_contact,EContactField field_id,const gchar * m365_id,JsonBuilder * builder,GCancellable * cancellable,GError ** error)430 ebb_m365_contact_add_phone (EBookBackendM365 *bbm365,
431 			    EContact *new_contact,
432 			    EContact *old_contact,
433 			    EContactField field_id,
434 			    const gchar *m365_id,
435 			    JsonBuilder *builder,
436 			    GCancellable *cancellable,
437 			    GError **error)
438 {
439 	void (* begin_func) (JsonBuilder *builder) = NULL;
440 	void (* end_func) (JsonBuilder *builder) = NULL;
441 	void (* add_func) (JsonBuilder *builder, const gchar *value) = NULL;
442 	const gchar *type_val = NULL;
443 	GSList *new_values, *old_values;
444 
445 	if (field_id == E_CONTACT_PHONE_BUSINESS) {
446 		begin_func = e_m365_contact_begin_business_phones;
447 		end_func = e_m365_contact_end_business_phones;
448 		add_func = e_m365_contact_add_business_phone;
449 		type_val = "WORK";
450 	} else if (field_id == E_CONTACT_PHONE_HOME) {
451 		begin_func = e_m365_contact_begin_home_phones;
452 		end_func = e_m365_contact_end_home_phones;
453 		add_func = e_m365_contact_add_home_phone;
454 		type_val = NULL; /* everything else is treated as "HOME" phone */
455 	} else {
456 		g_warning ("%s: Uncaught field '%s'", G_STRFUNC, e_contact_vcard_attribute (field_id));
457 	}
458 
459 	new_values = ebb_m365_extract_phones (new_contact, type_val);
460 	old_values = ebb_m365_extract_phones (old_contact, type_val);
461 
462 	if (!ebb_m365_string_values_equal (new_values, old_values)) {
463 		GSList *link;
464 
465 		begin_func (builder);
466 
467 		for (link = new_values; link; link = g_slist_next (link)) {
468 			const gchar *value = link->data;
469 
470 			add_func (builder, value);
471 		}
472 
473 		end_func (builder);
474 	}
475 
476 	g_slist_free_full (new_values, g_free);
477 	g_slist_free_full (old_values, g_free);
478 
479 	return TRUE;
480 }
481 
482 static gboolean
ebb_m365_contact_get_categories(EBookBackendM365 * bbm365,EM365Contact * m365_contact,EContact * inout_contact,EContactField field_id,EM365Connection * cnc,GCancellable * cancellable,GError ** error)483 ebb_m365_contact_get_categories (EBookBackendM365 *bbm365,
484 				 EM365Contact *m365_contact,
485 				 EContact *inout_contact,
486 				 EContactField field_id,
487 				 EM365Connection *cnc,
488 				 GCancellable *cancellable,
489 				 GError **error)
490 {
491 	JsonArray *values;
492 
493 	values = e_m365_contact_get_categories (m365_contact);
494 
495 	if (values) {
496 		GString *categories_str = NULL;
497 		guint ii, len;
498 
499 		len = json_array_get_length (values);
500 
501 		for (ii = 0; ii < len; ii++) {
502 			const gchar *str = json_array_get_string_element (values, ii);
503 
504 			if (str && *str) {
505 				if (!categories_str) {
506 					categories_str = g_string_new (str);
507 				} else {
508 					g_string_append_c (categories_str,  ',');
509 					g_string_append (categories_str, str);
510 				}
511 			}
512 		}
513 
514 		if (categories_str) {
515 			e_contact_set (inout_contact, field_id, categories_str->str);
516 			g_string_free (categories_str, TRUE);
517 		}
518 	}
519 
520 	return TRUE;
521 }
522 
523 static GSList *
ebb_m365_extract_categories(EContact * contact,EContactField field_id)524 ebb_m365_extract_categories (EContact *contact,
525 			     EContactField field_id)
526 {
527 	GSList *categories = NULL;
528 	const gchar *str;
529 
530 	if (!contact)
531 		return NULL;
532 
533 	str = e_contact_get_const (contact, field_id);
534 
535 	if (str && *str) {
536 		gchar **split_str;
537 		gint ii;
538 
539 		split_str = g_strsplit (str, ",", -1);
540 
541 		for (ii = 0; split_str && split_str[ii]; ii++) {
542 			gchar *item = split_str[ii];
543 
544 			if (item && *item)
545 				categories = g_slist_prepend (categories, item);
546 			else
547 				g_free (item);
548 
549 			split_str[ii] = NULL;
550 		}
551 
552 		g_free (split_str);
553 	}
554 
555 	return g_slist_reverse (categories);
556 }
557 
558 static gboolean
ebb_m365_contact_add_categories(EBookBackendM365 * bbm365,EContact * new_contact,EContact * old_contact,EContactField field_id,const gchar * m365_id,JsonBuilder * builder,GCancellable * cancellable,GError ** error)559 ebb_m365_contact_add_categories (EBookBackendM365 *bbm365,
560 				 EContact *new_contact,
561 				 EContact *old_contact,
562 				 EContactField field_id,
563 				 const gchar *m365_id,
564 				 JsonBuilder *builder,
565 				 GCancellable *cancellable,
566 				 GError **error)
567 {
568 	GSList *new_values, *old_values;
569 
570 	new_values = ebb_m365_extract_categories (new_contact, field_id);
571 	old_values = ebb_m365_extract_categories (old_contact, field_id);
572 
573 	if (!ebb_m365_string_values_equal (new_values, old_values)) {
574 		GSList *link;
575 
576 		e_m365_contact_begin_categories (builder);
577 
578 		for (link = new_values; link; link = g_slist_next (link)) {
579 			const gchar *value = link->data;
580 
581 			e_m365_contact_add_category (builder, value);
582 		}
583 
584 		e_m365_contact_end_categories (builder);
585 	}
586 
587 	g_slist_free_full (new_values, g_free);
588 	g_slist_free_full (old_values, g_free);
589 
590 	return TRUE;
591 }
592 
593 static gboolean
ebb_m365_contact_get_emails(EBookBackendM365 * bbm365,EM365Contact * m365_contact,EContact * inout_contact,EContactField field_id,EM365Connection * cnc,GCancellable * cancellable,GError ** error)594 ebb_m365_contact_get_emails (EBookBackendM365 *bbm365,
595 			     EM365Contact *m365_contact,
596 			     EContact *inout_contact,
597 			     EContactField field_id,
598 			     EM365Connection *cnc,
599 			     GCancellable *cancellable,
600 			     GError **error)
601 {
602 	JsonArray *values;
603 
604 	values = e_m365_contact_get_email_addresses (m365_contact);
605 
606 	if (values) {
607 		EVCard *vcard = E_VCARD (inout_contact);
608 		guint ii, len;
609 
610 		len = json_array_get_length (values);
611 
612 		for (ii = 0; ii < len; ii++) {
613 			EM365EmailAddress *address = json_array_get_object_element (values, len - ii - 1);
614 
615 			if (address) {
616 				EVCardAttribute *attr;
617 
618 				attr = e_vcard_attribute_new (NULL, EVC_EMAIL);
619 				e_vcard_attribute_add_param_with_value (attr, e_vcard_attribute_param_new (EVC_TYPE), "OTHER");
620 
621 				if (g_strcmp0 (e_m365_email_address_get_name (address), e_m365_email_address_get_address (address)) == 0) {
622 					e_vcard_add_attribute_with_value (vcard, attr, e_m365_email_address_get_address (address));
623 				} else {
624 					gchar *formatted;
625 
626 					formatted = camel_internet_address_format_address (
627 						e_m365_email_address_get_name (address),
628 						e_m365_email_address_get_address (address));
629 
630 					if (formatted && *formatted)
631 						e_vcard_add_attribute_with_value (vcard, attr, formatted);
632 					else
633 						e_vcard_attribute_free (attr);
634 
635 					g_free (formatted);
636 				}
637 			}
638 		}
639 	}
640 
641 	return TRUE;
642 }
643 
644 static gboolean
ebb_m365_parse_qp_email(const gchar * string,gchar ** name,gchar ** email)645 ebb_m365_parse_qp_email (const gchar *string,
646 			 gchar **name,
647 			 gchar **email)
648 {
649 	struct _camel_header_address *address;
650 	gboolean res = FALSE;
651 
652 	address = camel_header_address_decode (string, "UTF-8");
653 
654 	if (address) {
655 		/* report success only when we have filled both name and email address */
656 		if (address->type == CAMEL_HEADER_ADDRESS_NAME && address->name && *address->name && address->v.addr && *address->v.addr) {
657 			*name = g_strdup (address->name);
658 			*email = g_strdup (address->v.addr);
659 			res = TRUE;
660 		}
661 
662 		camel_header_address_unref (address);
663 	}
664 
665 	if (!res) {
666 		CamelInternetAddress *addr = camel_internet_address_new ();
667 		const gchar *const_name = NULL, *const_email = NULL;
668 
669 		if (camel_address_unformat (CAMEL_ADDRESS (addr), string) == 1 &&
670 		    camel_internet_address_get (addr, 0, &const_name, &const_email) &&
671 		    const_name && *const_name && const_email && *const_email) {
672 			*name = g_strdup (const_name);
673 			*email = g_strdup (const_email);
674 			res = TRUE;
675 		}
676 
677 		g_clear_object (&addr);
678 	}
679 
680 	return res;
681 }
682 
683 static gboolean
ebb_m365_contact_add_emails(EBookBackendM365 * bbm365,EContact * new_contact,EContact * old_contact,EContactField field_id,const gchar * m365_id,JsonBuilder * builder,GCancellable * cancellable,GError ** error)684 ebb_m365_contact_add_emails (EBookBackendM365 *bbm365,
685 			     EContact *new_contact,
686 			     EContact *old_contact,
687 			     EContactField field_id,
688 			     const gchar *m365_id,
689 			     JsonBuilder *builder,
690 			     GCancellable *cancellable,
691 			     GError **error)
692 {
693 	GList *new_values, *old_values;
694 
695 	new_values = e_contact_get (new_contact, field_id);
696 	old_values = old_contact ? e_contact_get (old_contact, field_id) : NULL;
697 
698 	if (!ebb_m365_string_list_values_equal (new_values, old_values)) {
699 		GList *link;
700 
701 		e_m365_contact_begin_email_addresses (builder);
702 
703 		for (link = new_values; link; link = g_list_next (link)) {
704 			const gchar *value = link->data;
705 			gchar *name = NULL, *address = NULL;
706 
707 			if (ebb_m365_parse_qp_email (value, &name, &address))
708 				e_m365_add_email_address (builder, NULL, name, address);
709 			else
710 				e_m365_add_email_address (builder, NULL, NULL, value);
711 
712 			g_free (name);
713 			g_free (address);
714 		}
715 
716 		e_m365_contact_end_email_addresses (builder);
717 	}
718 
719 	g_list_free_full (new_values, g_free);
720 	g_list_free_full (old_values, g_free);
721 
722 	return TRUE;
723 }
724 
725 static gboolean
ebb_m365_contact_add_file_as(EBookBackendM365 * bbm365,EContact * new_contact,EContact * old_contact,EContactField field_id,const gchar * m365_id,JsonBuilder * builder,GCancellable * cancellable,GError ** error)726 ebb_m365_contact_add_file_as (EBookBackendM365 *bbm365,
727 			      EContact *new_contact,
728 			      EContact *old_contact,
729 			      EContactField field_id,
730 			      const gchar *m365_id,
731 			      JsonBuilder *builder,
732 			      GCancellable *cancellable,
733 			      GError **error)
734 {
735 	const gchar *new_value;
736 
737 	ebb_m365_contact_add_string_attribute (new_contact, old_contact, field_id, builder, e_m365_contact_add_file_as);
738 
739 	new_value = e_contact_get_const (new_contact, E_CONTACT_FILE_AS);
740 
741 	/* Set it always, to not be overwritten by server re-calculations on other property changes */
742 	e_m365_contact_add_display_name (builder, new_value);
743 
744 	return TRUE;
745 }
746 
747 static gboolean
ebb_m365_contact_get_generation(EBookBackendM365 * bbm365,EM365Contact * m365_contact,EContact * inout_contact,EContactField field_id,EM365Connection * cnc,GCancellable * cancellable,GError ** error)748 ebb_m365_contact_get_generation (EBookBackendM365 *bbm365,
749 				 EM365Contact *m365_contact,
750 				 EContact *inout_contact,
751 				 EContactField field_id,
752 				 EM365Connection *cnc,
753 				 GCancellable *cancellable,
754 				 GError **error)
755 {
756 	const gchar *value;
757 
758 	value = e_m365_contact_get_generation (m365_contact);
759 
760 	if (value && *value) {
761 		EContactName *name = e_contact_get (inout_contact, field_id);
762 		gchar *prev;
763 
764 		if (!name)
765 			name = e_contact_name_new ();
766 
767 		prev = name->suffixes;
768 		name->suffixes = (gchar *) value;
769 
770 		e_contact_set (inout_contact, field_id, name);
771 
772 		name->suffixes = prev;
773 		e_contact_name_free (name);
774 	}
775 
776 	return TRUE;
777 }
778 
779 static gboolean
ebb_m365_contact_add_generation(EBookBackendM365 * bbm365,EContact * new_contact,EContact * old_contact,EContactField field_id,const gchar * m365_id,JsonBuilder * builder,GCancellable * cancellable,GError ** error)780 ebb_m365_contact_add_generation (EBookBackendM365 *bbm365,
781 				 EContact *new_contact,
782 				 EContact *old_contact,
783 				 EContactField field_id,
784 				 const gchar *m365_id,
785 				 JsonBuilder *builder,
786 				 GCancellable *cancellable,
787 				 GError **error)
788 {
789 	EContactName *new_value, *old_value;
790 
791 	new_value = e_contact_get (new_contact, field_id);
792 	old_value = old_contact ? e_contact_get (old_contact, field_id) : NULL;
793 
794 	if (!(new_value && old_value && g_strcmp0 (new_value->suffixes, old_value->suffixes) == 0))
795 		e_m365_contact_add_generation (builder, new_value ? new_value->suffixes : NULL);
796 
797 	e_contact_name_free (new_value);
798 	e_contact_name_free (old_value);
799 
800 	return TRUE;
801 }
802 
803 static gboolean
ebb_m365_contact_get_im_addresses(EBookBackendM365 * bbm365,EM365Contact * m365_contact,EContact * inout_contact,EContactField field_id,EM365Connection * cnc,GCancellable * cancellable,GError ** error)804 ebb_m365_contact_get_im_addresses (EBookBackendM365 *bbm365,
805 				   EM365Contact *m365_contact,
806 				   EContact *inout_contact,
807 				   EContactField field_id,
808 				   EM365Connection *cnc,
809 				   GCancellable *cancellable,
810 				   GError **error)
811 {
812 	JsonArray *values;
813 
814 	values = e_m365_contact_get_im_addresses (m365_contact);
815 
816 	if (values) {
817 		EVCard *vcard = E_VCARD (inout_contact);
818 		const gchar *field_name = e_contact_vcard_attribute (field_id);
819 		guint ii, len;
820 
821 		len = json_array_get_length (values);
822 
823 		for (ii = 0; ii < len; ii++) {
824 			const gchar *str = json_array_get_string_element (values, len - ii - 1);
825 
826 			if (str && *str) {
827 				EVCardAttribute *attr;
828 
829 				attr = e_vcard_attribute_new (NULL, field_name);
830 
831 				e_vcard_add_attribute_with_value (vcard, attr, str);
832 			}
833 		}
834 	}
835 
836 	return TRUE;
837 }
838 
839 static GSList * /* gchar * */
ebb_m365_extract_im_addresses(EContact * contact)840 ebb_m365_extract_im_addresses (EContact *contact)
841 {
842 	GSList *ims = NULL;
843 	GList *attrs, *link;
844 
845 	if (!contact)
846 		return NULL;
847 
848 	attrs = e_vcard_get_attributes (E_VCARD (contact));
849 
850 	for (link = attrs; link; link = g_list_next (link)) {
851 		EVCardAttribute *attr = link->data;
852 		const gchar *name;
853 
854 		if (!attr)
855 			continue;
856 
857 		name = e_vcard_attribute_get_name (attr);
858 
859 		if (!name || (
860 		    g_ascii_strcasecmp (name, EVC_X_GOOGLE_TALK) != 0 &&
861 		    g_ascii_strcasecmp (name, EVC_X_SKYPE) != 0 &&
862 		    g_ascii_strcasecmp (name, EVC_X_GADUGADU) != 0 &&
863 		    g_ascii_strcasecmp (name, EVC_X_AIM) != 0 &&
864 		    g_ascii_strcasecmp (name, EVC_X_GROUPWISE) != 0 &&
865 		    g_ascii_strcasecmp (name, EVC_X_JABBER) != 0 &&
866 		    g_ascii_strcasecmp (name, EVC_X_YAHOO) != 0 &&
867 		    g_ascii_strcasecmp (name, EVC_X_MSN) != 0 &&
868 		    g_ascii_strcasecmp (name, EVC_X_ICQ) != 0))
869 			continue;
870 
871 		ims = g_slist_prepend (ims, e_vcard_attribute_get_value (attr));
872 	}
873 
874 	return g_slist_reverse (ims);
875 }
876 
877 static gboolean
ebb_m365_contact_add_im_addresses(EBookBackendM365 * bbm365,EContact * new_contact,EContact * old_contact,EContactField field_id,const gchar * m365_id,JsonBuilder * builder,GCancellable * cancellable,GError ** error)878 ebb_m365_contact_add_im_addresses (EBookBackendM365 *bbm365,
879 				   EContact *new_contact,
880 				   EContact *old_contact,
881 				   EContactField field_id,
882 				   const gchar *m365_id,
883 				   JsonBuilder *builder,
884 				   GCancellable *cancellable,
885 				   GError **error)
886 {
887 	GSList *new_values, *old_values;
888 
889 	new_values = ebb_m365_extract_im_addresses (new_contact);
890 	old_values = ebb_m365_extract_im_addresses (old_contact);
891 
892 	if (!ebb_m365_string_values_equal (new_values, old_values)) {
893 		GSList *link;
894 
895 		e_m365_contact_begin_im_addresses (builder);
896 
897 		for (link = new_values; link; link = g_slist_next (link)) {
898 			const gchar *value = link->data;
899 
900 			if (value && *value)
901 				e_m365_contact_add_im_address (builder, value);
902 		}
903 
904 		e_m365_contact_end_im_addresses (builder);
905 	}
906 
907 	g_slist_free_full (new_values, g_free);
908 	g_slist_free_full (old_values, g_free);
909 
910 	return TRUE;
911 }
912 
913 static gboolean
ebb_m365_contact_get_middle_name(EBookBackendM365 * bbm365,EM365Contact * m365_contact,EContact * inout_contact,EContactField field_id,EM365Connection * cnc,GCancellable * cancellable,GError ** error)914 ebb_m365_contact_get_middle_name (EBookBackendM365 *bbm365,
915 				  EM365Contact *m365_contact,
916 				  EContact *inout_contact,
917 				  EContactField field_id,
918 				  EM365Connection *cnc,
919 				  GCancellable *cancellable,
920 				  GError **error)
921 {
922 	const gchar *value;
923 
924 	value = e_m365_contact_get_middle_name (m365_contact);
925 
926 	if (value && *value) {
927 		EContactName *name = e_contact_get (inout_contact, field_id);
928 		gchar *prev;
929 
930 		if (!name)
931 			name = e_contact_name_new ();
932 
933 		prev = name->additional;
934 		name->additional = (gchar *) value;
935 
936 		e_contact_set (inout_contact, field_id, name);
937 
938 		name->additional = prev;
939 		e_contact_name_free (name);
940 	}
941 
942 	return TRUE;
943 }
944 
945 static gboolean
ebb_m365_contact_add_middle_name(EBookBackendM365 * bbm365,EContact * new_contact,EContact * old_contact,EContactField field_id,const gchar * m365_id,JsonBuilder * builder,GCancellable * cancellable,GError ** error)946 ebb_m365_contact_add_middle_name (EBookBackendM365 *bbm365,
947 				  EContact *new_contact,
948 				  EContact *old_contact,
949 				  EContactField field_id,
950 				  const gchar *m365_id,
951 				  JsonBuilder *builder,
952 				  GCancellable *cancellable,
953 				  GError **error)
954 {
955 	EContactName *new_value, *old_value;
956 
957 	new_value = e_contact_get (new_contact, field_id);
958 	old_value = old_contact ? e_contact_get (old_contact, field_id) : NULL;
959 
960 	if (!(new_value && old_value && g_strcmp0 (new_value->additional, old_value->additional) == 0))
961 		e_m365_contact_add_middle_name (builder, new_value ? new_value->additional : NULL);
962 
963 	e_contact_name_free (new_value);
964 	e_contact_name_free (old_value);
965 
966 	return TRUE;
967 }
968 
969 static gboolean
ebb_m365_contact_get_title(EBookBackendM365 * bbm365,EM365Contact * m365_contact,EContact * inout_contact,EContactField field_id,EM365Connection * cnc,GCancellable * cancellable,GError ** error)970 ebb_m365_contact_get_title (EBookBackendM365 *bbm365,
971 			    EM365Contact *m365_contact,
972 			    EContact *inout_contact,
973 			    EContactField field_id,
974 			    EM365Connection *cnc,
975 			    GCancellable *cancellable,
976 			    GError **error)
977 {
978 	const gchar *value;
979 
980 	value = e_m365_contact_get_title (m365_contact);
981 
982 	if (value && *value) {
983 		EContactName *name = e_contact_get (inout_contact, field_id);
984 		gchar *prev;
985 
986 		if (!name)
987 			name = e_contact_name_new ();
988 
989 		prev = name->prefixes;
990 		name->prefixes = (gchar *) value;
991 
992 		e_contact_set (inout_contact, field_id, name);
993 
994 		name->prefixes = prev;
995 		e_contact_name_free (name);
996 	}
997 
998 	return TRUE;
999 }
1000 
1001 static gboolean
ebb_m365_contact_add_title(EBookBackendM365 * bbm365,EContact * new_contact,EContact * old_contact,EContactField field_id,const gchar * m365_id,JsonBuilder * builder,GCancellable * cancellable,GError ** error)1002 ebb_m365_contact_add_title (EBookBackendM365 *bbm365,
1003 			    EContact *new_contact,
1004 			    EContact *old_contact,
1005 			    EContactField field_id,
1006 			    const gchar *m365_id,
1007 			    JsonBuilder *builder,
1008 			    GCancellable *cancellable,
1009 			    GError **error)
1010 {
1011 	EContactName *new_value, *old_value;
1012 
1013 	new_value = e_contact_get (new_contact, field_id);
1014 	old_value = old_contact ? e_contact_get (old_contact, field_id) : NULL;
1015 
1016 	if (!(new_value && old_value && g_strcmp0 (new_value->prefixes, old_value->prefixes) == 0))
1017 		e_m365_contact_add_title (builder, new_value ? new_value->prefixes : NULL);
1018 
1019 	e_contact_name_free (new_value);
1020 	e_contact_name_free (old_value);
1021 
1022 	return TRUE;
1023 }
1024 
1025 static gboolean
ebb_m365_contact_get_photo(EBookBackendM365 * bbm365,EM365Contact * m365_contact,EContact * inout_contact,EContactField field_id,EM365Connection * cnc,GCancellable * cancellable,GError ** error)1026 ebb_m365_contact_get_photo (EBookBackendM365 *bbm365,
1027 			    EM365Contact *m365_contact,
1028 			    EContact *inout_contact,
1029 			    EContactField field_id,
1030 			    EM365Connection *cnc,
1031 			    GCancellable *cancellable,
1032 			    GError **error)
1033 {
1034 	GByteArray *photo_data = NULL;
1035 	GError *local_error = NULL;
1036 
1037 	LOCK (bbm365);
1038 
1039 	if (e_m365_connection_get_contact_photo_sync (cnc, NULL, bbm365->priv->folder_id,
1040 		e_m365_contact_get_id (m365_contact), &photo_data, cancellable, &local_error) &&
1041 	    photo_data && photo_data->len) {
1042 		EContactPhoto *photo;
1043 
1044 		photo = e_contact_photo_new ();
1045 		e_contact_photo_set_inlined (photo, photo_data->data, photo_data->len);
1046 		e_contact_photo_set_mime_type (photo, "image/jpeg");
1047 		e_contact_set (inout_contact, field_id, photo);
1048 		e_contact_photo_free (photo);
1049 	}
1050 
1051 	UNLOCK (bbm365);
1052 
1053 	if (photo_data)
1054 		g_byte_array_unref (photo_data);
1055 	g_clear_error (&local_error);
1056 
1057 	/* Even it could fail, ignore it and read as many contacts as possible, rather than stop on the first error */
1058 	return TRUE;
1059 }
1060 
1061 static gboolean
ebb_m365_contact_photo_equal(EContactPhoto * photo1,EContactPhoto * photo2)1062 ebb_m365_contact_photo_equal (EContactPhoto *photo1,
1063 			      EContactPhoto *photo2)
1064 {
1065 	const guchar *data1, *data2;
1066 	gsize len1 = 0, len2 = 0;
1067 
1068 	if (!photo1 && !photo2)
1069 		return TRUE;
1070 
1071 	if ((photo1 && !photo2) || (!photo1 && photo2))
1072 		return FALSE;
1073 
1074 	data1 = e_contact_photo_get_inlined (photo1, &len1);
1075 	data2 = e_contact_photo_get_inlined (photo2, &len2);
1076 
1077 	if (!data1 && !data2)
1078 		return TRUE;
1079 
1080 	return len1 == len2 &&
1081 		memcmp (data1, data2, len1) == 0;
1082 }
1083 
1084 static gboolean
ebb_m365_contact_add_photo(EBookBackendM365 * bbm365,EContact * new_contact,EContact * old_contact,EContactField field_id,const gchar * m365_id,JsonBuilder * builder,GCancellable * cancellable,GError ** error)1085 ebb_m365_contact_add_photo (EBookBackendM365 *bbm365,
1086 			    EContact *new_contact,
1087 			    EContact *old_contact,
1088 			    EContactField field_id,
1089 			    const gchar *m365_id,
1090 			    JsonBuilder *builder,
1091 			    GCancellable *cancellable,
1092 			    GError **error)
1093 {
1094 	EContactPhoto *new_value, *old_value;
1095 
1096 	new_value = e_contact_get (new_contact, field_id);
1097 	old_value = old_contact ? e_contact_get (old_contact, field_id) : NULL;
1098 
1099 	if (!ebb_m365_contact_photo_equal (new_value, old_value)) {
1100 		GByteArray *jpeg_photo = NULL, tmp;
1101 		GError *local_error = NULL;
1102 
1103 		if (new_value) {
1104 			gsize len = 0;
1105 
1106 			tmp.data = (guchar *) e_contact_photo_get_inlined (new_value, &len);
1107 
1108 			if (len && tmp.data) {
1109 				tmp.len = len;
1110 				jpeg_photo = &tmp;
1111 			}
1112 		}
1113 
1114 		LOCK (bbm365);
1115 
1116 		if (!e_m365_connection_update_contact_photo_sync (bbm365->priv->cnc, NULL, bbm365->priv->folder_id,
1117 			m365_id ? m365_id : e_contact_get_const (new_contact, E_CONTACT_UID), jpeg_photo, cancellable, &local_error)) {
1118 			if (local_error) {
1119 				g_propagate_error (error, local_error);
1120 				local_error = NULL;
1121 			}
1122 		}
1123 
1124 		UNLOCK (bbm365);
1125 
1126 		g_clear_error (&local_error);
1127 	}
1128 
1129 	e_contact_photo_free (new_value);
1130 	e_contact_photo_free (old_value);
1131 
1132 	return TRUE;
1133 }
1134 
1135 #define STRING_FIELD(fldid, getfn, addfn) { fldid, FALSE, getfn, NULL, addfn, NULL }
1136 #define COMPLEX_FIELD(fldid, getfn, addfn) { fldid, FALSE, NULL, getfn, NULL, addfn }
1137 #define COMPLEX_FIELD_2(fldid, getfn, addfn) { fldid, TRUE, NULL, getfn, NULL, addfn }
1138 #define COMPLEX_ADDFN(fldid, getfn, addfn) { fldid, FALSE, getfn, NULL, NULL, addfn }
1139 
1140 struct _mappings {
1141 	EContactField field_id;
1142 	gboolean add_in_second_go;
1143 	const gchar *	(* m365_get_func)	(EM365Contact *m365_contact);
1144 	gboolean	(* get_func)		(EBookBackendM365 *bbm365,
1145 						 EM365Contact *m365_contact,
1146 						 EContact *inout_contact,
1147 						 EContactField field_id,
1148 						 EM365Connection *cnc,
1149 						 GCancellable *cancellable,
1150 						 GError **error);
1151 	void		(* m365_add_func)	(JsonBuilder *builder,
1152 						 const gchar *value);
1153 	gboolean	(* add_func)		(EBookBackendM365 *bbm365,
1154 						 EContact *new_contact,
1155 						 EContact *old_contact, /* nullable */
1156 						 EContactField field_id,
1157 						 const gchar *m365_id,
1158 						 JsonBuilder *builder,
1159 						 GCancellable *cancellable,
1160 						 GError **error);
1161 } mappings[] = {
1162 	STRING_FIELD	(E_CONTACT_UID,			e_m365_contact_get_id,			NULL),
1163 	COMPLEX_FIELD	(E_CONTACT_REV,			ebb_m365_contact_get_rev,		NULL),
1164 	STRING_FIELD	(E_CONTACT_ASSISTANT,		e_m365_contact_get_assistant_name,	e_m365_contact_add_assistant_name),
1165 	COMPLEX_FIELD	(E_CONTACT_BIRTH_DATE,		ebb_m365_contact_get_birthday,		ebb_m365_contact_add_birthday),
1166 	COMPLEX_FIELD	(E_CONTACT_ADDRESS_WORK,	ebb_m365_contact_get_address,		ebb_m365_contact_add_address),
1167 	STRING_FIELD	(E_CONTACT_HOMEPAGE_URL,	e_m365_contact_get_business_home_page,	e_m365_contact_add_business_home_page),
1168 	COMPLEX_FIELD	(E_CONTACT_PHONE_BUSINESS,	ebb_m365_contact_get_phone,		ebb_m365_contact_add_phone),
1169 	COMPLEX_FIELD	(E_CONTACT_CATEGORIES,		ebb_m365_contact_get_categories,	ebb_m365_contact_add_categories),
1170 	STRING_FIELD	(E_CONTACT_ORG,			e_m365_contact_get_company_name,	e_m365_contact_add_company_name),
1171 	STRING_FIELD	(E_CONTACT_ORG_UNIT,		e_m365_contact_get_department,		e_m365_contact_add_department),
1172 	COMPLEX_FIELD	(E_CONTACT_EMAIL,		ebb_m365_contact_get_emails,		ebb_m365_contact_add_emails),
1173 	COMPLEX_FIELD	(E_CONTACT_NAME,		ebb_m365_contact_get_generation,	ebb_m365_contact_add_generation),
1174 	STRING_FIELD	(E_CONTACT_GIVEN_NAME,		e_m365_contact_get_given_name,		e_m365_contact_add_given_name),
1175 	COMPLEX_FIELD	(E_CONTACT_ADDRESS_HOME,	ebb_m365_contact_get_address,		ebb_m365_contact_add_address),
1176 	COMPLEX_FIELD	(E_CONTACT_PHONE_HOME,		ebb_m365_contact_get_phone,		ebb_m365_contact_add_phone),
1177 	COMPLEX_FIELD	(E_CONTACT_IM_MSN,		ebb_m365_contact_get_im_addresses,	ebb_m365_contact_add_im_addresses),
1178 	/* STRING_FIELD	(???,				e_m365_contact_get_initials,		e_m365_contact_add_initials), */
1179 	STRING_FIELD	(E_CONTACT_TITLE,		e_m365_contact_get_job_title,		e_m365_contact_add_job_title),
1180 	STRING_FIELD	(E_CONTACT_MANAGER,		e_m365_contact_get_manager,		e_m365_contact_add_manager),
1181 	COMPLEX_FIELD	(E_CONTACT_NAME,		ebb_m365_contact_get_middle_name,	ebb_m365_contact_add_middle_name),
1182 	STRING_FIELD	(E_CONTACT_PHONE_MOBILE,	e_m365_contact_get_mobile_phone,	e_m365_contact_add_mobile_phone),
1183 	STRING_FIELD	(E_CONTACT_NICKNAME,		e_m365_contact_get_nick_name,		e_m365_contact_add_nick_name),
1184 	STRING_FIELD	(E_CONTACT_OFFICE,		e_m365_contact_get_office_location,	e_m365_contact_add_office_location),
1185 	COMPLEX_FIELD	(E_CONTACT_ADDRESS_OTHER,	ebb_m365_contact_get_address,		ebb_m365_contact_add_address),
1186 	STRING_FIELD	(E_CONTACT_NOTE,		e_m365_contact_get_personal_notes,	e_m365_contact_add_personal_notes),
1187 	STRING_FIELD 	(E_CONTACT_ROLE,		e_m365_contact_get_profession,		e_m365_contact_add_profession),
1188 	STRING_FIELD 	(E_CONTACT_SPOUSE,		e_m365_contact_get_spouse_name,		e_m365_contact_add_spouse_name),
1189 	STRING_FIELD	(E_CONTACT_FAMILY_NAME,		e_m365_contact_get_surname,		e_m365_contact_add_surname),
1190 	COMPLEX_FIELD	(E_CONTACT_NAME,		ebb_m365_contact_get_title,		ebb_m365_contact_add_title),
1191 	/* STRING_FIELD	(???,				e_m365_contact_get_yomi_company_name,	e_m365_contact_add_yomi_company_name), */
1192 	/* STRING_FIELD	(???,				e_m365_contact_get_yomi_given_name,	e_m365_contact_add_yomi_given_name), */
1193 	/* STRING_FIELD	(???,				e_m365_contact_get_yomi_surname,	e_m365_contact_add_yomi_surname), */
1194 	COMPLEX_ADDFN	(E_CONTACT_FILE_AS,		e_m365_contact_get_file_as,		ebb_m365_contact_add_file_as),
1195 	COMPLEX_FIELD_2	(E_CONTACT_PHOTO,		ebb_m365_contact_get_photo,		ebb_m365_contact_add_photo)
1196 };
1197 
1198 static EContact *
ebb_m365_json_contact_to_vcard(EBookBackendM365 * bbm365,EM365Contact * m365_contact,EM365Connection * cnc,gchar ** out_object,GCancellable * cancellable,GError ** error)1199 ebb_m365_json_contact_to_vcard (EBookBackendM365 *bbm365,
1200 				EM365Contact *m365_contact,
1201 				EM365Connection *cnc,
1202 				gchar **out_object,
1203 				GCancellable *cancellable,
1204 				GError **error)
1205 {
1206 	EContact *contact;
1207 	gint ii;
1208 	gboolean success = TRUE;
1209 
1210 	g_return_val_if_fail (m365_contact != NULL, NULL);
1211 	g_return_val_if_fail (out_object != NULL, NULL);
1212 
1213 	*out_object = NULL;
1214 
1215 	contact = e_contact_new ();
1216 
1217 	for (ii = 0; success && ii < G_N_ELEMENTS (mappings); ii++) {
1218 		if (mappings[ii].m365_get_func) {
1219 			ebb_m365_contact_get_string_attribute (m365_contact, contact, mappings[ii].field_id, mappings[ii].m365_get_func);
1220 		} else if (mappings[ii].get_func) {
1221 			success = mappings[ii].get_func (bbm365, m365_contact, contact, mappings[ii].field_id, cnc, cancellable, error);
1222 		}
1223 	}
1224 
1225 	if (success)
1226 		*out_object = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
1227 	else
1228 		g_clear_object (&contact);
1229 
1230 	return contact;
1231 }
1232 
1233 static JsonBuilder *
ebb_m365_contact_to_json_locked(EBookBackendM365 * bbm365,EContact * new_contact,EContact * old_contact,GCancellable * cancellable,GError ** error)1234 ebb_m365_contact_to_json_locked (EBookBackendM365 *bbm365,
1235 				 EContact *new_contact,
1236 				 EContact *old_contact, /* nullable */
1237 				 GCancellable *cancellable,
1238 				 GError **error)
1239 {
1240 	JsonBuilder *builder;
1241 	gint ii;
1242 	gboolean success = TRUE;
1243 
1244 	g_return_val_if_fail (new_contact != NULL, NULL);
1245 
1246 	builder = json_builder_new_immutable ();
1247 	e_m365_json_begin_object_member (builder, NULL);
1248 
1249 	for (ii = 0; success && ii < G_N_ELEMENTS (mappings); ii++) {
1250 		if (mappings[ii].m365_add_func) {
1251 			ebb_m365_contact_add_string_attribute (new_contact, old_contact, mappings[ii].field_id, builder, mappings[ii].m365_add_func);
1252 		} else if (!mappings[ii].add_in_second_go && mappings[ii].add_func) {
1253 			success = mappings[ii].add_func (bbm365, new_contact, old_contact, mappings[ii].field_id, NULL, builder, cancellable, error);
1254 		}
1255 	}
1256 
1257 	e_m365_json_end_object_member (builder);
1258 
1259 	if (!success)
1260 		g_clear_object (&builder);
1261 
1262 	return builder;
1263 }
1264 
1265 static gboolean
ebb_m365_contact_to_json_2nd_go_locked(EBookBackendM365 * bbm365,EContact * new_contact,EContact * old_contact,const gchar * m365_id,GCancellable * cancellable,GError ** error)1266 ebb_m365_contact_to_json_2nd_go_locked (EBookBackendM365 *bbm365,
1267 					EContact *new_contact,
1268 					EContact *old_contact, /* nullable */
1269 					const gchar *m365_id,
1270 					GCancellable *cancellable,
1271 					GError **error)
1272 {
1273 	gint ii;
1274 	gboolean success = TRUE;
1275 
1276 	g_return_val_if_fail (new_contact != NULL, FALSE);
1277 
1278 	for (ii = 0; success && ii < G_N_ELEMENTS (mappings); ii++) {
1279 		if (mappings[ii].add_in_second_go && mappings[ii].add_func) {
1280 			success = mappings[ii].add_func (bbm365, new_contact, old_contact, mappings[ii].field_id, m365_id, NULL, cancellable, error);
1281 		}
1282 	}
1283 
1284 	return success;
1285 }
1286 
1287 static void
ebb_m365_convert_error_to_client_error(GError ** perror)1288 ebb_m365_convert_error_to_client_error (GError **perror)
1289 {
1290 	GError *error = NULL;
1291 
1292 	if (!perror || !*perror || (*perror)->domain == E_CLIENT_ERROR || (*perror)->domain == E_BOOK_CLIENT_ERROR)
1293 		return;
1294 
1295 	/*if ((*perror)->domain == EWS_CONNECTION_ERROR) {
1296 		switch ((*perror)->code) {
1297 		case EWS_CONNECTION_ERROR_AUTHENTICATION_FAILED:
1298 			error = EC_ERROR_EX (E_CLIENT_ERROR_AUTHENTICATION_FAILED, (*perror)->message);
1299 			break;
1300 		case EWS_CONNECTION_ERROR_FOLDERNOTFOUND:
1301 		case EWS_CONNECTION_ERROR_MANAGEDFOLDERNOTFOUND:
1302 		case EWS_CONNECTION_ERROR_PARENTFOLDERNOTFOUND:
1303 		case EWS_CONNECTION_ERROR_PUBLICFOLDERSERVERNOTFOUND:
1304 			error = EBC_ERROR_EX (E_BOOK_CLIENT_ERROR_NO_SUCH_BOOK, (*perror)->message);
1305 			break;
1306 		case EWS_CONNECTION_ERROR_EVENTNOTFOUND:
1307 		case EWS_CONNECTION_ERROR_ITEMNOTFOUND:
1308 			error = EBC_ERROR_EX (E_BOOK_CLIENT_ERROR_CONTACT_NOT_FOUND, (*perror)->message);
1309 			break;
1310 		case EWS_CONNECTION_ERROR_UNAVAILABLE:
1311 			g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_HOST_NOT_FOUND, (*perror)->message);
1312 			break;
1313 		}
1314 
1315 		if (!error)
1316 			error = EC_ERROR_EX (E_CLIENT_ERROR_OTHER_ERROR, (*perror)->message);
1317 	}*/
1318 
1319 	if (error) {
1320 		g_error_free (*perror);
1321 		*perror = error;
1322 	}
1323 }
1324 
1325 static void
ebb_m365_maybe_disconnect_sync(EBookBackendM365 * bbm365,GError ** in_perror,GCancellable * cancellable)1326 ebb_m365_maybe_disconnect_sync (EBookBackendM365 *bbm365,
1327 			        GError **in_perror,
1328 			        GCancellable *cancellable)
1329 {
1330 	g_return_if_fail (E_IS_BOOK_BACKEND_M365 (bbm365));
1331 
1332 	if (in_perror && g_error_matches (*in_perror, E_CLIENT_ERROR, E_CLIENT_ERROR_AUTHENTICATION_FAILED)) {
1333 		e_book_meta_backend_disconnect_sync (E_BOOK_META_BACKEND (bbm365), cancellable, NULL);
1334 		e_backend_schedule_credentials_required (E_BACKEND (bbm365), E_SOURCE_CREDENTIALS_REASON_REJECTED, NULL, 0, NULL, NULL, G_STRFUNC);
1335 	}
1336 }
1337 
1338 static gboolean
ebb_m365_unset_connection_sync(EBookBackendM365 * bbm365,gboolean is_disconnect,GCancellable * cancellable,GError ** error)1339 ebb_m365_unset_connection_sync (EBookBackendM365 *bbm365,
1340 				gboolean is_disconnect,
1341 				GCancellable *cancellable,
1342 				GError **error)
1343 {
1344 	gboolean success = TRUE;
1345 
1346 	g_return_val_if_fail (E_IS_BOOK_BACKEND_M365 (bbm365), FALSE);
1347 
1348 	LOCK (bbm365);
1349 
1350 	if (bbm365->priv->cnc) {
1351 		if (is_disconnect)
1352 			success = e_m365_connection_disconnect_sync (bbm365->priv->cnc, cancellable, error);
1353 	}
1354 
1355 	g_clear_object (&bbm365->priv->cnc);
1356 	g_clear_pointer (&bbm365->priv->folder_id, g_free);
1357 
1358 	UNLOCK (bbm365);
1359 
1360 	return success;
1361 }
1362 
1363 static gboolean
ebb_m365_connect_sync(EBookMetaBackend * meta_backend,const ENamedParameters * credentials,ESourceAuthenticationResult * out_auth_result,gchar ** out_certificate_pem,GTlsCertificateFlags * out_certificate_errors,GCancellable * cancellable,GError ** error)1364 ebb_m365_connect_sync (EBookMetaBackend *meta_backend,
1365 		       const ENamedParameters *credentials,
1366 		       ESourceAuthenticationResult *out_auth_result,
1367 		       gchar **out_certificate_pem,
1368 		       GTlsCertificateFlags *out_certificate_errors,
1369 		       GCancellable *cancellable,
1370 		       GError **error)
1371 {
1372 	EBookBackendM365 *bbm365;
1373 	gboolean success = FALSE;
1374 
1375 	g_return_val_if_fail (E_IS_BOOK_BACKEND_M365 (meta_backend), FALSE);
1376 	g_return_val_if_fail (out_auth_result != NULL, FALSE);
1377 
1378 	bbm365 = E_BOOK_BACKEND_M365 (meta_backend);
1379 
1380 	LOCK (bbm365);
1381 
1382 	if (bbm365->priv->cnc) {
1383 		UNLOCK (bbm365);
1384 
1385 		*out_auth_result = E_SOURCE_AUTHENTICATION_ACCEPTED;
1386 
1387 		return TRUE;
1388 	} else {
1389 		EBackend *backend;
1390 		ESourceRegistry *registry;
1391 		ESource *source;
1392 		EM365Connection *cnc = NULL;
1393 		ESourceM365Folder *m365_folder_extension;
1394 		CamelM365Settings *m365_settings;
1395 		gchar *folder_id;
1396 
1397 		backend = E_BACKEND (bbm365);
1398 		source = e_backend_get_source (backend);
1399 		registry = e_book_backend_get_registry (E_BOOK_BACKEND (bbm365));
1400 		m365_settings = camel_m365_settings_get_from_backend (backend, registry);
1401 		g_warn_if_fail (m365_settings != NULL);
1402 
1403 		m365_folder_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_M365_FOLDER);
1404 		folder_id = e_source_m365_folder_dup_id (m365_folder_extension);
1405 
1406 		if (folder_id) {
1407 			cnc = e_m365_connection_new_for_backend (backend, registry, source, m365_settings);
1408 
1409 			*out_auth_result = e_m365_connection_authenticate_sync (cnc, NULL, E_M365_FOLDER_KIND_CONTACTS, NULL, folder_id,
1410 				out_certificate_pem, out_certificate_errors, cancellable, error);
1411 
1412 			if (*out_auth_result == E_SOURCE_AUTHENTICATION_ACCEPTED) {
1413 				bbm365->priv->cnc = g_object_ref (cnc);
1414 
1415 				g_warn_if_fail (bbm365->priv->folder_id == NULL);
1416 
1417 				g_free (bbm365->priv->folder_id);
1418 				bbm365->priv->folder_id = folder_id;
1419 
1420 				folder_id = NULL;
1421 				success = TRUE;
1422 
1423 				e_book_backend_set_writable (E_BOOK_BACKEND (bbm365), TRUE);
1424 			}
1425 		} else {
1426 			*out_auth_result = E_SOURCE_AUTHENTICATION_ERROR;
1427 			g_propagate_error (error, EC_ERROR_EX (E_CLIENT_ERROR_OTHER_ERROR, _("Folder ID is not set")));
1428 		}
1429 
1430 		g_clear_object (&cnc);
1431 		g_free (folder_id);
1432 	}
1433 
1434 	UNLOCK (bbm365);
1435 
1436 	ebb_m365_convert_error_to_client_error (error);
1437 
1438 	return success;
1439 }
1440 
1441 static gboolean
ebb_m365_disconnect_sync(EBookMetaBackend * meta_backend,GCancellable * cancellable,GError ** error)1442 ebb_m365_disconnect_sync (EBookMetaBackend *meta_backend,
1443 			  GCancellable *cancellable,
1444 			  GError **error)
1445 {
1446 	g_return_val_if_fail (E_IS_BOOK_BACKEND_M365 (meta_backend), FALSE);
1447 
1448 	return ebb_m365_unset_connection_sync (E_BOOK_BACKEND_M365 (meta_backend), TRUE, cancellable, error);
1449 }
1450 
1451 typedef struct _ObjectsDeltaData {
1452 	EBookBackendM365 *bbm365;
1453 	ECache *cache;
1454 	GSList **out_created_objects;
1455 	GSList **out_modified_objects;
1456 	GSList **out_removed_objects;
1457 } ObjectsDeltaData;
1458 
1459 static gboolean
ebb_m365_get_objects_delta_cb(EM365Connection * cnc,const GSList * results,gpointer user_data,GCancellable * cancellable,GError ** error)1460 ebb_m365_get_objects_delta_cb (EM365Connection *cnc,
1461 			       const GSList *results, /* JsonObject * - the returned objects from the server */
1462 			       gpointer user_data,
1463 			       GCancellable *cancellable,
1464 			       GError **error)
1465 {
1466 	ObjectsDeltaData *odd = user_data;
1467 	GSList *link;
1468 
1469 	g_return_val_if_fail (odd != NULL, FALSE);
1470 
1471 	for (link = (GSList *) results; link && !g_cancellable_is_cancelled (cancellable); link = g_slist_next (link)) {
1472 		EM365Contact *contact = link->data;
1473 		const gchar *id;
1474 
1475 		if (!contact)
1476 			continue;
1477 
1478 		id = e_m365_contact_get_id (contact);
1479 
1480 		if (!id)
1481 			continue;
1482 
1483 		if (e_m365_delta_is_removed_object (contact)) {
1484 			*(odd->out_removed_objects) = g_slist_prepend (*(odd->out_removed_objects),
1485 				e_book_meta_backend_info_new (id, NULL, NULL, NULL));
1486 		} else {
1487 			GSList **out_slist;
1488 			EContact *vcard;
1489 			gchar *object;
1490 
1491 			if (e_cache_contains (odd->cache, id, E_CACHE_INCLUDE_DELETED))
1492 				out_slist = odd->out_modified_objects;
1493 			else
1494 				out_slist = odd->out_created_objects;
1495 
1496 			vcard = ebb_m365_json_contact_to_vcard (odd->bbm365, contact, cnc, &object, cancellable, error);
1497 
1498 			g_clear_object (&vcard);
1499 
1500 			if (!g_cancellable_is_cancelled (cancellable))
1501 				g_warn_if_fail (object != NULL);
1502 
1503 			if (object) {
1504 				EBookMetaBackendInfo *nfo;
1505 
1506 				nfo = e_book_meta_backend_info_new (id,
1507 					e_m365_contact_get_change_key (contact),
1508 					object, NULL);
1509 
1510 				nfo->extra = object; /* assumes ownership, to avoid unnecessary re-allocation */
1511 
1512 				*out_slist = g_slist_prepend (*out_slist, nfo);
1513 			}
1514 		}
1515 	}
1516 
1517 	return TRUE;
1518 }
1519 
1520 static gboolean
ebb_m365_get_changes_sync(EBookMetaBackend * meta_backend,const gchar * last_sync_tag,gboolean is_repeat,gchar ** out_new_sync_tag,gboolean * out_repeat,GSList ** out_created_objects,GSList ** out_modified_objects,GSList ** out_removed_objects,GCancellable * cancellable,GError ** error)1521 ebb_m365_get_changes_sync (EBookMetaBackend *meta_backend,
1522 			   const gchar *last_sync_tag,
1523 			   gboolean is_repeat,
1524 			   gchar **out_new_sync_tag,
1525 			   gboolean *out_repeat,
1526 			   GSList **out_created_objects,
1527 			   GSList **out_modified_objects,
1528 			   GSList **out_removed_objects,
1529 			   GCancellable *cancellable,
1530 			   GError **error)
1531 {
1532 	EBookBackendM365 *bbm365;
1533 	EBookCache *book_cache;
1534 	ObjectsDeltaData odd;
1535 	gboolean success;
1536 	GError *local_error = NULL;
1537 
1538 	g_return_val_if_fail (E_IS_BOOK_BACKEND_M365 (meta_backend), FALSE);
1539 	g_return_val_if_fail (out_new_sync_tag != NULL, FALSE);
1540 	g_return_val_if_fail (out_repeat != NULL, FALSE);
1541 	g_return_val_if_fail (out_created_objects != NULL, FALSE);
1542 	g_return_val_if_fail (out_modified_objects != NULL, FALSE);
1543 	g_return_val_if_fail (out_removed_objects != NULL, FALSE);
1544 
1545 	*out_created_objects = NULL;
1546 	*out_modified_objects = NULL;
1547 	*out_removed_objects = NULL;
1548 
1549 	bbm365 = E_BOOK_BACKEND_M365 (meta_backend);
1550 
1551 	book_cache = e_book_meta_backend_ref_cache (meta_backend);
1552 	g_return_val_if_fail (E_IS_BOOK_CACHE (book_cache), FALSE);
1553 
1554 	odd.bbm365 = bbm365;
1555 	odd.cache = E_CACHE (book_cache);
1556 	odd.out_created_objects = out_created_objects;
1557 	odd.out_modified_objects = out_modified_objects;
1558 	odd.out_removed_objects = out_removed_objects;
1559 
1560 	LOCK (bbm365);
1561 
1562 	success = e_m365_connection_get_objects_delta_sync (bbm365->priv->cnc, NULL,
1563 		E_M365_FOLDER_KIND_CONTACTS, bbm365->priv->folder_id, NULL, last_sync_tag, 0,
1564 		ebb_m365_get_objects_delta_cb, &odd,
1565 		out_new_sync_tag, cancellable, &local_error);
1566 
1567 	if (e_m365_connection_util_delta_token_failed (local_error)) {
1568 		GSList *known_uids = NULL, *link;
1569 
1570 		g_clear_error (&local_error);
1571 
1572 		if (e_book_cache_search_uids (book_cache, NULL, &known_uids, cancellable, error)) {
1573 			for (link = known_uids; link; link = g_slist_next (link)) {
1574 				const gchar *uid = link->data;
1575 
1576 				if (uid) {
1577 					*out_removed_objects = g_slist_prepend (*out_removed_objects,
1578 						e_book_meta_backend_info_new (uid, NULL, NULL, NULL));
1579 				}
1580 			}
1581 		}
1582 
1583 		e_cache_remove_all (E_CACHE (book_cache), cancellable, NULL);
1584 
1585 		g_slist_free_full (known_uids, g_free);
1586 
1587 		success = e_m365_connection_get_objects_delta_sync (bbm365->priv->cnc, NULL,
1588 			E_M365_FOLDER_KIND_CONTACTS, bbm365->priv->folder_id, NULL, NULL, 0,
1589 			ebb_m365_get_objects_delta_cb, &odd,
1590 			out_new_sync_tag, cancellable, &local_error);
1591 	} else if (local_error) {
1592 		g_propagate_error (error, local_error);
1593 	}
1594 
1595 	UNLOCK (bbm365);
1596 
1597 	ebb_m365_convert_error_to_client_error (error);
1598 	ebb_m365_maybe_disconnect_sync (bbm365, error, cancellable);
1599 
1600 	g_clear_object (&book_cache);
1601 
1602 	return success;
1603 }
1604 
1605 static gboolean
ebb_m365_load_contact_sync(EBookMetaBackend * meta_backend,const gchar * uid,const gchar * extra,EContact ** out_contact,gchar ** out_extra,GCancellable * cancellable,GError ** error)1606 ebb_m365_load_contact_sync (EBookMetaBackend *meta_backend,
1607 			    const gchar *uid,
1608 			    const gchar *extra,
1609 			    EContact **out_contact,
1610 			    gchar **out_extra,
1611 			    GCancellable *cancellable,
1612 			    GError **error)
1613 {
1614 	EBookBackendM365 *bbm365;
1615 	EM365Contact *contact = NULL;
1616 	gboolean success;
1617 
1618 	g_return_val_if_fail (E_IS_BOOK_BACKEND_M365 (meta_backend), FALSE);
1619 	g_return_val_if_fail (uid != NULL, FALSE);
1620 	g_return_val_if_fail (out_contact, FALSE);
1621 	g_return_val_if_fail (out_extra != NULL, FALSE);
1622 
1623 	bbm365 = E_BOOK_BACKEND_M365 (meta_backend);
1624 
1625 	LOCK (bbm365);
1626 
1627 	success = e_m365_connection_get_contact_sync (bbm365->priv->cnc, NULL,
1628 		bbm365->priv->folder_id, uid, &contact, cancellable, error);
1629 
1630 	if (success) {
1631 		*out_contact = ebb_m365_json_contact_to_vcard (bbm365, contact, bbm365->priv->cnc, out_extra, cancellable, error);
1632 
1633 		if (contact)
1634 			json_object_unref (contact);
1635 	}
1636 
1637 	UNLOCK (bbm365);
1638 
1639 	ebb_m365_convert_error_to_client_error (error);
1640 	ebb_m365_maybe_disconnect_sync (bbm365, error, cancellable);
1641 
1642 	return success;
1643 }
1644 
1645 static gboolean
ebb_m365_save_contact_sync(EBookMetaBackend * meta_backend,gboolean overwrite_existing,EConflictResolution conflict_resolution,EContact * contact,const gchar * extra,guint32 opflags,gchar ** out_new_uid,gchar ** out_new_extra,GCancellable * cancellable,GError ** error)1646 ebb_m365_save_contact_sync (EBookMetaBackend *meta_backend,
1647 			    gboolean overwrite_existing,
1648 			    EConflictResolution conflict_resolution,
1649 			    /* const */ EContact *contact,
1650 			    const gchar *extra,
1651 			    guint32 opflags,
1652 			    gchar **out_new_uid,
1653 			    gchar **out_new_extra,
1654 			    GCancellable *cancellable,
1655 			    GError **error)
1656 {
1657 	EBookBackendM365 *bbm365;
1658 	EContact *tmp_contact = NULL, *old_contact = NULL;
1659 	JsonBuilder *builder;
1660 	gboolean success = FALSE;
1661 
1662 	g_return_val_if_fail (E_IS_BOOK_BACKEND_M365 (meta_backend), FALSE);
1663 	g_return_val_if_fail (E_IS_CONTACT (contact), FALSE);
1664 	g_return_val_if_fail (out_new_uid != NULL, FALSE);
1665 	g_return_val_if_fail (out_new_extra != NULL, FALSE);
1666 
1667 	if (GPOINTER_TO_INT (e_contact_get (contact, E_CONTACT_IS_LIST))) {
1668 		g_propagate_error (error, EC_ERROR_EX (E_CLIENT_ERROR_NOT_SUPPORTED, _("Cannot save contact list into a Microsoft 365 address book")));
1669 		return FALSE;
1670 	}
1671 
1672 	bbm365 = E_BOOK_BACKEND_M365 (meta_backend);
1673 
1674 	LOCK (bbm365);
1675 
1676 	if (e_vcard_get_attribute (E_VCARD (contact), EVC_PHOTO)) {
1677 		tmp_contact = e_contact_duplicate (contact);
1678 		contact = tmp_contact;
1679 
1680 		e_contact_inline_local_photos (contact, NULL);
1681 	}
1682 
1683 	if (extra && *extra)
1684 		old_contact = e_contact_new_from_vcard (extra);
1685 
1686 	builder = ebb_m365_contact_to_json_locked (bbm365, contact, old_contact, cancellable, error);
1687 
1688 	if (builder) {
1689 		if (overwrite_existing) {
1690 			const gchar *uid = e_contact_get_const (contact, E_CONTACT_UID);
1691 
1692 			success = e_m365_connection_update_contact_sync (bbm365->priv->cnc, NULL, bbm365->priv->folder_id,
1693 				uid, builder, cancellable, error);
1694 
1695 			if (success)
1696 				success = ebb_m365_contact_to_json_2nd_go_locked (bbm365, contact, old_contact, uid, cancellable, error);
1697 
1698 			if (success)
1699 				*out_new_extra = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
1700 		} else {
1701 			EM365Contact *created_contact = NULL;
1702 
1703 			success = e_m365_connection_create_contact_sync (bbm365->priv->cnc, NULL, bbm365->priv->folder_id,
1704 				builder, &created_contact, cancellable, error);
1705 
1706 			if (success && created_contact) {
1707 				const gchar *m365_id = e_m365_contact_get_id (created_contact);
1708 
1709 				success = ebb_m365_contact_to_json_2nd_go_locked (bbm365, contact, old_contact, m365_id, cancellable, error);
1710 			}
1711 
1712 			if (success && created_contact) {
1713 				EContact *vcard;
1714 
1715 				*out_new_uid = g_strdup (e_m365_contact_get_id (created_contact));
1716 
1717 				vcard = ebb_m365_json_contact_to_vcard (bbm365, created_contact, bbm365->priv->cnc, out_new_extra, cancellable, error);
1718 
1719 				if (!vcard)
1720 					success = FALSE;
1721 
1722 				g_clear_object (&vcard);
1723 			}
1724 
1725 			if (created_contact)
1726 				json_object_unref (created_contact);
1727 		}
1728 
1729 		g_clear_object (&builder);
1730 	}
1731 
1732 	UNLOCK (bbm365);
1733 
1734 	ebb_m365_convert_error_to_client_error (error);
1735 	ebb_m365_maybe_disconnect_sync (bbm365, error, cancellable);
1736 
1737 	g_clear_object (&old_contact);
1738 	g_clear_object (&tmp_contact);
1739 
1740 	return success;
1741 }
1742 
1743 static gboolean
ebb_m365_remove_contact_sync(EBookMetaBackend * meta_backend,EConflictResolution conflict_resolution,const gchar * uid,const gchar * extra,const gchar * object,guint32 opflags,GCancellable * cancellable,GError ** error)1744 ebb_m365_remove_contact_sync (EBookMetaBackend *meta_backend,
1745 			      EConflictResolution conflict_resolution,
1746 			      const gchar *uid,
1747 			      const gchar *extra,
1748 			      const gchar *object,
1749 			      guint32 opflags,
1750 			      GCancellable *cancellable,
1751 			      GError **error)
1752 {
1753 	EBookBackendM365 *bbm365;
1754 	gboolean success;
1755 
1756 	g_return_val_if_fail (E_IS_BOOK_BACKEND_M365 (meta_backend), FALSE);
1757 
1758 	bbm365 = E_BOOK_BACKEND_M365 (meta_backend);
1759 
1760 	LOCK (bbm365);
1761 
1762 	success = e_m365_connection_delete_contact_sync (bbm365->priv->cnc, NULL,
1763 		bbm365->priv->folder_id, uid, cancellable, error);
1764 
1765 	UNLOCK (bbm365);
1766 
1767 	ebb_m365_convert_error_to_client_error (error);
1768 	ebb_m365_maybe_disconnect_sync (bbm365, error, cancellable);
1769 
1770 	return success;
1771 }
1772 
1773 static gboolean
ebb_m365_search_sync(EBookMetaBackend * meta_backend,const gchar * expr,gboolean meta_contact,GSList ** out_contacts,GCancellable * cancellable,GError ** error)1774 ebb_m365_search_sync (EBookMetaBackend *meta_backend,
1775 		      const gchar *expr,
1776 		      gboolean meta_contact,
1777 		      GSList **out_contacts,
1778 		      GCancellable *cancellable,
1779 		      GError **error)
1780 {
1781 	g_return_val_if_fail (E_IS_BOOK_BACKEND_M365 (meta_backend), FALSE);
1782 
1783 	/* Ignore errors, just try its best */
1784 	/*ebb_m365_update_cache_for_expression (E_BOOK_BACKEND_M365 (meta_backend), expr, cancellable, NULL);*/
1785 
1786 	/* Chain up to parent's method */
1787 	return E_BOOK_META_BACKEND_CLASS (e_book_backend_m365_parent_class)->search_sync (meta_backend, expr, meta_contact, out_contacts, cancellable, error);
1788 }
1789 
1790 static gboolean
ebb_m365_search_uids_sync(EBookMetaBackend * meta_backend,const gchar * expr,GSList ** out_uids,GCancellable * cancellable,GError ** error)1791 ebb_m365_search_uids_sync (EBookMetaBackend *meta_backend,
1792 			   const gchar *expr,
1793 			   GSList **out_uids,
1794 			   GCancellable *cancellable,
1795 			   GError **error)
1796 {
1797 	g_return_val_if_fail (E_IS_BOOK_BACKEND_M365 (meta_backend), FALSE);
1798 
1799 	/* Ignore errors, just try its best */
1800 	/*ebb_m365_update_cache_for_expression (E_BOOK_BACKEND_M365 (meta_backend), expr, cancellable, NULL);*/
1801 
1802 	/* Chain up to parent's method */
1803 	return E_BOOK_META_BACKEND_CLASS (e_book_backend_m365_parent_class)->search_uids_sync (meta_backend, expr,
1804 		out_uids, cancellable, error);
1805 }
1806 
1807 static gchar *
ebb_m365_get_backend_property(EBookBackend * book_backend,const gchar * prop_name)1808 ebb_m365_get_backend_property (EBookBackend *book_backend,
1809 			       const gchar *prop_name)
1810 {
1811 	g_return_val_if_fail (E_IS_BOOK_BACKEND_M365 (book_backend), NULL);
1812 	g_return_val_if_fail (prop_name != NULL, NULL);
1813 
1814 	if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_CAPABILITIES)) {
1815 		return g_strjoin (",",
1816 			"net",
1817 			"contact-lists",
1818 			"do-initial-query",
1819 			e_book_meta_backend_get_capabilities (E_BOOK_META_BACKEND (book_backend)),
1820 			NULL);
1821 	} else if (g_str_equal (prop_name, E_BOOK_BACKEND_PROPERTY_REQUIRED_FIELDS)) {
1822 		return g_strdup (e_contact_field_name (E_CONTACT_FILE_AS));
1823 	} else if (g_str_equal (prop_name, E_BOOK_BACKEND_PROPERTY_SUPPORTED_FIELDS)) {
1824 		GString *buffer;
1825 		gchar *fields;
1826 		gint ii;
1827 
1828 		buffer = g_string_sized_new (1024);
1829 
1830 		for (ii = 0; ii < G_N_ELEMENTS (mappings); ii++) {
1831 			if (buffer->len > 0)
1832 				g_string_append_c (buffer, ',');
1833 
1834 			g_string_append (buffer, e_contact_field_name (mappings[ii].field_id));
1835 		}
1836 
1837 		fields = g_strjoin (
1838 			",",
1839 			buffer->str,
1840 			e_contact_field_name (E_CONTACT_FULL_NAME),
1841 			e_contact_field_name (E_CONTACT_EMAIL_1),
1842 			e_contact_field_name (E_CONTACT_EMAIL_2),
1843 			e_contact_field_name (E_CONTACT_EMAIL_3),
1844 			e_contact_field_name (E_CONTACT_EMAIL_4),
1845 			NULL);
1846 
1847 		g_string_free (buffer, TRUE);
1848 
1849 		return fields;
1850 	}
1851 
1852 	/* Chain up to parent's method. */
1853 	return E_BOOK_BACKEND_CLASS (e_book_backend_m365_parent_class)->impl_get_backend_property (book_backend, prop_name);
1854 }
1855 
1856 static gboolean
ebb_m365_get_destination_address(EBackend * backend,gchar ** host,guint16 * port)1857 ebb_m365_get_destination_address (EBackend *backend,
1858 				  gchar **host,
1859 				  guint16 *port)
1860 {
1861 	g_return_val_if_fail (port != NULL, FALSE);
1862 	g_return_val_if_fail (host != NULL, FALSE);
1863 
1864 	/* Sanity checking */
1865 	if (!e_book_backend_get_registry (E_BOOK_BACKEND (backend)) ||
1866 	    !e_backend_get_source (backend))
1867 		return FALSE;
1868 
1869 	*host = g_strdup ("graph.microsoft.com");
1870 	*port = 443;
1871 
1872 	return TRUE;
1873 }
1874 
1875 static void
e_book_backend_m365_dispose(GObject * object)1876 e_book_backend_m365_dispose (GObject *object)
1877 {
1878 	EBookBackendM365 *bbm365 = E_BOOK_BACKEND_M365 (object);
1879 
1880 	ebb_m365_unset_connection_sync (bbm365, FALSE, NULL, NULL);
1881 
1882 	/* Chain up to parent's method. */
1883 	G_OBJECT_CLASS (e_book_backend_m365_parent_class)->dispose (object);
1884 }
1885 
1886 static void
e_book_backend_m365_finalize(GObject * object)1887 e_book_backend_m365_finalize (GObject *object)
1888 {
1889 	EBookBackendM365 *bbm365 = E_BOOK_BACKEND_M365 (object);
1890 
1891 	g_rec_mutex_clear (&bbm365->priv->property_lock);
1892 
1893 	/* Chain up to parent's method. */
1894 	G_OBJECT_CLASS (e_book_backend_m365_parent_class)->finalize (object);
1895 }
1896 
1897 static void
e_book_backend_m365_init(EBookBackendM365 * bbm365)1898 e_book_backend_m365_init (EBookBackendM365 *bbm365)
1899 {
1900 	bbm365->priv = e_book_backend_m365_get_instance_private (bbm365);
1901 
1902 	g_rec_mutex_init (&bbm365->priv->property_lock);
1903 }
1904 
1905 static void
e_book_backend_m365_class_init(EBookBackendM365Class * klass)1906 e_book_backend_m365_class_init (EBookBackendM365Class *klass)
1907 {
1908 	GObjectClass *object_class;
1909 	EBackendClass *backend_class;
1910 	EBookBackendClass *book_backend_class;
1911 	EBookMetaBackendClass *book_meta_backend_class;
1912 
1913 	book_meta_backend_class = E_BOOK_META_BACKEND_CLASS (klass);
1914 	book_meta_backend_class->backend_module_filename = "libebookbackendmicrosoft365.so";
1915 	book_meta_backend_class->backend_factory_type_name = "EBookBackendM365Factory";
1916 	book_meta_backend_class->connect_sync = ebb_m365_connect_sync;
1917 	book_meta_backend_class->disconnect_sync = ebb_m365_disconnect_sync;
1918 	book_meta_backend_class->get_changes_sync = ebb_m365_get_changes_sync;
1919 	book_meta_backend_class->load_contact_sync = ebb_m365_load_contact_sync;
1920 	book_meta_backend_class->save_contact_sync = ebb_m365_save_contact_sync;
1921 	book_meta_backend_class->remove_contact_sync = ebb_m365_remove_contact_sync;
1922 	book_meta_backend_class->search_sync = ebb_m365_search_sync;
1923 	book_meta_backend_class->search_uids_sync = ebb_m365_search_uids_sync;
1924 
1925 	book_backend_class = E_BOOK_BACKEND_CLASS (klass);
1926 	book_backend_class->impl_get_backend_property = ebb_m365_get_backend_property;
1927 
1928 	backend_class = E_BACKEND_CLASS (klass);
1929 	backend_class->get_destination_address = ebb_m365_get_destination_address;
1930 
1931 	object_class = G_OBJECT_CLASS (klass);
1932 	object_class->dispose = e_book_backend_m365_dispose;
1933 	object_class->finalize = e_book_backend_m365_finalize;
1934 }
1935