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, ¤t_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