GCC Code Coverage Report


Directory: src/
File: drive/msg-drive-service.c
Date: 2026-10-03 00:54:03
Exec Total Coverage
Lines: 320 434 73.7%
Functions: 21 23 91.3%
Branches: 90 200 45.0%

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 "msg-authorizer.h"
21 #include "msg-error.h"
22 #include "msg-input-stream.h"
23 #include "msg-private.h"
24 #include "msg-service.h"
25 #include "drive/msg-drive.h"
26 #include "drive/msg-drive-item.h"
27 #include "drive/msg-drive-item-file.h"
28 #include "drive/msg-drive-service.h"
29
30 struct _MsgDriveService {
31 MsgService parent_instance;
32
33 MsgDriveType type;
34 };
35
36
5/6
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 69 times.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 66 times.
75 G_DEFINE_TYPE (MsgDriveService, msg_drive_service, MSG_TYPE_SERVICE);
37
38 static gboolean
39 16 is_valid_name (const char *name)
40 {
41 16 g_auto (GStrv) split = NULL;
42 16 const char *invalid_names[] = {
43 ".lock",
44 "CON",
45 "PRN",
46 "AUX",
47 "NUL",
48 "COM0",
49 "COM1",
50 "COM2",
51 "COM3",
52 "COM4",
53 "COM5",
54 "COM6",
55 "COM7",
56 "COM8",
57 "COM9",
58 "LPT0",
59 "LPT1",
60 "LPT2",
61 "LPT3",
62 "LPT4",
63 "LPT5",
64 "LPT6",
65 "LPT7",
66 "LPT8",
67 "LPT9",
68 "_vti_",
69 "desktop.ini",
70 NULL
71 };
72 32 g_autofree char *lower_name = g_utf8_strdown (name, -1);
73
74 /* Check for invalid names */
75
2/2
✓ Branch 0 taken 432 times.
✓ Branch 1 taken 16 times.
448 for (int i = 0; invalid_names[i] != NULL; i++) {
76
1/2
✓ Branch 0 taken 432 times.
✗ Branch 1 not taken.
864 g_autofree char *invalid_name = g_utf8_strdown (invalid_names[i], -1);
77
78
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 432 times.
432 if (g_strcmp0 (lower_name, invalid_name) == 0)
79 ✗ return FALSE;
80 }
81
82 /* Leading or Trailing whitespaces are invalid */
83
8/16
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 16 times.
✓ Branch 4 taken 16 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 16 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 16 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 16 times.
✓ Branch 12 taken 16 times.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✓ Branch 15 taken 16 times.
16 if (g_str_has_prefix (name, " ") || g_str_has_suffix (name, " "))
84 ✗ return FALSE;
85
86 /* Must not start with ~$ */
87
4/8
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 16 times.
✓ Branch 4 taken 16 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 16 times.
16 if (g_str_has_prefix (name, "~$"))
88 ✗ return FALSE;
89
90 /* _vti_ cannot be part of the name */
91
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 if (strstr (name, "_vti_"))
92 ✗ return FALSE;
93
94 /* Not allowed chars: " * : < > ? / \ | */
95
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 if (strchr (name, '"') ||
96
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 strchr (name, '*') ||
97
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 strchr (name, ':') ||
98
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 strchr (name, '<') ||
99
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 strchr (name, '>') ||
100
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 strchr (name, '?') ||
101
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 strchr (name, '/') ||
102
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 strchr (name, '\\') ||
103
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 strchr (name, '|'))
104 ✗ return FALSE;
105
106 /* "forms" on root level for a library */
107 16 split = g_strsplit (name, "/", -1);
108
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
16 if (g_strv_length (split) == 2 && g_strcmp0 (split[1], "forms") == 0)
109 ✗ return FALSE;
110
111 16 return TRUE;
112 }
113
114 static void
115 2 msg_drive_service_init (__attribute__ ((unused)) MsgDriveService *self)
116 {
117 2 }
118
119 static void
120 3 msg_drive_service_class_init (__attribute__ ((unused)) MsgDriveServiceClass *class)
121 {
122 3 }
123
124 /**
125 * msg_drive_service_new:
126 * @authorizer: A authorizer
127 *
128 * Creates a new `MsgDriveService` using #MsgAuthorizer.
129 *
130 * Returns: the newly created `MsgDriveService`
131 */
132 MsgDriveService *
133 2 msg_drive_service_new (MsgAuthorizer *authorizer)
134 {
135 2 return g_object_new (MSG_TYPE_DRIVE_SERVICE, "authorizer", authorizer, NULL);
136 }
137
138 static gboolean
139 10 drive_already_added (GList *list,
140 MsgDrive *drive)
141 {
142 10 const char *drive_id = msg_drive_get_id (drive);
143 10 const char *drive_name = msg_drive_get_name (drive);
144
1/10
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
10 gboolean drive_is_bundle = drive_name && g_str_has_prefix (drive_name, "Bundles_");
145
146
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
10 for (GList *iter = list; iter && iter->data; iter = iter->next) {
147 ✗ MsgDrive *list_drive = MSG_DRIVE (iter->data);
148
149 ✗ if (g_strcmp0 (msg_drive_get_id (list_drive), drive_id) == 0 && drive_is_bundle)
150 ✗ return TRUE;
151 }
152
153 10 return FALSE;
154 }
155
156 /*
157 * Some consumer (personal) accounts are refused access to the drive
158 * enumeration endpoint (GET /me/drives answers 403 accessDenied) while the
159 * user's default drive is still fully accessible through GET /me/drive.
160 * Fetch the default drive directly and return it as a one-element list.
161 *
162 * Note that /me/drive returns a bare drive object, not a "value" array.
163 */
164 static GList *
165 ✗ get_default_drive (MsgDriveService *self,
166 GCancellable *cancellable,
167 GError **error)
168 {
169 ✗ g_autofree char *url = NULL;
170 ✗ g_autoptr (SoupMessage) message = NULL;
171 ✗ g_autoptr (JsonParser) parser = NULL;
172 ✗ JsonObject *root_object = NULL;
173 ✗ MsgDrive *drive = NULL;
174
175 ✗ url = g_strconcat (MSG_API_ENDPOINT, "/me/drive", NULL);
176 ✗ message = msg_service_build_message (MSG_SERVICE (self), "GET", url, NULL, FALSE);
177 ✗ parser = msg_service_send_and_parse_response (MSG_SERVICE (self), message, &root_object, cancellable, error);
178 ✗ if (!parser)
179 ✗ return NULL;
180
181 ✗ drive = msg_drive_new_from_json (root_object, error);
182 ✗ if (!drive)
183 ✗ return NULL;
184
185 ✗ self->type = msg_drive_get_drive_type (drive);
186
187 ✗ return g_list_append (NULL, drive);
188 }
189
190 /**
191 * msg_drive_service_get_drives:
192 * @self: a #MsgDriveService
193 * @cancellable: a #GCancellable
194 * @error: a #GError
195 *
196 * Queries the Microsoft Graph API for all the drives of the currently logged in user
197 *
198 * Returns: (element-type MsgDrive) (transfer full): all drives the user can access
199 */
200 GList *
201 10 msg_drive_service_get_drives (MsgDriveService *self,
202 GCancellable *cancellable,
203 GError **error)
204 {
205 10 g_autofree char *url = NULL;
206 10 JsonObject *root_object = NULL;
207 10 JsonArray *array = NULL;
208 10 guint array_length = 0, index = 0;
209 10 g_autolist (MsgDrive) list = NULL;
210 10 g_autoptr (GBytes) response = NULL;
211 10 g_autoptr (JsonParser) parser = NULL;
212
213
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if (!msg_service_refresh_authorization (MSG_SERVICE (self), cancellable, error))
214 ✗ return NULL;
215
216 10 url = g_strconcat (MSG_API_ENDPOINT, "/me/drives", NULL);
217
218 do {
219
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 g_autoptr (SoupMessage) message = NULL;
220
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 g_autoptr (GError) request_error = NULL;
221
222 10 message = msg_service_build_message (MSG_SERVICE (self), "GET", url, NULL, FALSE);
223 10 parser = msg_service_send_and_parse_response (MSG_SERVICE (self), message, &root_object, cancellable, &request_error);
224
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if (!parser) {
225 /* Only fall back while nothing has been enumerated yet, i.e. when the
226 * initial request itself was refused by Graph (an MSG_ERROR means the
227 * server answered with an error object, as opposed to a transport
228 * failure). Errors during pagination propagate unchanged. */
229 ✗ if (list == NULL && g_error_matches (request_error, MSG_ERROR, MSG_ERROR_FAILED))
230 ✗ return get_default_drive (self, cancellable, error);
231
232 ✗ g_propagate_error (error, g_steal_pointer (&request_error));
233 ✗ return NULL;
234 }
235
236 10 array = json_object_get_array_member (root_object, "value");
237
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 g_assert (array != NULL);
238
239 10 array_length = json_array_get_length (array);
240
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 10 times.
20 for (index = 0; index < array_length; index++) {
241 10 MsgDrive *drive = NULL;
242 10 JsonObject *drive_object = NULL;
243 10 g_autoptr (GError) local_error = NULL;
244
245 10 drive_object = json_array_get_object_element (array, index);
246 10 drive = msg_drive_new_from_json (drive_object, &local_error);
247
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 if (drive) {
248
249
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 if (!drive_already_added (list, drive)) {
250 10 self->type = msg_drive_get_drive_type (drive);
251 10 list = g_list_append (list, drive);
252 }
253 } else {
254 ✗ g_warning ("Could not parse drive object: %s", local_error->message);
255 }
256 }
257
258
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 g_clear_pointer (&url, g_free);
259 10 url = msg_service_get_next_link (root_object);
260
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 } while (url != NULL);
261
262 10 return g_steal_pointer (&list);
263 }
264
265 /**
266 * msg_drive_service_get_root:
267 * @self: a #MsgDriveService
268 * @drive: a #MsgDrive
269 * @cancellable: a #GCancellable
270 * @error: a #GError
271 *
272 * Get root item of selected drive
273 *
274 * Returns: (transfer full): root 'MsgDriveItem'
275 */
276 MsgDriveItem *
277 9 msg_drive_service_get_root (MsgDriveService *self,
278 MsgDrive *drive,
279 GCancellable *cancellable,
280 GError **error)
281 {
282 9 g_autofree char *url = NULL;
283 9 g_autoptr (SoupMessage) message = NULL;
284 9 JsonObject *root_object = NULL;
285 9 g_autoptr (GBytes) response = NULL;
286 9 g_autoptr (JsonParser) parser = NULL;
287
288
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 if (!msg_service_refresh_authorization (MSG_SERVICE (self), cancellable, error))
289 ✗ return NULL;
290
291 9 url = g_strconcat (MSG_API_ENDPOINT, "/drives/", msg_drive_get_id (drive), "/root?select=id,remoteItem,file,folder,parentReference,name,createdBy,lastModifiedBy,createdDateTime,lastModifiedDateTime,size", NULL);
292 9 message = msg_service_build_message (MSG_SERVICE (self), "GET", url, NULL, FALSE);
293 9 parser = msg_service_send_and_parse_response (MSG_SERVICE (self), message, &root_object, cancellable, error);
294
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 if (!parser)
295 ✗ return NULL;
296
297 9 return msg_drive_item_new_from_json (root_object, error);
298 }
299
300 /**
301 * msg_drive_service_download_item:
302 * @self: a #MsgDriveService
303 * @item: a #MsgDriveItem
304 * @cancellable: a #GCancellable
305 * @error: a #GError
306 *
307 * Download item
308 *
309 * Returns: (transfer full): input stream of drive item
310 */
311 GInputStream *
312 2 msg_drive_service_download_item (MsgDriveService *self,
313 MsgDriveItem *item,
314 GCancellable *cancellable,
315 GError **error)
316 {
317 2 g_autofree char *url = NULL;
318 2 const char *drive_id = NULL;
319 2 const char *id = NULL;
320
321
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!MSG_IS_DRIVE_ITEM_FILE (item)) {
322 ✗ g_warning ("Download only allowed for files");
323 ✗ return NULL;
324 }
325
326
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!msg_service_refresh_authorization (MSG_SERVICE (self), cancellable, error))
327 ✗ return NULL;
328
329
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (!msg_drive_item_is_shared (item)) {
330 2 drive_id = msg_drive_item_get_drive_id (item);
331 2 id = msg_drive_item_get_id (item);
332 } else {
333 ✗ drive_id = msg_drive_item_get_remote_drive_id (item);
334 ✗ id = msg_drive_item_get_remote_id (item);
335 }
336
337 2 url = g_strconcat (MSG_API_ENDPOINT,
338 "/drives/",
339 drive_id,
340 "/items/",
341 id,
342 "/content",
343 NULL);
344
345 2 return msg_input_stream_new (MSG_SERVICE (self), url);
346 }
347
348 /**
349 * msg_drive_service_list_children:
350 * @self: a #MsgDriveService
351 * @item: a #MsgDriveItem
352 * @cancellable: a #GCancellable
353 * @error: a #GError
354 *
355 * Get a list of all files in folder item
356 *
357 * Returns: (element-type MsgDriveItem) (transfer full): all items in folder
358 */
359 GList *
360 1 msg_drive_service_list_children (MsgDriveService *self,
361 MsgDriveItem *item,
362 GCancellable *cancellable,
363 GError **error)
364 {
365 1 g_autoptr (MsgDriveItem) child_item = NULL;
366 1 g_autofree char *url = NULL;
367 1 JsonObject *root_object = NULL;
368 1 JsonArray *array = NULL;
369 1 guint array_length = 0, index = 0;
370 1 JsonObject *item_object = NULL;
371 1 g_autolist (MsgDriveItem) children = NULL;
372 1 g_autoptr (GBytes) response = NULL;
373 1 g_autoptr (JsonParser) parser = NULL;
374 1 gboolean add_prefer_header = self->type == MSG_DRIVE_TYPE_BUSINESS;
375 1 const char *drive_id = NULL;
376 1 const char *id = NULL;
377
378
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!msg_service_refresh_authorization (MSG_SERVICE (self), cancellable, error))
379 ✗ return NULL;
380
381
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (!msg_drive_item_is_shared (item)) {
382 1 drive_id = msg_drive_item_get_drive_id (item);
383 1 id = msg_drive_item_get_id (item);
384 } else {
385 ✗ drive_id = msg_drive_item_get_remote_drive_id (item);
386 ✗ id = msg_drive_item_get_remote_id (item);
387 }
388
389 1 url = g_strconcat (MSG_API_ENDPOINT,
390 "/drives/",
391 drive_id,
392 "/items/",
393 id,
394 "/children",
395 "?$expand=thumbnails",
396 "&select=id,remoteItem,file,folder,parentReference,name,createdBy,lastModifiedBy,createdDateTime,lastModifiedDateTime,size",
397 NULL);
398
399 do {
400
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 g_autoptr (SoupMessage) message = NULL;
401
402 1 message = msg_service_build_message (MSG_SERVICE (self), "GET", url, NULL /*msg_drive_item_get_etag (item)*/, FALSE);
403
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (add_prefer_header)
404 ✗ soup_message_headers_append (soup_message_get_request_headers (message), "Prefer", "Include-Feature=AddToOneDrive");
405
406 1 parser = msg_service_send_and_parse_response (MSG_SERVICE (self), message, &root_object, cancellable, error);
407
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!parser)
408 ✗ return NULL;
409
410 1 array = json_object_get_array_member (root_object, "value");
411
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 g_assert (array != NULL);
412
413 1 array_length = json_array_get_length (array);
414
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 times.
6 for (index = 0; index < array_length; index++) {
415
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 g_autoptr (GError) local_error = NULL;
416 5 item_object = json_array_get_object_element (array, index);
417
418 5 child_item = msg_drive_item_new_from_json (item_object, &local_error);
419
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (local_error) {
420 ✗ g_warning ("Could not read child item: %s", local_error->message);
421 ✗ continue;
422 }
423
424 5 children = g_list_prepend (children, g_steal_pointer (&child_item));
425 }
426
427
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 g_clear_pointer (&url, g_free);
428 1 url = msg_service_get_next_link (root_object);
429
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 } while (url != NULL);
430
431 1 return g_steal_pointer (&children);
432 }
433
434 /**
435 * msg_drive_service_rename:
436 * @self: a #MsgDriveService
437 * @item: a #MsgDriveItem
438 * @new_name: new name of item
439 * @cancellable: a #GCancellable
440 * @error: a #GError
441 *
442 * Sets a new drive item name
443 *
444 * Returns: (transfer full): the renamed `MsgDriveItem`
445 */
446 MsgDriveItem *
447 1 msg_drive_service_rename (MsgDriveService *self,
448 MsgDriveItem *item,
449 const char *new_name,
450 GCancellable *cancellable,
451 GError **error)
452 {
453 1 g_autoptr (SoupMessage) message = NULL;
454 1 g_autoptr (JsonBuilder) builder = NULL;
455 1 g_autoptr (JsonNode) rename_node = NULL;
456 1 g_autofree char *url = NULL;
457 1 g_autofree char *json = NULL;
458 1 JsonObject *root_object = NULL;
459 1 g_autoptr (GBytes) response = NULL;
460 1 g_autoptr (GBytes) body = NULL;
461 1 g_autoptr (JsonParser) parser = NULL;
462
463
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!is_valid_name (new_name)) {
464 ✗ g_set_error (error,
465 msg_error_quark (),
466 MSG_ERROR_FAILED,
467 "Invalid characters in name");
468 ✗ return NULL;
469 }
470
471
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!msg_service_refresh_authorization (MSG_SERVICE (self), cancellable, error))
472 ✗ return NULL;
473
474 1 url = g_strconcat (MSG_API_ENDPOINT,
475 "/drives/",
476 msg_drive_item_get_drive_id (item),
477 "/items/",
478 msg_drive_item_get_id (item),
479 NULL);
480
481 1 message = msg_service_build_message (MSG_SERVICE (self), "PATCH", url, NULL, FALSE);
482
483 1 builder = json_builder_new ();
484 1 json_builder_begin_object (builder);
485 1 json_builder_set_member_name (builder, "name");
486 1 json_builder_add_string_value (builder, new_name);
487 1 json_builder_end_object (builder);
488 1 rename_node = json_builder_get_root (builder);
489 1 json = json_to_string (rename_node, TRUE);
490
491 1 body = g_bytes_new (json, strlen (json));
492 1 soup_message_set_request_body_from_bytes (message, "application/json", body);
493
494 1 parser = msg_service_send_and_parse_response (MSG_SERVICE (self), message, &root_object, cancellable, error);
495
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!parser)
496 ✗ return NULL;
497
498 1 return msg_drive_item_new_from_json (root_object, error);
499 }
500
501 /**
502 * msg_drive_service_download_url:
503 * @self: a #MsgDriveService
504 * @url: url to download
505 * @cancellable: a #GCancellable
506 * @error: a #GError
507 *
508 * Download url
509 *
510 * Returns: (transfer full): a new `GInputStream` for url
511 */
512 GInputStream *
513 1 msg_drive_service_download_url (MsgDriveService *self,
514 const char *url,
515 GCancellable *cancellable,
516 GError **error)
517 {
518 2 g_autoptr (SoupMessage) message = msg_service_build_message (MSG_SERVICE (self), "GET", url, NULL, FALSE);
519
520 1 return msg_service_send (MSG_SERVICE (self), message, cancellable, error);
521 }
522
523 /**
524 * msg_drive_service_create_folder:
525 * @self: a drive service
526 * @parent: parent drive item
527 * @name: name of new folder
528 * @cancellable: a cancellable
529 * @error: a error
530 *
531 * Creates a new folder called name under parent.
532 *
533 * Returns: (transfer full): a newly created `MsgDriveItem`
534 */
535 MsgDriveItem *
536 8 msg_drive_service_create_folder (MsgDriveService *self,
537 MsgDriveItem *parent,
538 const char *name,
539 GCancellable *cancellable,
540 GError **error)
541 {
542 8 g_autofree char *url = NULL;
543 8 g_autoptr (SoupMessage) message = NULL;
544 8 g_autofree char *json = NULL;
545 8 g_autoptr (JsonBuilder) builder = NULL;
546 8 g_autoptr (JsonNode) create_node = NULL;
547 8 JsonObject *root_object = NULL;
548 8 g_autoptr (GBytes) response = NULL;
549 8 g_autoptr (GBytes) body = NULL;
550 8 g_autoptr (JsonParser) parser = NULL;
551
552
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (!msg_service_refresh_authorization (MSG_SERVICE (self), cancellable, error))
553 ✗ return NULL;
554
555
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (!is_valid_name (name)) {
556 ✗ g_set_error (error,
557 msg_error_quark (),
558 MSG_ERROR_FAILED,
559 "Invalid characters in name");
560 ✗ return NULL;
561 }
562
563 8 url = g_strconcat (MSG_API_ENDPOINT,
564 "/drives/",
565 msg_drive_item_get_drive_id (parent),
566 "/items/",
567 msg_drive_item_get_id (parent),
568 "/children",
569 NULL);
570
571 8 message = msg_service_build_message (MSG_SERVICE (self), "POST", url, NULL, FALSE);
572
573 8 builder = json_builder_new ();
574 8 json_builder_begin_object (builder);
575 8 json_builder_set_member_name (builder, "name");
576 8 json_builder_add_string_value (builder, name);
577 8 json_builder_set_member_name (builder, "@microsoft.graph.conflictBehavior");
578 8 json_builder_add_string_value (builder, "rename");
579 8 json_builder_set_member_name (builder, "folder");
580 8 json_builder_begin_object (builder);
581 8 json_builder_end_object (builder);
582 8 json_builder_end_object (builder);
583 8 create_node = json_builder_get_root (builder);
584 8 json = json_to_string (create_node, TRUE);
585
586 8 body = g_bytes_new (json, strlen (json));
587 8 soup_message_set_request_body_from_bytes (message, "application/json", body);
588
589 8 parser = msg_service_send_and_parse_response (MSG_SERVICE (self), message, &root_object, cancellable, error);
590
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (!parser)
591 ✗ return NULL;
592
593 8 return msg_drive_item_new_from_json (root_object, error);
594 }
595
596 /**
597 * msg_drive_service_delete:
598 * @self: a drive service
599 * @item: a #MsgDriveItem
600 * @cancellable: a cancellable
601 * @error: a error
602 *
603 * Deletes item.
604 *
605 * Returns: %TRUE when item has been deleted, otherwise %FALSE
606 */
607 gboolean
608 11 msg_drive_service_delete (MsgDriveService *self,
609 MsgDriveItem *item,
610 GCancellable *cancellable,
611 GError **error)
612 {
613 11 g_autoptr (SoupMessage) message = NULL;
614 11 g_autofree char *url = NULL;
615 11 g_autoptr (GBytes) bytes = NULL;
616
617
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 11 times.
11 if (!msg_service_refresh_authorization (MSG_SERVICE (self), cancellable, error))
618 ✗ return FALSE;
619
620 11 url = g_strconcat (MSG_API_ENDPOINT,
621 "/drives/",
622 msg_drive_item_get_drive_id (item),
623 "/items/",
624 msg_drive_item_get_id (item),
625 NULL);
626
627 11 message = msg_service_build_message (MSG_SERVICE (self), "DELETE", url, NULL, FALSE);
628
629 11 bytes = msg_service_send_and_read (MSG_SERVICE (self), message, cancellable, error);
630 11 return bytes != NULL;
631 }
632
633 /**
634 * msg_drive_service_update:
635 * @self: a drive service
636 * @item: a drive item
637 * @cancellable: a cancellable
638 * @error: a error
639 *
640 * Creates an update stream for drive item in order to update it's content.
641 *
642 * Returns: (transfer full): an output stream
643 */
644 GOutputStream *
645 7 msg_drive_service_update (MsgDriveService *self,
646 MsgDriveItem *item,
647 GCancellable *cancellable,
648 GError **error)
649 {
650 7 GOutputStream *stream = NULL;
651 7 g_autofree char *url = NULL;
652 7 g_autoptr (JsonBuilder) builder = NULL;
653 7 g_autoptr (JsonNode) create_node = NULL;
654 7 g_autoptr (SoupMessage) message = NULL;
655 7 JsonObject *root_object = NULL;
656 7 g_autofree char *json = NULL;
657 7 g_autoptr (GBytes) response = NULL;
658 7 g_autoptr (GBytes) body = NULL;
659 const char *upload_url;
660 7 g_autoptr (JsonParser) parser = NULL;
661
662
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 if (!msg_service_refresh_authorization (MSG_SERVICE (self), cancellable, error))
663 ✗ return NULL;
664
665 7 url = g_strconcat (MSG_API_ENDPOINT,
666 "/drives/",
667 msg_drive_item_get_drive_id (item),
668 "/items/",
669 msg_drive_item_get_id (item),
670 "/createUploadSession",
671 NULL);
672
673 7 message = msg_service_build_message (MSG_SERVICE (self), "POST", url, NULL, FALSE);
674 7 builder = json_builder_new ();
675 7 json_builder_begin_object (builder);
676 7 json_builder_set_member_name (builder, "@microsoft.graph.conflictBehavior");
677 7 json_builder_add_string_value (builder, "rename");
678 7 json_builder_end_object (builder);
679 7 create_node = json_builder_get_root (builder);
680 7 json = json_to_string (create_node, TRUE);
681
682 7 body = g_bytes_new (json, strlen (json));
683 7 soup_message_set_request_body_from_bytes (message, "application/json", body);
684
685 7 parser = msg_service_send_and_parse_response (MSG_SERVICE (self), message, &root_object, cancellable, error);
686
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 if (!parser)
687 ✗ return NULL;
688
689
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 if (!json_object_has_member (root_object, "uploadUrl")) {
690 ✗ g_set_error (error,
691 msg_error_quark (),
692 MSG_ERROR_FAILED,
693 "No uploadUrl found");
694 ✗ return NULL;
695 }
696
697 7 upload_url = json_object_get_string_member (root_object, "uploadUrl");
698
699 7 stream = g_memory_output_stream_new_resizable ();
700 7 g_object_set_data_full (G_OBJECT (stream), "ms-graph-upload-url", g_strdup (upload_url), g_free);
701 7 g_object_set_data (G_OBJECT (stream), "ms-graph-item", item);
702
703 7 return stream;
704 }
705
706 /**
707 * msg_drive_service_update_finish:
708 * @self: a #MsgDriveService
709 * @item: a #MsgDriveItem
710 * @stream: stream where data is store and needs to be transfered
711 * @cancellable: a #GCancellable
712 * @error: a #GError
713 *
714 * Finish a update session of given #item.
715 *
716 * Returns: (transfer full): a new #MsgDriveItem or %NULL on error.
717 */
718 MsgDriveItem *
719 7 msg_drive_service_update_finish (MsgDriveService *self,
720 MsgDriveItem *item,
721 GOutputStream *stream,
722 GCancellable *cancellable,
723 GError **error)
724 {
725 7 g_autoptr (GBytes) bytes = NULL;
726 7 g_autofree char *url = NULL;
727 7 g_autoptr (SoupMessage) message = NULL;
728 7 JsonObject *root_object = NULL;
729 7 g_autoptr (GBytes) response = NULL;
730 7 g_autoptr (JsonParser) parser = NULL;
731
732
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 if (!msg_service_refresh_authorization (MSG_SERVICE (self), cancellable, error))
733 ✗ return NULL;
734
735 7 url = g_strconcat (MSG_API_ENDPOINT,
736 "/drives/",
737 msg_drive_item_get_drive_id (item),
738 "/items/",
739 msg_drive_item_get_id (item),
740 "/content",
741 NULL);
742
743 7 g_output_stream_close (stream, NULL, NULL);
744 7 bytes = g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (stream));
745
746 7 message = msg_service_build_message (MSG_SERVICE (self), "PUT", url, NULL, FALSE);
747 7 soup_message_set_request_body_from_bytes (message, "application/octet-stream", bytes);
748
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 g_clear_object (&stream);
749
750 7 parser = msg_service_send_and_parse_response (MSG_SERVICE (self), message, &root_object, cancellable, error);
751
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 if (!parser)
752 ✗ return NULL;
753
754 7 return msg_drive_item_new_from_json (root_object, error);
755 }
756
757 /**
758 * msg_drive_service_add_item_to_folder:
759 * @self: a msg drive service
760 * @parent: parent drive item
761 * @item: drive item to add
762 * @cancellable: a cancellable
763 * @error: a error
764 *
765 * Adds item to parent folder
766 *
767 * Returns: (transfer full): a new drive item
768 */
769 MsgDriveItem *
770 7 msg_drive_service_add_item_to_folder (MsgDriveService *self,
771 MsgDriveItem *parent,
772 MsgDriveItem *item,
773 GCancellable *cancellable,
774 GError **error)
775 {
776 7 g_autofree char *url = NULL;
777 7 g_autoptr (SoupMessage) message = NULL;
778 7 g_autofree char *escaped_name = NULL;
779 7 JsonObject *root_object = NULL;
780 7 g_autoptr (GBytes) response = NULL;
781 7 g_autoptr (GBytes) body = NULL;
782 7 g_autoptr (JsonParser) parser = NULL;
783
784
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 if (!is_valid_name (msg_drive_item_get_name (item))) {
785 ✗ g_set_error (error,
786 msg_error_quark (),
787 MSG_ERROR_FAILED,
788 "Invalid characters in name");
789 ✗ return NULL;
790 }
791
792
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 if (!msg_service_refresh_authorization (MSG_SERVICE (self), cancellable, error))
793 ✗ return FALSE;
794
795 7 escaped_name = g_uri_escape_string (msg_drive_item_get_name (item), NULL, TRUE);
796 7 url = g_strconcat (MSG_API_ENDPOINT,
797 "/drives/",
798 msg_drive_item_get_drive_id (parent),
799 "/items/",
800 msg_drive_item_get_id (parent),
801 ":/",
802 escaped_name,
803 ":/content",
804 NULL);
805
806 7 message = msg_service_build_message (MSG_SERVICE (self), "PUT", url, NULL, FALSE);
807 7 body = g_bytes_new ("", 0);
808 7 soup_message_set_request_body_from_bytes (message, "text/plain", body);
809
810 7 parser = msg_service_send_and_parse_response (MSG_SERVICE (self), message, &root_object, cancellable, error);
811
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 if (!parser)
812 ✗ return FALSE;
813
814 7 return msg_drive_item_new_from_json (root_object, error);
815 }
816
817 /**
818 * msg_drive_service_get_shared_with_me:
819 * @self: a #MsgDriveService
820 * @cancellable: a #GCancellable
821 * @error: a #GError
822 *
823 * Requests all shared with me items
824 *
825 * Returns: (element-type MsgDriveItem) (transfer full): shared with me list
826 */
827 GList *
828 1 msg_drive_service_get_shared_with_me (MsgDriveService *self,
829 GCancellable *cancellable,
830 GError **error)
831 {
832 1 g_autoptr (MsgDriveItem) child_item = NULL;
833 1 g_autofree char *url = NULL;
834 1 JsonObject *root_object = NULL;
835 1 JsonArray *array = NULL;
836 1 guint array_length = 0, index = 0;
837 1 JsonObject *item_object = NULL;
838 1 g_autolist (MsgDriveItem) children = NULL;
839 1 g_autoptr (GBytes) response = NULL;
840 1 g_autoptr (JsonParser) parser = NULL;
841
842
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!msg_service_refresh_authorization (MSG_SERVICE (self), cancellable, error))
843 ✗ return NULL;
844
845 1 url = g_strconcat (MSG_API_ENDPOINT,
846 "/me/drive/sharedWithMe",
847 "?select=id,remoteItem,file,folder,parentReference,name,createdBy,lastModifiedBy,createdDateTime,lastModifiedDateTime,size",
848 NULL);
849 do {
850
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 g_autoptr (SoupMessage) message = NULL;
851
852 1 message = msg_service_build_message (MSG_SERVICE (self), "GET", url, NULL, FALSE);
853 1 parser = msg_service_send_and_parse_response (MSG_SERVICE (self), message, &root_object, cancellable, error);
854
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!parser)
855 ✗ return NULL;
856
857 1 array = json_object_get_array_member (root_object, "value");
858
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 g_assert (array != NULL);
859
860 1 array_length = json_array_get_length (array);
861
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
5 for (index = 0; index < array_length; index++) {
862
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 g_autoptr (GError) local_error = NULL;
863 4 item_object = json_array_get_object_element (array, index);
864
865 4 child_item = msg_drive_item_new_from_json (item_object, &local_error);
866
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (local_error) {
867 ✗ g_warning ("Could not load shared with me item: %s", local_error->message);
868 ✗ continue;
869 }
870
871 4 children = g_list_prepend (children, g_steal_pointer (&child_item));
872 }
873
874
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 g_clear_pointer (&url, g_free);
875 1 url = msg_service_get_next_link (root_object);
876
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 } while (url != NULL);
877
878 1 return g_steal_pointer (&children);
879 }
880
881 /**
882 * msg_drive_service_copy_file:
883 * @self: #MsgDriveService
884 * @file: source #MsgDriveItem
885 * @destination: destination directory #MsgDriveItem
886 * @cancellable: a #GCancellable
887 * @error: a #GError
888 *
889 * Copy a file async on remote server to a new directory.
890 *
891 * Returns: %TRUE if accepted, %FALSE on error
892 */
893 gboolean
894 1 msg_drive_service_copy_file (MsgDriveService *self,
895 MsgDriveItem *file,
896 MsgDriveItem *destination,
897 GCancellable *cancellable,
898 GError **error)
899 {
900 1 g_autofree char *url = NULL;
901 1 g_autoptr (SoupMessage) message = NULL;
902 1 g_autofree char *json = NULL;
903 1 g_autoptr (JsonBuilder) builder = NULL;
904 1 g_autoptr (JsonNode) create_node = NULL;
905 1 GError *local_error = NULL;
906 1 g_autoptr (GBytes) response = NULL;
907 1 g_autoptr (GBytes) body = NULL;
908 1 g_autoptr (JsonParser) parser = NULL;
909
910
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!msg_service_refresh_authorization (MSG_SERVICE (self), cancellable, &local_error)) {
911 ✗ g_propagate_error (error, g_steal_pointer (&local_error));
912 ✗ return FALSE;
913 }
914
915 1 url = g_strconcat (MSG_API_ENDPOINT,
916 "/drives/",
917 msg_drive_item_get_drive_id (file),
918 "/items/",
919 msg_drive_item_get_id (file),
920 "/copy",
921 NULL);
922
923 1 message = msg_service_build_message (MSG_SERVICE (self), "POST", url, NULL, FALSE);
924
925 1 builder = json_builder_new ();
926 1 json_builder_begin_object (builder);
927
928 1 json_builder_set_member_name (builder, "parentReference");
929 1 json_builder_begin_object (builder);
930 1 json_builder_set_member_name (builder, "driveId");
931 1 json_builder_add_string_value (builder, msg_drive_item_get_drive_id (destination));
932 1 json_builder_set_member_name (builder, "id");
933 1 json_builder_add_string_value (builder, msg_drive_item_get_id (destination));
934 1 json_builder_end_object (builder);
935
936 1 json_builder_end_object (builder);
937 1 create_node = json_builder_get_root (builder);
938 1 json = json_to_string (create_node, TRUE);
939
940 1 body = g_bytes_new (json, strlen (json));
941 1 soup_message_set_request_body_from_bytes (message, "application/json", body);
942
943 1 response = msg_service_send_and_read (MSG_SERVICE (self), message, cancellable, &local_error);
944
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (local_error) {
945 ✗ g_propagate_error (error, g_steal_pointer (&local_error));
946 ✗ return FALSE;
947 }
948
949 1 return TRUE;
950 }
951
952 /**
953 * msg_drive_service_move_file:
954 * @self: #MsgDriveService
955 * @file: source #MsgDriveItem
956 * @destination: destination directory #MsgDriveItem
957 * @cancellable: a #GCancellable
958 * @error: a #GError
959 *
960 * Move a file async on remote server to a new directory.
961 *
962 * Returns: (transfer full): moved #MsgDriveItem
963 */
964 MsgDriveItem *
965 ✗ msg_drive_service_move_file (MsgDriveService *self,
966 MsgDriveItem *file,
967 MsgDriveItem *destination,
968 GCancellable *cancellable,
969 GError **error)
970 {
971 ✗ g_autofree char *url = NULL;
972 ✗ g_autoptr (SoupMessage) message = NULL;
973 ✗ g_autofree char *json = NULL;
974 ✗ g_autoptr (JsonBuilder) builder = NULL;
975 ✗ g_autoptr (JsonNode) create_node = NULL;
976 ✗ GError *local_error = NULL;
977 ✗ g_autoptr (GBytes) response = NULL;
978 ✗ g_autoptr (GBytes) body = NULL;
979 ✗ g_autoptr (JsonParser) parser = NULL;
980 ✗ MsgDriveItem *item = NULL;
981 ✗ JsonObject *root_object = NULL;
982
983 ✗ if (!msg_service_refresh_authorization (MSG_SERVICE (self), cancellable, &local_error)) {
984 ✗ g_propagate_error (error, g_steal_pointer (&local_error));
985 ✗ return NULL;
986 }
987
988 ✗ url = g_strconcat (MSG_API_ENDPOINT,
989 "/drives/",
990 msg_drive_item_get_drive_id (file),
991 "/items/",
992 msg_drive_item_get_id (file),
993 NULL);
994
995 ✗ message = msg_service_build_message (MSG_SERVICE (self), "PATCH", url, NULL, FALSE);
996
997 ✗ builder = json_builder_new ();
998 ✗ json_builder_begin_object (builder);
999
1000 ✗ json_builder_set_member_name (builder, "parentReference");
1001 ✗ json_builder_begin_object (builder);
1002 ✗ json_builder_set_member_name (builder, "id");
1003 ✗ json_builder_add_string_value (builder, msg_drive_item_get_id (destination));
1004 ✗ json_builder_end_object (builder);
1005
1006 ✗ json_builder_end_object (builder);
1007 ✗ create_node = json_builder_get_root (builder);
1008 ✗ json = json_to_string (create_node, TRUE);
1009
1010 ✗ body = g_bytes_new (json, strlen (json));
1011 ✗ soup_message_set_request_body_from_bytes (message, "application/json", body);
1012
1013 ✗ response = msg_service_send_and_read (MSG_SERVICE (self), message, cancellable, &local_error);
1014 ✗ if (local_error) {
1015 ✗ g_propagate_error (error, g_steal_pointer (&local_error));
1016 ✗ return NULL;
1017 }
1018
1019 ✗ parser = msg_service_parse_response (response, &root_object, &local_error);
1020 ✗ if (local_error != NULL) {
1021 ✗ g_propagate_error (error, g_steal_pointer (&local_error));
1022 ✗ return NULL;
1023 }
1024
1025 ✗ item = msg_drive_item_new_from_json (root_object, &local_error);
1026 ✗ if (local_error) {
1027 ✗ g_propagate_error (error, g_steal_pointer (&local_error));
1028 ✗ return FALSE;
1029 }
1030
1031 ✗ return item;
1032 }
1033