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 <glib.h>
11 #include <glib/gi18n-lib.h>
12 #include <json-glib/json-glib.h>
13 
14 #include "camel-m365-settings.h"
15 #include "e-m365-json-utils.h"
16 
17 #include "e-m365-connection.h"
18 
19 #define LOCK(x) g_rec_mutex_lock (&(x->priv->property_lock))
20 #define UNLOCK(x) g_rec_mutex_unlock (&(x->priv->property_lock))
21 
22 #define X_EVO_M365_DATA "X-EVO-M365-DATA"
23 
24 typedef enum _CSMFlags {
25 	CSM_DEFAULT		= 0,
26 	CSM_DISABLE_RESPONSE	= 1 << 0
27 } CSMFlags;
28 
29 struct _EM365ConnectionPrivate {
30 	GRecMutex property_lock;
31 
32 	ESource *source;
33 	CamelM365Settings *settings;
34 	SoupSession *soup_session;
35 	GProxyResolver *proxy_resolver;
36 	ESoupAuthBearer *bearer_auth;
37 
38 	gchar *user; /* The default user for the URL */
39 	gchar *impersonate_user;
40 
41 	gboolean ssl_info_set;
42 	gchar *ssl_certificate_pem;
43 	GTlsCertificateFlags ssl_certificate_errors;
44 
45 	gchar *hash_key; /* in the opened connections hash */
46 
47 	/* How many microseconds to wait, until can execute a new request.
48 	   This is to cover throttling and server unavailable responses.
49 	   https://docs.microsoft.com/en-us/graph/best-practices-concept#handling-expected-errors */
50 	gint64 backoff_for_usec;
51 };
52 
53 enum {
54 	PROP_0,
55 	PROP_PROXY_RESOLVER,
56 	PROP_SETTINGS,
57 	PROP_SOURCE,
58 	PROP_CONCURRENT_CONNECTIONS,
59 	PROP_USER,			/* This one is hidden, write only */
60 	PROP_USE_IMPERSONATION,		/* This one is hidden, write only */
61 	PROP_IMPERSONATE_USER		/* This one is hidden, write only */
62 };
63 
64 G_DEFINE_TYPE_WITH_PRIVATE (EM365Connection, e_m365_connection, G_TYPE_OBJECT)
65 
66 static GHashTable *opened_connections = NULL;
67 G_LOCK_DEFINE_STATIC (opened_connections);
68 
69 static gboolean
m365_log_enabled(void)70 m365_log_enabled (void)
71 {
72 	static gint log_enabled = -1;
73 
74 	if (log_enabled == -1)
75 		log_enabled = g_strcmp0 (g_getenv ("M365_DEBUG"), "1") == 0 ? 1 : 0;
76 
77 	return log_enabled == 1;
78 }
79 
80 static SoupSession *
m365_connection_ref_soup_session(EM365Connection * cnc)81 m365_connection_ref_soup_session (EM365Connection *cnc)
82 {
83 	SoupSession *soup_session = NULL;
84 
85 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), NULL);
86 
87 	LOCK (cnc);
88 
89 	if (cnc->priv->soup_session)
90 		soup_session = g_object_ref (cnc->priv->soup_session);
91 
92 	UNLOCK (cnc);
93 
94 	return soup_session;
95 }
96 
97 static void
m365_connection_utils_ensure_bearer_auth_usage(SoupSession * session,SoupMessage * message,ESoupAuthBearer * bearer)98 m365_connection_utils_ensure_bearer_auth_usage (SoupSession *session,
99 						SoupMessage *message,
100 						ESoupAuthBearer *bearer)
101 {
102 	SoupAuthManager *auth_manager;
103 	SoupSessionFeature *feature;
104 	SoupURI *soup_uri;
105 
106 	g_return_if_fail (SOUP_IS_SESSION (session));
107 
108 	/* Preload the SoupAuthManager with a valid "Bearer" token
109 	 * when using OAuth 2.0. This avoids an extra unauthorized
110 	 * HTTP round-trip, which apparently Google doesn't like. */
111 
112 	feature = soup_session_get_feature (SOUP_SESSION (session), SOUP_TYPE_AUTH_MANAGER);
113 
114 	if (!soup_session_feature_has_feature (feature, E_TYPE_SOUP_AUTH_BEARER)) {
115 		/* Add the "Bearer" auth type to support OAuth 2.0. */
116 		soup_session_feature_add_feature (feature, E_TYPE_SOUP_AUTH_BEARER);
117 	}
118 
119 	soup_uri = message ? soup_message_get_uri (message) : NULL;
120 	if (soup_uri && soup_uri->host && *soup_uri->host) {
121 		soup_uri = soup_uri_copy_host (soup_uri);
122 	} else {
123 		soup_uri = NULL;
124 	}
125 
126 	g_return_if_fail (soup_uri != NULL);
127 
128 	auth_manager = SOUP_AUTH_MANAGER (feature);
129 
130 	/* This will make sure the 'bearer' is used regardless of the current 'auth_manager' state.
131 	   See https://gitlab.gnome.org/GNOME/libsoup/-/issues/196 for more information. */
132 	soup_auth_manager_clear_cached_credentials (auth_manager);
133 	soup_auth_manager_use_auth (auth_manager, soup_uri, SOUP_AUTH (bearer));
134 
135 	soup_uri_free (soup_uri);
136 }
137 
138 static gboolean
m365_connection_utils_setup_bearer_auth(EM365Connection * cnc,SoupSession * session,SoupMessage * message,gboolean is_in_authenticate_handler,ESoupAuthBearer * bearer,GCancellable * cancellable,GError ** error)139 m365_connection_utils_setup_bearer_auth (EM365Connection *cnc,
140 					 SoupSession *session,
141 					 SoupMessage *message,
142 					 gboolean is_in_authenticate_handler,
143 					 ESoupAuthBearer *bearer,
144 					 GCancellable *cancellable,
145 					 GError **error)
146 {
147 	ESource *source;
148 	gchar *access_token = NULL;
149 	gint expires_in_seconds = -1;
150 	gboolean success = FALSE;
151 
152 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
153 	g_return_val_if_fail (E_IS_SOUP_AUTH_BEARER (bearer), FALSE);
154 
155 	source = e_m365_connection_get_source (cnc);
156 
157 	success = e_source_get_oauth2_access_token_sync (source, cancellable,
158 		&access_token, &expires_in_seconds, error);
159 
160 	if (success) {
161 		e_soup_auth_bearer_set_access_token (bearer, access_token, expires_in_seconds);
162 
163 		if (!is_in_authenticate_handler) {
164 			if (session)
165 				g_object_ref (session);
166 			else
167 				session = m365_connection_ref_soup_session (cnc);
168 
169 			m365_connection_utils_ensure_bearer_auth_usage (session, message, bearer);
170 
171 			g_clear_object (&session);
172 		}
173 	}
174 
175 	g_free (access_token);
176 
177 	return success;
178 }
179 
180 static gboolean
m365_connection_utils_prepare_bearer_auth(EM365Connection * cnc,SoupSession * session,SoupMessage * message,GCancellable * cancellable)181 m365_connection_utils_prepare_bearer_auth (EM365Connection *cnc,
182 					   SoupSession *session,
183 					   SoupMessage *message,
184 					   GCancellable *cancellable)
185 {
186 	ESource *source;
187 	ESoupAuthBearer *using_bearer_auth;
188 	gboolean success;
189 	GError *local_error = NULL;
190 
191 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
192 
193 	source = e_m365_connection_get_source (cnc);
194 	if (!source)
195 		return TRUE;
196 
197 	using_bearer_auth = e_m365_connection_ref_bearer_auth (cnc);
198 	if (using_bearer_auth) {
199 		success = m365_connection_utils_setup_bearer_auth (cnc, session, message, FALSE, using_bearer_auth, cancellable, &local_error);
200 		g_clear_object (&using_bearer_auth);
201 	} else {
202 		SoupAuth *soup_auth;
203 		SoupURI *soup_uri;
204 
205 		soup_uri = message ? soup_message_get_uri (message) : NULL;
206 		if (soup_uri && soup_uri->host && *soup_uri->host) {
207 			soup_uri = soup_uri_copy_host (soup_uri);
208 		} else {
209 			soup_uri = NULL;
210 		}
211 
212 		g_warn_if_fail (soup_uri != NULL);
213 
214 		if (!soup_uri) {
215 			soup_message_set_status_full (message, SOUP_STATUS_MALFORMED, "Cannot get host from message");
216 			return FALSE;
217 		}
218 
219 		soup_auth = g_object_new (E_TYPE_SOUP_AUTH_BEARER, SOUP_AUTH_HOST, soup_uri->host, NULL);
220 
221 		success = m365_connection_utils_setup_bearer_auth (cnc, session, message, FALSE, E_SOUP_AUTH_BEARER (soup_auth), cancellable, &local_error);
222 		if (success)
223 			e_m365_connection_set_bearer_auth (cnc, E_SOUP_AUTH_BEARER (soup_auth));
224 
225 		g_object_unref (soup_auth);
226 		soup_uri_free (soup_uri);
227 	}
228 
229 	if (!success) {
230 		if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
231 			soup_message_set_status (message, SOUP_STATUS_CANCELLED);
232 		else if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CONNECTION_REFUSED) ||
233 			 g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
234 			soup_message_set_status_full (message, SOUP_STATUS_UNAUTHORIZED, local_error->message);
235 		else
236 			soup_message_set_status_full (message, SOUP_STATUS_MALFORMED, local_error ? local_error->message : _("Unknown error"));
237 	}
238 
239 	g_clear_error (&local_error);
240 
241 	return success;
242 }
243 
244 static void
m365_connection_authenticate(SoupSession * session,SoupMessage * msg,SoupAuth * auth,gboolean retrying,gpointer user_data)245 m365_connection_authenticate (SoupSession *session,
246 			      SoupMessage *msg,
247 			      SoupAuth *auth,
248 			      gboolean retrying,
249 			      gpointer user_data)
250 {
251 	EM365Connection *cnc = user_data;
252 	ESoupAuthBearer *using_bearer_auth;
253 	GError *local_error = NULL;
254 
255 	g_return_if_fail (E_IS_M365_CONNECTION (cnc));
256 
257 	using_bearer_auth = e_m365_connection_ref_bearer_auth (cnc);
258 
259 	if (E_IS_SOUP_AUTH_BEARER (auth)) {
260 		g_object_ref (auth);
261 		g_warn_if_fail ((gpointer) using_bearer_auth == (gpointer) auth);
262 
263 		g_clear_object (&using_bearer_auth);
264 		using_bearer_auth = E_SOUP_AUTH_BEARER (auth);
265 
266 		e_m365_connection_set_bearer_auth (cnc, using_bearer_auth);
267 	}
268 
269 	if (!using_bearer_auth) {
270 		g_warn_if_reached ();
271 		return;
272 	}
273 
274 	m365_connection_utils_setup_bearer_auth (cnc, session, msg, TRUE, E_SOUP_AUTH_BEARER (auth), NULL, &local_error);
275 
276 	if (local_error)
277 		soup_message_set_status_full (msg, SOUP_STATUS_IO_ERROR, local_error->message);
278 
279 	g_object_unref (using_bearer_auth);
280 	g_clear_error (&local_error);
281 }
282 
283 static gboolean
m365_connection_utils_prepare_message(EM365Connection * cnc,SoupSession * session,SoupMessage * message,GCancellable * cancellable)284 m365_connection_utils_prepare_message (EM365Connection *cnc,
285 				       SoupSession *session,
286 				       SoupMessage *message,
287 				       GCancellable *cancellable)
288 {
289 	ESoupAuthBearer *using_bearer_auth;
290 	ESource *source;
291 	GError *local_error = NULL;
292 
293 	source = e_m365_connection_get_source (cnc);
294 	if (source)
295 		e_soup_ssl_trust_connect (message, source);
296 
297 	if (!m365_connection_utils_prepare_bearer_auth (cnc, session, message, cancellable))
298 		return FALSE;
299 
300 	using_bearer_auth = e_m365_connection_ref_bearer_auth (cnc);
301 
302 	if (using_bearer_auth &&
303 	    e_soup_auth_bearer_is_expired (using_bearer_auth) &&
304 	    !m365_connection_utils_setup_bearer_auth (cnc, session, message, FALSE, using_bearer_auth, cancellable, &local_error)) {
305 		if (local_error) {
306 			soup_message_set_status_full (message, SOUP_STATUS_BAD_REQUEST, local_error->message);
307 			g_clear_error (&local_error);
308 		} else {
309 			soup_message_set_status (message, SOUP_STATUS_BAD_REQUEST);
310 		}
311 
312 		g_object_unref (using_bearer_auth);
313 
314 		return FALSE;
315 	}
316 
317 	g_clear_object (&using_bearer_auth);
318 
319 	return TRUE;
320 }
321 
322 static void
m365_connection_set_settings(EM365Connection * cnc,CamelM365Settings * settings)323 m365_connection_set_settings (EM365Connection *cnc,
324 			      CamelM365Settings *settings)
325 {
326 	g_return_if_fail (E_IS_M365_CONNECTION (cnc));
327 	g_return_if_fail (CAMEL_IS_M365_SETTINGS (settings));
328 	g_return_if_fail (cnc->priv->settings == NULL);
329 
330 	cnc->priv->settings = g_object_ref (settings);
331 
332 	e_binding_bind_property (
333 		cnc->priv->settings, "user",
334 		cnc, "user",
335 		G_BINDING_DEFAULT |
336 		G_BINDING_SYNC_CREATE);
337 
338 	e_binding_bind_property (
339 		cnc->priv->settings, "use-impersonation",
340 		cnc, "use-impersonation",
341 		G_BINDING_DEFAULT |
342 		G_BINDING_SYNC_CREATE);
343 
344 	/* No need to G_BINDING_SYNC_CREATE, because the 'use-impersonation' already updated the value */
345 	e_binding_bind_property (
346 		cnc->priv->settings, "impersonate-user",
347 		cnc, "impersonate-user",
348 		G_BINDING_DEFAULT);
349 }
350 
351 static void
m365_connection_set_source(EM365Connection * cnc,ESource * source)352 m365_connection_set_source (EM365Connection *cnc,
353 			    ESource *source)
354 {
355 	g_return_if_fail (E_IS_M365_CONNECTION (cnc));
356 	g_return_if_fail (E_IS_SOURCE (source));
357 	g_return_if_fail (cnc->priv->source == NULL);
358 
359 	cnc->priv->source = g_object_ref (source);
360 }
361 
362 static void
m365_connection_take_user(EM365Connection * cnc,gchar * user)363 m365_connection_take_user (EM365Connection *cnc,
364 			   gchar *user)
365 {
366 	g_return_if_fail (E_IS_M365_CONNECTION (cnc));
367 
368 	LOCK (cnc);
369 
370 	if (!user || !*user)
371 		g_clear_pointer (&user, g_free);
372 
373 	g_free (cnc->priv->user);
374 	cnc->priv->user = user;
375 
376 	UNLOCK (cnc);
377 }
378 
379 static void
m365_connection_take_impersonate_user(EM365Connection * cnc,gchar * impersonate_user)380 m365_connection_take_impersonate_user (EM365Connection *cnc,
381 				       gchar *impersonate_user)
382 {
383 	g_return_if_fail (E_IS_M365_CONNECTION (cnc));
384 
385 	LOCK (cnc);
386 
387 	if (!impersonate_user || !*impersonate_user ||
388 	    !camel_m365_settings_get_use_impersonation (cnc->priv->settings)) {
389 		g_clear_pointer (&impersonate_user, g_free);
390 	}
391 
392 	if (g_strcmp0 (impersonate_user, cnc->priv->impersonate_user) != 0) {
393 		g_free (cnc->priv->impersonate_user);
394 		cnc->priv->impersonate_user = impersonate_user;
395 	} else {
396 		g_clear_pointer (&impersonate_user, g_free);
397 	}
398 
399 	UNLOCK (cnc);
400 }
401 
402 static void
m365_connection_set_use_impersonation(EM365Connection * cnc,gboolean use_impersonation)403 m365_connection_set_use_impersonation (EM365Connection *cnc,
404 				       gboolean use_impersonation)
405 {
406 	g_return_if_fail (E_IS_M365_CONNECTION (cnc));
407 
408 	LOCK (cnc);
409 
410 	if (!use_impersonation)
411 		m365_connection_take_impersonate_user (cnc, NULL);
412 	else
413 		m365_connection_take_impersonate_user (cnc, camel_m365_settings_dup_impersonate_user (cnc->priv->settings));
414 
415 	UNLOCK (cnc);
416 }
417 
418 static void
m365_connection_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)419 m365_connection_set_property (GObject *object,
420 			      guint property_id,
421 			      const GValue *value,
422 			      GParamSpec *pspec)
423 {
424 	switch (property_id) {
425 		case PROP_PROXY_RESOLVER:
426 			e_m365_connection_set_proxy_resolver (
427 				E_M365_CONNECTION (object),
428 				g_value_get_object (value));
429 			return;
430 
431 		case PROP_SETTINGS:
432 			m365_connection_set_settings (
433 				E_M365_CONNECTION (object),
434 				g_value_get_object (value));
435 			return;
436 
437 		case PROP_SOURCE:
438 			m365_connection_set_source (
439 				E_M365_CONNECTION (object),
440 				g_value_get_object (value));
441 			return;
442 
443 		case PROP_CONCURRENT_CONNECTIONS:
444 			e_m365_connection_set_concurrent_connections (
445 				E_M365_CONNECTION (object),
446 				g_value_get_uint (value));
447 			return;
448 
449 		case PROP_USER:
450 			m365_connection_take_user (
451 				E_M365_CONNECTION (object),
452 				g_value_dup_string (value));
453 			return;
454 
455 		case PROP_USE_IMPERSONATION:
456 			m365_connection_set_use_impersonation (
457 				E_M365_CONNECTION (object),
458 				g_value_get_boolean (value));
459 			return;
460 
461 		case PROP_IMPERSONATE_USER:
462 			m365_connection_take_impersonate_user (
463 				E_M365_CONNECTION (object),
464 				g_value_dup_string (value));
465 			return;
466 	}
467 
468 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
469 }
470 
471 static void
m365_connection_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)472 m365_connection_get_property (GObject *object,
473 			      guint property_id,
474 			      GValue *value,
475 			      GParamSpec *pspec)
476 {
477 	switch (property_id) {
478 		case PROP_PROXY_RESOLVER:
479 			g_value_take_object (
480 				value,
481 				e_m365_connection_ref_proxy_resolver (
482 				E_M365_CONNECTION (object)));
483 			return;
484 
485 		case PROP_SETTINGS:
486 			g_value_set_object (
487 				value,
488 				e_m365_connection_get_settings (
489 				E_M365_CONNECTION (object)));
490 			return;
491 
492 		case PROP_SOURCE:
493 			g_value_set_object (
494 				value,
495 				e_m365_connection_get_source (
496 				E_M365_CONNECTION (object)));
497 			return;
498 
499 		case PROP_CONCURRENT_CONNECTIONS:
500 			g_value_set_uint (
501 				value,
502 				e_m365_connection_get_concurrent_connections (
503 				E_M365_CONNECTION (object)));
504 			return;
505 	}
506 
507 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
508 }
509 
510 static void
m365_connection_constructed(GObject * object)511 m365_connection_constructed (GObject *object)
512 {
513 	EM365Connection *cnc = E_M365_CONNECTION (object);
514 
515 	/* Chain up to parent's method. */
516 	G_OBJECT_CLASS (e_m365_connection_parent_class)->constructed (object);
517 
518 	if (m365_log_enabled ()) {
519 		SoupLogger *logger = soup_logger_new (SOUP_LOGGER_LOG_BODY, -1);
520 
521 		soup_session_add_feature (cnc->priv->soup_session, SOUP_SESSION_FEATURE (logger));
522 
523 		g_object_unref (logger);
524 	}
525 
526 	soup_session_add_feature_by_type (cnc->priv->soup_session, SOUP_TYPE_COOKIE_JAR);
527 	soup_session_add_feature_by_type (cnc->priv->soup_session, E_TYPE_SOUP_AUTH_BEARER);
528 	soup_session_remove_feature_by_type (cnc->priv->soup_session, SOUP_TYPE_AUTH_BASIC);
529 
530 	g_signal_connect (
531 		cnc->priv->soup_session, "authenticate",
532 		G_CALLBACK (m365_connection_authenticate), cnc);
533 
534 	cnc->priv->hash_key = camel_network_settings_dup_user (CAMEL_NETWORK_SETTINGS (cnc->priv->settings));
535 
536 	if (!cnc->priv->hash_key)
537 		cnc->priv->hash_key = g_strdup ("no-user");
538 
539 	e_binding_bind_property (
540 		cnc->priv->settings, "timeout",
541 		cnc->priv->soup_session, SOUP_SESSION_TIMEOUT,
542 		G_BINDING_SYNC_CREATE);
543 }
544 
545 static void
m365_connection_dispose(GObject * object)546 m365_connection_dispose (GObject *object)
547 {
548 	EM365Connection *cnc = E_M365_CONNECTION (object);
549 
550 	G_LOCK (opened_connections);
551 
552 	/* Remove the connection from the opened connections */
553 	if (opened_connections &&
554 	    g_hash_table_lookup (opened_connections, cnc->priv->hash_key) == (gpointer) object) {
555 		g_hash_table_remove (opened_connections, cnc->priv->hash_key);
556 		if (g_hash_table_size (opened_connections) == 0) {
557 			g_hash_table_destroy (opened_connections);
558 			opened_connections = NULL;
559 		}
560 	}
561 
562 	G_UNLOCK (opened_connections);
563 
564 	LOCK (cnc);
565 
566 	if (cnc->priv->soup_session) {
567 		g_signal_handlers_disconnect_by_func (
568 			cnc->priv->soup_session,
569 			m365_connection_authenticate, object);
570 	}
571 
572 	g_clear_object (&cnc->priv->source);
573 	g_clear_object (&cnc->priv->settings);
574 	g_clear_object (&cnc->priv->soup_session);
575 	g_clear_object (&cnc->priv->proxy_resolver);
576 	g_clear_object (&cnc->priv->bearer_auth);
577 
578 	UNLOCK (cnc);
579 
580 	/* Chain up to parent's method. */
581 	G_OBJECT_CLASS (e_m365_connection_parent_class)->dispose (object);
582 }
583 
584 static void
m365_connection_finalize(GObject * object)585 m365_connection_finalize (GObject *object)
586 {
587 	EM365Connection *cnc = E_M365_CONNECTION (object);
588 
589 	g_rec_mutex_clear (&cnc->priv->property_lock);
590 	g_clear_pointer (&cnc->priv->ssl_certificate_pem, g_free);
591 	g_clear_pointer (&cnc->priv->user, g_free);
592 	g_clear_pointer (&cnc->priv->impersonate_user, g_free);
593 	g_free (cnc->priv->hash_key);
594 
595 	/* Chain up to parent's method. */
596 	G_OBJECT_CLASS (e_m365_connection_parent_class)->finalize (object);
597 }
598 
599 static void
e_m365_connection_class_init(EM365ConnectionClass * class)600 e_m365_connection_class_init (EM365ConnectionClass *class)
601 {
602 	GObjectClass *object_class;
603 
604 	object_class = G_OBJECT_CLASS (class);
605 	object_class->set_property = m365_connection_set_property;
606 	object_class->get_property = m365_connection_get_property;
607 	object_class->constructed = m365_connection_constructed;
608 	object_class->dispose = m365_connection_dispose;
609 	object_class->finalize = m365_connection_finalize;
610 
611 	g_object_class_install_property (
612 		object_class,
613 		PROP_PROXY_RESOLVER,
614 		g_param_spec_object (
615 			"proxy-resolver",
616 			"Proxy Resolver",
617 			"The proxy resolver for this backend",
618 			G_TYPE_PROXY_RESOLVER,
619 			G_PARAM_READWRITE |
620 			G_PARAM_STATIC_STRINGS));
621 
622 	g_object_class_install_property (
623 		object_class,
624 		PROP_SETTINGS,
625 		g_param_spec_object (
626 			"settings",
627 			"Settings",
628 			"Connection settings",
629 			CAMEL_TYPE_M365_SETTINGS,
630 			G_PARAM_READWRITE |
631 			G_PARAM_CONSTRUCT_ONLY |
632 			G_PARAM_STATIC_STRINGS));
633 
634 	g_object_class_install_property (
635 		object_class,
636 		PROP_SOURCE,
637 		g_param_spec_object (
638 			"source",
639 			"Source",
640 			"Corresponding ESource",
641 			E_TYPE_SOURCE,
642 			G_PARAM_READWRITE |
643 			G_PARAM_CONSTRUCT_ONLY |
644 			G_PARAM_STATIC_STRINGS));
645 
646 	g_object_class_install_property (
647 		object_class,
648 		PROP_CONCURRENT_CONNECTIONS,
649 		g_param_spec_uint (
650 			"concurrent-connections",
651 			"Concurrent Connections",
652 			"Number of concurrent connections to use",
653 			MIN_CONCURRENT_CONNECTIONS,
654 			MAX_CONCURRENT_CONNECTIONS,
655 			1,
656 			/* Do not construct it, otherwise it overrides the value derived from CamelM365Settings */
657 			G_PARAM_READWRITE |
658 			G_PARAM_EXPLICIT_NOTIFY |
659 			G_PARAM_STATIC_STRINGS));
660 
661 	g_object_class_install_property (
662 		object_class,
663 		PROP_USER,
664 		g_param_spec_string (
665 			"user",
666 			NULL,
667 			NULL,
668 			NULL,
669 			G_PARAM_WRITABLE |
670 			G_PARAM_STATIC_STRINGS));
671 
672 	g_object_class_install_property (
673 		object_class,
674 		PROP_USE_IMPERSONATION,
675 		g_param_spec_boolean (
676 			"use-impersonation",
677 			NULL,
678 			NULL,
679 			FALSE,
680 			G_PARAM_WRITABLE |
681 			G_PARAM_STATIC_STRINGS));
682 
683 	g_object_class_install_property (
684 		object_class,
685 		PROP_IMPERSONATE_USER,
686 		g_param_spec_string (
687 			"impersonate-user",
688 			NULL,
689 			NULL,
690 			NULL,
691 			G_PARAM_WRITABLE |
692 			G_PARAM_STATIC_STRINGS));
693 }
694 
695 static void
e_m365_connection_init(EM365Connection * cnc)696 e_m365_connection_init (EM365Connection *cnc)
697 {
698 	cnc->priv = e_m365_connection_get_instance_private (cnc);
699 
700 	g_rec_mutex_init (&cnc->priv->property_lock);
701 
702 	cnc->priv->backoff_for_usec = 0;
703 	cnc->priv->soup_session = soup_session_new_with_options (
704 		SOUP_SESSION_TIMEOUT, 90,
705 		SOUP_SESSION_SSL_STRICT, TRUE,
706 		SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE,
707 		NULL);
708 
709 	/* Do not use G_BINDING_SYNC_CREATE, because we don't have a GProxyResolver yet anyway. */
710 	e_binding_bind_property (
711 		cnc, "proxy-resolver",
712 		cnc->priv->soup_session, "proxy-resolver",
713 		G_BINDING_DEFAULT);
714 }
715 
716 gboolean
e_m365_connection_util_delta_token_failed(const GError * error)717 e_m365_connection_util_delta_token_failed (const GError *error)
718 {
719 	return g_error_matches (error, SOUP_HTTP_ERROR, SOUP_STATUS_UNAUTHORIZED) ||
720 	       g_error_matches (error, SOUP_HTTP_ERROR, SOUP_STATUS_BAD_REQUEST);
721 }
722 
723 EM365Connection *
e_m365_connection_new(ESource * source,CamelM365Settings * settings)724 e_m365_connection_new (ESource *source,
725 		       CamelM365Settings *settings)
726 {
727 	g_return_val_if_fail (E_IS_SOURCE (source), NULL);
728 	g_return_val_if_fail (CAMEL_IS_M365_SETTINGS (settings), NULL);
729 
730 	return e_m365_connection_new_full (source, settings, TRUE);
731 }
732 
733 EM365Connection *
e_m365_connection_new_for_backend(EBackend * backend,ESourceRegistry * registry,ESource * source,CamelM365Settings * settings)734 e_m365_connection_new_for_backend (EBackend *backend,
735 				   ESourceRegistry *registry,
736 				   ESource *source,
737 				   CamelM365Settings *settings)
738 {
739 	ESource *backend_source, *parent_source;
740 
741 	g_return_val_if_fail (E_IS_BACKEND (backend), NULL);
742 	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
743 	g_return_val_if_fail (E_IS_SOURCE (source), NULL);
744 	g_return_val_if_fail (CAMEL_IS_M365_SETTINGS (settings), NULL);
745 
746 	backend_source = e_backend_get_source (backend);
747 
748 	if (!backend_source)
749 		return e_m365_connection_new (source, settings);
750 
751 	parent_source = e_source_registry_find_extension (registry, source, E_SOURCE_EXTENSION_COLLECTION);
752 
753 	if (parent_source) {
754 		EM365Connection *cnc;
755 
756 		cnc = e_m365_connection_new (parent_source, settings);
757 
758 		g_object_unref (parent_source);
759 
760 		return cnc;
761 	}
762 
763 	return e_m365_connection_new (source, settings);
764 }
765 
766 EM365Connection *
e_m365_connection_new_full(ESource * source,CamelM365Settings * settings,gboolean allow_reuse)767 e_m365_connection_new_full (ESource *source,
768 			    CamelM365Settings *settings,
769 			    gboolean allow_reuse)
770 {
771 	EM365Connection *cnc;
772 
773 	if (allow_reuse) {
774 		gchar *hash_key = camel_network_settings_dup_user (CAMEL_NETWORK_SETTINGS (settings));
775 
776 		if (hash_key) {
777 			G_LOCK (opened_connections);
778 
779 			if (opened_connections) {
780 				cnc = g_hash_table_lookup (opened_connections, hash_key);
781 
782 				if (cnc) {
783 					g_object_ref (cnc);
784 					G_UNLOCK (opened_connections);
785 
786 					g_free (hash_key);
787 
788 					return cnc;
789 				}
790 			}
791 
792 			G_UNLOCK (opened_connections);
793 		}
794 
795 		g_free (hash_key);
796 	}
797 
798 	cnc = g_object_new (E_TYPE_M365_CONNECTION,
799 		"source", source,
800 		"settings", settings,
801 		NULL);
802 
803 	if (allow_reuse && cnc->priv->hash_key) {
804 		G_LOCK (opened_connections);
805 
806 		if (!opened_connections)
807 			opened_connections = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
808 
809 		g_hash_table_insert (opened_connections, g_strdup (cnc->priv->hash_key), cnc);
810 
811 		G_UNLOCK (opened_connections);
812 	}
813 
814 	return cnc;
815 }
816 
817 ESource *
e_m365_connection_get_source(EM365Connection * cnc)818 e_m365_connection_get_source (EM365Connection *cnc)
819 {
820 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), NULL);
821 
822 	return cnc->priv->source;
823 }
824 
825 CamelM365Settings *
e_m365_connection_get_settings(EM365Connection * cnc)826 e_m365_connection_get_settings (EM365Connection *cnc)
827 {
828 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), NULL);
829 
830 	return cnc->priv->settings;
831 }
832 
833 guint
e_m365_connection_get_concurrent_connections(EM365Connection * cnc)834 e_m365_connection_get_concurrent_connections (EM365Connection *cnc)
835 {
836 	guint current_cc = 0;
837 
838 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), 1);
839 
840 	LOCK (cnc);
841 
842 	g_object_get (G_OBJECT (cnc->priv->soup_session), SOUP_SESSION_MAX_CONNS, &current_cc, NULL);
843 
844 	UNLOCK (cnc);
845 
846 	return current_cc;
847 }
848 
849 void
e_m365_connection_set_concurrent_connections(EM365Connection * cnc,guint concurrent_connections)850 e_m365_connection_set_concurrent_connections (EM365Connection *cnc,
851 					      guint concurrent_connections)
852 {
853 	guint current_cc;
854 
855 	g_return_if_fail (E_IS_M365_CONNECTION (cnc));
856 
857 	concurrent_connections = CLAMP (concurrent_connections, MIN_CONCURRENT_CONNECTIONS, MAX_CONCURRENT_CONNECTIONS);
858 
859 	current_cc = e_m365_connection_get_concurrent_connections (cnc);
860 
861 	if (current_cc == concurrent_connections)
862 		return;
863 
864 	LOCK (cnc);
865 
866 	g_object_set (G_OBJECT (cnc->priv->soup_session),
867 		SOUP_SESSION_MAX_CONNS, concurrent_connections,
868 		SOUP_SESSION_MAX_CONNS_PER_HOST, concurrent_connections,
869 		NULL);
870 
871 	UNLOCK (cnc);
872 
873 	g_object_notify (G_OBJECT (cnc), "concurrent-connections");
874 }
875 
876 GProxyResolver *
e_m365_connection_ref_proxy_resolver(EM365Connection * cnc)877 e_m365_connection_ref_proxy_resolver (EM365Connection *cnc)
878 {
879 	GProxyResolver *proxy_resolver = NULL;
880 
881 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), NULL);
882 
883 	LOCK (cnc);
884 
885 	if (cnc->priv->proxy_resolver)
886 		proxy_resolver = g_object_ref (cnc->priv->proxy_resolver);
887 
888 	UNLOCK (cnc);
889 
890 	return proxy_resolver;
891 }
892 
893 void
e_m365_connection_set_proxy_resolver(EM365Connection * cnc,GProxyResolver * proxy_resolver)894 e_m365_connection_set_proxy_resolver (EM365Connection *cnc,
895 				      GProxyResolver *proxy_resolver)
896 {
897 	gboolean notify = FALSE;
898 
899 	g_return_if_fail (E_IS_M365_CONNECTION (cnc));
900 
901 	LOCK (cnc);
902 
903 	/* Emitting a "notify" signal unnecessarily might have
904 	 * unwanted side effects like cancelling a SoupMessage.
905 	 * Only emit if we now have a different GProxyResolver. */
906 
907 	if (proxy_resolver != cnc->priv->proxy_resolver) {
908 		g_clear_object (&cnc->priv->proxy_resolver);
909 		cnc->priv->proxy_resolver = proxy_resolver;
910 
911 		if (proxy_resolver)
912 			g_object_ref (proxy_resolver);
913 
914 		notify = TRUE;
915 	}
916 
917 	UNLOCK (cnc);
918 
919 	if (notify)
920 		g_object_notify (G_OBJECT (cnc), "proxy-resolver");
921 }
922 
923 ESoupAuthBearer *
e_m365_connection_ref_bearer_auth(EM365Connection * cnc)924 e_m365_connection_ref_bearer_auth (EM365Connection *cnc)
925 {
926 	ESoupAuthBearer *res = NULL;
927 
928 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), NULL);
929 
930 	LOCK (cnc);
931 
932 	if (cnc->priv->bearer_auth)
933 		res = g_object_ref (cnc->priv->bearer_auth);
934 
935 	UNLOCK (cnc);
936 
937 	return res;
938 }
939 
940 void
e_m365_connection_set_bearer_auth(EM365Connection * cnc,ESoupAuthBearer * bearer_auth)941 e_m365_connection_set_bearer_auth (EM365Connection *cnc,
942 				   ESoupAuthBearer *bearer_auth)
943 {
944 	g_return_if_fail (E_IS_M365_CONNECTION (cnc));
945 
946 	LOCK (cnc);
947 
948 	if (cnc->priv->bearer_auth != bearer_auth) {
949 		g_clear_object (&cnc->priv->bearer_auth);
950 
951 		cnc->priv->bearer_auth = bearer_auth;
952 
953 		if (cnc->priv->bearer_auth)
954 			g_object_ref (cnc->priv->bearer_auth);
955 	}
956 
957 	UNLOCK (cnc);
958 }
959 
960 static void
m365_connection_request_cancelled_cb(GCancellable * cancellable,gpointer user_data)961 m365_connection_request_cancelled_cb (GCancellable *cancellable,
962 				      gpointer user_data)
963 {
964 	EFlag *flag = user_data;
965 
966 	g_return_if_fail (flag != NULL);
967 
968 	e_flag_set (flag);
969 }
970 
971 static void
m365_connection_extract_ssl_data(EM365Connection * cnc,SoupMessage * message)972 m365_connection_extract_ssl_data (EM365Connection *cnc,
973 				  SoupMessage *message)
974 {
975 	GTlsCertificate *certificate = NULL;
976 
977 	g_return_if_fail (E_IS_M365_CONNECTION (cnc));
978 	g_return_if_fail (SOUP_IS_MESSAGE (message));
979 
980 	LOCK (cnc);
981 
982 	g_clear_pointer (&cnc->priv->ssl_certificate_pem, g_free);
983 	cnc->priv->ssl_info_set = FALSE;
984 
985 	g_object_get (G_OBJECT (message),
986 		"tls-certificate", &certificate,
987 		"tls-errors", &cnc->priv->ssl_certificate_errors,
988 		NULL);
989 
990 	if (certificate) {
991 		g_object_get (certificate, "certificate-pem", &cnc->priv->ssl_certificate_pem, NULL);
992 		cnc->priv->ssl_info_set = TRUE;
993 
994 		g_object_unref (certificate);
995 	}
996 
997 	UNLOCK (cnc);
998 }
999 
1000 /* An example error response:
1001 
1002   {
1003     "error": {
1004       "code": "BadRequest",
1005       "message": "Parsing Select and Expand failed.",
1006       "innerError": {
1007         "request-id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
1008         "date": "2020-06-10T13:44:43"
1009       }
1010     }
1011   }
1012 
1013  */
1014 static gboolean
m365_connection_extract_error(JsonNode * node,guint status_code,GError ** error)1015 m365_connection_extract_error (JsonNode *node,
1016 			       guint status_code,
1017 			       GError **error)
1018 {
1019 	JsonObject *object;
1020 	const gchar *code, *message;
1021 
1022 	if (!node || !JSON_NODE_HOLDS_OBJECT (node))
1023 		return FALSE;
1024 
1025 	object = e_m365_json_get_object_member (json_node_get_object (node), "error");
1026 
1027 	if (!object)
1028 		return FALSE;
1029 
1030 	code = e_m365_json_get_string_member (object, "code", NULL);
1031 	message = e_m365_json_get_string_member (object, "message", NULL);
1032 
1033 	if (!code && !message)
1034 		return FALSE;
1035 
1036 	if (!status_code || SOUP_STATUS_IS_SUCCESSFUL (status_code))
1037 		status_code = SOUP_STATUS_MALFORMED;
1038 	else if (g_strcmp0 (code, "ErrorInvalidUser") == 0)
1039 		status_code = SOUP_STATUS_UNAUTHORIZED;
1040 
1041 	if (code && message)
1042 		g_set_error (error, SOUP_HTTP_ERROR, status_code, "%s: %s", code, message);
1043 	else
1044 		g_set_error_literal (error, SOUP_HTTP_ERROR, status_code, code ? code : message);
1045 
1046 	return TRUE;
1047 }
1048 
1049 typedef gboolean (* EM365ResponseFunc)	(EM365Connection *cnc,
1050 					 SoupMessage *message,
1051 					 GInputStream *input_stream,
1052 					 JsonNode *node,
1053 					 gpointer user_data,
1054 					 gchar **out_next_link,
1055 					 GCancellable *cancellable,
1056 					 GError **error);
1057 
1058 /* (transfer full) (nullable): Free the *out_node with json_node_unref(), if not NULL;
1059    It can return 'success', even when the *out_node is NULL. */
1060 gboolean
e_m365_connection_json_node_from_message(SoupMessage * message,GInputStream * input_stream,JsonNode ** out_node,GCancellable * cancellable,GError ** error)1061 e_m365_connection_json_node_from_message (SoupMessage *message,
1062 					  GInputStream *input_stream,
1063 					  JsonNode **out_node,
1064 					  GCancellable *cancellable,
1065 					  GError **error)
1066 {
1067 	JsonObject *message_json_object;
1068 	gboolean success = TRUE;
1069 	GError *local_error = NULL;
1070 
1071 	g_return_val_if_fail (SOUP_IS_MESSAGE (message), FALSE);
1072 	g_return_val_if_fail (out_node != NULL, FALSE);
1073 
1074 	*out_node = NULL;
1075 
1076 	message_json_object = g_object_get_data (G_OBJECT (message), X_EVO_M365_DATA);
1077 
1078 	if (message_json_object) {
1079 		*out_node = json_node_init_object (json_node_new (JSON_NODE_OBJECT), message_json_object);
1080 
1081 		success = !m365_connection_extract_error (*out_node, message->status_code, &local_error);
1082 	} else {
1083 		const gchar *content_type;
1084 
1085 		content_type = message->response_headers ? soup_message_headers_get_content_type (message->response_headers, NULL) : NULL;
1086 
1087 		if (content_type && g_ascii_strcasecmp (content_type, "application/json") == 0) {
1088 			JsonParser *json_parser;
1089 
1090 			json_parser = json_parser_new_immutable ();
1091 
1092 			if (input_stream) {
1093 				success = json_parser_load_from_stream (json_parser, input_stream, cancellable, error);
1094 			} else {
1095 				SoupBuffer *sbuffer;
1096 
1097 				sbuffer = soup_message_body_flatten (message->response_body);
1098 
1099 				if (sbuffer) {
1100 					success = json_parser_load_from_data (json_parser, sbuffer->data, sbuffer->length, error);
1101 					soup_buffer_free (sbuffer);
1102 				} else {
1103 					/* This should not happen, it's for safety check only, thus the string is not localized */
1104 					success = FALSE;
1105 					g_set_error_literal (&local_error, G_IO_ERROR, G_IO_ERROR_FAILED, "No JSON data found");
1106 				}
1107 			}
1108 
1109 			if (success) {
1110 				*out_node = json_parser_steal_root (json_parser);
1111 
1112 				success = !m365_connection_extract_error (*out_node, message->status_code, &local_error);
1113 			}
1114 
1115 			g_object_unref (json_parser);
1116 		}
1117 	}
1118 
1119 	if (!success && *out_node) {
1120 		json_node_unref (*out_node);
1121 		*out_node = NULL;
1122 	}
1123 
1124 	if (local_error)
1125 		g_propagate_error (error, local_error);
1126 
1127 	return success;
1128 }
1129 
1130 static gboolean
m365_connection_send_request_sync(EM365Connection * cnc,SoupMessage * message,EM365ResponseFunc response_func,EM365ConnectionRawDataFunc raw_data_func,gpointer func_user_data,GCancellable * cancellable,GError ** error)1131 m365_connection_send_request_sync (EM365Connection *cnc,
1132 				   SoupMessage *message,
1133 				   EM365ResponseFunc response_func,
1134 				   EM365ConnectionRawDataFunc raw_data_func,
1135 				   gpointer func_user_data,
1136 				   GCancellable *cancellable,
1137 				   GError **error)
1138 {
1139 	SoupSession *soup_session;
1140 	gint need_retry_seconds = 5;
1141 	gboolean success = FALSE, need_retry = TRUE;
1142 
1143 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
1144 	g_return_val_if_fail (SOUP_IS_MESSAGE (message), FALSE);
1145 	g_return_val_if_fail (response_func != NULL || raw_data_func != NULL, FALSE);
1146 	g_return_val_if_fail (response_func == NULL || raw_data_func == NULL, FALSE);
1147 
1148 	while (need_retry && !g_cancellable_is_cancelled (cancellable) && message->status_code != SOUP_STATUS_CANCELLED) {
1149 		need_retry = FALSE;
1150 
1151 		LOCK (cnc);
1152 
1153 		if (cnc->priv->backoff_for_usec) {
1154 			EFlag *flag;
1155 			gint64 wait_ms;
1156 			gulong handler_id = 0;
1157 
1158 			wait_ms = cnc->priv->backoff_for_usec / G_TIME_SPAN_MILLISECOND;
1159 
1160 			UNLOCK (cnc);
1161 
1162 			flag = e_flag_new ();
1163 
1164 			if (cancellable) {
1165 				handler_id = g_cancellable_connect (cancellable, G_CALLBACK (m365_connection_request_cancelled_cb),
1166 					flag, NULL);
1167 			}
1168 
1169 			while (wait_ms > 0 && !g_cancellable_is_cancelled (cancellable) && message->status_code != SOUP_STATUS_CANCELLED) {
1170 				gint64 now = g_get_monotonic_time ();
1171 				gint left_minutes, left_seconds;
1172 
1173 				left_minutes = wait_ms / 60000;
1174 				left_seconds = (wait_ms / 1000) % 60;
1175 
1176 				if (left_minutes > 0) {
1177 					camel_operation_push_message (cancellable,
1178 						g_dngettext (GETTEXT_PACKAGE,
1179 							"Microsoft 365 server is busy, waiting to retry (%d:%02d minute)",
1180 							"Microsoft 365 server is busy, waiting to retry (%d:%02d minutes)", left_minutes),
1181 						left_minutes, left_seconds);
1182 				} else {
1183 					camel_operation_push_message (cancellable,
1184 						g_dngettext (GETTEXT_PACKAGE,
1185 							"Microsoft 365 server is busy, waiting to retry (%d second)",
1186 							"Microsoft 365 server is busy, waiting to retry (%d seconds)", left_seconds),
1187 						left_seconds);
1188 				}
1189 
1190 				e_flag_wait_until (flag, now + (G_TIME_SPAN_MILLISECOND * (wait_ms > 1000 ? 1000 : wait_ms)));
1191 				e_flag_clear (flag);
1192 
1193 				now = g_get_monotonic_time () - now;
1194 				now = now / G_TIME_SPAN_MILLISECOND;
1195 
1196 				if (now >= wait_ms)
1197 					wait_ms = 0;
1198 				wait_ms -= now;
1199 
1200 				camel_operation_pop_message (cancellable);
1201 			}
1202 
1203 			if (handler_id)
1204 				g_cancellable_disconnect (cancellable, handler_id);
1205 
1206 			e_flag_free (flag);
1207 
1208 			LOCK (cnc);
1209 
1210 			cnc->priv->backoff_for_usec = 0;
1211 		}
1212 
1213 		if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
1214 			UNLOCK (cnc);
1215 
1216 			soup_message_set_status (message, SOUP_STATUS_CANCELLED);
1217 
1218 			return FALSE;
1219 		}
1220 
1221 		soup_session = cnc->priv->soup_session ? g_object_ref (cnc->priv->soup_session) : NULL;
1222 
1223 		g_clear_pointer (&cnc->priv->ssl_certificate_pem, g_free);
1224 		cnc->priv->ssl_certificate_errors = 0;
1225 		cnc->priv->ssl_info_set = FALSE;
1226 
1227 		UNLOCK (cnc);
1228 
1229 		if (soup_session &&
1230 		    m365_connection_utils_prepare_message (cnc, soup_session, message, cancellable)) {
1231 			GInputStream *input_stream;
1232 
1233 			input_stream = soup_session_send (soup_session, message, cancellable, error);
1234 
1235 			success = input_stream != NULL;
1236 
1237 			if (success && m365_log_enabled ())
1238 				input_stream = e_soup_logger_attach (message, input_stream);
1239 
1240 			/* Throttling - https://docs.microsoft.com/en-us/graph/throttling  */
1241 			if (message->status_code == 429 ||
1242 			    /* https://docs.microsoft.com/en-us/graph/best-practices-concept#handling-expected-errors */
1243 			    message->status_code == SOUP_STATUS_SERVICE_UNAVAILABLE) {
1244 				need_retry = TRUE;
1245 			} else if (message->status_code == SOUP_STATUS_SSL_FAILED) {
1246 				m365_connection_extract_ssl_data (cnc, message);
1247 			}
1248 
1249 			if (need_retry) {
1250 				const gchar *retry_after_str;
1251 				gint64 retry_after;
1252 
1253 				retry_after_str = message->response_headers ? soup_message_headers_get_one (message->response_headers, "Retry-After") : NULL;
1254 
1255 				if (retry_after_str && *retry_after_str)
1256 					retry_after = g_ascii_strtoll (retry_after_str, NULL, 10);
1257 				else
1258 					retry_after = 0;
1259 
1260 				if (retry_after > 0)
1261 					need_retry_seconds = retry_after;
1262 				else if (need_retry_seconds < 120)
1263 					need_retry_seconds *= 2;
1264 
1265 				LOCK (cnc);
1266 
1267 				if (cnc->priv->backoff_for_usec < need_retry_seconds * G_USEC_PER_SEC)
1268 					cnc->priv->backoff_for_usec = need_retry_seconds * G_USEC_PER_SEC;
1269 
1270 				if (message->status_code == SOUP_STATUS_SERVICE_UNAVAILABLE)
1271 					soup_session_abort (soup_session);
1272 
1273 				UNLOCK (cnc);
1274 
1275 				success = FALSE;
1276 			} else if (success && raw_data_func && SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
1277 				success = raw_data_func (cnc, message, input_stream, func_user_data, cancellable, error);
1278 			} else if (success) {
1279 				JsonNode *node = NULL;
1280 
1281 				success = e_m365_connection_json_node_from_message (message, input_stream, &node, cancellable, error);
1282 
1283 				if (success) {
1284 					gchar *next_link = NULL;
1285 
1286 					success = response_func && response_func (cnc, message, input_stream, node, func_user_data, &next_link, cancellable, error);
1287 
1288 					if (success && next_link && *next_link) {
1289 						SoupURI *suri;
1290 
1291 						suri = soup_uri_new (next_link);
1292 
1293 						/* Check whether the server returned correct nextLink URI */
1294 						g_warn_if_fail (suri != NULL);
1295 
1296 						if (suri) {
1297 							need_retry = TRUE;
1298 
1299 							soup_message_set_uri (message, suri);
1300 							soup_uri_free (suri);
1301 						}
1302 					}
1303 
1304 					g_free (next_link);
1305 				} else if (error && !*error && message->status_code && !SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
1306 					if (message->status_code == SOUP_STATUS_CANCELLED) {
1307 						g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_CANCELLED,
1308 							message->reason_phrase ? message->reason_phrase : soup_status_get_phrase (message->status_code));
1309 					} else {
1310 						g_set_error_literal (error, SOUP_HTTP_ERROR, message->status_code,
1311 							message->reason_phrase ? message->reason_phrase : soup_status_get_phrase (message->status_code));
1312 					}
1313 				}
1314 
1315 				if (node)
1316 					json_node_unref (node);
1317 			}
1318 
1319 			g_clear_object (&input_stream);
1320 		} else {
1321 			if (!message->status_code)
1322 				soup_message_set_status (message, SOUP_STATUS_CANCELLED);
1323 
1324 			if (message->status_code == SOUP_STATUS_CANCELLED) {
1325 				g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_CANCELLED,
1326 					message->reason_phrase ? message->reason_phrase : soup_status_get_phrase (message->status_code));
1327 			} else {
1328 				g_set_error_literal (error, SOUP_HTTP_ERROR, message->status_code,
1329 					message->reason_phrase ? message->reason_phrase : soup_status_get_phrase (message->status_code));
1330 			}
1331 		}
1332 
1333 		g_clear_object (&soup_session);
1334 
1335 		if (need_retry) {
1336 			success = FALSE;
1337 			g_clear_error (error);
1338 		}
1339 	}
1340 
1341 	return success;
1342 }
1343 
1344 static gboolean
e_m365_read_no_response_cb(EM365Connection * cnc,SoupMessage * message,GInputStream * raw_data_stream,gpointer user_data,GCancellable * cancellable,GError ** error)1345 e_m365_read_no_response_cb (EM365Connection *cnc,
1346 			    SoupMessage *message,
1347 			    GInputStream *raw_data_stream,
1348 			    gpointer user_data,
1349 			    GCancellable *cancellable,
1350 			    GError **error)
1351 {
1352 	/* This is used when no response is expected from the server.
1353 	   Read the data stream only if debugging is on, in case
1354 	   the server returns anything interesting. */
1355 
1356 	if (m365_log_enabled ()) {
1357 		gchar buffer[4096];
1358 
1359 		while (g_input_stream_read (raw_data_stream, buffer, sizeof (buffer), cancellable, error) > 0) {
1360 			/* Do nothing, just read it, thus it's shown in the debug output */
1361 		}
1362 	}
1363 
1364 	return TRUE;
1365 }
1366 
1367 static gboolean
e_m365_read_to_byte_array_cb(EM365Connection * cnc,SoupMessage * message,GInputStream * raw_data_stream,gpointer user_data,GCancellable * cancellable,GError ** error)1368 e_m365_read_to_byte_array_cb (EM365Connection *cnc,
1369 			      SoupMessage *message,
1370 			      GInputStream *raw_data_stream,
1371 			      gpointer user_data,
1372 			      GCancellable *cancellable,
1373 			      GError **error)
1374 {
1375 	GByteArray **out_byte_array = user_data;
1376 	gchar buffer[4096];
1377 	gssize n_read;
1378 
1379 	g_return_val_if_fail (message != NULL, FALSE);
1380 	g_return_val_if_fail (out_byte_array != NULL, FALSE);
1381 
1382 	if (!*out_byte_array) {
1383 		goffset content_length;
1384 
1385 		content_length = soup_message_headers_get_content_length (message->response_headers);
1386 
1387 		if (!content_length || content_length > 65536)
1388 			content_length = 65535;
1389 
1390 		*out_byte_array = g_byte_array_sized_new (content_length);
1391 	}
1392 
1393 	while (n_read = g_input_stream_read (raw_data_stream, buffer, sizeof (buffer), cancellable, error), n_read > 0) {
1394 		g_byte_array_append (*out_byte_array, (const guint8 *) buffer, n_read);
1395 	}
1396 
1397 	return !n_read;
1398 }
1399 
1400 typedef struct _EM365ResponseData {
1401 	EM365ConnectionJsonFunc json_func;
1402 	gpointer func_user_data;
1403 	gboolean read_only_once; /* To be able to just try authentication */
1404 	GSList **out_items; /* JsonObject * */
1405 	gchar **out_delta_link; /* set only if available and not NULL */
1406 	GPtrArray *inout_requests; /* SoupMessage *, for the batch request */
1407 } EM365ResponseData;
1408 
1409 static gboolean
e_m365_read_valued_response_cb(EM365Connection * cnc,SoupMessage * message,GInputStream * input_stream,JsonNode * node,gpointer user_data,gchar ** out_next_link,GCancellable * cancellable,GError ** error)1410 e_m365_read_valued_response_cb (EM365Connection *cnc,
1411 				SoupMessage *message,
1412 				GInputStream *input_stream,
1413 				JsonNode *node,
1414 				gpointer user_data,
1415 				gchar **out_next_link,
1416 				GCancellable *cancellable,
1417 				GError **error)
1418 {
1419 	EM365ResponseData *response_data = user_data;
1420 	JsonObject *object;
1421 	JsonArray *value;
1422 	const gchar *delta_link;
1423 	GSList *items = NULL;
1424 	gboolean can_continue = TRUE;
1425 	guint ii, len;
1426 
1427 	g_return_val_if_fail (response_data != NULL, FALSE);
1428 	g_return_val_if_fail (out_next_link != NULL, FALSE);
1429 	g_return_val_if_fail (JSON_NODE_HOLDS_OBJECT (node), FALSE);
1430 
1431 	object = json_node_get_object (node);
1432 	g_return_val_if_fail (object != NULL, FALSE);
1433 
1434 	if (!response_data->read_only_once)
1435 		*out_next_link = g_strdup (e_m365_json_get_string_member (object, "@odata.nextLink", NULL));
1436 
1437 	delta_link = e_m365_json_get_string_member (object, "@odata.deltaLink", NULL);
1438 
1439 	if (delta_link && response_data->out_delta_link)
1440 		*response_data->out_delta_link = g_strdup (delta_link);
1441 
1442 	value = e_m365_json_get_array_member (object, "value");
1443 	g_return_val_if_fail (value != NULL, FALSE);
1444 
1445 	len = json_array_get_length (value);
1446 
1447 	for (ii = 0; ii < len; ii++) {
1448 		JsonNode *elem = json_array_get_element (value, ii);
1449 
1450 		g_warn_if_fail (JSON_NODE_HOLDS_OBJECT (elem));
1451 
1452 		if (JSON_NODE_HOLDS_OBJECT (elem)) {
1453 			JsonObject *elem_object = json_node_get_object (elem);
1454 
1455 			if (elem_object) {
1456 				if (response_data->out_items)
1457 					*response_data->out_items = g_slist_prepend (*response_data->out_items, json_object_ref (elem_object));
1458 				else
1459 					items = g_slist_prepend (items, json_object_ref (elem_object));
1460 			}
1461 		}
1462 	}
1463 
1464 	if (response_data->json_func)
1465 		can_continue = response_data->json_func (cnc, items, response_data->func_user_data, cancellable, error);
1466 
1467 	g_slist_free_full (items, (GDestroyNotify) json_object_unref);
1468 
1469 	return can_continue;
1470 }
1471 
1472 static gboolean
e_m365_read_json_object_response_cb(EM365Connection * cnc,SoupMessage * message,GInputStream * input_stream,JsonNode * node,gpointer user_data,gchar ** out_next_link,GCancellable * cancellable,GError ** error)1473 e_m365_read_json_object_response_cb (EM365Connection *cnc,
1474 				     SoupMessage *message,
1475 				     GInputStream *input_stream,
1476 				     JsonNode *node,
1477 				     gpointer user_data,
1478 				     gchar **out_next_link,
1479 				     GCancellable *cancellable,
1480 				     GError **error)
1481 {
1482 	JsonObject **out_json_object = user_data;
1483 	JsonObject *object;
1484 
1485 	g_return_val_if_fail (out_json_object != NULL, FALSE);
1486 	g_return_val_if_fail (out_next_link != NULL, FALSE);
1487 	g_return_val_if_fail (JSON_NODE_HOLDS_OBJECT (node), FALSE);
1488 
1489 	object = json_node_get_object (node);
1490 	g_return_val_if_fail (object != NULL, FALSE);
1491 
1492 	*out_json_object = json_object_ref (object);
1493 
1494 	return TRUE;
1495 }
1496 
1497 static SoupMessage *
m365_connection_new_soup_message(const gchar * method,const gchar * uri,CSMFlags csm_flags,GError ** error)1498 m365_connection_new_soup_message (const gchar *method,
1499 				  const gchar *uri,
1500 				  CSMFlags csm_flags,
1501 				  GError **error)
1502 {
1503 	SoupMessage *message;
1504 
1505 	g_return_val_if_fail (method != NULL, NULL);
1506 	g_return_val_if_fail (uri != NULL, NULL);
1507 
1508 	message = soup_message_new (method, uri);
1509 
1510 	if (message) {
1511 		soup_message_headers_append (message->request_headers, "Connection", "Close");
1512 		soup_message_headers_append (message->request_headers, "User-Agent", "Evolution-M365/" VERSION);
1513 
1514 		/* Disable caching for proxies (RFC 4918, section 10.4.5) */
1515 		soup_message_headers_append (message->request_headers, "Cache-Control", "no-cache");
1516 		soup_message_headers_append (message->request_headers, "Pragma", "no-cache");
1517 
1518 		if ((csm_flags & CSM_DISABLE_RESPONSE) != 0)
1519 			soup_message_headers_append (message->request_headers, "Prefer", "return=minimal");
1520 	} else {
1521 		g_set_error (error, SOUP_HTTP_ERROR, SOUP_STATUS_MALFORMED, _("Malformed URI: “%s”"), uri);
1522 	}
1523 
1524 	return message;
1525 }
1526 
1527 gboolean
e_m365_connection_get_ssl_error_details(EM365Connection * cnc,gchar ** out_certificate_pem,GTlsCertificateFlags * out_certificate_errors)1528 e_m365_connection_get_ssl_error_details (EM365Connection *cnc,
1529 					 gchar **out_certificate_pem,
1530 					 GTlsCertificateFlags *out_certificate_errors)
1531 {
1532 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
1533 	g_return_val_if_fail (out_certificate_pem != NULL, FALSE);
1534 	g_return_val_if_fail (out_certificate_errors != NULL, FALSE);
1535 
1536 	LOCK (cnc);
1537 
1538 	if (!cnc->priv->ssl_info_set) {
1539 		UNLOCK (cnc);
1540 		return FALSE;
1541 	}
1542 
1543 	*out_certificate_pem = g_strdup (cnc->priv->ssl_certificate_pem);
1544 	*out_certificate_errors = cnc->priv->ssl_certificate_errors;
1545 
1546 	UNLOCK (cnc);
1547 
1548 	return TRUE;
1549 }
1550 
1551 ESourceAuthenticationResult
e_m365_connection_authenticate_sync(EM365Connection * cnc,const gchar * user_override,EM365FolderKind kind,const gchar * group_id,const gchar * folder_id,gchar ** out_certificate_pem,GTlsCertificateFlags * out_certificate_errors,GCancellable * cancellable,GError ** error)1552 e_m365_connection_authenticate_sync (EM365Connection *cnc,
1553 				     const gchar *user_override,
1554 				     EM365FolderKind kind,
1555 				     const gchar *group_id,
1556 				     const gchar *folder_id,
1557 				     gchar **out_certificate_pem,
1558 				     GTlsCertificateFlags *out_certificate_errors,
1559 				     GCancellable *cancellable,
1560 				     GError **error)
1561 {
1562 	ESourceAuthenticationResult result = E_SOURCE_AUTHENTICATION_ERROR;
1563 	JsonObject *object = NULL;
1564 	gboolean success = FALSE;
1565 	GError *local_error = NULL;
1566 
1567 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), result);
1568 
1569 	switch (kind) {
1570 	default:
1571 		g_warn_if_reached ();
1572 		/* Falls through */
1573 	case E_M365_FOLDER_KIND_UNKNOWN:
1574 	case E_M365_FOLDER_KIND_MAIL:
1575 		if (!folder_id || !*folder_id)
1576 			folder_id = "inbox";
1577 
1578 		success = e_m365_connection_get_mail_folder_sync (cnc, user_override, folder_id, "displayName", &object, cancellable, &local_error);
1579 		break;
1580 	case E_M365_FOLDER_KIND_CONTACTS:
1581 		if (!folder_id || !*folder_id)
1582 			folder_id = "contacts";
1583 
1584 		success = e_m365_connection_get_contacts_folder_sync (cnc, user_override, folder_id, "displayName", &object, cancellable, &local_error);
1585 		break;
1586 	case E_M365_FOLDER_KIND_CALENDAR:
1587 		if (folder_id && !*folder_id)
1588 			folder_id = NULL;
1589 
1590 		success = e_m365_connection_get_calendar_folder_sync (cnc, user_override, group_id, folder_id, "name", &object, cancellable, error);
1591 		break;
1592 	case E_M365_FOLDER_KIND_TASKS:
1593 		if (!folder_id || !*folder_id)
1594 			folder_id = "tasks";
1595 
1596 		success = e_m365_connection_get_task_folder_sync (cnc, user_override, group_id, folder_id, "name", &object, cancellable, error);
1597 		break;
1598 	}
1599 
1600 	if (success) {
1601 		result = E_SOURCE_AUTHENTICATION_ACCEPTED;
1602 	} else {
1603 		if (g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_CANCELLED)) {
1604 			local_error->domain = G_IO_ERROR;
1605 			local_error->code = G_IO_ERROR_CANCELLED;
1606 		} else if (g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_SSL_FAILED)) {
1607 			result = E_SOURCE_AUTHENTICATION_ERROR_SSL_FAILED;
1608 
1609 			if (out_certificate_pem || out_certificate_errors)
1610 				e_m365_connection_get_ssl_error_details (cnc, out_certificate_pem, out_certificate_errors);
1611 		} else if (g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_UNAUTHORIZED)) {
1612 			ESoupAuthBearer *bearer;
1613 
1614 			bearer = e_m365_connection_ref_bearer_auth (cnc);
1615 
1616 			if (bearer) {
1617 				LOCK (cnc);
1618 
1619 				if (cnc->priv->impersonate_user) {
1620 					g_propagate_error (error, local_error);
1621 					local_error = NULL;
1622 				} else {
1623 					result = E_SOURCE_AUTHENTICATION_REJECTED;
1624 				}
1625 
1626 				UNLOCK (cnc);
1627 			} else {
1628 				result = E_SOURCE_AUTHENTICATION_REQUIRED;
1629 			}
1630 
1631 			g_clear_object (&bearer);
1632 			g_clear_error (&local_error);
1633 		}
1634 
1635 		if (local_error) {
1636 			g_propagate_error (error, local_error);
1637 			local_error = NULL;
1638 		}
1639 	}
1640 
1641 	if (object)
1642 		json_object_unref (object);
1643 
1644 	g_clear_error (&local_error);
1645 
1646 	return result;
1647 }
1648 
1649 gboolean
e_m365_connection_disconnect_sync(EM365Connection * cnc,GCancellable * cancellable,GError ** error)1650 e_m365_connection_disconnect_sync (EM365Connection *cnc,
1651 				   GCancellable *cancellable,
1652 				   GError **error)
1653 {
1654 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
1655 
1656 	LOCK (cnc);
1657 
1658 	soup_session_abort (cnc->priv->soup_session);
1659 
1660 	UNLOCK (cnc);
1661 
1662 	return TRUE;
1663 }
1664 
1665 /* Expects NULL-terminated pair of parameters 'name', 'value'; if 'value' is NULL, the parameter is skipped.
1666    An empty 'name' can add the 'value' into the path. These can be only before query parameters. */
1667 gchar *
e_m365_connection_construct_uri(EM365Connection * cnc,gboolean include_user,const gchar * user_override,EM365ApiVersion api_version,const gchar * api_part,const gchar * resource,const gchar * id,const gchar * path,...)1668 e_m365_connection_construct_uri (EM365Connection *cnc,
1669 				 gboolean include_user,
1670 				 const gchar *user_override,
1671 				 EM365ApiVersion api_version,
1672 				 const gchar *api_part,
1673 				 const gchar *resource,
1674 				 const gchar *id,
1675 				 const gchar *path,
1676 				 ...)
1677 {
1678 	va_list args;
1679 	const gchar *name, *value;
1680 	gboolean first_param = TRUE;
1681 	GString *uri;
1682 
1683 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), NULL);
1684 
1685 	if (!api_part)
1686 		api_part = "users";
1687 
1688 	uri = g_string_sized_new (128);
1689 
1690 	/* https://graph.microsoft.com/v1.0/users/XUSERX/mailFolders */
1691 
1692 	g_string_append (uri, "https://graph.microsoft.com");
1693 
1694 	switch (api_version) {
1695 	case E_M365_API_V1_0:
1696 		g_string_append_c (uri, '/');
1697 		g_string_append (uri, "v1.0");
1698 		break;
1699 	case E_M365_API_BETA:
1700 		g_string_append_c (uri, '/');
1701 		g_string_append (uri, "beta");
1702 		break;
1703 	}
1704 
1705 	if (*api_part) {
1706 		g_string_append_c (uri, '/');
1707 		g_string_append (uri, api_part);
1708 	}
1709 
1710 	if (include_user) {
1711 		const gchar *use_user;
1712 
1713 		LOCK (cnc);
1714 
1715 		if (user_override)
1716 			use_user = user_override;
1717 		else if (cnc->priv->impersonate_user)
1718 			use_user = cnc->priv->impersonate_user;
1719 		else
1720 			use_user = cnc->priv->user;
1721 
1722 		if (use_user) {
1723 			gchar *encoded;
1724 
1725 			encoded = soup_uri_encode (use_user, NULL);
1726 
1727 			g_string_append_c (uri, '/');
1728 			g_string_append (uri, encoded);
1729 
1730 			g_free (encoded);
1731 		}
1732 
1733 		UNLOCK (cnc);
1734 	}
1735 
1736 	if (resource && *resource) {
1737 		g_string_append_c (uri, '/');
1738 		g_string_append (uri, resource);
1739 	}
1740 
1741 	if (id && *id) {
1742 		g_string_append_c (uri, '/');
1743 		g_string_append (uri, id);
1744 	}
1745 
1746 	if (path && *path) {
1747 		g_string_append_c (uri, '/');
1748 		g_string_append (uri, path);
1749 	}
1750 
1751 	va_start (args, path);
1752 
1753 	name = va_arg (args, const gchar *);
1754 
1755 	while (name) {
1756 		value = va_arg (args, const gchar *);
1757 
1758 		if (*name && value) {
1759 			g_string_append_c (uri, first_param ? '?' : '&');
1760 
1761 			first_param = FALSE;
1762 
1763 			g_string_append (uri, name);
1764 			g_string_append_c (uri, '=');
1765 
1766 			if (*value) {
1767 				gchar *encoded;
1768 
1769 				encoded = soup_uri_encode (value, NULL);
1770 
1771 				g_string_append (uri, encoded);
1772 
1773 				g_free (encoded);
1774 			}
1775 		} else if (!*name && value && *value) {
1776 			/* Warn when adding path after additional query parameters */
1777 			g_warn_if_fail (first_param);
1778 
1779 			g_string_append_c (uri, '/');
1780 			g_string_append (uri, value);
1781 		}
1782 
1783 		name = va_arg (args, const gchar *);
1784 	}
1785 
1786 	va_end (args);
1787 
1788 	return g_string_free (uri, FALSE);
1789 }
1790 
1791 static void
e_m365_connection_set_json_body(SoupMessage * message,JsonBuilder * builder)1792 e_m365_connection_set_json_body (SoupMessage *message,
1793 				 JsonBuilder *builder)
1794 {
1795 	JsonGenerator *generator;
1796 	JsonNode *node;
1797 	gchar *data;
1798 	gsize data_length = 0;
1799 
1800 	g_return_if_fail (SOUP_IS_MESSAGE (message));
1801 	g_return_if_fail (builder != NULL);
1802 
1803 	node = json_builder_get_root (builder);
1804 
1805 	generator = json_generator_new ();
1806 	json_generator_set_root (generator, node);
1807 
1808 	data = json_generator_to_data (generator, &data_length);
1809 
1810 	soup_message_headers_set_content_type (message->request_headers, "application/json", NULL);
1811 
1812 	if (data)
1813 		soup_message_body_append_take (message->request_body, (guchar *) data, data_length);
1814 
1815 	g_object_unref (generator);
1816 	json_node_unref (node);
1817 }
1818 
1819 static void
e_m365_fill_message_headers_cb(JsonObject * object,const gchar * member_name,JsonNode * member_node,gpointer user_data)1820 e_m365_fill_message_headers_cb (JsonObject *object,
1821 				const gchar *member_name,
1822 				JsonNode *member_node,
1823 				gpointer user_data)
1824 {
1825 	SoupMessage *message = user_data;
1826 
1827 	g_return_if_fail (message != NULL);
1828 	g_return_if_fail (member_name != NULL);
1829 	g_return_if_fail (member_node != NULL);
1830 
1831 	if (JSON_NODE_HOLDS_VALUE (member_node)) {
1832 		const gchar *value;
1833 
1834 		value = json_node_get_string (member_node);
1835 
1836 		if (value)
1837 			soup_message_headers_replace (message->response_headers, member_name, value);
1838 	}
1839 }
1840 
1841 static void
e_m365_connection_fill_batch_response(SoupMessage * message,JsonObject * object)1842 e_m365_connection_fill_batch_response (SoupMessage *message,
1843 				       JsonObject *object)
1844 {
1845 	JsonObject *subobject;
1846 
1847 	g_return_if_fail (SOUP_IS_MESSAGE (message));
1848 	g_return_if_fail (object != NULL);
1849 
1850 	message->status_code = e_m365_json_get_int_member (object, "status", SOUP_STATUS_MALFORMED);
1851 
1852 	subobject = e_m365_json_get_object_member (object, "headers");
1853 
1854 	if (subobject)
1855 		json_object_foreach_member (subobject, e_m365_fill_message_headers_cb, message);
1856 
1857 	subobject = e_m365_json_get_object_member (object, "body");
1858 
1859 	if (subobject)
1860 		g_object_set_data_full (G_OBJECT (message), X_EVO_M365_DATA, json_object_ref (subobject), (GDestroyNotify) json_object_unref);
1861 }
1862 
1863 static gboolean
e_m365_read_batch_response_cb(EM365Connection * cnc,SoupMessage * message,GInputStream * input_stream,JsonNode * node,gpointer user_data,gchar ** out_next_link,GCancellable * cancellable,GError ** error)1864 e_m365_read_batch_response_cb (EM365Connection *cnc,
1865 			       SoupMessage *message,
1866 			       GInputStream *input_stream,
1867 			       JsonNode *node,
1868 			       gpointer user_data,
1869 			       gchar **out_next_link,
1870 			       GCancellable *cancellable,
1871 			       GError **error)
1872 {
1873 	GPtrArray *requests = user_data;
1874 	JsonObject *object;
1875 	JsonArray *responses;
1876 	guint ii, len;
1877 
1878 	g_return_val_if_fail (requests != NULL, FALSE);
1879 	g_return_val_if_fail (out_next_link != NULL, FALSE);
1880 	g_return_val_if_fail (JSON_NODE_HOLDS_OBJECT (node), FALSE);
1881 
1882 	object = json_node_get_object (node);
1883 	g_return_val_if_fail (object != NULL, FALSE);
1884 
1885 	*out_next_link = g_strdup (e_m365_json_get_string_member (object, "@odata.nextLink", NULL));
1886 
1887 	responses = e_m365_json_get_array_member (object, "responses");
1888 	g_return_val_if_fail (responses != NULL, FALSE);
1889 
1890 	len = json_array_get_length (responses);
1891 
1892 	for (ii = 0; ii < len; ii++) {
1893 		JsonNode *elem = json_array_get_element (responses, ii);
1894 
1895 		g_warn_if_fail (JSON_NODE_HOLDS_OBJECT (elem));
1896 
1897 		if (JSON_NODE_HOLDS_OBJECT (elem)) {
1898 			JsonObject *elem_object = json_node_get_object (elem);
1899 
1900 			if (elem_object) {
1901 				const gchar *id_str;
1902 
1903 				id_str = e_m365_json_get_string_member (elem_object, "id", NULL);
1904 
1905 				if (id_str) {
1906 					guint id;
1907 
1908 					id = (guint) g_ascii_strtoull (id_str, NULL, 10);
1909 
1910 					if (id < requests->len)
1911 						e_m365_connection_fill_batch_response (g_ptr_array_index (requests, id), elem_object);
1912 				}
1913 			}
1914 		}
1915 	}
1916 
1917 	return TRUE;
1918 }
1919 
1920 /* https://docs.microsoft.com/en-us/graph/json-batching */
1921 
1922 static gboolean
e_m365_connection_batch_request_internal_sync(EM365Connection * cnc,EM365ApiVersion api_version,GPtrArray * requests,GCancellable * cancellable,GError ** error)1923 e_m365_connection_batch_request_internal_sync (EM365Connection *cnc,
1924 					       EM365ApiVersion api_version,
1925 					       GPtrArray *requests, /* SoupMessage * */
1926 					       GCancellable *cancellable,
1927 					       GError **error)
1928 {
1929 	SoupMessage *message;
1930 	JsonBuilder *builder;
1931 	gboolean success = TRUE;
1932 	gchar *uri, buff[128];
1933 	guint ii;
1934 
1935 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
1936 	g_return_val_if_fail (requests != NULL, FALSE);
1937 	g_return_val_if_fail (requests->len > 0, FALSE);
1938 	g_return_val_if_fail (requests->len <= E_M365_BATCH_MAX_REQUESTS, FALSE);
1939 
1940 	uri = e_m365_connection_construct_uri (cnc, FALSE, NULL, api_version, "",
1941 		"$batch", NULL, NULL, NULL);
1942 
1943 	message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DEFAULT, error);
1944 
1945 	if (!message) {
1946 		g_free (uri);
1947 
1948 		return FALSE;
1949 	}
1950 
1951 	g_free (uri);
1952 
1953 	builder = json_builder_new_immutable ();
1954 
1955 	e_m365_json_begin_object_member (builder, NULL);
1956 	e_m365_json_begin_array_member (builder, "requests");
1957 
1958 	for (ii = 0; success && ii < requests->len; ii++) {
1959 		SoupMessageHeadersIter iter;
1960 		SoupMessage *submessage;
1961 		SoupURI *suri;
1962 		gboolean has_headers = FALSE;
1963 		const gchar *hdr_name, *hdr_value, *use_uri;
1964 		gboolean is_application_json = FALSE;
1965 
1966 		submessage = g_ptr_array_index (requests, ii);
1967 
1968 		if (!submessage)
1969 			continue;
1970 
1971 		submessage->status_code = SOUP_STATUS_IO_ERROR;
1972 
1973 		suri = soup_message_get_uri (submessage);
1974 		uri = suri ? soup_uri_to_string (suri, TRUE) : NULL;
1975 
1976 		if (!uri) {
1977 			submessage->status_code = SOUP_STATUS_MALFORMED;
1978 			continue;
1979 		}
1980 
1981 		use_uri = uri;
1982 
1983 		/* The 'url' is without the API part, it is derived from the main request */
1984 		if (g_str_has_prefix (use_uri, "/v1.0/") ||
1985 		    g_str_has_prefix (use_uri, "/beta/"))
1986 			use_uri += 5;
1987 
1988 		g_snprintf (buff, sizeof (buff), "%d", ii);
1989 
1990 		e_m365_json_begin_object_member (builder, NULL);
1991 
1992 		e_m365_json_add_string_member (builder, "id", buff);
1993 		e_m365_json_add_string_member (builder, "method", submessage->method);
1994 		e_m365_json_add_string_member (builder, "url", use_uri);
1995 
1996 		g_free (uri);
1997 
1998 		soup_message_headers_iter_init (&iter, submessage->request_headers);
1999 
2000 		while (soup_message_headers_iter_next (&iter, &hdr_name, &hdr_value)) {
2001 			if (hdr_name && *hdr_name && hdr_value &&
2002 			    !camel_strcase_equal (hdr_name, "Connection") &&
2003 			    !camel_strcase_equal (hdr_name, "User-Agent")) {
2004 				if (camel_strcase_equal (hdr_name, "Content-Type") &&
2005 				    camel_strcase_equal (hdr_value, "application/json"))
2006 					is_application_json = TRUE;
2007 
2008 				if (!has_headers) {
2009 					has_headers = TRUE;
2010 
2011 					e_m365_json_begin_object_member (builder, "headers");
2012 				}
2013 
2014 				e_m365_json_add_string_member (builder, hdr_name, hdr_value);
2015 			}
2016 		}
2017 
2018 		if (has_headers)
2019 			e_m365_json_end_object_member (builder); /* headers */
2020 
2021 		if (submessage->request_body) {
2022 			SoupBuffer *sbuffer;
2023 
2024 			sbuffer = soup_message_body_flatten (submessage->request_body);
2025 
2026 			if (sbuffer && sbuffer->length > 0) {
2027 				if (is_application_json) {
2028 					/* The server needs it unpacked, not as a plain string */
2029 					JsonParser *parser;
2030 					JsonNode *node;
2031 
2032 					parser = json_parser_new_immutable ();
2033 
2034 					success = json_parser_load_from_data (parser, sbuffer->data, sbuffer->length, error);
2035 
2036 					if (!success)
2037 						g_prefix_error (error, "%s", _("Failed to parse own Json data"));
2038 
2039 					node = success ? json_parser_steal_root (parser) : NULL;
2040 
2041 					if (node) {
2042 						json_builder_set_member_name (builder, "body");
2043 						json_builder_add_value (builder, node);
2044 					}
2045 
2046 					g_clear_object (&parser);
2047 				} else {
2048 					e_m365_json_add_string_member (builder, "body", sbuffer->data);
2049 				}
2050 			}
2051 
2052 			if (sbuffer)
2053 				soup_buffer_free (sbuffer);
2054 		}
2055 
2056 		e_m365_json_end_object_member (builder); /* unnamed object */
2057 	}
2058 
2059 	e_m365_json_end_array_member (builder);
2060 	e_m365_json_end_object_member (builder);
2061 
2062 	e_m365_connection_set_json_body (message, builder);
2063 
2064 	soup_message_headers_append (message->request_headers, "Accept", "application/json");
2065 
2066 	g_object_unref (builder);
2067 
2068 	success = success && m365_connection_send_request_sync (cnc, message, e_m365_read_batch_response_cb, NULL, requests, cancellable, error);
2069 
2070 	g_clear_object (&message);
2071 
2072 	return success;
2073 }
2074 
2075 /* The 'requests' contains a SoupMessage * objects, from which are read
2076    the request data and on success the SoupMessage's 'response' properties
2077    are filled accordingly.
2078  */
2079 gboolean
e_m365_connection_batch_request_sync(EM365Connection * cnc,EM365ApiVersion api_version,GPtrArray * requests,GCancellable * cancellable,GError ** error)2080 e_m365_connection_batch_request_sync (EM365Connection *cnc,
2081 				      EM365ApiVersion api_version,
2082 				      GPtrArray *requests, /* SoupMessage * */
2083 				      GCancellable *cancellable,
2084 				      GError **error)
2085 {
2086 	GPtrArray *use_requests;
2087 	gint need_retry_seconds = 5;
2088 	gboolean success, need_retry = TRUE;
2089 
2090 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
2091 	g_return_val_if_fail (requests != NULL, FALSE);
2092 	g_return_val_if_fail (requests->len > 0, FALSE);
2093 	g_return_val_if_fail (requests->len <= E_M365_BATCH_MAX_REQUESTS, FALSE);
2094 
2095 	use_requests = requests;
2096 
2097 	while (need_retry) {
2098 		need_retry = FALSE;
2099 
2100 		success = e_m365_connection_batch_request_internal_sync (cnc, api_version, use_requests, cancellable, error);
2101 
2102 		if (success) {
2103 			GPtrArray *new_requests = NULL;
2104 			gint delay_seconds = 0;
2105 			gint ii;
2106 
2107 			for (ii = 0; ii < use_requests->len; ii++) {
2108 				SoupMessage *message = g_ptr_array_index (use_requests, ii);
2109 
2110 				if (!message)
2111 					continue;
2112 
2113 				/* Throttling - https://docs.microsoft.com/en-us/graph/throttling  */
2114 				if (message->status_code == 429 ||
2115 				    /* https://docs.microsoft.com/en-us/graph/best-practices-concept#handling-expected-errors */
2116 				    message->status_code == SOUP_STATUS_SERVICE_UNAVAILABLE) {
2117 					const gchar *retry_after_str;
2118 					gint64 retry_after;
2119 
2120 					need_retry = TRUE;
2121 
2122 					if (!new_requests)
2123 						new_requests = g_ptr_array_sized_new (use_requests->len);
2124 
2125 					g_ptr_array_add (new_requests, message);
2126 
2127 					retry_after_str = message->response_headers ? soup_message_headers_get_one (message->response_headers, "Retry-After") : NULL;
2128 
2129 					if (retry_after_str && *retry_after_str)
2130 						retry_after = g_ascii_strtoll (retry_after_str, NULL, 10);
2131 					else
2132 						retry_after = 0;
2133 
2134 					if (retry_after > 0)
2135 						delay_seconds = MAX (delay_seconds, retry_after);
2136 					else
2137 						delay_seconds = MAX (delay_seconds, need_retry_seconds);
2138 				}
2139 			}
2140 
2141 			if (new_requests) {
2142 				if (delay_seconds)
2143 					need_retry_seconds = delay_seconds;
2144 				else if (need_retry_seconds < 120)
2145 					need_retry_seconds *= 2;
2146 
2147 				LOCK (cnc);
2148 
2149 				if (cnc->priv->backoff_for_usec < need_retry_seconds * G_USEC_PER_SEC)
2150 					cnc->priv->backoff_for_usec = need_retry_seconds * G_USEC_PER_SEC;
2151 
2152 				UNLOCK (cnc);
2153 
2154 				if (use_requests != requests)
2155 					g_ptr_array_free (use_requests, TRUE);
2156 
2157 				use_requests = new_requests;
2158 			}
2159 		}
2160 	}
2161 
2162 	if (use_requests != requests)
2163 		g_ptr_array_free (use_requests, TRUE);
2164 
2165 	return success;
2166 }
2167 
2168 /* This can be used as an EM365ConnectionJsonFunc function, it only
2169    copies items of 'results' into 'user_data', which is supposed
2170    to be a pointer to a GSList *. */
2171 gboolean
e_m365_connection_call_gather_into_slist(EM365Connection * cnc,const GSList * results,gpointer user_data,GCancellable * cancellable,GError ** error)2172 e_m365_connection_call_gather_into_slist (EM365Connection *cnc,
2173 					  const GSList *results, /* JsonObject * - the returned objects from the server */
2174 					  gpointer user_data, /* expects GSList **, aka pointer to a GSList *, where it copies the 'results' */
2175 					  GCancellable *cancellable,
2176 					  GError **error)
2177 {
2178 	GSList **out_results = user_data, *link;
2179 
2180 	g_return_val_if_fail (out_results != NULL, FALSE);
2181 
2182 	for (link = (GSList *) results; link; link = g_slist_next (link)) {
2183 		JsonObject *obj = link->data;
2184 
2185 		if (obj)
2186 			*out_results = g_slist_prepend (*out_results, json_object_ref (obj));
2187 	}
2188 
2189 	return TRUE;
2190 }
2191 
2192 /* https://docs.microsoft.com/en-us/graph/api/outlookuser-list-mastercategories?view=graph-rest-1.0&tabs=http */
2193 
2194 gboolean
e_m365_connection_get_categories_sync(EM365Connection * cnc,const gchar * user_override,GSList ** out_categories,GCancellable * cancellable,GError ** error)2195 e_m365_connection_get_categories_sync (EM365Connection *cnc,
2196 				       const gchar *user_override,
2197 				       GSList **out_categories, /* EM365Category * */
2198 				       GCancellable *cancellable,
2199 				       GError **error)
2200 {
2201 	EM365ResponseData rd;
2202 	SoupMessage *message;
2203 	gchar *uri;
2204 	gboolean success;
2205 
2206 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
2207 	g_return_val_if_fail (out_categories != NULL, FALSE);
2208 
2209 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
2210 		"outlook",
2211 		"masterCategories",
2212 		NULL,
2213 		NULL);
2214 
2215 	message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
2216 
2217 	if (!message) {
2218 		g_free (uri);
2219 
2220 		return FALSE;
2221 	}
2222 
2223 	g_free (uri);
2224 
2225 	memset (&rd, 0, sizeof (EM365ResponseData));
2226 
2227 	rd.out_items = out_categories;
2228 
2229 	success = m365_connection_send_request_sync (cnc, message, e_m365_read_valued_response_cb, NULL, &rd, cancellable, error);
2230 
2231 	g_clear_object (&message);
2232 
2233 	return success;
2234 }
2235 
2236 /* https://docs.microsoft.com/en-us/graph/api/user-list-mailfolders?view=graph-rest-1.0&tabs=http */
2237 
2238 gboolean
e_m365_connection_list_mail_folders_sync(EM365Connection * cnc,const gchar * user_override,const gchar * from_path,const gchar * select,GSList ** out_folders,GCancellable * cancellable,GError ** error)2239 e_m365_connection_list_mail_folders_sync (EM365Connection *cnc,
2240 					  const gchar *user_override, /* for which user, NULL to use the account user */
2241 					  const gchar *from_path, /* path for the folder to read, NULL for top user folder */
2242 					  const gchar *select, /* properties to select, nullable */
2243 					  GSList **out_folders, /* EM365MailFolder * - the returned mailFolder objects */
2244 					  GCancellable *cancellable,
2245 					  GError **error)
2246 {
2247 	EM365ResponseData rd;
2248 	SoupMessage *message;
2249 	gchar *uri;
2250 	gboolean success;
2251 
2252 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
2253 	g_return_val_if_fail (out_folders != NULL, FALSE);
2254 
2255 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
2256 		"mailFolders",
2257 		NULL,
2258 		from_path,
2259 		"$select", select,
2260 		NULL);
2261 
2262 	message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
2263 
2264 	if (!message) {
2265 		g_free (uri);
2266 
2267 		return FALSE;
2268 	}
2269 
2270 	g_free (uri);
2271 
2272 	memset (&rd, 0, sizeof (EM365ResponseData));
2273 
2274 	rd.out_items = out_folders;
2275 
2276 	success = m365_connection_send_request_sync (cnc, message, e_m365_read_valued_response_cb, NULL, &rd, cancellable, error);
2277 
2278 	g_clear_object (&message);
2279 
2280 	return success;
2281 }
2282 
2283 gboolean
e_m365_connection_get_folders_delta_sync(EM365Connection * cnc,const gchar * user_override,EM365FolderKind kind,const gchar * select,const gchar * delta_link,guint max_page_size,EM365ConnectionJsonFunc func,gpointer func_user_data,gchar ** out_delta_link,GCancellable * cancellable,GError ** error)2284 e_m365_connection_get_folders_delta_sync (EM365Connection *cnc,
2285 					  const gchar *user_override, /* for which user, NULL to use the account user */
2286 					  EM365FolderKind kind,
2287 					  const gchar *select, /* properties to select, nullable */
2288 					  const gchar *delta_link, /* previous delta link */
2289 					  guint max_page_size, /* 0 for default by the server */
2290 					  EM365ConnectionJsonFunc func, /* function to call with each result set */
2291 					  gpointer func_user_data, /* user data passed into the 'func' */
2292 					  gchar **out_delta_link,
2293 					  GCancellable *cancellable,
2294 					  GError **error)
2295 {
2296 	EM365ResponseData rd;
2297 	SoupMessage *message = NULL;
2298 	gboolean success;
2299 
2300 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
2301 	g_return_val_if_fail (out_delta_link != NULL, FALSE);
2302 	g_return_val_if_fail (func != NULL, FALSE);
2303 
2304 	if (delta_link)
2305 		message = m365_connection_new_soup_message (SOUP_METHOD_GET, delta_link, CSM_DEFAULT, NULL);
2306 
2307 	if (!message) {
2308 		const gchar *kind_str = NULL;
2309 		gchar *uri;
2310 
2311 		switch (kind) {
2312 		case E_M365_FOLDER_KIND_CONTACTS:
2313 			kind_str = "contactFolders";
2314 			break;
2315 		case E_M365_FOLDER_KIND_MAIL:
2316 			kind_str = "mailFolders";
2317 			break;
2318 		default:
2319 			g_warn_if_reached ();
2320 			break;
2321 		}
2322 
2323 		g_return_val_if_fail (kind_str != NULL, FALSE);
2324 
2325 		uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
2326 			kind_str,
2327 			NULL,
2328 			"delta",
2329 			"$select", select,
2330 			NULL);
2331 
2332 		message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
2333 
2334 		if (!message) {
2335 			g_free (uri);
2336 
2337 			return FALSE;
2338 		}
2339 
2340 		g_free (uri);
2341 	}
2342 
2343 	if (max_page_size > 0) {
2344 		gchar *prefer_value;
2345 
2346 		prefer_value = g_strdup_printf ("odata.maxpagesize=%u", max_page_size);
2347 
2348 		soup_message_headers_append (message->request_headers, "Prefer", prefer_value);
2349 
2350 		g_free (prefer_value);
2351 	}
2352 
2353 	memset (&rd, 0, sizeof (EM365ResponseData));
2354 
2355 	rd.json_func = func;
2356 	rd.func_user_data = func_user_data;
2357 	rd.out_delta_link = out_delta_link;
2358 
2359 	success = m365_connection_send_request_sync (cnc, message, e_m365_read_valued_response_cb, NULL, &rd, cancellable, error);
2360 
2361 	g_clear_object (&message);
2362 
2363 	return success;
2364 }
2365 
2366 /* https://docs.microsoft.com/en-us/graph/api/mailfolder-get?view=graph-rest-1.0&tabs=http */
2367 
2368 gboolean
e_m365_connection_get_mail_folder_sync(EM365Connection * cnc,const gchar * user_override,const gchar * folder_id,const gchar * select,EM365MailFolder ** out_folder,GCancellable * cancellable,GError ** error)2369 e_m365_connection_get_mail_folder_sync (EM365Connection *cnc,
2370 					const gchar *user_override, /* for which user, NULL to use the account user */
2371 					const gchar *folder_id, /* nullable - then the 'inbox' is used */
2372 					const gchar *select, /* nullable - properties to select */
2373 					EM365MailFolder **out_folder,
2374 					GCancellable *cancellable,
2375 					GError **error)
2376 {
2377 	SoupMessage *message;
2378 	gchar *uri;
2379 	gboolean success;
2380 
2381 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
2382 	g_return_val_if_fail (out_folder != NULL, FALSE);
2383 
2384 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
2385 		"mailFolders",
2386 		folder_id ? folder_id : "inbox",
2387 		NULL,
2388 		"$select", select,
2389 		NULL);
2390 
2391 	message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
2392 
2393 	if (!message) {
2394 		g_free (uri);
2395 
2396 		return FALSE;
2397 	}
2398 
2399 	g_free (uri);
2400 
2401 	success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL, out_folder, cancellable, error);
2402 
2403 	g_clear_object (&message);
2404 
2405 	return success;
2406 }
2407 
2408 /* https://docs.microsoft.com/en-us/graph/api/user-post-mailfolders?view=graph-rest-1.0&tabs=http
2409    https://docs.microsoft.com/en-us/graph/api/mailfolder-post-childfolders?view=graph-rest-1.0&tabs=http */
2410 
2411 gboolean
e_m365_connection_create_mail_folder_sync(EM365Connection * cnc,const gchar * user_override,const gchar * parent_folder_id,const gchar * display_name,EM365MailFolder ** out_mail_folder,GCancellable * cancellable,GError ** error)2412 e_m365_connection_create_mail_folder_sync (EM365Connection *cnc,
2413 					   const gchar *user_override, /* for which user, NULL to use the account user */
2414 					   const gchar *parent_folder_id, /* NULL for the folder root */
2415 					   const gchar *display_name,
2416 					   EM365MailFolder **out_mail_folder,
2417 					   GCancellable *cancellable,
2418 					   GError **error)
2419 {
2420 	SoupMessage *message;
2421 	JsonBuilder *builder;
2422 	gboolean success;
2423 	gchar *uri;
2424 
2425 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
2426 	g_return_val_if_fail (display_name != NULL, FALSE);
2427 	g_return_val_if_fail (out_mail_folder != NULL, FALSE);
2428 
2429 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
2430 		"mailFolders",
2431 		parent_folder_id,
2432 		parent_folder_id ? "childFolders" : NULL,
2433 		NULL);
2434 
2435 	message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DEFAULT, error);
2436 
2437 	if (!message) {
2438 		g_free (uri);
2439 
2440 		return FALSE;
2441 	}
2442 
2443 	g_free (uri);
2444 
2445 	builder = json_builder_new_immutable ();
2446 
2447 	e_m365_json_begin_object_member (builder, NULL);
2448 	e_m365_json_add_string_member (builder, "displayName", display_name);
2449 	e_m365_json_end_object_member (builder);
2450 
2451 	e_m365_connection_set_json_body (message, builder);
2452 
2453 	g_object_unref (builder);
2454 
2455 	success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL, out_mail_folder, cancellable, error);
2456 
2457 	g_clear_object (&message);
2458 
2459 	return success;
2460 }
2461 
2462 /* https://docs.microsoft.com/en-us/graph/api/mailfolder-delete?view=graph-rest-1.0&tabs=http */
2463 
2464 gboolean
e_m365_connection_delete_mail_folder_sync(EM365Connection * cnc,const gchar * user_override,const gchar * folder_id,GCancellable * cancellable,GError ** error)2465 e_m365_connection_delete_mail_folder_sync (EM365Connection *cnc,
2466 					   const gchar *user_override, /* for which user, NULL to use the account user */
2467 					   const gchar *folder_id,
2468 					   GCancellable *cancellable,
2469 					   GError **error)
2470 {
2471 	SoupMessage *message;
2472 	gboolean success;
2473 	gchar *uri;
2474 
2475 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
2476 	g_return_val_if_fail (folder_id != NULL, FALSE);
2477 
2478 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
2479 		"mailFolders", folder_id, NULL, NULL);
2480 
2481 	message = m365_connection_new_soup_message (SOUP_METHOD_DELETE, uri, CSM_DEFAULT, error);
2482 
2483 	if (!message) {
2484 		g_free (uri);
2485 
2486 		return FALSE;
2487 	}
2488 
2489 	g_free (uri);
2490 
2491 	success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL, cancellable, error);
2492 
2493 	g_clear_object (&message);
2494 
2495 	return success;
2496 }
2497 
2498 /* https://docs.microsoft.com/en-us/graph/api/mailfolder-copy?view=graph-rest-1.0&tabs=http
2499    https://docs.microsoft.com/en-us/graph/api/mailfolder-move?view=graph-rest-1.0&tabs=http
2500  */
2501 gboolean
e_m365_connection_copy_move_mail_folder_sync(EM365Connection * cnc,const gchar * user_override,const gchar * src_folder_id,const gchar * des_folder_id,gboolean do_copy,EM365MailFolder ** out_mail_folder,GCancellable * cancellable,GError ** error)2502 e_m365_connection_copy_move_mail_folder_sync (EM365Connection *cnc,
2503 					      const gchar *user_override, /* for which user, NULL to use the account user */
2504 					      const gchar *src_folder_id,
2505 					      const gchar *des_folder_id,
2506 					      gboolean do_copy,
2507 					      EM365MailFolder **out_mail_folder,
2508 					      GCancellable *cancellable,
2509 					      GError **error)
2510 {
2511 	SoupMessage *message;
2512 	JsonBuilder *builder;
2513 	gboolean success;
2514 	gchar *uri;
2515 
2516 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
2517 	g_return_val_if_fail (src_folder_id != NULL, FALSE);
2518 	g_return_val_if_fail (des_folder_id != NULL, FALSE);
2519 
2520 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
2521 		"mailFolders",
2522 		src_folder_id,
2523 		do_copy ? "copy" : "move",
2524 		NULL);
2525 
2526 	message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DEFAULT, error);
2527 
2528 	if (!message) {
2529 		g_free (uri);
2530 
2531 		return FALSE;
2532 	}
2533 
2534 	g_free (uri);
2535 
2536 	builder = json_builder_new_immutable ();
2537 
2538 	e_m365_json_begin_object_member (builder, NULL);
2539 	e_m365_json_add_string_member (builder, "destinationId", des_folder_id);
2540 	e_m365_json_end_object_member (builder);
2541 
2542 	e_m365_connection_set_json_body (message, builder);
2543 
2544 	g_object_unref (builder);
2545 
2546 	success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL, out_mail_folder, cancellable, error);
2547 
2548 	g_clear_object (&message);
2549 
2550 	return success;
2551 }
2552 
2553 /* https://docs.microsoft.com/en-us/graph/api/mailfolder-update?view=graph-rest-1.0&tabs=http */
2554 
2555 gboolean
e_m365_connection_rename_mail_folder_sync(EM365Connection * cnc,const gchar * user_override,const gchar * folder_id,const gchar * display_name,EM365MailFolder ** out_mail_folder,GCancellable * cancellable,GError ** error)2556 e_m365_connection_rename_mail_folder_sync (EM365Connection *cnc,
2557 					   const gchar *user_override, /* for which user, NULL to use the account user */
2558 					   const gchar *folder_id,
2559 					   const gchar *display_name,
2560 					   EM365MailFolder **out_mail_folder,
2561 					   GCancellable *cancellable,
2562 					   GError **error)
2563 {
2564 	SoupMessage *message;
2565 	JsonBuilder *builder;
2566 	gboolean success;
2567 	gchar *uri;
2568 
2569 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
2570 	g_return_val_if_fail (folder_id != NULL, FALSE);
2571 	g_return_val_if_fail (display_name != NULL, FALSE);
2572 
2573 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
2574 		"mailFolders",
2575 		folder_id,
2576 		NULL,
2577 		NULL);
2578 
2579 	message = m365_connection_new_soup_message ("PATCH", uri, CSM_DEFAULT, error);
2580 
2581 	if (!message) {
2582 		g_free (uri);
2583 
2584 		return FALSE;
2585 	}
2586 
2587 	g_free (uri);
2588 
2589 	builder = json_builder_new_immutable ();
2590 
2591 	e_m365_json_begin_object_member (builder, NULL);
2592 	e_m365_json_add_string_member (builder, "displayName", display_name);
2593 	e_m365_json_end_object_member (builder);
2594 
2595 	e_m365_connection_set_json_body (message, builder);
2596 
2597 	g_object_unref (builder);
2598 
2599 	success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL, out_mail_folder, cancellable, error);
2600 
2601 	g_clear_object (&message);
2602 
2603 	return success;
2604 }
2605 
2606 /* https://docs.microsoft.com/en-us/graph/api/message-delta?view=graph-rest-1.0&tabs=http
2607    https://docs.microsoft.com/en-us/graph/api/contact-delta?view=graph-rest-1.0&tabs=http */
2608 
2609 gboolean
e_m365_connection_get_objects_delta_sync(EM365Connection * cnc,const gchar * user_override,EM365FolderKind kind,const gchar * folder_id,const gchar * select,const gchar * delta_link,guint max_page_size,EM365ConnectionJsonFunc func,gpointer func_user_data,gchar ** out_delta_link,GCancellable * cancellable,GError ** error)2610 e_m365_connection_get_objects_delta_sync (EM365Connection *cnc,
2611 					  const gchar *user_override, /* for which user, NULL to use the account user */
2612 					  EM365FolderKind kind,
2613 					  const gchar *folder_id, /* folder ID to get delta messages in */
2614 					  const gchar *select, /* properties to select, nullable */
2615 					  const gchar *delta_link, /* previous delta link */
2616 					  guint max_page_size, /* 0 for default by the server */
2617 					  EM365ConnectionJsonFunc func, /* function to call with each result set */
2618 					  gpointer func_user_data, /* user data passed into the 'func' */
2619 					  gchar **out_delta_link,
2620 					  GCancellable *cancellable,
2621 					  GError **error)
2622 {
2623 	EM365ResponseData rd;
2624 	SoupMessage *message = NULL;
2625 	gboolean success;
2626 
2627 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
2628 	g_return_val_if_fail (folder_id != NULL, FALSE);
2629 	g_return_val_if_fail (out_delta_link != NULL, FALSE);
2630 	g_return_val_if_fail (func != NULL, FALSE);
2631 
2632 	if (delta_link)
2633 		message = m365_connection_new_soup_message (SOUP_METHOD_GET, delta_link, CSM_DEFAULT, NULL);
2634 
2635 	if (!message) {
2636 		const gchar *kind_str = NULL, *kind_path_str = NULL;
2637 		gchar *uri;
2638 
2639 		switch (kind) {
2640 		case E_M365_FOLDER_KIND_CONTACTS:
2641 			kind_str = "contactFolders";
2642 			kind_path_str = "contacts";
2643 			break;
2644 		case E_M365_FOLDER_KIND_MAIL:
2645 			kind_str = "mailFolders";
2646 			kind_path_str = "messages";
2647 			break;
2648 		default:
2649 			g_warn_if_reached ();
2650 			break;
2651 		}
2652 
2653 		g_return_val_if_fail (kind_str != NULL && kind_path_str != NULL, FALSE);
2654 
2655 		uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
2656 			kind_str,
2657 			folder_id,
2658 			kind_path_str,
2659 			"", "delta",
2660 			"$select", select,
2661 			NULL);
2662 
2663 		message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
2664 
2665 		if (!message) {
2666 			g_free (uri);
2667 
2668 			return FALSE;
2669 		}
2670 
2671 		g_free (uri);
2672 	}
2673 
2674 	if (max_page_size > 0) {
2675 		gchar *prefer_value;
2676 
2677 		prefer_value = g_strdup_printf ("odata.maxpagesize=%u", max_page_size);
2678 
2679 		soup_message_headers_append (message->request_headers, "Prefer", prefer_value);
2680 
2681 		g_free (prefer_value);
2682 	}
2683 
2684 	memset (&rd, 0, sizeof (EM365ResponseData));
2685 
2686 	rd.json_func = func;
2687 	rd.func_user_data = func_user_data;
2688 	rd.out_delta_link = out_delta_link;
2689 
2690 	success = m365_connection_send_request_sync (cnc, message, e_m365_read_valued_response_cb, NULL, &rd, cancellable, error);
2691 
2692 	g_clear_object (&message);
2693 
2694 	return success;
2695 }
2696 
2697 /* https://docs.microsoft.com/en-us/graph/api/message-get?view=graph-rest-1.0&tabs=http */
2698 
2699 gboolean
e_m365_connection_get_mail_message_sync(EM365Connection * cnc,const gchar * user_override,const gchar * folder_id,const gchar * message_id,EM365ConnectionRawDataFunc func,gpointer func_user_data,GCancellable * cancellable,GError ** error)2700 e_m365_connection_get_mail_message_sync (EM365Connection *cnc,
2701 					 const gchar *user_override, /* for which user, NULL to use the account user */
2702 					 const gchar *folder_id,
2703 					 const gchar *message_id,
2704 					 EM365ConnectionRawDataFunc func,
2705 					 gpointer func_user_data,
2706 					 GCancellable *cancellable,
2707 					 GError **error)
2708 {
2709 	SoupMessage *message;
2710 	gboolean success;
2711 	gchar *uri;
2712 
2713 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
2714 	g_return_val_if_fail (folder_id != NULL, FALSE);
2715 	g_return_val_if_fail (message_id != NULL, FALSE);
2716 	g_return_val_if_fail (func != NULL, FALSE);
2717 
2718 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
2719 		"messages",
2720 		message_id,
2721 		"$value",
2722 		NULL);
2723 
2724 	message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
2725 
2726 	if (!message) {
2727 		g_free (uri);
2728 
2729 		return FALSE;
2730 	}
2731 
2732 	g_free (uri);
2733 
2734 	success = m365_connection_send_request_sync (cnc, message, NULL, func, func_user_data, cancellable, error);
2735 
2736 	g_clear_object (&message);
2737 
2738 	return success;
2739 }
2740 
2741 /* https://docs.microsoft.com/en-us/graph/api/user-post-messages?view=graph-rest-1.0&tabs=http */
2742 
2743 gboolean
e_m365_connection_create_mail_message_sync(EM365Connection * cnc,const gchar * user_override,const gchar * folder_id,JsonBuilder * mail_message,EM365MailMessage ** out_created_message,GCancellable * cancellable,GError ** error)2744 e_m365_connection_create_mail_message_sync (EM365Connection *cnc,
2745 					    const gchar *user_override, /* for which user, NULL to use the account user */
2746 					    const gchar *folder_id, /* if NULL, then goes to the Drafts folder */
2747 					    JsonBuilder *mail_message, /* filled mailMessage object */
2748 					    EM365MailMessage **out_created_message, /* free with json_object_unref() */
2749 					    GCancellable *cancellable,
2750 					    GError **error)
2751 {
2752 	SoupMessage *message;
2753 	gboolean success;
2754 	gchar *uri;
2755 
2756 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
2757 	g_return_val_if_fail (mail_message != NULL, FALSE);
2758 	g_return_val_if_fail (out_created_message != NULL, FALSE);
2759 
2760 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
2761 		folder_id ? "mailFolders" : "messages",
2762 		folder_id,
2763 		folder_id ? "messages" : NULL,
2764 		NULL);
2765 
2766 	message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DEFAULT, error);
2767 
2768 	if (!message) {
2769 		g_free (uri);
2770 
2771 		return FALSE;
2772 	}
2773 
2774 	g_free (uri);
2775 
2776 	e_m365_connection_set_json_body (message, mail_message);
2777 
2778 	success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL, out_created_message, cancellable, error);
2779 
2780 	g_clear_object (&message);
2781 
2782 	return success;
2783 }
2784 
2785 /* https://docs.microsoft.com/en-us/graph/api/message-post-attachments?view=graph-rest-1.0&tabs=http */
2786 
2787 gboolean
e_m365_connection_add_mail_message_attachment_sync(EM365Connection * cnc,const gchar * user_override,const gchar * message_id,JsonBuilder * attachment,gchar ** out_attachment_id,GCancellable * cancellable,GError ** error)2788 e_m365_connection_add_mail_message_attachment_sync (EM365Connection *cnc,
2789 						    const gchar *user_override, /* for which user, NULL to use the account user */
2790 						    const gchar *message_id, /* the message to add it to */
2791 						    JsonBuilder *attachment, /* filled attachment object */
2792 						    gchar **out_attachment_id,
2793 						    GCancellable *cancellable,
2794 						    GError **error)
2795 {
2796 	SoupMessage *message;
2797 	JsonObject *added_attachment = NULL;
2798 	gboolean success;
2799 	gchar *uri;
2800 
2801 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
2802 	g_return_val_if_fail (attachment != NULL, FALSE);
2803 
2804 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
2805 		"messages",
2806 		message_id,
2807 		"attachments",
2808 		NULL);
2809 
2810 	message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DEFAULT, error);
2811 
2812 	if (!message) {
2813 		g_free (uri);
2814 
2815 		return FALSE;
2816 	}
2817 
2818 	g_free (uri);
2819 
2820 	e_m365_connection_set_json_body (message, attachment);
2821 
2822 	success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL, &added_attachment, cancellable, error);
2823 
2824 	if (success && added_attachment && out_attachment_id)
2825 		*out_attachment_id = g_strdup (e_m365_attachment_get_id (added_attachment));
2826 
2827 	if (added_attachment)
2828 		json_object_unref (added_attachment);
2829 
2830 	g_clear_object (&message);
2831 
2832 	return success;
2833 }
2834 
2835 /* https://docs.microsoft.com/en-us/graph/api/message-update?view=graph-rest-1.0&tabs=http */
2836 
2837 SoupMessage *
e_m365_connection_prepare_update_mail_message(EM365Connection * cnc,const gchar * user_override,const gchar * message_id,JsonBuilder * mail_message,GError ** error)2838 e_m365_connection_prepare_update_mail_message (EM365Connection *cnc,
2839 					       const gchar *user_override, /* for which user, NULL to use the account user */
2840 					       const gchar *message_id,
2841 					       JsonBuilder *mail_message, /* values to update, as a mailMessage object */
2842 					       GError **error)
2843 {
2844 	SoupMessage *message;
2845 	gchar *uri;
2846 
2847 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), NULL);
2848 	g_return_val_if_fail (message_id != NULL, NULL);
2849 	g_return_val_if_fail (mail_message != NULL, NULL);
2850 
2851 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
2852 		"messages",
2853 		message_id,
2854 		NULL,
2855 		NULL);
2856 
2857 	/* The server returns the mailMessage object back, but it can be ignored here */
2858 	message = m365_connection_new_soup_message ("PATCH", uri, CSM_DISABLE_RESPONSE, error);
2859 
2860 	if (!message) {
2861 		g_free (uri);
2862 
2863 		return NULL;
2864 	}
2865 
2866 	g_free (uri);
2867 
2868 	e_m365_connection_set_json_body (message, mail_message);
2869 
2870 	return message;
2871 }
2872 
2873 gboolean
e_m365_connection_update_mail_message_sync(EM365Connection * cnc,const gchar * user_override,const gchar * message_id,JsonBuilder * mail_message,GCancellable * cancellable,GError ** error)2874 e_m365_connection_update_mail_message_sync (EM365Connection *cnc,
2875 					    const gchar *user_override, /* for which user, NULL to use the account user */
2876 					    const gchar *message_id,
2877 					    JsonBuilder *mail_message, /* values to update, as a mailMessage object */
2878 					    GCancellable *cancellable,
2879 					    GError **error)
2880 {
2881 	SoupMessage *message;
2882 	gboolean success;
2883 
2884 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
2885 	g_return_val_if_fail (message_id != NULL, FALSE);
2886 	g_return_val_if_fail (mail_message != NULL, FALSE);
2887 
2888 	message = e_m365_connection_prepare_update_mail_message (cnc, user_override, message_id, mail_message, error);
2889 
2890 	if (!message)
2891 		return FALSE;
2892 
2893 	success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL, cancellable, error);
2894 
2895 	g_clear_object (&message);
2896 
2897 	return success;
2898 }
2899 
2900 /* https://docs.microsoft.com/en-us/graph/api/message-copy?view=graph-rest-1.0&tabs=http
2901    https://docs.microsoft.com/en-us/graph/api/message-move?view=graph-rest-1.0&tabs=http
2902  */
2903 static SoupMessage *
e_m365_connection_prepare_copy_move_mail_message(EM365Connection * cnc,const gchar * user_override,const gchar * message_id,const gchar * des_folder_id,gboolean do_copy,GError ** error)2904 e_m365_connection_prepare_copy_move_mail_message (EM365Connection *cnc,
2905 						  const gchar *user_override,
2906 						  const gchar *message_id,
2907 						  const gchar *des_folder_id,
2908 						  gboolean do_copy,
2909 						  GError **error)
2910 {
2911 	SoupMessage *message;
2912 	JsonBuilder *builder;
2913 	gchar *uri;
2914 
2915 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), NULL);
2916 	g_return_val_if_fail (message_id != NULL, NULL);
2917 
2918 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
2919 		"messages",
2920 		message_id,
2921 		do_copy ? "copy" : "move",
2922 		NULL);
2923 
2924 	message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DEFAULT, error);
2925 
2926 	if (!message) {
2927 		g_free (uri);
2928 
2929 		return FALSE;
2930 	}
2931 
2932 	g_free (uri);
2933 
2934 	builder = json_builder_new_immutable ();
2935 
2936 	e_m365_json_begin_object_member (builder, NULL);
2937 	e_m365_json_add_string_member (builder, "destinationId", des_folder_id);
2938 	e_m365_json_end_object_member (builder);
2939 
2940 	e_m365_connection_set_json_body (message, builder);
2941 
2942 	g_object_unref (builder);
2943 
2944 	return message;
2945 }
2946 
2947 /* out_des_message_ids: Camel-pooled gchar *, new ids, in the same order as in message_ids; can be partial */
2948 gboolean
e_m365_connection_copy_move_mail_messages_sync(EM365Connection * cnc,const gchar * user_override,const GSList * message_ids,const gchar * des_folder_id,gboolean do_copy,GSList ** out_des_message_ids,GCancellable * cancellable,GError ** error)2949 e_m365_connection_copy_move_mail_messages_sync (EM365Connection *cnc,
2950 						const gchar *user_override, /* for which user, NULL to use the account user */
2951 						const GSList *message_ids, /* const gchar * */
2952 						const gchar *des_folder_id,
2953 						gboolean do_copy,
2954 						GSList **out_des_message_ids, /* Camel-pooled gchar * */
2955 						GCancellable *cancellable,
2956 						GError **error)
2957 {
2958 	gboolean success = TRUE;
2959 
2960 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
2961 	g_return_val_if_fail (message_ids != NULL, FALSE);
2962 	g_return_val_if_fail (des_folder_id != NULL, FALSE);
2963 	g_return_val_if_fail (out_des_message_ids != NULL, FALSE);
2964 
2965 	*out_des_message_ids = NULL;
2966 
2967 	if (g_slist_next (message_ids)) {
2968 		GPtrArray *requests;
2969 		GSList *link;
2970 		guint total, done = 0;
2971 
2972 		total = g_slist_length ((GSList *) message_ids);
2973 		requests = g_ptr_array_new_full (MIN (E_M365_BATCH_MAX_REQUESTS, MIN (total, 50)), g_object_unref);
2974 
2975 		for (link = (GSList *) message_ids; link && success; link = g_slist_next (link)) {
2976 			const gchar *id = link->data;
2977 			SoupMessage *message;
2978 
2979 			message = e_m365_connection_prepare_copy_move_mail_message (cnc, user_override, id, des_folder_id, do_copy, error);
2980 
2981 			if (!message) {
2982 				success = FALSE;
2983 				break;
2984 			}
2985 
2986 			g_ptr_array_add (requests, message);
2987 
2988 			if (requests->len == E_M365_BATCH_MAX_REQUESTS || !link->next) {
2989 				if (requests->len == 1) {
2990 					JsonObject *response = NULL;
2991 
2992 					success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL, &response, cancellable, error);
2993 
2994 					if (response) {
2995 						*out_des_message_ids = g_slist_prepend (*out_des_message_ids,
2996 							(gpointer) camel_pstring_strdup (e_m365_mail_message_get_id (response)));
2997 						json_object_unref (response);
2998 					} else {
2999 						success = FALSE;
3000 					}
3001 				} else {
3002 					success = e_m365_connection_batch_request_sync (cnc, E_M365_API_V1_0, requests, cancellable, error);
3003 
3004 					if (success) {
3005 						guint ii;
3006 
3007 						for (ii = 0; success && ii < requests->len; ii++) {
3008 							JsonNode *node = NULL;
3009 
3010 							message = g_ptr_array_index (requests, ii);
3011 
3012 							success = e_m365_connection_json_node_from_message (message, NULL, &node, cancellable, error);
3013 
3014 							if (success && node && JSON_NODE_HOLDS_OBJECT (node)) {
3015 								JsonObject *response;
3016 
3017 								response = json_node_get_object (node);
3018 
3019 								if (response) {
3020 									*out_des_message_ids = g_slist_prepend (*out_des_message_ids,
3021 										(gpointer) camel_pstring_strdup (e_m365_mail_message_get_id (response)));
3022 								} else {
3023 									success = FALSE;
3024 								}
3025 							} else {
3026 								success = FALSE;
3027 							}
3028 
3029 							if (node)
3030 								json_node_unref (node);
3031 						}
3032 					}
3033 				}
3034 
3035 				g_ptr_array_remove_range (requests, 0, requests->len);
3036 
3037 				done += requests->len;
3038 
3039 				camel_operation_progress (cancellable, done * 100.0 / total);
3040 			}
3041 		}
3042 
3043 		g_ptr_array_free (requests, TRUE);
3044 	} else {
3045 		SoupMessage *message;
3046 
3047 		message = e_m365_connection_prepare_copy_move_mail_message (cnc, user_override, message_ids->data, des_folder_id, do_copy, error);
3048 
3049 		if (message) {
3050 			JsonObject *response = NULL;
3051 
3052 			success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL, &response, cancellable, error);
3053 
3054 			if (response) {
3055 				*out_des_message_ids = g_slist_prepend (*out_des_message_ids,
3056 					(gpointer) camel_pstring_strdup (e_m365_mail_message_get_id (response)));
3057 				json_object_unref (response);
3058 			} else {
3059 				success = FALSE;
3060 			}
3061 
3062 			g_clear_object (&message);
3063 		} else {
3064 			success = FALSE;
3065 		}
3066 	}
3067 
3068 	*out_des_message_ids = g_slist_reverse (*out_des_message_ids);
3069 
3070 	return success;
3071 }
3072 
3073 /* https://docs.microsoft.com/en-us/graph/api/message-delete?view=graph-rest-1.0&tabs=http */
3074 
3075 static SoupMessage *
e_m365_connection_prepare_delete_mail_message(EM365Connection * cnc,const gchar * user_override,const gchar * message_id,GError ** error)3076 e_m365_connection_prepare_delete_mail_message (EM365Connection *cnc,
3077 					       const gchar *user_override, /* for which user, NULL to use the account user */
3078 					       const gchar *message_id,
3079 					       GError **error)
3080 {
3081 	SoupMessage *message;
3082 	gchar *uri;
3083 
3084 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), NULL);
3085 	g_return_val_if_fail (message_id != NULL, NULL);
3086 
3087 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
3088 		"messages",
3089 		message_id,
3090 		NULL,
3091 		NULL);
3092 
3093 	message = m365_connection_new_soup_message (SOUP_METHOD_DELETE, uri, CSM_DEFAULT, error);
3094 
3095 	if (!message) {
3096 		g_free (uri);
3097 
3098 		return NULL;
3099 	}
3100 
3101 	g_free (uri);
3102 
3103 	return message;
3104 }
3105 
3106 gboolean
e_m365_connection_delete_mail_messages_sync(EM365Connection * cnc,const gchar * user_override,const GSList * message_ids,GSList ** out_deleted_ids,GCancellable * cancellable,GError ** error)3107 e_m365_connection_delete_mail_messages_sync (EM365Connection *cnc,
3108 					     const gchar *user_override, /* for which user, NULL to use the account user */
3109 					     const GSList *message_ids, /* const gchar * */
3110 					     GSList **out_deleted_ids, /* (transfer container): const gchar *, borrowed from message_ids */
3111 					     GCancellable *cancellable,
3112 					     GError **error)
3113 {
3114 	gboolean success = TRUE;
3115 
3116 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
3117 	g_return_val_if_fail (message_ids != NULL, FALSE);
3118 
3119 	if (g_slist_next (message_ids)) {
3120 		GPtrArray *requests;
3121 		GSList *link, *from_link = (GSList *) message_ids;
3122 		guint total, done = 0;
3123 
3124 		total = g_slist_length ((GSList *) message_ids);
3125 		requests = g_ptr_array_new_full (MIN (E_M365_BATCH_MAX_REQUESTS, MIN (total, 50)), g_object_unref);
3126 
3127 		for (link = (GSList *) message_ids; link && success; link = g_slist_next (link)) {
3128 			const gchar *id = link->data;
3129 			SoupMessage *message;
3130 
3131 			message = e_m365_connection_prepare_delete_mail_message (cnc, user_override, id, error);
3132 
3133 			if (!message) {
3134 				success = FALSE;
3135 				break;
3136 			}
3137 
3138 			g_ptr_array_add (requests, message);
3139 
3140 			if (requests->len == E_M365_BATCH_MAX_REQUESTS || !link->next) {
3141 				if (requests->len == 1) {
3142 					success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL, cancellable, error);
3143 				} else {
3144 					success = e_m365_connection_batch_request_sync (cnc, E_M365_API_V1_0, requests, cancellable, error);
3145 				}
3146 
3147 				if (success && out_deleted_ids) {
3148 					while (from_link) {
3149 						*out_deleted_ids = g_slist_prepend (*out_deleted_ids, from_link->data);
3150 
3151 						if (from_link == link)
3152 							break;
3153 
3154 						from_link = g_slist_next (from_link);
3155 					}
3156 				}
3157 
3158 				g_ptr_array_remove_range (requests, 0, requests->len);
3159 				from_link = g_slist_next (link);
3160 
3161 				done += requests->len;
3162 
3163 				camel_operation_progress (cancellable, done * 100.0 / total);
3164 			}
3165 		}
3166 
3167 		g_ptr_array_free (requests, TRUE);
3168 	} else {
3169 		SoupMessage *message;
3170 
3171 		message = e_m365_connection_prepare_delete_mail_message (cnc, user_override, message_ids->data, error);
3172 
3173 		if (message) {
3174 			success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL, cancellable, error);
3175 
3176 			if (success && out_deleted_ids)
3177 				*out_deleted_ids = g_slist_prepend (*out_deleted_ids, message_ids->data);
3178 
3179 			g_clear_object (&message);
3180 		} else {
3181 			success = FALSE;
3182 		}
3183 	}
3184 
3185 	if (out_deleted_ids && *out_deleted_ids && g_slist_next (*out_deleted_ids))
3186 		*out_deleted_ids = g_slist_reverse (*out_deleted_ids);
3187 
3188 	return success;
3189 }
3190 
3191 /* https://docs.microsoft.com/en-us/graph/api/message-send?view=graph-rest-1.0&tabs=http */
3192 
3193 gboolean
e_m365_connection_send_mail_message_sync(EM365Connection * cnc,const gchar * user_override,const gchar * message_id,GCancellable * cancellable,GError ** error)3194 e_m365_connection_send_mail_message_sync (EM365Connection *cnc,
3195 				     const gchar *user_override, /* for which user, NULL to use the account user */
3196 				     const gchar *message_id,
3197 				     GCancellable *cancellable,
3198 				     GError **error)
3199 {
3200 	SoupMessage *message;
3201 	gboolean success;
3202 	gchar *uri;
3203 
3204 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
3205 	g_return_val_if_fail (message_id != NULL, FALSE);
3206 
3207 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
3208 		"messages",
3209 		message_id,
3210 		"send",
3211 		NULL);
3212 
3213 	message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DEFAULT, error);
3214 
3215 	if (!message) {
3216 		g_free (uri);
3217 
3218 		return FALSE;
3219 	}
3220 
3221 	g_free (uri);
3222 
3223 	soup_message_headers_append (message->request_headers, "Content-Length", "0");
3224 
3225 	success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL, cancellable, error);
3226 
3227 	g_clear_object (&message);
3228 
3229 	return success;
3230 }
3231 
3232 /* https://docs.microsoft.com/en-us/graph/api/user-sendmail?view=graph-rest-1.0&tabs=http */
3233 
3234 gboolean
e_m365_connection_send_mail_sync(EM365Connection * cnc,const gchar * user_override,JsonBuilder * request,GCancellable * cancellable,GError ** error)3235 e_m365_connection_send_mail_sync (EM365Connection *cnc,
3236 				  const gchar *user_override, /* for which user, NULL to use the account user */
3237 				  JsonBuilder *request, /* filled sendMail object */
3238 				  GCancellable *cancellable,
3239 				  GError **error)
3240 {
3241 	SoupMessage *message;
3242 	gboolean success;
3243 	gchar *uri;
3244 
3245 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
3246 	g_return_val_if_fail (request != NULL, FALSE);
3247 
3248 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
3249 		"sendMail", NULL, NULL, NULL);
3250 
3251 	message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DEFAULT, error);
3252 
3253 	if (!message) {
3254 		g_free (uri);
3255 
3256 		return FALSE;
3257 	}
3258 
3259 	g_free (uri);
3260 
3261 	e_m365_connection_set_json_body (message, request);
3262 
3263 	success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL, cancellable, error);
3264 
3265 	g_clear_object (&message);
3266 
3267 	return success;
3268 }
3269 
3270 /* https://docs.microsoft.com/en-us/graph/api/contactfolder-get?view=graph-rest-1.0&tabs=http */
3271 
3272 gboolean
e_m365_connection_get_contacts_folder_sync(EM365Connection * cnc,const gchar * user_override,const gchar * folder_id,const gchar * select,EM365Folder ** out_folder,GCancellable * cancellable,GError ** error)3273 e_m365_connection_get_contacts_folder_sync (EM365Connection *cnc,
3274 					    const gchar *user_override, /* for which user, NULL to use the account user */
3275 					    const gchar *folder_id, /* nullable - then the default 'contacts' folder is returned */
3276 					    const gchar *select, /* nullable - properties to select */
3277 					    EM365Folder **out_folder,
3278 					    GCancellable *cancellable,
3279 					    GError **error)
3280 {
3281 	SoupMessage *message;
3282 	gchar *uri;
3283 	gboolean success;
3284 
3285 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
3286 	g_return_val_if_fail (out_folder != NULL, FALSE);
3287 
3288 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
3289 		"contactFolders",
3290 		folder_id ? folder_id : "contacts",
3291 		NULL,
3292 		NULL);
3293 
3294 	message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
3295 
3296 	if (!message) {
3297 		g_free (uri);
3298 
3299 		return FALSE;
3300 	}
3301 
3302 	g_free (uri);
3303 
3304 	success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL, out_folder, cancellable, error);
3305 
3306 	g_clear_object (&message);
3307 
3308 	return success;
3309 }
3310 
3311 /* https://docs.microsoft.com/en-us/graph/api/profilephoto-get?view=graph-rest-1.0 */
3312 
3313 gboolean
e_m365_connection_get_contact_photo_sync(EM365Connection * cnc,const gchar * user_override,const gchar * folder_id,const gchar * contact_id,GByteArray ** out_photo,GCancellable * cancellable,GError ** error)3314 e_m365_connection_get_contact_photo_sync (EM365Connection *cnc,
3315 					  const gchar *user_override, /* for which user, NULL to use the account user */
3316 					  const gchar *folder_id,
3317 					  const gchar *contact_id,
3318 					  GByteArray **out_photo,
3319 					  GCancellable *cancellable,
3320 					  GError **error)
3321 {
3322 	SoupMessage *message;
3323 	gboolean success;
3324 	gchar *uri;
3325 
3326 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
3327 	g_return_val_if_fail (folder_id != NULL, FALSE);
3328 	g_return_val_if_fail (contact_id != NULL, FALSE);
3329 	g_return_val_if_fail (out_photo != NULL, FALSE);
3330 
3331 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
3332 		"contactFolders",
3333 		folder_id,
3334 		"contacts",
3335 		"", contact_id,
3336 		"", "photo",
3337 		"", "$value",
3338 		NULL);
3339 
3340 	message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
3341 
3342 	if (!message) {
3343 		g_free (uri);
3344 
3345 		return FALSE;
3346 	}
3347 
3348 	g_free (uri);
3349 
3350 	success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_to_byte_array_cb, out_photo, cancellable, error);
3351 
3352 	g_clear_object (&message);
3353 
3354 	return success;
3355 }
3356 
3357 /* https://docs.microsoft.com/en-us/graph/api/profilephoto-update?view=graph-rest-1.0&tabs=http */
3358 
3359 gboolean
e_m365_connection_update_contact_photo_sync(EM365Connection * cnc,const gchar * user_override,const gchar * folder_id,const gchar * contact_id,const GByteArray * jpeg_photo,GCancellable * cancellable,GError ** error)3360 e_m365_connection_update_contact_photo_sync (EM365Connection *cnc,
3361 					     const gchar *user_override, /* for which user, NULL to use the account user */
3362 					     const gchar *folder_id,
3363 					     const gchar *contact_id,
3364 					     const GByteArray *jpeg_photo, /* nullable, to remove the photo */
3365 					     GCancellable *cancellable,
3366 					     GError **error)
3367 {
3368 	SoupMessage *message;
3369 	gboolean success;
3370 	gchar *uri;
3371 
3372 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
3373 
3374 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
3375 		"contactFolders",
3376 		folder_id,
3377 		"contacts",
3378 		"", contact_id,
3379 		"", "photo",
3380 		"", "$value",
3381 		NULL);
3382 
3383 	message = m365_connection_new_soup_message (SOUP_METHOD_PUT, uri, CSM_DEFAULT, error);
3384 
3385 	if (!message) {
3386 		g_free (uri);
3387 
3388 		return FALSE;
3389 	}
3390 
3391 	g_free (uri);
3392 
3393 	soup_message_headers_set_content_type (message->request_headers, "image/jpeg", NULL);
3394 	soup_message_headers_set_content_length (message->request_headers, jpeg_photo ? jpeg_photo->len : 0);
3395 
3396 	if (jpeg_photo)
3397 		soup_message_body_append (message->request_body, SOUP_MEMORY_STATIC, jpeg_photo->data, jpeg_photo->len);
3398 
3399 	success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL, cancellable, error);
3400 
3401 	g_clear_object (&message);
3402 
3403 	return success;
3404 }
3405 
3406 /* https://docs.microsoft.com/en-us/graph/api/contact-get?view=graph-rest-1.0&tabs=http */
3407 
3408 gboolean
e_m365_connection_get_contact_sync(EM365Connection * cnc,const gchar * user_override,const gchar * folder_id,const gchar * contact_id,EM365Contact ** out_contact,GCancellable * cancellable,GError ** error)3409 e_m365_connection_get_contact_sync (EM365Connection *cnc,
3410 				    const gchar *user_override, /* for which user, NULL to use the account user */
3411 				    const gchar *folder_id,
3412 				    const gchar *contact_id,
3413 				    EM365Contact **out_contact,
3414 				    GCancellable *cancellable,
3415 				    GError **error)
3416 {
3417 	SoupMessage *message;
3418 	gboolean success;
3419 	gchar *uri;
3420 
3421 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
3422 	g_return_val_if_fail (folder_id != NULL, FALSE);
3423 	g_return_val_if_fail (contact_id != NULL, FALSE);
3424 	g_return_val_if_fail (out_contact != NULL, FALSE);
3425 
3426 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
3427 		"contactFolders",
3428 		folder_id,
3429 		"contacts",
3430 		"", contact_id,
3431 		NULL);
3432 
3433 	message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
3434 
3435 	if (!message) {
3436 		g_free (uri);
3437 
3438 		return FALSE;
3439 	}
3440 
3441 	g_free (uri);
3442 
3443 	success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL, out_contact, cancellable, error);
3444 
3445 	g_clear_object (&message);
3446 
3447 	return success;
3448 }
3449 
3450 /* https://docs.microsoft.com/en-us/graph/api/user-post-contacts?view=graph-rest-1.0&tabs=http */
3451 
3452 gboolean
e_m365_connection_create_contact_sync(EM365Connection * cnc,const gchar * user_override,const gchar * folder_id,JsonBuilder * contact,EM365Contact ** out_created_contact,GCancellable * cancellable,GError ** error)3453 e_m365_connection_create_contact_sync (EM365Connection *cnc,
3454 				       const gchar *user_override, /* for which user, NULL to use the account user */
3455 				       const gchar *folder_id, /* if NULL, then goes to the Drafts folder */
3456 				       JsonBuilder *contact, /* filled contact object */
3457 				       EM365Contact **out_created_contact, /* free with json_object_unref() */
3458 				       GCancellable *cancellable,
3459 				       GError **error)
3460 {
3461 	SoupMessage *message;
3462 	gboolean success;
3463 	gchar *uri;
3464 
3465 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
3466 	g_return_val_if_fail (contact != NULL, FALSE);
3467 	g_return_val_if_fail (out_created_contact != NULL, FALSE);
3468 
3469 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
3470 		folder_id ? "contactFolders" : "contacts",
3471 		folder_id,
3472 		folder_id ? "contacts" : NULL,
3473 		NULL);
3474 
3475 	message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DEFAULT, error);
3476 
3477 	if (!message) {
3478 		g_free (uri);
3479 
3480 		return FALSE;
3481 	}
3482 
3483 	g_free (uri);
3484 
3485 	e_m365_connection_set_json_body (message, contact);
3486 
3487 	success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL, out_created_contact, cancellable, error);
3488 
3489 	g_clear_object (&message);
3490 
3491 	return success;
3492 }
3493 
3494 /* https://docs.microsoft.com/en-us/graph/api/contact-update?view=graph-rest-1.0&tabs=http */
3495 
3496 gboolean
e_m365_connection_update_contact_sync(EM365Connection * cnc,const gchar * user_override,const gchar * folder_id,const gchar * contact_id,JsonBuilder * contact,GCancellable * cancellable,GError ** error)3497 e_m365_connection_update_contact_sync (EM365Connection *cnc,
3498 				       const gchar *user_override, /* for which user, NULL to use the account user */
3499 				       const gchar *folder_id,
3500 				       const gchar *contact_id,
3501 				       JsonBuilder *contact, /* values to update, as a contact object */
3502 				       GCancellable *cancellable,
3503 				       GError **error)
3504 {
3505 	SoupMessage *message;
3506 	gchar *uri;
3507 	gboolean success;
3508 
3509 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
3510 	g_return_val_if_fail (contact_id != NULL, FALSE);
3511 	g_return_val_if_fail (contact != NULL, FALSE);
3512 
3513 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
3514 		folder_id ? "contactFolders" : "contacts",
3515 		folder_id,
3516 		folder_id ? "contacts" : contact_id,
3517 		"", folder_id ? contact_id : NULL,
3518 		NULL);
3519 
3520 	/* The server returns the contact object back, but it can be ignored here */
3521 	message = m365_connection_new_soup_message ("PATCH", uri, CSM_DISABLE_RESPONSE, error);
3522 
3523 	if (!message) {
3524 		g_free (uri);
3525 
3526 		return FALSE;
3527 	}
3528 
3529 	g_free (uri);
3530 
3531 	e_m365_connection_set_json_body (message, contact);
3532 
3533 	success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL, cancellable, error);
3534 
3535 	g_clear_object (&message);
3536 
3537 	return success;
3538 }
3539 
3540 /* https://docs.microsoft.com/en-us/graph/api/contact-delete?view=graph-rest-1.0&tabs=http */
3541 
3542 gboolean
e_m365_connection_delete_contact_sync(EM365Connection * cnc,const gchar * user_override,const gchar * folder_id,const gchar * contact_id,GCancellable * cancellable,GError ** error)3543 e_m365_connection_delete_contact_sync (EM365Connection *cnc,
3544 				       const gchar *user_override, /* for which user, NULL to use the account user */
3545 				       const gchar *folder_id,
3546 				       const gchar *contact_id,
3547 				       GCancellable *cancellable,
3548 				       GError **error)
3549 {
3550 	SoupMessage *message;
3551 	gchar *uri;
3552 	gboolean success;
3553 
3554 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
3555 	g_return_val_if_fail (contact_id != NULL, FALSE);
3556 
3557 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
3558 		folder_id ? "contactFolders" : "contacts",
3559 		folder_id,
3560 		folder_id ? "contacts" : contact_id,
3561 		"", folder_id ? contact_id : NULL,
3562 		NULL);
3563 
3564 	message = m365_connection_new_soup_message (SOUP_METHOD_DELETE, uri, CSM_DEFAULT, error);
3565 
3566 	if (!message) {
3567 		g_free (uri);
3568 
3569 		return FALSE;
3570 	}
3571 
3572 	g_free (uri);
3573 
3574 	success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL, cancellable, error);
3575 
3576 	g_clear_object (&message);
3577 
3578 	return success;
3579 }
3580 
3581 /* https://docs.microsoft.com/en-us/graph/api/user-list-calendargroups?view=graph-rest-1.0&tabs=http */
3582 
3583 gboolean
e_m365_connection_list_calendar_groups_sync(EM365Connection * cnc,const gchar * user_override,GSList ** out_groups,GCancellable * cancellable,GError ** error)3584 e_m365_connection_list_calendar_groups_sync (EM365Connection *cnc,
3585 					     const gchar *user_override, /* for which user, NULL to use the account user */
3586 					     GSList **out_groups, /* EM365CalendarGroup * - the returned calendarGroup objects */
3587 					     GCancellable *cancellable,
3588 					     GError **error)
3589 {
3590 	EM365ResponseData rd;
3591 	SoupMessage *message;
3592 	gchar *uri;
3593 	gboolean success;
3594 
3595 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
3596 	g_return_val_if_fail (out_groups != NULL, FALSE);
3597 
3598 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
3599 		"calendarGroups", NULL, NULL, NULL);
3600 
3601 	message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
3602 
3603 	if (!message) {
3604 		g_free (uri);
3605 
3606 		return FALSE;
3607 	}
3608 
3609 	g_free (uri);
3610 
3611 	memset (&rd, 0, sizeof (EM365ResponseData));
3612 
3613 	rd.out_items = out_groups;
3614 
3615 	success = m365_connection_send_request_sync (cnc, message, e_m365_read_valued_response_cb, NULL, &rd, cancellable, error);
3616 
3617 	g_clear_object (&message);
3618 
3619 	return success;
3620 }
3621 
3622 /* https://docs.microsoft.com/en-us/graph/api/user-post-calendargroups?view=graph-rest-1.0&tabs=http */
3623 
3624 gboolean
e_m365_connection_create_calendar_group_sync(EM365Connection * cnc,const gchar * user_override,const gchar * name,EM365CalendarGroup ** out_created_group,GCancellable * cancellable,GError ** error)3625 e_m365_connection_create_calendar_group_sync (EM365Connection *cnc,
3626 					      const gchar *user_override, /* for which user, NULL to use the account user */
3627 					      const gchar *name,
3628 					      EM365CalendarGroup **out_created_group,
3629 					      GCancellable *cancellable,
3630 					      GError **error)
3631 {
3632 	SoupMessage *message;
3633 	JsonBuilder *builder;
3634 	gboolean success;
3635 	gchar *uri;
3636 
3637 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
3638 	g_return_val_if_fail (name != NULL, FALSE);
3639 	g_return_val_if_fail (out_created_group != NULL, FALSE);
3640 
3641 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
3642 		"calendarGroups", NULL, NULL, NULL);
3643 
3644 	message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DEFAULT, error);
3645 
3646 	if (!message) {
3647 		g_free (uri);
3648 
3649 		return FALSE;
3650 	}
3651 
3652 	g_free (uri);
3653 
3654 	builder = json_builder_new_immutable ();
3655 
3656 	e_m365_json_begin_object_member (builder, NULL);
3657 	e_m365_json_add_string_member (builder, "name", name);
3658 	e_m365_json_end_object_member (builder);
3659 
3660 	e_m365_connection_set_json_body (message, builder);
3661 
3662 	g_object_unref (builder);
3663 
3664 	success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL, out_created_group, cancellable, error);
3665 
3666 	g_clear_object (&message);
3667 
3668 	return success;
3669 }
3670 
3671 /* https://docs.microsoft.com/en-us/graph/api/calendargroup-get?view=graph-rest-1.0&tabs=http */
3672 
3673 gboolean
e_m365_connection_get_calendar_group_sync(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,EM365CalendarGroup ** out_group,GCancellable * cancellable,GError ** error)3674 e_m365_connection_get_calendar_group_sync (EM365Connection *cnc,
3675 					   const gchar *user_override, /* for which user, NULL to use the account user */
3676 					   const gchar *group_id,
3677 					   EM365CalendarGroup **out_group,
3678 					   GCancellable *cancellable,
3679 					   GError **error)
3680 {
3681 	SoupMessage *message;
3682 	gboolean success;
3683 	gchar *uri;
3684 
3685 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
3686 	g_return_val_if_fail (group_id != NULL, FALSE);
3687 	g_return_val_if_fail (out_group != NULL, FALSE);
3688 
3689 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
3690 		"calendarGroups",
3691 		group_id,
3692 		NULL,
3693 		NULL);
3694 
3695 	message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
3696 
3697 	if (!message) {
3698 		g_free (uri);
3699 
3700 		return FALSE;
3701 	}
3702 
3703 	g_free (uri);
3704 
3705 	success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL, out_group, cancellable, error);
3706 
3707 	g_clear_object (&message);
3708 
3709 	return success;
3710 }
3711 
3712 /* https://docs.microsoft.com/en-us/graph/api/calendargroup-update?view=graph-rest-1.0&tabs=http */
3713 
3714 gboolean
e_m365_connection_update_calendar_group_sync(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,const gchar * name,GCancellable * cancellable,GError ** error)3715 e_m365_connection_update_calendar_group_sync (EM365Connection *cnc,
3716 					      const gchar *user_override, /* for which user, NULL to use the account user */
3717 					      const gchar *group_id,
3718 					      const gchar *name,
3719 					      GCancellable *cancellable,
3720 					      GError **error)
3721 {
3722 	SoupMessage *message;
3723 	JsonBuilder *builder;
3724 	gboolean success;
3725 	gchar *uri;
3726 
3727 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
3728 	g_return_val_if_fail (group_id != NULL, FALSE);
3729 	g_return_val_if_fail (name != NULL, FALSE);
3730 
3731 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
3732 		"calendarGroups",
3733 		group_id,
3734 		NULL,
3735 		NULL);
3736 
3737 	message = m365_connection_new_soup_message ("PATCH", uri, CSM_DISABLE_RESPONSE, error);
3738 
3739 	if (!message) {
3740 		g_free (uri);
3741 
3742 		return FALSE;
3743 	}
3744 
3745 	g_free (uri);
3746 
3747 	builder = json_builder_new_immutable ();
3748 
3749 	e_m365_json_begin_object_member (builder, NULL);
3750 	e_m365_json_add_string_member (builder, "name", name);
3751 	e_m365_json_end_object_member (builder);
3752 
3753 	e_m365_connection_set_json_body (message, builder);
3754 
3755 	g_object_unref (builder);
3756 
3757 	success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL, cancellable, error);
3758 
3759 	g_clear_object (&message);
3760 
3761 	return success;
3762 }
3763 
3764 /* https://docs.microsoft.com/en-us/graph/api/calendargroup-delete?view=graph-rest-1.0&tabs=http */
3765 
3766 gboolean
e_m365_connection_delete_calendar_group_sync(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,GCancellable * cancellable,GError ** error)3767 e_m365_connection_delete_calendar_group_sync (EM365Connection *cnc,
3768 					      const gchar *user_override, /* for which user, NULL to use the account user */
3769 					      const gchar *group_id,
3770 					      GCancellable *cancellable,
3771 					      GError **error)
3772 {
3773 	SoupMessage *message;
3774 	gboolean success;
3775 	gchar *uri;
3776 
3777 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
3778 	g_return_val_if_fail (group_id != NULL, FALSE);
3779 
3780 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
3781 		"calendarGroups", group_id, NULL, NULL);
3782 
3783 	message = m365_connection_new_soup_message (SOUP_METHOD_DELETE, uri, CSM_DEFAULT, error);
3784 
3785 	if (!message) {
3786 		g_free (uri);
3787 
3788 		return FALSE;
3789 	}
3790 
3791 	g_free (uri);
3792 
3793 	success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL, cancellable, error);
3794 
3795 	g_clear_object (&message);
3796 
3797 	return success;
3798 }
3799 
3800 /* https://docs.microsoft.com/en-us/graph/api/resources/calendar?view=graph-rest-1.0 */
3801 
3802 gboolean
e_m365_connection_list_calendars_sync(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,const gchar * select,GSList ** out_calendars,GCancellable * cancellable,GError ** error)3803 e_m365_connection_list_calendars_sync (EM365Connection *cnc,
3804 				       const gchar *user_override, /* for which user, NULL to use the account user */
3805 				       const gchar *group_id, /* nullable, calendar group id for group calendars */
3806 				       const gchar *select, /* properties to select, nullable */
3807 				       GSList **out_calendars, /* EM365Calendar * - the returned calendar objects */
3808 				       GCancellable *cancellable,
3809 				       GError **error)
3810 {
3811 	EM365ResponseData rd;
3812 	SoupMessage *message;
3813 	gchar *uri;
3814 	gboolean success;
3815 
3816 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
3817 	g_return_val_if_fail (out_calendars != NULL, FALSE);
3818 
3819 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
3820 		group_id ? "calendarGroups" : "calendars",
3821 		group_id,
3822 		group_id ? "calendars" : NULL,
3823 		"$select", select,
3824 		NULL);
3825 
3826 	message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
3827 
3828 	if (!message) {
3829 		g_free (uri);
3830 
3831 		return FALSE;
3832 	}
3833 
3834 	g_free (uri);
3835 
3836 	memset (&rd, 0, sizeof (EM365ResponseData));
3837 
3838 	rd.out_items = out_calendars;
3839 
3840 	success = m365_connection_send_request_sync (cnc, message, e_m365_read_valued_response_cb, NULL, &rd, cancellable, error);
3841 
3842 	g_clear_object (&message);
3843 
3844 	return success;
3845 }
3846 
3847 /* https://docs.microsoft.com/en-us/graph/api/calendargroup-post-calendars?view=graph-rest-1.0&tabs=http */
3848 
3849 gboolean
e_m365_connection_create_calendar_sync(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,JsonBuilder * calendar,EM365Calendar ** out_created_calendar,GCancellable * cancellable,GError ** error)3850 e_m365_connection_create_calendar_sync (EM365Connection *cnc,
3851 					const gchar *user_override, /* for which user, NULL to use the account user */
3852 					const gchar *group_id, /* nullable, then the default group is used */
3853 					JsonBuilder *calendar,
3854 					EM365Calendar **out_created_calendar,
3855 					GCancellable *cancellable,
3856 					GError **error)
3857 {
3858 	SoupMessage *message;
3859 	gboolean success;
3860 	gchar *uri;
3861 
3862 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
3863 	g_return_val_if_fail (calendar != NULL, FALSE);
3864 	g_return_val_if_fail (out_created_calendar != NULL, FALSE);
3865 
3866 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
3867 		group_id ? "calendarGroups" : "calendars",
3868 		group_id,
3869 		"calendars",
3870 		NULL);
3871 
3872 	message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DEFAULT, error);
3873 
3874 	if (!message) {
3875 		g_free (uri);
3876 
3877 		return FALSE;
3878 	}
3879 
3880 	g_free (uri);
3881 
3882 	e_m365_connection_set_json_body (message, calendar);
3883 
3884 	success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL, out_created_calendar, cancellable, error);
3885 
3886 	g_clear_object (&message);
3887 
3888 	return success;
3889 }
3890 
3891 /* https://docs.microsoft.com/en-us/graph/api/calendar-get?view=graph-rest-1.0&tabs=http */
3892 
3893 gboolean
e_m365_connection_get_calendar_folder_sync(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,const gchar * calendar_id,const gchar * select,EM365Calendar ** out_calendar,GCancellable * cancellable,GError ** error)3894 e_m365_connection_get_calendar_folder_sync (EM365Connection *cnc,
3895 					    const gchar *user_override, /* for which user, NULL to use the account user */
3896 					    const gchar *group_id, /* nullable - then the default group is used */
3897 					    const gchar *calendar_id, /* nullable - then the default calendar is used */
3898 					    const gchar *select, /* nullable - properties to select */
3899 					    EM365Calendar **out_calendar,
3900 					    GCancellable *cancellable,
3901 					    GError **error)
3902 {
3903 	SoupMessage *message;
3904 	gchar *uri;
3905 	gboolean success;
3906 
3907 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
3908 	g_return_val_if_fail (out_calendar != NULL, FALSE);
3909 
3910 	if (group_id && calendar_id) {
3911 		uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
3912 			"calendarGroups",
3913 			group_id,
3914 			"calendars",
3915 			"", calendar_id,
3916 			"$select", select,
3917 			NULL);
3918 	} else if (group_id) {
3919 		uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, "groups",
3920 			group_id,
3921 			"calendar",
3922 			NULL,
3923 			"$select", select,
3924 			NULL);
3925 	} else if (calendar_id) {
3926 		uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
3927 			"calendars",
3928 			calendar_id,
3929 			NULL,
3930 			"$select", select,
3931 			NULL);
3932 	} else {
3933 		uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
3934 			"calendar",
3935 			NULL,
3936 			NULL,
3937 			"$select", select,
3938 			NULL);
3939 	}
3940 
3941 	message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
3942 
3943 	if (!message) {
3944 		g_free (uri);
3945 
3946 		return FALSE;
3947 	}
3948 
3949 	g_free (uri);
3950 
3951 	success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL, out_calendar, cancellable, error);
3952 
3953 	g_clear_object (&message);
3954 
3955 	return success;
3956 }
3957 
3958 /* https://docs.microsoft.com/en-us/graph/api/calendar-update?view=graph-rest-1.0&tabs=http */
3959 
3960 gboolean
e_m365_connection_update_calendar_sync(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,const gchar * calendar_id,const gchar * name,EM365CalendarColorType color,GCancellable * cancellable,GError ** error)3961 e_m365_connection_update_calendar_sync (EM365Connection *cnc,
3962 					const gchar *user_override, /* for which user, NULL to use the account user */
3963 					const gchar *group_id, /* nullable - then the default group is used */
3964 					const gchar *calendar_id,
3965 					const gchar *name, /* nullable - to keep the existing name */
3966 					EM365CalendarColorType color,
3967 					GCancellable *cancellable,
3968 					GError **error)
3969 {
3970 	SoupMessage *message;
3971 	JsonBuilder *builder;
3972 	gboolean success;
3973 	gchar *uri;
3974 
3975 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
3976 	g_return_val_if_fail (calendar_id != NULL, FALSE);
3977 
3978 	/* Nothing to change */
3979 	if (!name && (color == E_M365_CALENDAR_COLOR_NOT_SET || color == E_M365_CALENDAR_COLOR_UNKNOWN))
3980 		return TRUE;
3981 
3982 	if (group_id) {
3983 		uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
3984 			"calendarGroups",
3985 			group_id,
3986 			"calendars",
3987 			"", calendar_id,
3988 			NULL);
3989 	} else {
3990 		uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
3991 			"calendars",
3992 			calendar_id,
3993 			NULL,
3994 			NULL);
3995 	}
3996 
3997 	message = m365_connection_new_soup_message ("PATCH", uri, CSM_DISABLE_RESPONSE, error);
3998 
3999 	if (!message) {
4000 		g_free (uri);
4001 
4002 		return FALSE;
4003 	}
4004 
4005 	g_free (uri);
4006 
4007 	builder = json_builder_new_immutable ();
4008 
4009 	e_m365_json_begin_object_member (builder, NULL);
4010 	e_m365_calendar_add_name (builder, name);
4011 	e_m365_calendar_add_color (builder, color);
4012 	e_m365_json_end_object_member (builder);
4013 
4014 	e_m365_connection_set_json_body (message, builder);
4015 
4016 	g_object_unref (builder);
4017 
4018 	success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL, cancellable, error);
4019 
4020 	g_clear_object (&message);
4021 
4022 	return success;
4023 }
4024 
4025 /* https://docs.microsoft.com/en-us/graph/api/calendar-delete?view=graph-rest-1.0&tabs=http */
4026 
4027 gboolean
e_m365_connection_delete_calendar_sync(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,const gchar * calendar_id,GCancellable * cancellable,GError ** error)4028 e_m365_connection_delete_calendar_sync (EM365Connection *cnc,
4029 					const gchar *user_override, /* for which user, NULL to use the account user */
4030 					const gchar *group_id, /* nullable - then the default group is used */
4031 					const gchar *calendar_id,
4032 					GCancellable *cancellable,
4033 					GError **error)
4034 {
4035 	SoupMessage *message;
4036 	gboolean success;
4037 	gchar *uri;
4038 
4039 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
4040 	g_return_val_if_fail (calendar_id != NULL, FALSE);
4041 
4042 	if (group_id) {
4043 		uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
4044 			"calendarGroups",
4045 			group_id,
4046 			"calendars",
4047 			"", calendar_id,
4048 			NULL);
4049 	} else {
4050 		uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
4051 			"calendars",
4052 			calendar_id,
4053 			NULL,
4054 			NULL);
4055 	}
4056 
4057 	message = m365_connection_new_soup_message (SOUP_METHOD_DELETE, uri, CSM_DEFAULT, error);
4058 
4059 	if (!message) {
4060 		g_free (uri);
4061 
4062 		return FALSE;
4063 	}
4064 
4065 	g_free (uri);
4066 
4067 	success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL, cancellable, error);
4068 
4069 	g_clear_object (&message);
4070 
4071 	return success;
4072 }
4073 
4074 static void
m365_connection_prefer_outlook_timezone(SoupMessage * message,const gchar * prefer_outlook_timezone)4075 m365_connection_prefer_outlook_timezone (SoupMessage *message,
4076 					 const gchar *prefer_outlook_timezone)
4077 {
4078 	g_return_if_fail (SOUP_IS_MESSAGE (message));
4079 
4080 	if (prefer_outlook_timezone && *prefer_outlook_timezone) {
4081 		gchar *prefer_value;
4082 
4083 		prefer_value = g_strdup_printf ("outlook.timezone=\"%s\"", prefer_outlook_timezone);
4084 
4085 		soup_message_headers_append (message->request_headers, "Prefer", prefer_value);
4086 
4087 		g_free (prefer_value);
4088 	}
4089 }
4090 
4091 /* https://docs.microsoft.com/en-us/graph/api/user-list-events?view=graph-rest-1.0&tabs=http */
4092 
4093 gboolean
e_m365_connection_list_events_sync(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,const gchar * calendar_id,const gchar * prefer_outlook_timezone,const gchar * select,GSList ** out_events,GCancellable * cancellable,GError ** error)4094 e_m365_connection_list_events_sync (EM365Connection *cnc,
4095 				    const gchar *user_override, /* for which user, NULL to use the account user */
4096 				    const gchar *group_id, /* nullable, calendar group id for group calendars */
4097 				    const gchar *calendar_id,
4098 				    const gchar *prefer_outlook_timezone, /* nullable - then UTC, otherwise that zone for the returned times */
4099 				    const gchar *select, /* nullable - properties to select */
4100 				    GSList **out_events, /* EM365Event * - the returned event objects */
4101 				    GCancellable *cancellable,
4102 				    GError **error)
4103 {
4104 	EM365ResponseData rd;
4105 	SoupMessage *message;
4106 	gchar *uri;
4107 	gboolean success;
4108 
4109 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
4110 	g_return_val_if_fail (calendar_id != NULL, FALSE);
4111 	g_return_val_if_fail (out_events != NULL, FALSE);
4112 
4113 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
4114 		group_id ? "calendarGroups" : "calendars",
4115 		group_id,
4116 		group_id ? "calendars" : NULL,
4117 		"", calendar_id,
4118 		"", "events",
4119 		"$select", select,
4120 		NULL);
4121 
4122 	message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
4123 
4124 	if (!message) {
4125 		g_free (uri);
4126 
4127 		return FALSE;
4128 	}
4129 
4130 	g_free (uri);
4131 
4132 	m365_connection_prefer_outlook_timezone (message, prefer_outlook_timezone);
4133 
4134 	soup_message_headers_append (message->request_headers, "Prefer", "outlook.body-content-type=\"text\"");
4135 
4136 	memset (&rd, 0, sizeof (EM365ResponseData));
4137 
4138 	rd.out_items = out_events;
4139 
4140 	success = m365_connection_send_request_sync (cnc, message, e_m365_read_valued_response_cb, NULL, &rd, cancellable, error);
4141 
4142 	g_clear_object (&message);
4143 
4144 	return success;
4145 }
4146 
4147 /* https://docs.microsoft.com/en-us/graph/api/user-post-events?view=graph-rest-1.0&tabs=http
4148    https://docs.microsoft.com/en-us/graph/api/group-post-events?view=graph-rest-1.0&tabs=http */
4149 
4150 gboolean
e_m365_connection_create_event_sync(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,const gchar * calendar_id,JsonBuilder * event,EM365Event ** out_created_event,GCancellable * cancellable,GError ** error)4151 e_m365_connection_create_event_sync (EM365Connection *cnc,
4152 				     const gchar *user_override, /* for which user, NULL to use the account user */
4153 				     const gchar *group_id, /* nullable, then the default group is used */
4154 				     const gchar *calendar_id,
4155 				     JsonBuilder *event,
4156 				     EM365Event **out_created_event,
4157 				     GCancellable *cancellable,
4158 				     GError **error)
4159 {
4160 	SoupMessage *message;
4161 	gboolean success;
4162 	gchar *uri;
4163 
4164 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
4165 	g_return_val_if_fail (calendar_id != NULL, FALSE);
4166 	g_return_val_if_fail (event != NULL, FALSE);
4167 	g_return_val_if_fail (out_created_event != NULL, FALSE);
4168 
4169 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
4170 		group_id ? "calendarGroups" : "calendars",
4171 		group_id,
4172 		group_id ? "calendars" : NULL,
4173 		"", calendar_id,
4174 		"", "events",
4175 		NULL);
4176 
4177 	message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DEFAULT, error);
4178 
4179 	if (!message) {
4180 		g_free (uri);
4181 
4182 		return FALSE;
4183 	}
4184 
4185 	g_free (uri);
4186 
4187 	e_m365_connection_set_json_body (message, event);
4188 
4189 	success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL, out_created_event, cancellable, error);
4190 
4191 	g_clear_object (&message);
4192 
4193 	return success;
4194 }
4195 
4196 /* https://docs.microsoft.com/en-us/graph/api/event-get?view=graph-rest-1.0&tabs=http */
4197 
4198 SoupMessage *
e_m365_connection_prepare_get_event(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,const gchar * calendar_id,const gchar * event_id,const gchar * prefer_outlook_timezone,const gchar * select,GError ** error)4199 e_m365_connection_prepare_get_event (EM365Connection *cnc,
4200 				     const gchar *user_override, /* for which user, NULL to use the account user */
4201 				     const gchar *group_id, /* nullable, then the default group is used */
4202 				     const gchar *calendar_id,
4203 				     const gchar *event_id,
4204 				     const gchar *prefer_outlook_timezone, /* nullable - then UTC, otherwise that zone for the returned times */
4205 				     const gchar *select, /* nullable - properties to select */
4206 				     GError **error)
4207 {
4208 	SoupMessage *message;
4209 	gchar *uri;
4210 
4211 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), NULL);
4212 	g_return_val_if_fail (calendar_id != NULL, NULL);
4213 	g_return_val_if_fail (event_id != NULL, NULL);
4214 
4215 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
4216 		group_id ? "calendarGroups" : "calendars",
4217 		group_id,
4218 		group_id ? "calendars" : NULL,
4219 		"", calendar_id,
4220 		"", "events",
4221 		"", event_id,
4222 		"$select", select,
4223 		NULL);
4224 
4225 	message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
4226 
4227 	if (!message) {
4228 		g_free (uri);
4229 
4230 		return NULL;
4231 	}
4232 
4233 	g_free (uri);
4234 
4235 	m365_connection_prefer_outlook_timezone (message, prefer_outlook_timezone);
4236 	soup_message_headers_append (message->request_headers, "Prefer", "outlook.body-content-type=\"text\"");
4237 
4238 	return message;
4239 }
4240 
4241 gboolean
e_m365_connection_get_event_sync(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,const gchar * calendar_id,const gchar * event_id,const gchar * prefer_outlook_timezone,const gchar * select,EM365Event ** out_event,GCancellable * cancellable,GError ** error)4242 e_m365_connection_get_event_sync (EM365Connection *cnc,
4243 				  const gchar *user_override, /* for which user, NULL to use the account user */
4244 				  const gchar *group_id, /* nullable, then the default group is used */
4245 				  const gchar *calendar_id,
4246 				  const gchar *event_id,
4247 				  const gchar *prefer_outlook_timezone, /* nullable - then UTC, otherwise that zone for the returned times */
4248 				  const gchar *select, /* nullable - properties to select */
4249 				  EM365Event **out_event,
4250 				  GCancellable *cancellable,
4251 				  GError **error)
4252 {
4253 	SoupMessage *message;
4254 	gboolean success;
4255 
4256 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
4257 	g_return_val_if_fail (calendar_id != NULL, FALSE);
4258 	g_return_val_if_fail (event_id != NULL, FALSE);
4259 	g_return_val_if_fail (out_event != NULL, FALSE);
4260 
4261 	message = e_m365_connection_prepare_get_event (cnc, user_override, group_id, calendar_id, event_id, prefer_outlook_timezone, select, error);
4262 
4263 	if (!message)
4264 		return FALSE;
4265 
4266 	success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL, out_event, cancellable, error);
4267 
4268 	g_clear_object (&message);
4269 
4270 	return success;
4271 }
4272 
4273 gboolean
e_m365_connection_get_events_sync(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,const gchar * calendar_id,const GSList * event_ids,const gchar * prefer_outlook_timezone,const gchar * select,GSList ** out_events,GCancellable * cancellable,GError ** error)4274 e_m365_connection_get_events_sync (EM365Connection *cnc,
4275 				   const gchar *user_override, /* for which user, NULL to use the account user */
4276 				   const gchar *group_id, /* nullable, then the default group is used */
4277 				   const gchar *calendar_id,
4278 				   const GSList *event_ids, /* const gchar * */
4279 				   const gchar *prefer_outlook_timezone, /* nullable - then UTC, otherwise that zone for the returned times */
4280 				   const gchar *select, /* nullable - properties to select */
4281 				   GSList **out_events, /* EM365Event *, in the same order as event_ids; can return partial list */
4282 				   GCancellable *cancellable,
4283 				   GError **error)
4284 {
4285 	gboolean success = TRUE;
4286 
4287 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
4288 	g_return_val_if_fail (calendar_id != NULL, FALSE);
4289 	g_return_val_if_fail (event_ids != NULL, FALSE);
4290 	g_return_val_if_fail (out_events != NULL, FALSE);
4291 
4292 	if (g_slist_next (event_ids)) {
4293 		GPtrArray *requests;
4294 		GSList *link;
4295 		guint total, done = 0;
4296 
4297 		total = g_slist_length ((GSList *) event_ids);
4298 		requests = g_ptr_array_new_full (MIN (E_M365_BATCH_MAX_REQUESTS, MIN (total, 50)), g_object_unref);
4299 
4300 		for (link = (GSList *) event_ids; link && success; link = g_slist_next (link)) {
4301 			const gchar *id = link->data;
4302 			SoupMessage *message;
4303 
4304 			message = e_m365_connection_prepare_get_event (cnc, user_override, group_id, calendar_id, id, prefer_outlook_timezone, select, error);
4305 
4306 			if (!message) {
4307 				success = FALSE;
4308 				break;
4309 			}
4310 
4311 			g_ptr_array_add (requests, message);
4312 
4313 			if (requests->len == E_M365_BATCH_MAX_REQUESTS || !link->next) {
4314 				if (requests->len == 1) {
4315 					EM365Event *event = NULL;
4316 
4317 					success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL, &event, cancellable, error);
4318 
4319 					if (success)
4320 						*out_events = g_slist_prepend (*out_events, event);
4321 				} else {
4322 					success = e_m365_connection_batch_request_sync (cnc, E_M365_API_V1_0, requests, cancellable, error);
4323 
4324 					if (success) {
4325 						guint ii;
4326 
4327 						for (ii = 0; ii < requests->len && success; ii++) {
4328 							JsonNode *node = NULL;
4329 
4330 							message = requests->pdata[ii];
4331 							success = e_m365_connection_json_node_from_message (message, NULL, &node, cancellable, error);
4332 
4333 							if (success && node && JSON_NODE_HOLDS_OBJECT (node)) {
4334 								JsonObject *response;
4335 
4336 								response = json_node_get_object (node);
4337 
4338 								if (response) {
4339 									*out_events = g_slist_prepend (*out_events, json_object_ref (response));
4340 								} else {
4341 									success = FALSE;
4342 								}
4343 							} else {
4344 								success = FALSE;
4345 							}
4346 
4347 							if (node)
4348 								json_node_unref (node);
4349 						}
4350 					}
4351 				}
4352 
4353 				g_ptr_array_remove_range (requests, 0, requests->len);
4354 
4355 				done += requests->len;
4356 
4357 				camel_operation_progress (cancellable, done * 100.0 / total);
4358 			}
4359 		}
4360 
4361 		g_ptr_array_free (requests, TRUE);
4362 	} else {
4363 		SoupMessage *message;
4364 
4365 		message = e_m365_connection_prepare_get_event (cnc, user_override, group_id, calendar_id, event_ids->data, prefer_outlook_timezone, select, error);
4366 
4367 		if (message) {
4368 			EM365Event *event = NULL;
4369 
4370 			success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL, &event, cancellable, error);
4371 
4372 			if (success)
4373 				*out_events = g_slist_prepend (*out_events, event);
4374 
4375 			g_clear_object (&message);
4376 		} else {
4377 			success = FALSE;
4378 		}
4379 	}
4380 
4381 	*out_events = g_slist_reverse (*out_events);
4382 
4383 	return success;
4384 }
4385 
4386 /* https://docs.microsoft.com/en-us/graph/api/event-update?view=graph-rest-1.0&tabs=http */
4387 
4388 gboolean
e_m365_connection_update_event_sync(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,const gchar * calendar_id,const gchar * event_id,JsonBuilder * event,GCancellable * cancellable,GError ** error)4389 e_m365_connection_update_event_sync (EM365Connection *cnc,
4390 				     const gchar *user_override, /* for which user, NULL to use the account user */
4391 				     const gchar *group_id, /* nullable - then the default group is used */
4392 				     const gchar *calendar_id,
4393 				     const gchar *event_id,
4394 				     JsonBuilder *event,
4395 				     GCancellable *cancellable,
4396 				     GError **error)
4397 {
4398 	SoupMessage *message;
4399 	gboolean success;
4400 	gchar *uri;
4401 
4402 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
4403 	g_return_val_if_fail (calendar_id != NULL, FALSE);
4404 	g_return_val_if_fail (event_id != NULL, FALSE);
4405 	g_return_val_if_fail (event != NULL, FALSE);
4406 
4407 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
4408 		group_id ? "calendarGroups" : "calendars",
4409 		group_id,
4410 		group_id ? "calendars" : NULL,
4411 		"", calendar_id,
4412 		"", "events",
4413 		"", event_id,
4414 		NULL);
4415 
4416 	message = m365_connection_new_soup_message ("PATCH", uri, CSM_DISABLE_RESPONSE, error);
4417 
4418 	if (!message) {
4419 		g_free (uri);
4420 
4421 		return FALSE;
4422 	}
4423 
4424 	g_free (uri);
4425 
4426 	e_m365_connection_set_json_body (message, event);
4427 
4428 	success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL, cancellable, error);
4429 
4430 	g_clear_object (&message);
4431 
4432 	return success;
4433 }
4434 
4435 /* https://docs.microsoft.com/en-us/graph/api/event-delete?view=graph-rest-1.0&tabs=http */
4436 
4437 gboolean
e_m365_connection_delete_event_sync(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,const gchar * calendar_id,const gchar * event_id,GCancellable * cancellable,GError ** error)4438 e_m365_connection_delete_event_sync (EM365Connection *cnc,
4439 				     const gchar *user_override, /* for which user, NULL to use the account user */
4440 				     const gchar *group_id, /* nullable - then the default group is used */
4441 				     const gchar *calendar_id,
4442 				     const gchar *event_id,
4443 				     GCancellable *cancellable,
4444 				     GError **error)
4445 {
4446 	SoupMessage *message;
4447 	gboolean success;
4448 	gchar *uri;
4449 
4450 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
4451 	g_return_val_if_fail (calendar_id != NULL, FALSE);
4452 	g_return_val_if_fail (event_id != NULL, FALSE);
4453 
4454 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
4455 		group_id ? "calendarGroups" : "calendars",
4456 		group_id,
4457 		group_id ? "calendars" : NULL,
4458 		"", calendar_id,
4459 		"", "events",
4460 		"", event_id,
4461 		NULL);
4462 
4463 	message = m365_connection_new_soup_message (SOUP_METHOD_DELETE, uri, CSM_DEFAULT, error);
4464 
4465 	if (!message) {
4466 		g_free (uri);
4467 
4468 		return FALSE;
4469 	}
4470 
4471 	g_free (uri);
4472 
4473 	success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL, cancellable, error);
4474 
4475 	g_clear_object (&message);
4476 
4477 	return success;
4478 }
4479 
4480 /* https://docs.microsoft.com/en-us/graph/api/event-accept?view=graph-rest-1.0&tabs=http
4481    https://docs.microsoft.com/en-us/graph/api/event-tentativelyaccept?view=graph-rest-1.0&tabs=http
4482    https://docs.microsoft.com/en-us/graph/api/event-decline?view=graph-rest-1.0&tabs=http */
4483 
4484 gboolean
e_m365_connection_response_event_sync(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,const gchar * calendar_id,const gchar * event_id,EM365ResponseType response,const gchar * comment,gboolean send_response,GCancellable * cancellable,GError ** error)4485 e_m365_connection_response_event_sync (EM365Connection *cnc,
4486 				       const gchar *user_override, /* for which user, NULL to use the account user */
4487 				       const gchar *group_id, /* nullable - then the default group is used */
4488 				       const gchar *calendar_id,
4489 				       const gchar *event_id,
4490 				       EM365ResponseType response, /* uses only accepted/tentatively accepted/declined values */
4491 				       const gchar *comment, /* nullable */
4492 				       gboolean send_response,
4493 				       GCancellable *cancellable,
4494 				       GError **error)
4495 {
4496 	JsonBuilder *builder;
4497 	SoupMessage *message;
4498 	gboolean success;
4499 	gchar *uri;
4500 
4501 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
4502 	g_return_val_if_fail (calendar_id != NULL, FALSE);
4503 	g_return_val_if_fail (event_id != NULL, FALSE);
4504 	g_return_val_if_fail (response == E_M365_RESPONSE_ACCEPTED || response == E_M365_RESPONSE_TENTATIVELY_ACCEPTED || response == E_M365_RESPONSE_DECLINED, FALSE);
4505 
4506 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
4507 		group_id ? "calendarGroups" : "calendars",
4508 		group_id,
4509 		group_id ? "calendars" : NULL,
4510 		"", calendar_id,
4511 		"", "events",
4512 		"", event_id,
4513 		"", response == E_M365_RESPONSE_TENTATIVELY_ACCEPTED ? "tentativelyAccept" :
4514 		    response == E_M365_RESPONSE_DECLINED ? "decline" : "accept",
4515 		NULL);
4516 
4517 	message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DISABLE_RESPONSE, error);
4518 
4519 	if (!message) {
4520 		g_free (uri);
4521 
4522 		return FALSE;
4523 	}
4524 
4525 	g_free (uri);
4526 
4527 	builder = json_builder_new_immutable ();
4528 
4529 	e_m365_json_begin_object_member (builder, NULL);
4530 	e_m365_json_add_nonempty_string_member (builder, "comment", comment);
4531 	e_m365_json_add_boolean_member (builder, "sendResponse", send_response);
4532 	e_m365_json_end_object_member (builder);
4533 
4534 	e_m365_connection_set_json_body (message, builder);
4535 
4536 	g_object_unref (builder);
4537 
4538 	success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL, cancellable, error);
4539 
4540 	g_clear_object (&message);
4541 
4542 	return success;
4543 }
4544 
4545 /* https://docs.microsoft.com/en-us/graph/api/event-dismissreminder?view=graph-rest-1.0&tabs=http */
4546 
4547 gboolean
e_m365_connection_dismiss_reminder_sync(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,const gchar * calendar_id,const gchar * event_id,GCancellable * cancellable,GError ** error)4548 e_m365_connection_dismiss_reminder_sync (EM365Connection *cnc,
4549 					 const gchar *user_override, /* for which user, NULL to use the account user */
4550 					 const gchar *group_id, /* nullable - then the default group is used */
4551 					 const gchar *calendar_id,
4552 					 const gchar *event_id,
4553 					 GCancellable *cancellable,
4554 					 GError **error)
4555 {
4556 	SoupMessage *message;
4557 	gboolean success;
4558 	gchar *uri;
4559 
4560 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
4561 	g_return_val_if_fail (calendar_id != NULL, FALSE);
4562 	g_return_val_if_fail (event_id != NULL, FALSE);
4563 
4564 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
4565 		group_id ? "calendarGroups" : "calendars",
4566 		group_id,
4567 		group_id ? "calendars" : NULL,
4568 		"", calendar_id,
4569 		"", "events",
4570 		"", event_id,
4571 		"", "dismissReminder",
4572 		NULL);
4573 
4574 	message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DEFAULT, error);
4575 
4576 	if (!message) {
4577 		g_free (uri);
4578 
4579 		return FALSE;
4580 	}
4581 
4582 	g_free (uri);
4583 
4584 	success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL, cancellable, error);
4585 
4586 	g_clear_object (&message);
4587 
4588 	return success;
4589 }
4590 
4591 /* https://docs.microsoft.com/en-us/graph/api/event-list-attachments?view=graph-rest-1.0&tabs=http */
4592 
4593 gboolean
e_m365_connection_list_event_attachments_sync(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,const gchar * calendar_id,const gchar * event_id,const gchar * select,GSList ** out_attachments,GCancellable * cancellable,GError ** error)4594 e_m365_connection_list_event_attachments_sync (EM365Connection *cnc,
4595 					       const gchar *user_override, /* for which user, NULL to use the account user */
4596 					       const gchar *group_id, /* nullable, then the default group is used */
4597 					       const gchar *calendar_id,
4598 					       const gchar *event_id,
4599 					       const gchar *select, /* nullable - properties to select */
4600 					       GSList **out_attachments, /* EM365Attachment * */
4601 					       GCancellable *cancellable,
4602 					       GError **error)
4603 {
4604 	EM365ResponseData rd;
4605 	SoupMessage *message;
4606 	gchar *uri;
4607 	gboolean success;
4608 
4609 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
4610 	g_return_val_if_fail (calendar_id != NULL, FALSE);
4611 	g_return_val_if_fail (event_id != NULL, FALSE);
4612 	g_return_val_if_fail (out_attachments != NULL, FALSE);
4613 
4614 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
4615 		group_id ? "calendarGroups" : "calendars",
4616 		group_id,
4617 		group_id ? "calendars" : NULL,
4618 		"", calendar_id,
4619 		"", "events",
4620 		"", event_id,
4621 		"", "attachments",
4622 		"$select", select,
4623 		NULL);
4624 
4625 	message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
4626 
4627 	if (!message) {
4628 		g_free (uri);
4629 
4630 		return FALSE;
4631 	}
4632 
4633 	g_free (uri);
4634 
4635 	memset (&rd, 0, sizeof (EM365ResponseData));
4636 
4637 	rd.out_items = out_attachments;
4638 
4639 	success = m365_connection_send_request_sync (cnc, message, e_m365_read_valued_response_cb, NULL, &rd, cancellable, error);
4640 
4641 	g_clear_object (&message);
4642 
4643 	return success;
4644 }
4645 
4646 /* https://docs.microsoft.com/en-us/graph/api/attachment-get?view=graph-rest-1.0&tabs=http */
4647 
4648 gboolean
e_m365_connection_get_event_attachment_sync(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,const gchar * calendar_id,const gchar * event_id,const gchar * attachment_id,EM365ConnectionRawDataFunc func,gpointer func_user_data,GCancellable * cancellable,GError ** error)4649 e_m365_connection_get_event_attachment_sync (EM365Connection *cnc,
4650 					     const gchar *user_override, /* for which user, NULL to use the account user */
4651 					     const gchar *group_id, /* nullable, then the default group is used */
4652 					     const gchar *calendar_id,
4653 					     const gchar *event_id,
4654 					     const gchar *attachment_id,
4655 					     EM365ConnectionRawDataFunc func,
4656 					     gpointer func_user_data,
4657 					     GCancellable *cancellable,
4658 					     GError **error)
4659 {
4660 	SoupMessage *message;
4661 	gboolean success;
4662 	gchar *uri;
4663 
4664 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
4665 	g_return_val_if_fail (calendar_id != NULL, FALSE);
4666 	g_return_val_if_fail (event_id != NULL, FALSE);
4667 	g_return_val_if_fail (attachment_id != NULL, FALSE);
4668 	g_return_val_if_fail (func != NULL, FALSE);
4669 
4670 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
4671 		group_id ? "calendarGroups" : "calendars",
4672 		group_id,
4673 		group_id ? "calendars" : NULL,
4674 		"", calendar_id,
4675 		"", "events",
4676 		"", event_id,
4677 		"", "attachments",
4678 		"", attachment_id,
4679 		"", "$value",
4680 		NULL);
4681 
4682 	message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
4683 
4684 	if (!message) {
4685 		g_free (uri);
4686 
4687 		return FALSE;
4688 	}
4689 
4690 	g_free (uri);
4691 
4692 	success = m365_connection_send_request_sync (cnc, message, NULL, func, func_user_data, cancellable, error);
4693 
4694 	g_clear_object (&message);
4695 
4696 	return success;
4697 }
4698 
4699 /* https://docs.microsoft.com/en-us/graph/api/event-post-attachments?view=graph-rest-1.0&tabs=http */
4700 
4701 gboolean
e_m365_connection_add_event_attachment_sync(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,const gchar * calendar_id,const gchar * event_id,JsonBuilder * in_attachment,EM365Attachment ** out_attachment,GCancellable * cancellable,GError ** error)4702 e_m365_connection_add_event_attachment_sync (EM365Connection *cnc,
4703 					     const gchar *user_override, /* for which user, NULL to use the account user */
4704 					     const gchar *group_id, /* nullable, then the default group is used */
4705 					     const gchar *calendar_id,
4706 					     const gchar *event_id,
4707 					     JsonBuilder *in_attachment,
4708 					     EM365Attachment **out_attachment, /* nullable */
4709 					     GCancellable *cancellable,
4710 					     GError **error)
4711 {
4712 	SoupMessage *message;
4713 	gboolean success;
4714 	gchar *uri;
4715 
4716 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
4717 	g_return_val_if_fail (calendar_id != NULL, FALSE);
4718 	g_return_val_if_fail (event_id != NULL, FALSE);
4719 	g_return_val_if_fail (in_attachment != NULL, FALSE);
4720 
4721 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
4722 		group_id ? "calendarGroups" : "calendars",
4723 		group_id,
4724 		group_id ? "calendars" : NULL,
4725 		"", calendar_id,
4726 		"", "events",
4727 		"", event_id,
4728 		"", "attachments",
4729 		NULL);
4730 
4731 	message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, out_attachment ? CSM_DEFAULT : CSM_DISABLE_RESPONSE, error);
4732 
4733 	if (!message) {
4734 		g_free (uri);
4735 
4736 		return FALSE;
4737 	}
4738 
4739 	g_free (uri);
4740 
4741 	e_m365_connection_set_json_body (message, in_attachment);
4742 
4743 	success = m365_connection_send_request_sync (cnc, message, out_attachment ? e_m365_read_json_object_response_cb : NULL,
4744 		out_attachment ? NULL : e_m365_read_no_response_cb, out_attachment, cancellable, error);
4745 
4746 	g_clear_object (&message);
4747 
4748 	return success;
4749 }
4750 
4751 /* https://docs.microsoft.com/en-us/graph/api/attachment-delete?view=graph-rest-1.0&tabs=http */
4752 
4753 gboolean
e_m365_connection_delete_event_attachment_sync(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,const gchar * calendar_id,const gchar * event_id,const gchar * attachment_id,GCancellable * cancellable,GError ** error)4754 e_m365_connection_delete_event_attachment_sync (EM365Connection *cnc,
4755 						const gchar *user_override, /* for which user, NULL to use the account user */
4756 						const gchar *group_id, /* nullable, then the default group is used */
4757 						const gchar *calendar_id,
4758 						const gchar *event_id,
4759 						const gchar *attachment_id,
4760 						GCancellable *cancellable,
4761 						GError **error)
4762 {
4763 	SoupMessage *message;
4764 	gboolean success;
4765 	gchar *uri;
4766 
4767 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
4768 	g_return_val_if_fail (calendar_id != NULL, FALSE);
4769 	g_return_val_if_fail (event_id != NULL, FALSE);
4770 	g_return_val_if_fail (attachment_id != NULL, FALSE);
4771 
4772 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
4773 		group_id ? "calendarGroups" : "calendars",
4774 		group_id,
4775 		group_id ? "calendars" : NULL,
4776 		"", calendar_id,
4777 		"", "events",
4778 		"", event_id,
4779 		"", "attachments",
4780 		"", attachment_id,
4781 		NULL);
4782 
4783 	message = m365_connection_new_soup_message (SOUP_METHOD_DELETE, uri, CSM_DEFAULT, error);
4784 
4785 	if (!message) {
4786 		g_free (uri);
4787 
4788 		return FALSE;
4789 	}
4790 
4791 	g_free (uri);
4792 
4793 	success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL, cancellable, error);
4794 
4795 	g_clear_object (&message);
4796 
4797 	return success;
4798 }
4799 
4800 /* https://docs.microsoft.com/en-us/graph/api/calendar-getschedule?view=graph-rest-1.0&tabs=http */
4801 
4802 gboolean
e_m365_connection_get_schedule_sync(EM365Connection * cnc,const gchar * user_override,gint interval_minutes,time_t start_time,time_t end_time,const GSList * email_addresses,GSList ** out_infos,GCancellable * cancellable,GError ** error)4803 e_m365_connection_get_schedule_sync (EM365Connection *cnc,
4804 				     const gchar *user_override, /* for which user, NULL to use the account user */
4805 				     gint interval_minutes, /* between 5 and 1440, -1 to use the default (30) */
4806 				     time_t start_time,
4807 				     time_t end_time,
4808 				     const GSList *email_addresses, /* const gchar * - SMTP addresses to query */
4809 				     GSList **out_infos, /* EM365ScheduleInformation * */
4810 				     GCancellable *cancellable,
4811 				     GError **error)
4812 {
4813 	EM365ResponseData rd;
4814 	JsonBuilder *builder;
4815 	SoupMessage *message;
4816 	GSList *link;
4817 	gboolean success;
4818 	gchar *uri;
4819 
4820 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
4821 	g_return_val_if_fail (email_addresses != NULL, FALSE);
4822 	g_return_val_if_fail (out_infos != NULL, FALSE);
4823 
4824 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
4825 		"calendar",
4826 		"getSchedule",
4827 		NULL,
4828 		NULL);
4829 
4830 	message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DEFAULT, error);
4831 
4832 	if (!message) {
4833 		g_free (uri);
4834 
4835 		return FALSE;
4836 	}
4837 
4838 	g_free (uri);
4839 
4840 	builder = json_builder_new_immutable ();
4841 
4842 	e_m365_json_begin_object_member (builder, NULL);
4843 
4844 	if (interval_minutes > 0)
4845 		e_m365_json_add_int_member (builder, "interval", interval_minutes);
4846 
4847 	e_m365_add_date_time (builder, "startTime", start_time, "UTC");
4848 	e_m365_add_date_time (builder, "endTime", end_time, "UTC");
4849 	e_m365_json_begin_array_member (builder, "schedules");
4850 
4851 	for (link = (GSList *) email_addresses; link; link = g_slist_next (link)) {
4852 		const gchar *addr = link->data;
4853 
4854 		if (addr && *addr)
4855 			json_builder_add_string_value (builder, addr);
4856 	}
4857 
4858 	e_m365_json_end_array_member (builder); /* "schedules" */
4859 	e_m365_json_end_object_member (builder);
4860 
4861 	e_m365_connection_set_json_body (message, builder);
4862 
4863 	g_object_unref (builder);
4864 
4865 	memset (&rd, 0, sizeof (EM365ResponseData));
4866 
4867 	rd.out_items = out_infos;
4868 
4869 	success = m365_connection_send_request_sync (cnc, message, e_m365_read_valued_response_cb, NULL, &rd, cancellable, error);
4870 
4871 	g_clear_object (&message);
4872 
4873 	return success;
4874 }
4875 
4876 /* https://docs.microsoft.com/en-us/graph/api/outlookuser-list-taskgroups?view=graph-rest-beta&tabs=http */
4877 
4878 gboolean
e_m365_connection_list_task_groups_sync(EM365Connection * cnc,const gchar * user_override,GSList ** out_groups,GCancellable * cancellable,GError ** error)4879 e_m365_connection_list_task_groups_sync (EM365Connection *cnc,
4880 						 const gchar *user_override, /* for which user, NULL to use the account user */
4881 						 GSList **out_groups, /* EM365TaskGroup * - the returned outlookTaskGroup objects */
4882 						 GCancellable *cancellable,
4883 						 GError **error)
4884 {
4885 	EM365ResponseData rd;
4886 	SoupMessage *message;
4887 	gchar *uri;
4888 	gboolean success;
4889 
4890 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
4891 	g_return_val_if_fail (out_groups != NULL, FALSE);
4892 
4893 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
4894 		"outlook", "taskGroups", NULL, NULL);
4895 
4896 	message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
4897 
4898 	if (!message) {
4899 		g_free (uri);
4900 
4901 		return FALSE;
4902 	}
4903 
4904 	g_free (uri);
4905 
4906 	memset (&rd, 0, sizeof (EM365ResponseData));
4907 
4908 	rd.out_items = out_groups;
4909 
4910 	success = m365_connection_send_request_sync (cnc, message, e_m365_read_valued_response_cb, NULL, &rd, cancellable, error);
4911 
4912 	g_clear_object (&message);
4913 
4914 	return success;
4915 }
4916 
4917 /* https://docs.microsoft.com/en-us/graph/api/outlookuser-post-taskgroups?view=graph-rest-beta&tabs=http */
4918 
4919 gboolean
e_m365_connection_create_task_group_sync(EM365Connection * cnc,const gchar * user_override,const gchar * name,EM365TaskGroup ** out_created_group,GCancellable * cancellable,GError ** error)4920 e_m365_connection_create_task_group_sync (EM365Connection *cnc,
4921 					  const gchar *user_override, /* for which user, NULL to use the account user */
4922 					  const gchar *name,
4923 					  EM365TaskGroup **out_created_group,
4924 					  GCancellable *cancellable,
4925 					  GError **error)
4926 {
4927 	SoupMessage *message;
4928 	JsonBuilder *builder;
4929 	gboolean success;
4930 	gchar *uri;
4931 
4932 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
4933 	g_return_val_if_fail (name != NULL, FALSE);
4934 	g_return_val_if_fail (out_created_group != NULL, FALSE);
4935 
4936 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
4937 		"outlook", "taskGroups", NULL, NULL);
4938 
4939 	message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DEFAULT, error);
4940 
4941 	if (!message) {
4942 		g_free (uri);
4943 
4944 		return FALSE;
4945 	}
4946 
4947 	g_free (uri);
4948 
4949 	builder = json_builder_new_immutable ();
4950 
4951 	e_m365_json_begin_object_member (builder, NULL);
4952 	e_m365_json_add_string_member (builder, "name", name);
4953 	e_m365_json_end_object_member (builder);
4954 
4955 	e_m365_connection_set_json_body (message, builder);
4956 
4957 	g_object_unref (builder);
4958 
4959 	success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL, out_created_group, cancellable, error);
4960 
4961 	g_clear_object (&message);
4962 
4963 	return success;
4964 }
4965 
4966 /* https://docs.microsoft.com/en-us/graph/api/outlooktaskgroup-get?view=graph-rest-beta&tabs=http */
4967 
4968 gboolean
e_m365_connection_get_task_group_sync(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,EM365TaskGroup ** out_group,GCancellable * cancellable,GError ** error)4969 e_m365_connection_get_task_group_sync (EM365Connection *cnc,
4970 				       const gchar *user_override, /* for which user, NULL to use the account user */
4971 				       const gchar *group_id,
4972 				       EM365TaskGroup **out_group,
4973 				       GCancellable *cancellable,
4974 				       GError **error)
4975 {
4976 	SoupMessage *message;
4977 	gboolean success;
4978 	gchar *uri;
4979 
4980 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
4981 	g_return_val_if_fail (group_id != NULL, FALSE);
4982 	g_return_val_if_fail (out_group != NULL, FALSE);
4983 
4984 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
4985 		"outlook",
4986 		"taskGroups",
4987 		group_id,
4988 		NULL);
4989 
4990 	message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
4991 
4992 	if (!message) {
4993 		g_free (uri);
4994 
4995 		return FALSE;
4996 	}
4997 
4998 	g_free (uri);
4999 
5000 	success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL, out_group, cancellable, error);
5001 
5002 	g_clear_object (&message);
5003 
5004 	return success;
5005 }
5006 
5007 /* https://docs.microsoft.com/en-us/graph/api/outlooktaskgroup-update?view=graph-rest-beta&tabs=http */
5008 
5009 gboolean
e_m365_connection_update_task_group_sync(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,const gchar * name,GCancellable * cancellable,GError ** error)5010 e_m365_connection_update_task_group_sync (EM365Connection *cnc,
5011 					  const gchar *user_override, /* for which user, NULL to use the account user */
5012 					  const gchar *group_id,
5013 					  const gchar *name,
5014 					  GCancellable *cancellable,
5015 					  GError **error)
5016 {
5017 	SoupMessage *message;
5018 	JsonBuilder *builder;
5019 	gboolean success;
5020 	gchar *uri;
5021 
5022 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
5023 	g_return_val_if_fail (group_id != NULL, FALSE);
5024 	g_return_val_if_fail (name != NULL, FALSE);
5025 
5026 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
5027 		"outlook",
5028 		"taskGroups",
5029 		group_id,
5030 		NULL,
5031 		NULL);
5032 
5033 	message = m365_connection_new_soup_message ("PATCH", uri, CSM_DISABLE_RESPONSE, error);
5034 
5035 	if (!message) {
5036 		g_free (uri);
5037 
5038 		return FALSE;
5039 	}
5040 
5041 	g_free (uri);
5042 
5043 	builder = json_builder_new_immutable ();
5044 
5045 	e_m365_json_begin_object_member (builder, NULL);
5046 	e_m365_json_add_string_member (builder, "name", name);
5047 	e_m365_json_end_object_member (builder);
5048 
5049 	e_m365_connection_set_json_body (message, builder);
5050 
5051 	g_object_unref (builder);
5052 
5053 	success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL, cancellable, error);
5054 
5055 	g_clear_object (&message);
5056 
5057 	return success;
5058 }
5059 
5060 /* https://docs.microsoft.com/en-us/graph/api/outlooktaskfolder-delete?view=graph-rest-beta&tabs=http */
5061 
5062 gboolean
e_m365_connection_delete_task_group_sync(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,GCancellable * cancellable,GError ** error)5063 e_m365_connection_delete_task_group_sync (EM365Connection *cnc,
5064 					  const gchar *user_override, /* for which user, NULL to use the account user */
5065 					  const gchar *group_id,
5066 					  GCancellable *cancellable,
5067 					  GError **error)
5068 {
5069 	SoupMessage *message;
5070 	gboolean success;
5071 	gchar *uri;
5072 
5073 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
5074 	g_return_val_if_fail (group_id != NULL, FALSE);
5075 
5076 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
5077 		"outlook", "taskGroups", group_id, NULL);
5078 
5079 	message = m365_connection_new_soup_message (SOUP_METHOD_DELETE, uri, CSM_DEFAULT, error);
5080 
5081 	if (!message) {
5082 		g_free (uri);
5083 
5084 		return FALSE;
5085 	}
5086 
5087 	g_free (uri);
5088 
5089 	success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL, cancellable, error);
5090 
5091 	g_clear_object (&message);
5092 
5093 	return success;
5094 }
5095 
5096 /* https://docs.microsoft.com/en-us/graph/api/outlookuser-list-taskfolders?view=graph-rest-beta&tabs=http */
5097 
5098 gboolean
e_m365_connection_list_task_folders_sync(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,const gchar * select,GSList ** out_folders,GCancellable * cancellable,GError ** error)5099 e_m365_connection_list_task_folders_sync (EM365Connection *cnc,
5100 					  const gchar *user_override, /* for which user, NULL to use the account user */
5101 					  const gchar *group_id, /* nullable, task group id for group task folders */
5102 					  const gchar *select, /* properties to select, nullable */
5103 					  GSList **out_folders, /* EM365TaskFolder * - the returned outlookTaskFolder objects */
5104 					  GCancellable *cancellable,
5105 					  GError **error)
5106 {
5107 	EM365ResponseData rd;
5108 	SoupMessage *message;
5109 	gchar *uri;
5110 	gboolean success;
5111 
5112 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
5113 	g_return_val_if_fail (out_folders != NULL, FALSE);
5114 
5115 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
5116 		"outlook",
5117 		group_id ? "taskGroups" : "taskFolders",
5118 		group_id,
5119 		"", group_id ? "taskFolders" : NULL,
5120 		"$select", select,
5121 		NULL);
5122 
5123 	message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
5124 
5125 	if (!message) {
5126 		g_free (uri);
5127 
5128 		return FALSE;
5129 	}
5130 
5131 	g_free (uri);
5132 
5133 	memset (&rd, 0, sizeof (EM365ResponseData));
5134 
5135 	rd.out_items = out_folders;
5136 
5137 	success = m365_connection_send_request_sync (cnc, message, e_m365_read_valued_response_cb, NULL, &rd, cancellable, error);
5138 
5139 	g_clear_object (&message);
5140 
5141 	return success;
5142 }
5143 
5144 /* https://docs.microsoft.com/en-us/graph/api/outlookuser-post-taskfolders?view=graph-rest-beta&tabs=http */
5145 
5146 gboolean
e_m365_connection_create_task_folder_sync(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,JsonBuilder * task_folder,EM365TaskFolder ** out_created_folder,GCancellable * cancellable,GError ** error)5147 e_m365_connection_create_task_folder_sync (EM365Connection *cnc,
5148 					   const gchar *user_override, /* for which user, NULL to use the account user */
5149 					   const gchar *group_id, /* nullable, then the default group is used */
5150 					   JsonBuilder *task_folder,
5151 					   EM365TaskFolder **out_created_folder,
5152 					   GCancellable *cancellable,
5153 					   GError **error)
5154 {
5155 	SoupMessage *message;
5156 	gboolean success;
5157 	gchar *uri;
5158 
5159 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
5160 	g_return_val_if_fail (task_folder != NULL, FALSE);
5161 	g_return_val_if_fail (out_created_folder != NULL, FALSE);
5162 
5163 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
5164 		"outlook",
5165 		group_id ? "taskGroups" : "taskFolders",
5166 		group_id,
5167 		"", "taskFolders",
5168 		NULL);
5169 
5170 	message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DEFAULT, error);
5171 
5172 	if (!message) {
5173 		g_free (uri);
5174 
5175 		return FALSE;
5176 	}
5177 
5178 	g_free (uri);
5179 
5180 	e_m365_connection_set_json_body (message, task_folder);
5181 
5182 	success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL, out_created_folder, cancellable, error);
5183 
5184 	g_clear_object (&message);
5185 
5186 	return success;
5187 }
5188 
5189 /* https://docs.microsoft.com/en-us/graph/api/outlooktaskfolder-get?view=graph-rest-beta&tabs=http */
5190 
5191 gboolean
e_m365_connection_get_task_folder_sync(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,const gchar * task_folder_id,const gchar * select,EM365TaskFolder ** out_task_folder,GCancellable * cancellable,GError ** error)5192 e_m365_connection_get_task_folder_sync (EM365Connection *cnc,
5193 					const gchar *user_override, /* for which user, NULL to use the account user */
5194 					const gchar *group_id, /* nullable - then the default group is used */
5195 					const gchar *task_folder_id,
5196 					const gchar *select, /* nullable - properties to select */
5197 					EM365TaskFolder **out_task_folder,
5198 					GCancellable *cancellable,
5199 					GError **error)
5200 {
5201 	SoupMessage *message;
5202 	gchar *uri;
5203 	gboolean success;
5204 
5205 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
5206 	g_return_val_if_fail (task_folder_id != NULL, FALSE);
5207 	g_return_val_if_fail (out_task_folder != NULL, FALSE);
5208 
5209 	if (group_id) {
5210 		uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
5211 			"outlook",
5212 			"taskGroups",
5213 			group_id,
5214 			"", "taskFolders",
5215 			"", task_folder_id,
5216 			"$select", select,
5217 			NULL);
5218 	} else {
5219 		uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
5220 			"outlook",
5221 			"taskFolders",
5222 			task_folder_id,
5223 			"$select", select,
5224 			NULL);
5225 	}
5226 
5227 	message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
5228 
5229 	if (!message) {
5230 		g_free (uri);
5231 
5232 		return FALSE;
5233 	}
5234 
5235 	g_free (uri);
5236 
5237 	success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL, out_task_folder, cancellable, error);
5238 
5239 	g_clear_object (&message);
5240 
5241 	return success;
5242 }
5243 
5244 /* https://docs.microsoft.com/en-us/graph/api/outlooktaskfolder-update?view=graph-rest-beta&tabs=http */
5245 
5246 gboolean
e_m365_connection_update_task_folder_sync(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,const gchar * task_folder_id,const gchar * name,GCancellable * cancellable,GError ** error)5247 e_m365_connection_update_task_folder_sync (EM365Connection *cnc,
5248 					   const gchar *user_override, /* for which user, NULL to use the account user */
5249 					   const gchar *group_id, /* nullable - then the default group is used */
5250 					   const gchar *task_folder_id,
5251 					   const gchar *name,
5252 					   GCancellable *cancellable,
5253 					   GError **error)
5254 {
5255 	SoupMessage *message;
5256 	JsonBuilder *builder;
5257 	gboolean success;
5258 	gchar *uri;
5259 
5260 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
5261 	g_return_val_if_fail (task_folder_id != NULL, FALSE);
5262 	g_return_val_if_fail (name != NULL, FALSE);
5263 
5264 	if (group_id) {
5265 		uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
5266 			"outlook",
5267 			"taskGroups",
5268 			group_id,
5269 			"", "taskFolders",
5270 			"", task_folder_id,
5271 			NULL);
5272 	} else {
5273 		uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
5274 			"outlook",
5275 			"taskFolders",
5276 			task_folder_id,
5277 			NULL);
5278 	}
5279 
5280 	message = m365_connection_new_soup_message ("PATCH", uri, CSM_DISABLE_RESPONSE, error);
5281 
5282 	if (!message) {
5283 		g_free (uri);
5284 
5285 		return FALSE;
5286 	}
5287 
5288 	g_free (uri);
5289 
5290 	builder = json_builder_new_immutable ();
5291 
5292 	e_m365_json_begin_object_member (builder, NULL);
5293 	e_m365_json_add_string_member (builder, "name", name);
5294 	e_m365_json_end_object_member (builder);
5295 
5296 	e_m365_connection_set_json_body (message, builder);
5297 
5298 	g_object_unref (builder);
5299 
5300 	success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL, cancellable, error);
5301 
5302 	g_clear_object (&message);
5303 
5304 	return success;
5305 }
5306 
5307 /* https://docs.microsoft.com/en-us/graph/api/outlooktaskfolder-delete?view=graph-rest-beta&tabs=http */
5308 
5309 gboolean
e_m365_connection_delete_task_folder_sync(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,const gchar * task_folder_id,GCancellable * cancellable,GError ** error)5310 e_m365_connection_delete_task_folder_sync (EM365Connection *cnc,
5311 					   const gchar *user_override, /* for which user, NULL to use the account user */
5312 					   const gchar *group_id, /* nullable - then the default group is used */
5313 					   const gchar *task_folder_id,
5314 					   GCancellable *cancellable,
5315 					   GError **error)
5316 {
5317 	SoupMessage *message;
5318 	gboolean success;
5319 	gchar *uri;
5320 
5321 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
5322 	g_return_val_if_fail (task_folder_id != NULL, FALSE);
5323 
5324 	if (group_id) {
5325 		uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
5326 			"outlook",
5327 			"taskGroups",
5328 			group_id,
5329 			"", "taskFolders",
5330 			"", task_folder_id,
5331 			NULL);
5332 	} else {
5333 		uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
5334 			"outlook",
5335 			"taskFolders",
5336 			task_folder_id,
5337 			NULL);
5338 	}
5339 
5340 	message = m365_connection_new_soup_message (SOUP_METHOD_DELETE, uri, CSM_DEFAULT, error);
5341 
5342 	if (!message) {
5343 		g_free (uri);
5344 
5345 		return FALSE;
5346 	}
5347 
5348 	g_free (uri);
5349 
5350 	success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL, cancellable, error);
5351 
5352 	g_clear_object (&message);
5353 
5354 	return success;
5355 }
5356 
5357 /* https://docs.microsoft.com/en-us/graph/api/outlooktaskfolder-list-tasks?view=graph-rest-beta&tabs=http */
5358 
5359 gboolean
e_m365_connection_list_tasks_sync(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,const gchar * task_folder_id,const gchar * prefer_outlook_timezone,const gchar * select,GSList ** out_tasks,GCancellable * cancellable,GError ** error)5360 e_m365_connection_list_tasks_sync (EM365Connection *cnc,
5361 				   const gchar *user_override, /* for which user, NULL to use the account user */
5362 				   const gchar *group_id, /* nullable, task group id for group task folders */
5363 				   const gchar *task_folder_id,
5364 				   const gchar *prefer_outlook_timezone, /* nullable - then UTC, otherwise that zone for the returned times */
5365 				   const gchar *select, /* nullable - properties to select */
5366 				   GSList **out_tasks, /* EM365Task * - the returned task objects */
5367 				   GCancellable *cancellable,
5368 				   GError **error)
5369 {
5370 	EM365ResponseData rd;
5371 	SoupMessage *message;
5372 	gchar *uri;
5373 	gboolean success;
5374 
5375 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
5376 	g_return_val_if_fail (task_folder_id != NULL, FALSE);
5377 	g_return_val_if_fail (out_tasks != NULL, FALSE);
5378 
5379 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
5380 		"outlook",
5381 		group_id ? "taskGroups" : "taskFolders",
5382 		group_id,
5383 		"", group_id ? "taskFolders" : NULL,
5384 		"", task_folder_id,
5385 		"", "tasks",
5386 		"$select", select,
5387 		NULL);
5388 
5389 	message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
5390 
5391 	if (!message) {
5392 		g_free (uri);
5393 
5394 		return FALSE;
5395 	}
5396 
5397 	g_free (uri);
5398 
5399 	m365_connection_prefer_outlook_timezone (message, prefer_outlook_timezone);
5400 	soup_message_headers_append (message->request_headers, "Prefer", "outlook.body-content-type=\"text\"");
5401 
5402 	memset (&rd, 0, sizeof (EM365ResponseData));
5403 
5404 	rd.out_items = out_tasks;
5405 
5406 	success = m365_connection_send_request_sync (cnc, message, e_m365_read_valued_response_cb, NULL, &rd, cancellable, error);
5407 
5408 	g_clear_object (&message);
5409 
5410 	return success;
5411 }
5412 
5413 /* https://docs.microsoft.com/en-us/graph/api/outlookuser-post-tasks?view=graph-rest-beta&tabs=csharp */
5414 
5415 gboolean
e_m365_connection_create_task_sync(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,const gchar * task_folder_id,JsonBuilder * task,EM365Task ** out_created_task,GCancellable * cancellable,GError ** error)5416 e_m365_connection_create_task_sync (EM365Connection *cnc,
5417 				    const gchar *user_override, /* for which user, NULL to use the account user */
5418 				    const gchar *group_id, /* nullable, then the default group is used */
5419 				    const gchar *task_folder_id,
5420 				    JsonBuilder *task,
5421 				    EM365Task **out_created_task,
5422 				    GCancellable *cancellable,
5423 				    GError **error)
5424 {
5425 	SoupMessage *message;
5426 	gboolean success;
5427 	gchar *uri;
5428 
5429 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
5430 	g_return_val_if_fail (task_folder_id != NULL, FALSE);
5431 	g_return_val_if_fail (task != NULL, FALSE);
5432 	g_return_val_if_fail (out_created_task != NULL, FALSE);
5433 
5434 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
5435 		"outlook",
5436 		group_id ? "taskGroups" : "taskFolders",
5437 		group_id,
5438 		"", group_id ? "taskFolders" : NULL,
5439 		"", task_folder_id,
5440 		"", "tasks",
5441 		NULL);
5442 
5443 	message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DEFAULT, error);
5444 
5445 	if (!message) {
5446 		g_free (uri);
5447 
5448 		return FALSE;
5449 	}
5450 
5451 	g_free (uri);
5452 
5453 	e_m365_connection_set_json_body (message, task);
5454 
5455 	success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL, out_created_task, cancellable, error);
5456 
5457 	g_clear_object (&message);
5458 
5459 	return success;
5460 }
5461 
5462 /* https://docs.microsoft.com/en-us/graph/api/outlooktask-get?view=graph-rest-beta&tabs=http */
5463 
5464 SoupMessage *
e_m365_connection_prepare_get_task(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,const gchar * task_folder_id,const gchar * task_id,const gchar * prefer_outlook_timezone,const gchar * select,GError ** error)5465 e_m365_connection_prepare_get_task (EM365Connection *cnc,
5466 				    const gchar *user_override, /* for which user, NULL to use the account user */
5467 				    const gchar *group_id, /* nullable, then the default group is used */
5468 				    const gchar *task_folder_id,
5469 				    const gchar *task_id,
5470 				    const gchar *prefer_outlook_timezone, /* nullable - then UTC, otherwise that zone for the returned times */
5471 				    const gchar *select, /* nullable - properties to select */
5472 				    GError **error)
5473 {
5474 	SoupMessage *message;
5475 	gchar *uri;
5476 
5477 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), NULL);
5478 	g_return_val_if_fail (task_folder_id != NULL, NULL);
5479 	g_return_val_if_fail (task_id != NULL, NULL);
5480 
5481 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
5482 		"outlook",
5483 		group_id ? "taskGroups" : "taskFolders",
5484 		group_id,
5485 		"", group_id ? "taskFolders" : NULL,
5486 		"", task_folder_id,
5487 		"", "tasks",
5488 		"", task_id,
5489 		"$select", select,
5490 		NULL);
5491 
5492 	message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
5493 
5494 	if (!message) {
5495 		g_free (uri);
5496 
5497 		return NULL;
5498 	}
5499 
5500 	g_free (uri);
5501 
5502 	m365_connection_prefer_outlook_timezone (message, prefer_outlook_timezone);
5503 	soup_message_headers_append (message->request_headers, "Prefer", "outlook.body-content-type=\"text\"");
5504 
5505 	return message;
5506 }
5507 
5508 gboolean
e_m365_connection_get_task_sync(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,const gchar * task_folder_id,const gchar * task_id,const gchar * prefer_outlook_timezone,const gchar * select,EM365Task ** out_task,GCancellable * cancellable,GError ** error)5509 e_m365_connection_get_task_sync (EM365Connection *cnc,
5510 				 const gchar *user_override, /* for which user, NULL to use the account user */
5511 				 const gchar *group_id, /* nullable, then the default group is used */
5512 				 const gchar *task_folder_id,
5513 				 const gchar *task_id,
5514 				 const gchar *prefer_outlook_timezone, /* nullable - then UTC, otherwise that zone for the returned times */
5515 				 const gchar *select, /* nullable - properties to select */
5516 				 EM365Task **out_task,
5517 				 GCancellable *cancellable,
5518 				 GError **error)
5519 {
5520 	SoupMessage *message;
5521 	gboolean success;
5522 
5523 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
5524 	g_return_val_if_fail (task_folder_id != NULL, FALSE);
5525 	g_return_val_if_fail (task_id != NULL, FALSE);
5526 	g_return_val_if_fail (out_task != NULL, FALSE);
5527 
5528 	message = e_m365_connection_prepare_get_task (cnc, user_override, group_id, task_folder_id, task_id, prefer_outlook_timezone, select, error);
5529 
5530 	if (!message)
5531 		return FALSE;
5532 
5533 	success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL, out_task, cancellable, error);
5534 
5535 	g_clear_object (&message);
5536 
5537 	return success;
5538 }
5539 
5540 gboolean
e_m365_connection_get_tasks_sync(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,const gchar * task_folder_id,const GSList * task_ids,const gchar * prefer_outlook_timezone,const gchar * select,GSList ** out_tasks,GCancellable * cancellable,GError ** error)5541 e_m365_connection_get_tasks_sync (EM365Connection *cnc,
5542 				  const gchar *user_override, /* for which user, NULL to use the account user */
5543 				  const gchar *group_id, /* nullable, then the default group is used */
5544 				  const gchar *task_folder_id,
5545 				  const GSList *task_ids, /* const gchar * */
5546 				  const gchar *prefer_outlook_timezone, /* nullable - then UTC, otherwise that zone for the returned times */
5547 				  const gchar *select, /* nullable - properties to select */
5548 				  GSList **out_tasks, /* EM365Task *, in the same order as task_ids; can return partial list */
5549 				  GCancellable *cancellable,
5550 				  GError **error)
5551 {
5552 	gboolean success = TRUE;
5553 
5554 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
5555 	g_return_val_if_fail (task_folder_id != NULL, FALSE);
5556 	g_return_val_if_fail (task_ids != NULL, FALSE);
5557 	g_return_val_if_fail (out_tasks != NULL, FALSE);
5558 
5559 	if (g_slist_next (task_ids)) {
5560 		GPtrArray *requests;
5561 		GSList *link;
5562 		guint total, done = 0;
5563 
5564 		total = g_slist_length ((GSList *) task_ids);
5565 		requests = g_ptr_array_new_full (MIN (E_M365_BATCH_MAX_REQUESTS, MIN (total, 50)), g_object_unref);
5566 
5567 		for (link = (GSList *) task_ids; link && success; link = g_slist_next (link)) {
5568 			const gchar *id = link->data;
5569 			SoupMessage *message;
5570 
5571 			message = e_m365_connection_prepare_get_task (cnc, user_override, group_id, task_folder_id, id, prefer_outlook_timezone, select, error);
5572 
5573 			if (!message) {
5574 				success = FALSE;
5575 				break;
5576 			}
5577 
5578 			g_ptr_array_add (requests, message);
5579 
5580 			if (requests->len == E_M365_BATCH_MAX_REQUESTS || !link->next) {
5581 				if (requests->len == 1) {
5582 					EM365Task *task = NULL;
5583 
5584 					success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL, &task, cancellable, error);
5585 
5586 					if (success)
5587 						*out_tasks = g_slist_prepend (*out_tasks, task);
5588 				} else {
5589 					success = e_m365_connection_batch_request_sync (cnc, E_M365_API_BETA, requests, cancellable, error);
5590 
5591 					if (success) {
5592 						guint ii;
5593 
5594 						for (ii = 0; ii < requests->len && success; ii++) {
5595 							JsonNode *node = NULL;
5596 
5597 							message = requests->pdata[ii];
5598 							success = e_m365_connection_json_node_from_message (message, NULL, &node, cancellable, error);
5599 
5600 							if (success && node && JSON_NODE_HOLDS_OBJECT (node)) {
5601 								JsonObject *response;
5602 
5603 								response = json_node_get_object (node);
5604 
5605 								if (response) {
5606 									*out_tasks = g_slist_prepend (*out_tasks, json_object_ref (response));
5607 								} else {
5608 									success = FALSE;
5609 								}
5610 							} else {
5611 								success = FALSE;
5612 							}
5613 
5614 							if (node)
5615 								json_node_unref (node);
5616 						}
5617 					}
5618 				}
5619 
5620 				g_ptr_array_remove_range (requests, 0, requests->len);
5621 
5622 				done += requests->len;
5623 
5624 				camel_operation_progress (cancellable, done * 100.0 / total);
5625 			}
5626 		}
5627 
5628 		g_ptr_array_free (requests, TRUE);
5629 	} else {
5630 		SoupMessage *message;
5631 
5632 		message = e_m365_connection_prepare_get_task (cnc, user_override, group_id, task_folder_id, task_ids->data, prefer_outlook_timezone, select, error);
5633 
5634 		if (message) {
5635 			EM365Task *task = NULL;
5636 
5637 			success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL, &task, cancellable, error);
5638 
5639 			if (success)
5640 				*out_tasks = g_slist_prepend (*out_tasks, task);
5641 
5642 			g_clear_object (&message);
5643 		} else {
5644 			success = FALSE;
5645 		}
5646 	}
5647 
5648 	*out_tasks = g_slist_reverse (*out_tasks);
5649 
5650 	return success;
5651 }
5652 
5653 /* https://docs.microsoft.com/en-us/graph/api/outlooktask-update?view=graph-rest-beta&tabs=http */
5654 
5655 gboolean
e_m365_connection_update_task_sync(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,const gchar * task_folder_id,const gchar * task_id,JsonBuilder * task,GCancellable * cancellable,GError ** error)5656 e_m365_connection_update_task_sync (EM365Connection *cnc,
5657 				    const gchar *user_override, /* for which user, NULL to use the account user */
5658 				    const gchar *group_id, /* nullable - then the default group is used */
5659 				    const gchar *task_folder_id,
5660 				    const gchar *task_id,
5661 				    JsonBuilder *task,
5662 				    GCancellable *cancellable,
5663 				    GError **error)
5664 {
5665 	SoupMessage *message;
5666 	gboolean success;
5667 	gchar *uri;
5668 
5669 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
5670 	g_return_val_if_fail (task_folder_id != NULL, FALSE);
5671 	g_return_val_if_fail (task_id != NULL, FALSE);
5672 	g_return_val_if_fail (task != NULL, FALSE);
5673 
5674 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
5675 		"outlook",
5676 		group_id ? "taskGroups" : "taskFolders",
5677 		group_id,
5678 		"", group_id ? "taskFolders" : NULL,
5679 		"", task_folder_id,
5680 		"", "tasks",
5681 		"", task_id,
5682 		NULL);
5683 
5684 	message = m365_connection_new_soup_message ("PATCH", uri, CSM_DISABLE_RESPONSE, error);
5685 
5686 	if (!message) {
5687 		g_free (uri);
5688 
5689 		return FALSE;
5690 	}
5691 
5692 	g_free (uri);
5693 
5694 	e_m365_connection_set_json_body (message, task);
5695 
5696 	success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL, cancellable, error);
5697 
5698 	g_clear_object (&message);
5699 
5700 	return success;
5701 }
5702 
5703 /* https://docs.microsoft.com/en-us/graph/api/outlooktask-delete?view=graph-rest-beta&tabs=http */
5704 
5705 gboolean
e_m365_connection_delete_task_sync(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,const gchar * task_folder_id,const gchar * task_id,GCancellable * cancellable,GError ** error)5706 e_m365_connection_delete_task_sync (EM365Connection *cnc,
5707 				    const gchar *user_override, /* for which user, NULL to use the account user */
5708 				    const gchar *group_id, /* nullable - then the default group is used */
5709 				    const gchar *task_folder_id,
5710 				    const gchar *task_id,
5711 				    GCancellable *cancellable,
5712 				    GError **error)
5713 {
5714 	SoupMessage *message;
5715 	gboolean success;
5716 	gchar *uri;
5717 
5718 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
5719 	g_return_val_if_fail (task_folder_id != NULL, FALSE);
5720 	g_return_val_if_fail (task_id != NULL, FALSE);
5721 
5722 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
5723 		"outlook",
5724 		group_id ? "taskGroups" : "taskFolders",
5725 		group_id,
5726 		"", group_id ? "taskFolders" : NULL,
5727 		"", task_folder_id,
5728 		"", "tasks",
5729 		"", task_id,
5730 		NULL);
5731 
5732 	message = m365_connection_new_soup_message (SOUP_METHOD_DELETE, uri, CSM_DEFAULT, error);
5733 
5734 	if (!message) {
5735 		g_free (uri);
5736 
5737 		return FALSE;
5738 	}
5739 
5740 	g_free (uri);
5741 
5742 	success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL, cancellable, error);
5743 
5744 	g_clear_object (&message);
5745 
5746 	return success;
5747 }
5748 
5749 /* https://docs.microsoft.com/en-us/graph/api/outlooktask-complete?view=graph-rest-beta */
5750 
5751 gboolean
e_m365_connection_complete_task_sync(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,const gchar * task_folder_id,const gchar * task_id,GCancellable * cancellable,GError ** error)5752 e_m365_connection_complete_task_sync (EM365Connection *cnc,
5753 				      const gchar *user_override, /* for which user, NULL to use the account user */
5754 				      const gchar *group_id, /* nullable - then the default group is used */
5755 				      const gchar *task_folder_id,
5756 				      const gchar *task_id,
5757 				      GCancellable *cancellable,
5758 				      GError **error)
5759 {
5760 	SoupMessage *message;
5761 	gboolean success;
5762 	gchar *uri;
5763 
5764 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
5765 	g_return_val_if_fail (task_folder_id != NULL, FALSE);
5766 	g_return_val_if_fail (task_id != NULL, FALSE);
5767 
5768 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
5769 		"outlook",
5770 		group_id ? "taskGroups" : "taskFolders",
5771 		group_id,
5772 		"", group_id ? "taskFolders" : NULL,
5773 		"", task_folder_id,
5774 		"", "tasks",
5775 		"", task_id,
5776 		"", "complete",
5777 		NULL);
5778 
5779 	message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DISABLE_RESPONSE, error);
5780 
5781 	if (!message) {
5782 		g_free (uri);
5783 
5784 		return FALSE;
5785 	}
5786 
5787 	g_free (uri);
5788 
5789 	success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL, cancellable, error);
5790 
5791 	g_clear_object (&message);
5792 
5793 	return success;
5794 }
5795 
5796 /* https://docs.microsoft.com/en-us/graph/api/outlooktask-list-attachments?view=graph-rest-beta&tabs=http */
5797 
5798 gboolean
e_m365_connection_list_task_attachments_sync(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,const gchar * task_folder_id,const gchar * task_id,const gchar * select,GSList ** out_attachments,GCancellable * cancellable,GError ** error)5799 e_m365_connection_list_task_attachments_sync (EM365Connection *cnc,
5800 					      const gchar *user_override, /* for which user, NULL to use the account user */
5801 					      const gchar *group_id, /* nullable, then the default group is used */
5802 					      const gchar *task_folder_id,
5803 					      const gchar *task_id,
5804 					      const gchar *select, /* nullable - properties to select */
5805 					      GSList **out_attachments, /* EM365Attachment * */
5806 					      GCancellable *cancellable,
5807 					      GError **error)
5808 {
5809 	EM365ResponseData rd;
5810 	SoupMessage *message;
5811 	gchar *uri;
5812 	gboolean success;
5813 
5814 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
5815 	g_return_val_if_fail (task_folder_id != NULL, FALSE);
5816 	g_return_val_if_fail (task_id != NULL, FALSE);
5817 	g_return_val_if_fail (out_attachments != NULL, FALSE);
5818 
5819 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
5820 		"outlook",
5821 		group_id ? "taskGroups" : "taskFolders",
5822 		group_id,
5823 		"", group_id ? "taskFolders" : NULL,
5824 		"", task_folder_id,
5825 		"", "tasks",
5826 		"", task_id,
5827 		"", "attachments",
5828 		"$select", select,
5829 		NULL);
5830 
5831 	message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
5832 
5833 	if (!message) {
5834 		g_free (uri);
5835 
5836 		return FALSE;
5837 	}
5838 
5839 	g_free (uri);
5840 
5841 	memset (&rd, 0, sizeof (EM365ResponseData));
5842 
5843 	rd.out_items = out_attachments;
5844 
5845 	success = m365_connection_send_request_sync (cnc, message, e_m365_read_valued_response_cb, NULL, &rd, cancellable, error);
5846 
5847 	g_clear_object (&message);
5848 
5849 	return success;
5850 }
5851 
5852 /* https://docs.microsoft.com/en-us/graph/api/attachment-get?view=graph-rest-beta&tabs=http */
5853 
5854 gboolean
e_m365_connection_get_task_attachment_sync(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,const gchar * task_folder_id,const gchar * task_id,const gchar * attachment_id,EM365ConnectionRawDataFunc func,gpointer func_user_data,GCancellable * cancellable,GError ** error)5855 e_m365_connection_get_task_attachment_sync (EM365Connection *cnc,
5856 					    const gchar *user_override, /* for which user, NULL to use the account user */
5857 					    const gchar *group_id, /* nullable, then the default group is used */
5858 					    const gchar *task_folder_id,
5859 					    const gchar *task_id,
5860 					    const gchar *attachment_id,
5861 					    EM365ConnectionRawDataFunc func,
5862 					    gpointer func_user_data,
5863 					    GCancellable *cancellable,
5864 					    GError **error)
5865 {
5866 	SoupMessage *message;
5867 	gboolean success;
5868 	gchar *uri;
5869 
5870 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
5871 	g_return_val_if_fail (task_folder_id != NULL, FALSE);
5872 	g_return_val_if_fail (task_id != NULL, FALSE);
5873 	g_return_val_if_fail (attachment_id != NULL, FALSE);
5874 	g_return_val_if_fail (func != NULL, FALSE);
5875 
5876 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
5877 		"outlook",
5878 		group_id ? "taskGroups" : "taskFolders",
5879 		group_id,
5880 		"", group_id ? "taskFolders" : NULL,
5881 		"", task_folder_id,
5882 		"", "tasks",
5883 		"", task_id,
5884 		"", "attachments",
5885 		"", attachment_id,
5886 		"", "$value",
5887 		NULL);
5888 
5889 	message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
5890 
5891 	if (!message) {
5892 		g_free (uri);
5893 
5894 		return FALSE;
5895 	}
5896 
5897 	g_free (uri);
5898 
5899 	success = m365_connection_send_request_sync (cnc, message, NULL, func, func_user_data, cancellable, error);
5900 
5901 	g_clear_object (&message);
5902 
5903 	return success;
5904 }
5905 
5906 /* https://docs.microsoft.com/en-us/graph/api/outlooktask-post-attachments?view=graph-rest-beta&tabs=http */
5907 
5908 gboolean
e_m365_connection_add_task_attachment_sync(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,const gchar * task_folder_id,const gchar * task_id,JsonBuilder * in_attachment,EM365Attachment ** out_attachment,GCancellable * cancellable,GError ** error)5909 e_m365_connection_add_task_attachment_sync (EM365Connection *cnc,
5910 					    const gchar *user_override, /* for which user, NULL to use the account user */
5911 					    const gchar *group_id, /* nullable, then the default group is used */
5912 					    const gchar *task_folder_id,
5913 					    const gchar *task_id,
5914 					    JsonBuilder *in_attachment,
5915 					    EM365Attachment **out_attachment, /* nullable */
5916 					    GCancellable *cancellable,
5917 					    GError **error)
5918 {
5919 	SoupMessage *message;
5920 	gboolean success;
5921 	gchar *uri;
5922 
5923 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
5924 	g_return_val_if_fail (task_folder_id != NULL, FALSE);
5925 	g_return_val_if_fail (task_id != NULL, FALSE);
5926 	g_return_val_if_fail (in_attachment != NULL, FALSE);
5927 
5928 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
5929 		"outlook",
5930 		group_id ? "taskGroups" : "taskFolders",
5931 		group_id,
5932 		"", group_id ? "taskFolders" : NULL,
5933 		"", task_folder_id,
5934 		"", "tasks",
5935 		"", task_id,
5936 		"", "attachments",
5937 		NULL);
5938 
5939 	message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, out_attachment ? CSM_DEFAULT : CSM_DISABLE_RESPONSE, error);
5940 
5941 	if (!message) {
5942 		g_free (uri);
5943 
5944 		return FALSE;
5945 	}
5946 
5947 	g_free (uri);
5948 
5949 	e_m365_connection_set_json_body (message, in_attachment);
5950 
5951 	success = m365_connection_send_request_sync (cnc, message, out_attachment ? e_m365_read_json_object_response_cb : NULL,
5952 		out_attachment ? NULL : e_m365_read_no_response_cb, out_attachment, cancellable, error);
5953 
5954 	g_clear_object (&message);
5955 
5956 	return success;
5957 }
5958 
5959 /* https://docs.microsoft.com/en-us/graph/api/attachment-delete?view=graph-rest-beta&tabs=http */
5960 
5961 gboolean
e_m365_connection_delete_task_attachment_sync(EM365Connection * cnc,const gchar * user_override,const gchar * group_id,const gchar * task_folder_id,const gchar * task_id,const gchar * attachment_id,GCancellable * cancellable,GError ** error)5962 e_m365_connection_delete_task_attachment_sync (EM365Connection *cnc,
5963 					       const gchar *user_override, /* for which user, NULL to use the account user */
5964 					       const gchar *group_id, /* nullable, then the default group is used */
5965 					       const gchar *task_folder_id,
5966 					       const gchar *task_id,
5967 					       const gchar *attachment_id,
5968 					       GCancellable *cancellable,
5969 					       GError **error)
5970 {
5971 	SoupMessage *message;
5972 	gboolean success;
5973 	gchar *uri;
5974 
5975 	g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
5976 	g_return_val_if_fail (task_folder_id != NULL, FALSE);
5977 	g_return_val_if_fail (task_id != NULL, FALSE);
5978 	g_return_val_if_fail (attachment_id != NULL, FALSE);
5979 
5980 	uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
5981 		"outlook",
5982 		group_id ? "taskGroups" : "taskFolders",
5983 		group_id,
5984 		"", group_id ? "taskFolders" : NULL,
5985 		"", task_folder_id,
5986 		"", "tasks",
5987 		"", task_id,
5988 		"", "attachments",
5989 		"", attachment_id,
5990 		NULL);
5991 
5992 	message = m365_connection_new_soup_message (SOUP_METHOD_DELETE, uri, CSM_DEFAULT, error);
5993 
5994 	if (!message) {
5995 		g_free (uri);
5996 
5997 		return FALSE;
5998 	}
5999 
6000 	g_free (uri);
6001 
6002 	success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL, cancellable, error);
6003 
6004 	g_clear_object (&message);
6005 
6006 	return success;
6007 }
6008