GCC Code Coverage Report


Directory: src/
File: mail/msg-mail-service.c
Date: 2026-03-07 01:53:34
Exec Total Coverage
Lines: 202 232 87.1%
Functions: 13 13 100.0%
Branches: 56 94 59.6%

Line Branch Exec Source
1 /* Copyright 2023-2024 Jan-Michael Brummer
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU Lesser General Public License as published by
5 * the Free Software Foundation, either version 3 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17 #include <libsoup/soup.h>
18 #include <json-glib/json-glib.h>
19
20 #include <stdio.h>
21
22 #include "msg-authorizer.h"
23 #include "msg-error.h"
24 #include "msg-private.h"
25 #include "msg-service.h"
26 #include "msg-mail-message.h"
27 #include "msg-mail-folder.h"
28 #include "msg-mail-service.h"
29
30 struct _MsgMailService {
31 MsgService parent_instance;
32 };
33
34
5/6
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 26 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 24 times.
30 G_DEFINE_TYPE (MsgMailService, msg_mail_service, MSG_TYPE_SERVICE);
35
36 static void
37 1 msg_mail_service_init (__attribute__ ((unused)) MsgMailService *self)
38 {
39 1 }
40
41 static void
42 2 msg_mail_service_class_init (__attribute__ ((unused)) MsgMailServiceClass *class)
43 {
44 2 }
45
46 MsgMailService *
47 1 msg_mail_service_new (MsgAuthorizer *authorizer)
48 {
49 1 return g_object_new (MSG_TYPE_MAIL_SERVICE, "authorizer", authorizer, NULL);
50 }
51
52 /**
53 * msg_mail_service_get_messages
54 * @self: a #MsgMailService
55 * @next_link: next link if available
56 * @out_next_link: next next link
57 * @delta_link: delta link if used
58 * @out_delta_link: new delta link
59 * @max_page_size: maximal page size
60 * @cancellable: a #GCancellable
61 * @error: a #GError
62 *
63 * Get all mails for given service
64 *
65 * Returns: (element-type MsgMailMessage) (transfer full): all mails the user can access
66 */
67 GList *
68 2 msg_mail_service_get_messages (MsgMailService *self,
69 MsgMailFolder *folder,
70 const char *next_link,
71 char **out_next_link,
72 const char *delta_link,
73 char **out_delta_link,
74 int max_page_size,
75 GCancellable *cancellable,
76 GError **error)
77 {
78 2 JsonObject *root_object = NULL;
79 2 g_autofree char *url = NULL;
80 2 g_autolist (MsgMailMessage) list = NULL;
81 2 JsonArray *array = NULL;
82 2 guint array_length = 0, index = 0;
83 2 g_autoptr (GBytes) response = NULL;
84 2 g_autoptr (JsonParser) parser = NULL;
85
86
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!msg_service_refresh_authorization (MSG_SERVICE (self), cancellable, error))
87 return NULL;
88
89
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (next_link)
90 url = g_strdup (next_link);
91
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 else if (delta_link)
92 url = g_strdup (delta_link);
93 else
94 2 url = g_strconcat (MSG_API_ENDPOINT, "/me/mailFolders//", msg_mail_folder_get_id (folder), "/messages/delta?$select=from,subject,toRecipients,ccRecipients,hasAttachments,bodyPreview,receivedDateTime,isRead,id", NULL);
95
96 do {
97
1/3
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
2 g_autoptr (SoupMessage) message = NULL;
98
99 2 message = msg_service_build_message (MSG_SERVICE (self), "GET", url, NULL, FALSE);
100
101
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (max_page_size > 0) {
102 g_autofree char *prefer_value = g_strdup_printf ("odata.maxpagesize=%u", max_page_size);
103 soup_message_headers_append (soup_message_get_request_headers (message), "Prefer", prefer_value);
104 }
105
106 2 parser = msg_service_send_and_parse_response (MSG_SERVICE (self), message, &root_object, cancellable, error);
107
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!parser)
108 return NULL;
109
110 2 array = json_object_get_array_member (root_object, "value");
111
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 g_assert (array != NULL);
112
113 2 array_length = json_array_get_length (array);
114
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 for (index = 0; index < array_length; index++) {
115 2 g_autoptr (GError) local_error = NULL;
116 2 MsgMailMessage *msg = NULL;
117 2 JsonObject *mail_object = NULL;
118
119 2 mail_object = json_array_get_object_element (array, index);
120
121 2 msg = msg_mail_message_new_from_json (mail_object, &local_error);
122
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (msg) {
123 2 list = g_list_append (list, msg);
124 } else {
125 g_warning ("Could not parse mail object: %s", local_error->message);
126 g_clear_error (&local_error);
127 }
128 }
129
130
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 g_clear_pointer (&url, g_free);
131
132
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 if (json_object_has_member (root_object, "@odata.deltaLink") && out_delta_link) {
133 *out_delta_link = g_strdup (json_object_get_string_member (root_object, "@odata.deltaLink"));
134 }
135
136 2 url = msg_service_get_next_link (root_object);
137
138
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (out_next_link) {
139 *out_next_link = g_strdup (url);
140 break;
141 }
142
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 } while (url != NULL);
143
144 2 return g_steal_pointer (&list);
145 }
146
147 /**
148 * msg_mail_service_get_mail_folders
149 * @self: a #MsgMailService
150 * @cancellable: a #GCancellable
151 * @error: a #GError
152 *
153 * Get all folders for given service
154 *
155 * Returns: (element-type MsgMailFolder) (transfer full): all mail folders the user can access
156 */
157 GList *
158 1 msg_mail_service_get_mail_folders (MsgMailService *self,
159 char *delta_url,
160 char **delta_url_out,
161 GCancellable *cancellable,
162 GError **error)
163 {
164 1 JsonObject *root_object = NULL;
165 1 g_autofree char *url = NULL;
166 1 g_autolist (MsgMailFolder) list = NULL;
167 1 JsonArray *array = NULL;
168 1 guint array_length = 0, index = 0;
169 1 g_autoptr (GBytes) response = NULL;
170 1 g_autoptr (JsonParser) parser = NULL;
171
172
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!msg_service_refresh_authorization (MSG_SERVICE (self), cancellable, error))
173 return NULL;
174
175
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (delta_url) {
176 url = g_strdup (delta_url);
177 } else {
178 1 url = g_strconcat (MSG_API_ENDPOINT, "/me/mailFolders/delta", NULL);
179 }
180
181 do {
182
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 g_autoptr (SoupMessage) message = NULL;
183
184 1 message = msg_service_build_message (MSG_SERVICE (self), "GET", url, NULL, FALSE);
185 1 parser = msg_service_send_and_parse_response (MSG_SERVICE (self), message, &root_object, cancellable, error);
186
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!parser)
187 return NULL;
188
189 1 array = json_object_get_array_member (root_object, "value");
190
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 g_assert (array != NULL);
191
192 1 array_length = json_array_get_length (array);
193
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 1 times.
9 for (index = 0; index < array_length; index++) {
194 8 g_autoptr (GError) local_error = NULL;
195 8 MsgMailFolder *folder = NULL;
196 8 JsonObject *object = NULL;
197
198 8 object = json_array_get_object_element (array, index);
199
200 8 folder = msg_mail_folder_new_from_json (object, &local_error);
201
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 if (folder) {
202 8 list = g_list_append (list, folder);
203 } else {
204 g_warning ("Could not parse mail folder object: %s", local_error->message);
205 g_clear_error (&local_error);
206 }
207 }
208
209
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 if (json_object_has_member (root_object, "@odata.deltaLink") && delta_url_out) {
210 *delta_url_out = g_strdup (json_object_get_string_member (root_object, "@odata.deltaLink"));
211 }
212
213
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 g_clear_pointer (&url, g_free);
214 1 url = msg_service_get_next_link (root_object);
215
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 } while (url != NULL);
216
217 1 return g_steal_pointer (&list);
218 }
219
220 /**
221 * msg_mail_service_get_mail_folder
222 * @self: a #MsgMailService
223 * @type: a #MsgMailMailFolderType
224 * @cancellable: a #GCancellable
225 * @error: a #GError
226 *
227 * Get a specific mail folder for given service
228 *
229 * Returns: (transfer full): a #MsgMailFolder
230 */
231 MsgMailFolder *
232 9 msg_mail_service_get_mail_folder (MsgMailService *self,
233 MsgMailFolderType type,
234 GCancellable *cancellable,
235 GError **error)
236 {
237 9 g_autoptr (SoupMessage) message = NULL;
238 9 g_autofree char *url = NULL;
239 9 g_autoptr (GBytes) response = NULL;
240 9 JsonObject *root_object = NULL;
241 9 g_autoptr (JsonParser) parser = NULL;
242 9 MsgMailFolder *folder = NULL;
243
244
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 if (!msg_service_refresh_authorization (MSG_SERVICE (self), cancellable, error))
245 return NULL;
246
247
7/8
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
9 switch (type) {
248 1 case MSG_MAIL_FOLDER_TYPE_INBOX:
249 1 url = g_strconcat (MSG_API_ENDPOINT, "/me/mailFolders/inbox", NULL);
250 1 break;
251 3 case MSG_MAIL_FOLDER_TYPE_DRAFTS:
252 3 url = g_strconcat (MSG_API_ENDPOINT, "/me/mailFolders/drafts", NULL);
253 3 break;
254 1 case MSG_MAIL_FOLDER_TYPE_SENT_ITEMS:
255 1 url = g_strconcat (MSG_API_ENDPOINT, "/me/mailFolders/sentitems", NULL);
256 1 break;
257 1 case MSG_MAIL_FOLDER_TYPE_JUNK_EMAIL:
258 1 url = g_strconcat (MSG_API_ENDPOINT, "/me/mailFolders/junkemail", NULL);
259 1 break;
260 1 case MSG_MAIL_FOLDER_TYPE_DELETED_ITEMS:
261 1 url = g_strconcat (MSG_API_ENDPOINT, "/me/mailFolders/deleteditems", NULL);
262 1 break;
263 1 case MSG_MAIL_FOLDER_TYPE_OUTBOX:
264 1 url = g_strconcat (MSG_API_ENDPOINT, "/me/mailFolders/outbox", NULL);
265 1 break;
266 1 case MSG_MAIL_FOLDER_TYPE_ARCHIVE:
267 1 url = g_strconcat (MSG_API_ENDPOINT, "/me/mailFolders/archive", NULL);
268 1 break;
269 case MSG_MAIL_FOLDER_TYPE_0:
270 case MSG_MAIL_FOLDER_TYPE_OTHER:
271 case MSG_MAIL_FOLDER_TYPE_MAX:
272 default:
273 break;
274 }
275
276 9 message = msg_service_build_message (MSG_SERVICE (self), "GET", url, NULL, FALSE);
277 9 parser = msg_service_send_and_parse_response (MSG_SERVICE (self), message, &root_object, cancellable, error);
278
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 if (!parser)
279 return NULL;
280
281 9 folder = msg_mail_folder_new_from_json (root_object, error);
282 if (folder) {
283 /* msg_mail_folder_set_folder_type (folder, type); */
284 }
285
286 9 return folder;
287 }
288
289 /**
290 * msg_mail_service_delete_message:
291 * @self: a #MsgMailService
292 * @mail: a #MsgMail
293 * @cancellable: a #GCancellable
294 * @error: a #GError
295 *
296 * Delets #mail.
297 *
298 * Returns: %TRUE for succes, else &FALSE
299 */
300 gboolean
301 2 msg_mail_service_delete_message (MsgMailService *self,
302 MsgMailMessage *message,
303 GCancellable *cancellable,
304 GError **error)
305 {
306 2 g_autoptr (SoupMessage) soup_message = NULL;
307 2 g_autofree char *url = NULL;
308 2 g_autoptr (GBytes) response = NULL;
309 2 g_autoptr (JsonParser) parser = NULL;
310
311
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!msg_service_refresh_authorization (MSG_SERVICE (self), cancellable, error))
312 return FALSE;
313
314 2 url = g_strconcat (MSG_API_ENDPOINT, "/me/messages/", msg_mail_message_get_id (message), NULL);
315 2 soup_message = msg_service_build_message (MSG_SERVICE (self), "DELETE", url, NULL, FALSE);
316 2 response = msg_service_send_and_read (MSG_SERVICE (self), soup_message, cancellable, error);
317
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!response)
318 return FALSE;
319
320 2 return TRUE;
321 }
322
323 /**
324 * msg_mail_service_create_draft_message:
325 * @self: a #MsgContextService
326 * @mail: a #MsgMail
327 * @cancellable: a #GCancellable
328 * @error: a #GError
329 *
330 * Create new draft mail #mail and return new mail object.
331 *
332 * Returns: (transfer full): a new #MsgMail
333 */
334 MsgMailMessage *
335 2 msg_mail_service_create_draft_message (MsgMailService *self,
336 MsgMailMessage *message,
337 GCancellable *cancellable,
338 GError **error)
339 {
340 2 g_autoptr (SoupMessage) soup_message = NULL;
341 2 JsonObject *root_object = NULL;
342 2 g_autofree char *url = NULL;
343 2 g_autoptr (JsonParser) parser = NULL;
344 2 g_autoptr (JsonBuilder) builder = NULL;
345 2 g_autoptr (JsonNode) node = NULL;
346 2 g_autofree char *json = NULL;
347 2 GBytes *body = NULL;
348
349
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!msg_service_refresh_authorization (MSG_SERVICE (self), cancellable, error))
350 return NULL;
351
352 2 url = g_strconcat (MSG_API_ENDPOINT, "/me/messages", NULL);
353 2 soup_message = msg_service_build_message (MSG_SERVICE (self), "POST", url, NULL, FALSE);
354
355 2 builder = json_builder_new ();
356 2 json_builder_begin_object (builder);
357
358 2 json_builder_set_member_name (builder, "subject");
359 2 json_builder_add_string_value (builder, msg_mail_message_get_subject (message));
360
361 2 json_builder_set_member_name (builder, "body");
362 2 json_builder_begin_object (builder);
363 2 json_builder_set_member_name (builder, "content");
364 2 json_builder_add_string_value (builder, msg_mail_message_get_body_preview (message));
365 2 json_builder_end_object (builder);
366
367 2 json_builder_set_member_name (builder, "toRecipients");
368 2 json_builder_begin_array (builder);
369 2 json_builder_begin_object (builder);
370 2 json_builder_set_member_name (builder, "emailAddress");
371 2 json_builder_begin_object (builder);
372 2 json_builder_set_member_name (builder, "address");
373 2 json_builder_add_string_value (builder, msg_mail_message_get_receiver (message));
374 2 json_builder_end_object (builder);
375 2 json_builder_end_object (builder);
376 2 json_builder_end_array (builder);
377
378 2 json_builder_end_object (builder);
379
380 2 node = json_builder_get_root (builder);
381 2 json = json_to_string (node, TRUE);
382
383 2 body = g_bytes_new (json, strlen (json));
384 2 soup_message_set_request_body_from_bytes (soup_message, "application/json", body);
385
386 2 parser = msg_service_send_and_parse_response (MSG_SERVICE (self), soup_message, &root_object, cancellable, error);
387
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!parser)
388 return NULL;
389
390 2 return msg_mail_message_new_from_json (root_object, error);
391 }
392
393 GBytes *
394 1 msg_mail_service_get_mime_message (MsgMailService *self,
395 MsgMailMessage *message,
396 GCancellable *cancellable,
397 GError **error)
398 {
399 1 g_autoptr (SoupMessage) soup_message = NULL;
400 1 g_autofree char *url = NULL;
401 1 GBytes *body = NULL;
402
403
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!msg_service_refresh_authorization (MSG_SERVICE (self), cancellable, error))
404 return NULL;
405
406 1 url = g_strconcat (MSG_API_ENDPOINT, "/me/messages/", msg_mail_message_get_id (message), "/$value", NULL);
407 1 soup_message = msg_service_build_message (MSG_SERVICE (self), "GET", url, NULL, FALSE);
408
409 1 body = msg_service_send_and_read (MSG_SERVICE (self), soup_message, cancellable, error);
410 1 return body;
411 }
412
413 char *
414 7 msg_mail_service_get_folder_id (MsgMailService *self,
415 MsgMailFolderType type,
416 GCancellable *cancellable,
417 GError **error)
418 {
419 7 g_autoptr (SoupMessage) message = NULL;
420 7 g_autofree char *url = NULL;
421 7 g_autoptr (GBytes) response = NULL;
422 7 JsonObject *root_object = NULL;
423 7 g_autoptr (JsonParser) parser = NULL;
424 7 char *id = NULL;
425
426
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 if (!msg_service_refresh_authorization (MSG_SERVICE (self), cancellable, error))
427 return NULL;
428
429
7/9
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
7 switch (type) {
430 1 case MSG_MAIL_FOLDER_TYPE_INBOX:
431 1 url = g_strconcat (MSG_API_ENDPOINT, "/me/mailFolders/inbox", "?$select=id", NULL);
432 1 break;
433 1 case MSG_MAIL_FOLDER_TYPE_DRAFTS:
434 1 url = g_strconcat (MSG_API_ENDPOINT, "/me/mailFolders/drafts", "?$select=id", NULL);
435 1 break;
436 1 case MSG_MAIL_FOLDER_TYPE_SENT_ITEMS:
437 1 url = g_strconcat (MSG_API_ENDPOINT, "/me/mailFolders/sentitems", "?$select=id", NULL);
438 1 break;
439 1 case MSG_MAIL_FOLDER_TYPE_JUNK_EMAIL:
440 1 url = g_strconcat (MSG_API_ENDPOINT, "/me/mailFolders/junkemail", "?$select=id", NULL);
441 1 break;
442 1 case MSG_MAIL_FOLDER_TYPE_DELETED_ITEMS:
443 1 url = g_strconcat (MSG_API_ENDPOINT, "/me/mailFolders/deleteditems", "?$select=id", NULL);
444 1 break;
445 1 case MSG_MAIL_FOLDER_TYPE_OUTBOX:
446 1 url = g_strconcat (MSG_API_ENDPOINT, "/me/mailFolders/outbox", "?$select=id", NULL);
447 1 break;
448 1 case MSG_MAIL_FOLDER_TYPE_ARCHIVE:
449 1 url = g_strconcat (MSG_API_ENDPOINT, "/me/mailFolders/archive", "?$select=id", NULL);
450 1 break;
451 case MSG_MAIL_FOLDER_TYPE_0:
452 case MSG_MAIL_FOLDER_TYPE_OTHER:
453 case MSG_MAIL_FOLDER_TYPE_MAX:
454 return NULL;
455 }
456
457 7 message = msg_service_build_message (MSG_SERVICE (self), "GET", url, NULL, FALSE);
458 7 parser = msg_service_send_and_parse_response (MSG_SERVICE (self), message, &root_object, cancellable, error);
459
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 if (!parser)
460 return NULL;
461
462
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 if (json_object_has_member (root_object, "id")) {
463 14 id = g_strdup (json_object_get_string_member (root_object, "id"));
464 }
465
466 7 return id;
467 }
468