1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2017 Red Hat, Inc. (www.redhat.com)
4  *
5  * This library is free software: you can redistribute it and/or modify it
6  * under the terms of the GNU Lesser General Public License as published by
7  * the Free Software Foundation.
8  *
9  * This library is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
12  * for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public License
15  * along with this library. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 /**
19  * SECTION: e-webdav-session
20  * @include: libedataserver/libedataserver.h
21  * @short_description: A WebDAV, CalDAV and CardDAV session
22  *
23  * The #EWebDAVSession is a class to work with WebDAV (RFC 4918),
24  * CalDAV (RFC 4791) or CardDAV (RFC 6352) servers, providing API
25  * for common requests/responses, on top of an #ESoupSession. It
26  * supports also Access Control Protocol (RFC 3744).
27  **/
28 
29 #include "evolution-data-server-config.h"
30 
31 #include <stdio.h>
32 #include <string.h>
33 #include <glib/gi18n-lib.h>
34 
35 #include "camel/camel.h"
36 
37 #include "e-source-authentication.h"
38 #include "e-source-webdav.h"
39 #include "e-xml-utils.h"
40 
41 #include "e-webdav-session.h"
42 
43 #define BUFFER_SIZE 16384
44 
45 struct _EWebDAVSessionPrivate {
46 	gchar *last_dav_error_code;
47 };
48 
G_DEFINE_TYPE_WITH_PRIVATE(EWebDAVSession,e_webdav_session,E_TYPE_SOUP_SESSION)49 G_DEFINE_TYPE_WITH_PRIVATE (EWebDAVSession, e_webdav_session, E_TYPE_SOUP_SESSION)
50 
51 G_DEFINE_BOXED_TYPE (EWebDAVResource, e_webdav_resource, e_webdav_resource_copy, e_webdav_resource_free)
52 G_DEFINE_BOXED_TYPE (EWebDAVPropertyChange, e_webdav_property_change, e_webdav_property_change_copy, e_webdav_property_change_free)
53 G_DEFINE_BOXED_TYPE (EWebDAVPrivilege, e_webdav_privilege, e_webdav_privilege_copy, e_webdav_privilege_free)
54 G_DEFINE_BOXED_TYPE (EWebDAVAccessControlEntry, e_webdav_access_control_entry, e_webdav_access_control_entry_copy, e_webdav_access_control_entry_free)
55 
56 /**
57  * e_webdav_resource_new:
58  * @kind: an #EWebDAVResourceKind of the resource
59  * @supports: bit-or of #EWebDAVResourceSupports values
60  * @href: href of the resource
61  * @etag: (nullable): optional ETag of the resource, or %NULL
62  * @display_name: (nullable): optional display name of the resource, or %NULL
63  * @content_type: (nullable): optional Content-Type of the resource, or %NULL
64  * @content_length: optional Content-Length of the resource, or 0
65  * @creation_date: optional date of creation of the resource, or 0
66  * @last_modified: optional last modified time of the resource, or 0
67  * @description: (nullable): optional description of the resource, or %NULL
68  * @color: (nullable): optional color of the resource, or %NULL
69  * @order: sort order of the resource, or (guint) -1
70  *
71  * Some values of the resource are not always valid, depending on the @kind,
72  * but also whether server stores such values and whether it had been asked
73  * for them to be fetched.
74  *
75  * The @etag for %E_WEBDAV_RESOURCE_KIND_COLLECTION can be a change tag instead.
76  *
77  * Returns: (transfer full): A newly created #EWebDAVResource, prefilled with
78  *    given values. Free it with e_webdav_resource_free(), when no longer needed.
79  *
80  * Since: 3.26
81  **/
82 EWebDAVResource *
83 e_webdav_resource_new (EWebDAVResourceKind kind,
84 		       guint32 supports,
85 		       const gchar *href,
86 		       const gchar *etag,
87 		       const gchar *display_name,
88 		       const gchar *content_type,
89 		       gsize content_length,
90 		       glong creation_date,
91 		       glong last_modified,
92 		       const gchar *description,
93 		       const gchar *color,
94 		       guint order)
95 {
96 	EWebDAVResource *resource;
97 
98 	resource = g_slice_new0 (EWebDAVResource);
99 	resource->kind = kind;
100 	resource->supports = supports;
101 	resource->href = g_strdup (href);
102 	resource->etag = g_strdup (etag);
103 	resource->display_name = g_strdup (display_name);
104 	resource->content_type = g_strdup (content_type);
105 	resource->content_length = content_length;
106 	resource->creation_date = creation_date;
107 	resource->last_modified = last_modified;
108 	resource->description = g_strdup (description);
109 	resource->color = g_strdup (color);
110 	resource->order = order;
111 
112 	return resource;
113 }
114 
115 /**
116  * e_webdav_resource_copy:
117  * @src: (nullable): an #EWebDAVResource to make a copy of
118  *
119  * Returns: (transfer full): A new #EWebDAVResource prefilled with
120  *    the same values as @src, or %NULL, when @src is %NULL.
121  *    Free it with e_webdav_resource_free(), when no longer needed.
122  *
123  * Since: 3.26
124  **/
125 EWebDAVResource *
e_webdav_resource_copy(const EWebDAVResource * src)126 e_webdav_resource_copy (const EWebDAVResource *src)
127 {
128 	if (!src)
129 		return NULL;
130 
131 	return e_webdav_resource_new (src->kind,
132 		src->supports,
133 		src->href,
134 		src->etag,
135 		src->display_name,
136 		src->content_type,
137 		src->content_length,
138 		src->creation_date,
139 		src->last_modified,
140 		src->description,
141 		src->color,
142 		src->order);
143 }
144 
145 /**
146  * e_webdav_resource_free:
147  * @ptr: (nullable): an #EWebDAVResource
148  *
149  * Frees an #EWebDAVResource previously created with e_webdav_resource_new()
150  * or e_webdav_resource_copy(). The function does nothing, if @ptr is %NULL.
151  *
152  * Since: 3.26
153  **/
154 void
e_webdav_resource_free(gpointer ptr)155 e_webdav_resource_free (gpointer ptr)
156 {
157 	EWebDAVResource *resource = ptr;
158 
159 	if (resource) {
160 		g_free (resource->href);
161 		g_free (resource->etag);
162 		g_free (resource->display_name);
163 		g_free (resource->content_type);
164 		g_free (resource->description);
165 		g_free (resource->color);
166 		g_slice_free (EWebDAVResource, resource);
167 	}
168 }
169 
170 static EWebDAVPropertyChange *
e_webdav_property_change_new(EWebDAVPropertyChangeKind kind,const gchar * ns_uri,const gchar * name,const gchar * value)171 e_webdav_property_change_new (EWebDAVPropertyChangeKind kind,
172 			      const gchar *ns_uri,
173 			      const gchar *name,
174 			      const gchar *value)
175 {
176 	EWebDAVPropertyChange *change;
177 
178 	change = g_slice_new0 (EWebDAVPropertyChange);
179 	change->kind = kind;
180 	change->ns_uri = g_strdup (ns_uri);
181 	change->name = g_strdup (name);
182 	change->value = g_strdup (value);
183 
184 	return change;
185 }
186 
187 /**
188  * e_webdav_property_change_new_set:
189  * @ns_uri: namespace URI of the property
190  * @name: name of the property
191  * @value: (nullable): value of the property, or %NULL for empty value
192  *
193  * Creates a new #EWebDAVPropertyChange of kind %E_WEBDAV_PROPERTY_SET,
194  * which is used to modify or set the property value. The @value is a string
195  * representation of the value to store. It can be %NULL, but it means
196  * an empty value, not to remove it. To remove property use
197  * e_webdav_property_change_new_remove() instead.
198  *
199  * Returns: (transfer full): A new #EWebDAVPropertyChange. Free it with
200  *    e_webdav_property_change_free(), when no longer needed.
201  *
202  * Since: 3.26
203  **/
204 EWebDAVPropertyChange *
e_webdav_property_change_new_set(const gchar * ns_uri,const gchar * name,const gchar * value)205 e_webdav_property_change_new_set (const gchar *ns_uri,
206 				  const gchar *name,
207 				  const gchar *value)
208 {
209 	g_return_val_if_fail (ns_uri != NULL, NULL);
210 	g_return_val_if_fail (name != NULL, NULL);
211 
212 	return e_webdav_property_change_new (E_WEBDAV_PROPERTY_SET, ns_uri, name, value);
213 }
214 
215 /**
216  * e_webdav_property_change_new_remove:
217  * @ns_uri: namespace URI of the property
218  * @name: name of the property
219  *
220  * Creates a new #EWebDAVPropertyChange of kind %E_WEBDAV_PROPERTY_REMOVE,
221  * which is used to remove the given property. To change property value
222  * use e_webdav_property_change_new_set() instead.
223  *
224  * Returns: (transfer full): A new #EWebDAVPropertyChange. Free it with
225  *    e_webdav_property_change_free(), when no longer needed.
226  *
227  * Since: 3.26
228  **/
229 EWebDAVPropertyChange *
e_webdav_property_change_new_remove(const gchar * ns_uri,const gchar * name)230 e_webdav_property_change_new_remove (const gchar *ns_uri,
231 				     const gchar *name)
232 {
233 	g_return_val_if_fail (ns_uri != NULL, NULL);
234 	g_return_val_if_fail (name != NULL, NULL);
235 
236 	return e_webdav_property_change_new (E_WEBDAV_PROPERTY_REMOVE, ns_uri, name, NULL);
237 }
238 
239 /**
240  * e_webdav_property_change_copy:
241  * @src: (nullable): an #EWebDAVPropertyChange to make a copy of
242  *
243  * Returns: (transfer full): A new #EWebDAVPropertyChange prefilled with
244  *    the same values as @src, or %NULL, when @src is %NULL.
245  *    Free it with e_webdav_property_change_free(), when no longer needed.
246  *
247  * Since: 3.26
248  **/
249 EWebDAVPropertyChange *
e_webdav_property_change_copy(const EWebDAVPropertyChange * src)250 e_webdav_property_change_copy (const EWebDAVPropertyChange *src)
251 {
252 	if (!src)
253 		return NULL;
254 
255 	return e_webdav_property_change_new (
256 		src->kind,
257 		src->ns_uri,
258 		src->name,
259 		src->value);
260 }
261 
262 /**
263  * e_webdav_property_change_free:
264  * @ptr: (nullable): an #EWebDAVPropertyChange
265  *
266  * Frees an #EWebDAVPropertyChange previously created with e_webdav_property_change_new_set(),
267  * e_webdav_property_change_new_remove() or or e_webdav_property_change_copy().
268  * The function does nothing, if @ptr is %NULL.
269  *
270  * Since: 3.26
271  **/
272 void
e_webdav_property_change_free(gpointer ptr)273 e_webdav_property_change_free (gpointer ptr)
274 {
275 	EWebDAVPropertyChange *change = ptr;
276 
277 	if (change) {
278 		g_free (change->ns_uri);
279 		g_free (change->name);
280 		g_free (change->value);
281 		g_slice_free (EWebDAVPropertyChange, change);
282 	}
283 }
284 
285 /**
286  * e_webdav_privilege_new:
287  * @ns_uri: (nullable): a namespace URI
288  * @name: (nullable): element name
289  * @description: (nullable): human read-able description, or %NULL
290  * @kind: an #EWebDAVPrivilegeKind
291  * @hint: an #EWebDAVPrivilegeHint
292  *
293  * Describes one privilege entry. The @hint can be %E_WEBDAV_PRIVILEGE_HINT_UNKNOWN
294  * for privileges which are not known to the #EWebDAVSession. It's possible, because
295  * the servers can define their own privileges. The hint is also tried to pair with
296  * known hints when it's %E_WEBDAV_PRIVILEGE_HINT_UNKNOWN.
297  *
298  * The @ns_uri and @name can be %NULL only if the @hint is one of the known
299  * privileges. Otherwise it's an error to pass either of the two as %NULL.
300  *
301  * Returns: (transfer full): A newly created #EWebDAVPrivilege, prefilled with
302  *    given values. Free it with e_webdav_privilege_free(), when no longer needed.
303  *
304  * Since: 3.26
305  **/
306 EWebDAVPrivilege *
e_webdav_privilege_new(const gchar * ns_uri,const gchar * name,const gchar * description,EWebDAVPrivilegeKind kind,EWebDAVPrivilegeHint hint)307 e_webdav_privilege_new (const gchar *ns_uri,
308 			const gchar *name,
309 			const gchar *description,
310 			EWebDAVPrivilegeKind kind,
311 			EWebDAVPrivilegeHint hint)
312 {
313 	EWebDAVPrivilege *privilege;
314 
315 	if ((!ns_uri || !name) && hint != E_WEBDAV_PRIVILEGE_HINT_UNKNOWN) {
316 		const gchar *use_ns_uri = NULL, *use_name = NULL;
317 
318 		switch (hint) {
319 		case E_WEBDAV_PRIVILEGE_HINT_UNKNOWN:
320 			break;
321 		case E_WEBDAV_PRIVILEGE_HINT_READ:
322 			use_name = "read";
323 			break;
324 		case E_WEBDAV_PRIVILEGE_HINT_WRITE:
325 			use_name = "write";
326 			break;
327 		case E_WEBDAV_PRIVILEGE_HINT_WRITE_PROPERTIES:
328 			use_name = "write-properties";
329 			break;
330 		case E_WEBDAV_PRIVILEGE_HINT_WRITE_CONTENT:
331 			use_name = "write-content";
332 			break;
333 		case E_WEBDAV_PRIVILEGE_HINT_UNLOCK:
334 			use_name = "unlock";
335 			break;
336 		case E_WEBDAV_PRIVILEGE_HINT_READ_ACL:
337 			use_name = "read-acl";
338 			break;
339 		case E_WEBDAV_PRIVILEGE_HINT_WRITE_ACL:
340 			use_name = "write-acl";
341 			break;
342 		case E_WEBDAV_PRIVILEGE_HINT_READ_CURRENT_USER_PRIVILEGE_SET:
343 			use_name = "read-current-user-privilege-set";
344 			break;
345 		case E_WEBDAV_PRIVILEGE_HINT_BIND:
346 			use_name = "bind";
347 			break;
348 		case E_WEBDAV_PRIVILEGE_HINT_UNBIND:
349 			use_name = "unbind";
350 			break;
351 		case E_WEBDAV_PRIVILEGE_HINT_ALL:
352 			use_name = "all";
353 			break;
354 		case E_WEBDAV_PRIVILEGE_HINT_CALDAV_READ_FREE_BUSY:
355 			use_ns_uri = E_WEBDAV_NS_CALDAV;
356 			use_name = "read-free-busy";
357 			break;
358 		}
359 
360 		if (use_name) {
361 			ns_uri = use_ns_uri ? use_ns_uri : E_WEBDAV_NS_DAV;
362 			name = use_name;
363 		}
364 	}
365 
366 	g_return_val_if_fail (ns_uri != NULL, NULL);
367 	g_return_val_if_fail (name != NULL, NULL);
368 
369 	if (hint == E_WEBDAV_PRIVILEGE_HINT_UNKNOWN) {
370 		if (g_str_equal (ns_uri, E_WEBDAV_NS_DAV)) {
371 			if (g_str_equal (name, "read"))
372 				hint = E_WEBDAV_PRIVILEGE_HINT_READ;
373 			else if (g_str_equal (name, "write"))
374 				hint = E_WEBDAV_PRIVILEGE_HINT_WRITE;
375 			else if (g_str_equal (name, "write-properties"))
376 				hint = E_WEBDAV_PRIVILEGE_HINT_WRITE_PROPERTIES;
377 			else if (g_str_equal (name, "write-content"))
378 				hint = E_WEBDAV_PRIVILEGE_HINT_WRITE_CONTENT;
379 			else if (g_str_equal (name, "unlock"))
380 				hint = E_WEBDAV_PRIVILEGE_HINT_UNLOCK;
381 			else if (g_str_equal (name, "read-acl"))
382 				hint = E_WEBDAV_PRIVILEGE_HINT_READ_ACL;
383 			else if (g_str_equal (name, "write-acl"))
384 				hint = E_WEBDAV_PRIVILEGE_HINT_WRITE_ACL;
385 			else if (g_str_equal (name, "read-current-user-privilege-set"))
386 				hint = E_WEBDAV_PRIVILEGE_HINT_READ_CURRENT_USER_PRIVILEGE_SET;
387 			else if (g_str_equal (name, "bind"))
388 				hint = E_WEBDAV_PRIVILEGE_HINT_BIND;
389 			else if (g_str_equal (name, "unbind"))
390 				hint = E_WEBDAV_PRIVILEGE_HINT_UNBIND;
391 			else if (g_str_equal (name, "all"))
392 				hint = E_WEBDAV_PRIVILEGE_HINT_ALL;
393 		} else if (g_str_equal (ns_uri, E_WEBDAV_NS_CALDAV)) {
394 			if (g_str_equal (name, "read-free-busy"))
395 				hint = E_WEBDAV_PRIVILEGE_HINT_CALDAV_READ_FREE_BUSY;
396 		}
397 	}
398 
399 	privilege = g_slice_new (EWebDAVPrivilege);
400 	privilege->ns_uri = g_strdup (ns_uri);
401 	privilege->name = g_strdup (name);
402 	privilege->description = g_strdup (description);
403 	privilege->kind = kind;
404 	privilege->hint = hint;
405 
406 	return privilege;
407 }
408 
409 /**
410  * e_webdav_privilege_copy:
411  * @src: (nullable): an #EWebDAVPrivilege to make a copy of
412  *
413  * Returns: (transfer full): A new #EWebDAVPrivilege prefilled with
414  *    the same values as @src, or %NULL, when @src is %NULL.
415  *    Free it with e_webdav_privilege_free(), when no longer needed.
416  *
417  * Since: 3.26
418  **/
419 EWebDAVPrivilege *
e_webdav_privilege_copy(const EWebDAVPrivilege * src)420 e_webdav_privilege_copy (const EWebDAVPrivilege *src)
421 {
422 	if (!src)
423 		return NULL;
424 
425 	return e_webdav_privilege_new (
426 		src->ns_uri,
427 		src->name,
428 		src->description,
429 		src->kind,
430 		src->hint);
431 }
432 
433 /**
434  * e_webdav_privilege_free:
435  * @ptr: (nullable): an #EWebDAVPrivilege
436  *
437  * Frees an #EWebDAVPrivilege previously created with e_webdav_privilege_new()
438  * or e_webdav_privilege_copy(). The function does nothing, if @ptr is %NULL.
439  *
440  * Since: 3.26
441  **/
442 void
e_webdav_privilege_free(gpointer ptr)443 e_webdav_privilege_free (gpointer ptr)
444 {
445 	EWebDAVPrivilege *privilege = ptr;
446 
447 	if (privilege) {
448 		g_free (privilege->ns_uri);
449 		g_free (privilege->name);
450 		g_free (privilege->description);
451 		g_slice_free (EWebDAVPrivilege, privilege);
452 	}
453 }
454 
455 /**
456  * e_webdav_access_control_entry_new:
457  * @principal_kind: an #EWebDAVACEPrincipalKind
458  * @principal_href: (nullable): principal href; should be set only if @principal_kind is @E_WEBDAV_ACE_PRINCIPAL_HREF
459  * @flags: bit-or of #EWebDAVACEFlag values
460  * @inherited_href: (nullable): href of the resource from which inherits; should be set only if @flags contain E_WEBDAV_ACE_FLAG_INHERITED
461  *
462  * Describes one Access Control Entry (ACE).
463  *
464  * The @flags should always contain either %E_WEBDAV_ACE_FLAG_GRANT or
465  * %E_WEBDAV_ACE_FLAG_DENY value.
466  *
467  * Use e_webdav_access_control_entry_append_privilege() to add respective
468  * privileges to the entry.
469  *
470  * Returns: (transfer full): A newly created #EWebDAVAccessControlEntry, prefilled with
471  *    given values. Free it with e_webdav_access_control_entry_free(), when no longer needed.
472  *
473  * Since: 3.26
474  **/
475 EWebDAVAccessControlEntry *
e_webdav_access_control_entry_new(EWebDAVACEPrincipalKind principal_kind,const gchar * principal_href,guint32 flags,const gchar * inherited_href)476 e_webdav_access_control_entry_new (EWebDAVACEPrincipalKind principal_kind,
477 				   const gchar *principal_href,
478 				   guint32 flags,
479 				   const gchar *inherited_href)
480 {
481 	EWebDAVAccessControlEntry *ace;
482 
483 	if (principal_kind == E_WEBDAV_ACE_PRINCIPAL_HREF)
484 		g_return_val_if_fail (principal_href != NULL, NULL);
485 	else
486 		g_return_val_if_fail (principal_href == NULL, NULL);
487 
488 	if ((flags & E_WEBDAV_ACE_FLAG_INHERITED) != 0)
489 		g_return_val_if_fail (inherited_href != NULL, NULL);
490 	else
491 		g_return_val_if_fail (inherited_href == NULL, NULL);
492 
493 	ace = g_slice_new0 (EWebDAVAccessControlEntry);
494 	ace->principal_kind = principal_kind;
495 	ace->principal_href = g_strdup (principal_href);
496 	ace->flags = flags;
497 	ace->inherited_href = g_strdup (inherited_href);
498 	ace->privileges = NULL;
499 
500 	return ace;
501 }
502 
503 /**
504  * e_webdav_access_control_entry_copy:
505  * @src: (nullable): an #EWebDAVAccessControlEntry to make a copy of
506  *
507  * Returns: (transfer full): A new #EWebDAVAccessControlEntry prefilled with
508  *    the same values as @src, or %NULL, when @src is %NULL.
509  *    Free it with e_webdav_access_control_entry_free(), when no longer needed.
510  *
511  * Since: 3.26
512  **/
513 EWebDAVAccessControlEntry *
e_webdav_access_control_entry_copy(const EWebDAVAccessControlEntry * src)514 e_webdav_access_control_entry_copy (const EWebDAVAccessControlEntry *src)
515 {
516 	EWebDAVAccessControlEntry *ace;
517 	GSList *link;
518 
519 	if (!src)
520 		return NULL;
521 
522 	ace = e_webdav_access_control_entry_new (
523 		src->principal_kind,
524 		src->principal_href,
525 		src->flags,
526 		src->inherited_href);
527 	if (!ace)
528 		return NULL;
529 
530 	for (link = src->privileges; link; link = g_slist_next (link)) {
531 		EWebDAVPrivilege *privilege = link->data;
532 
533 		if (privilege)
534 			ace->privileges = g_slist_prepend (ace->privileges, e_webdav_privilege_copy (privilege));
535 	}
536 
537 	ace->privileges = g_slist_reverse (ace->privileges);
538 
539 	return ace;
540 }
541 
542 /**
543  * e_webdav_access_control_entry_free:
544  * @ptr: (nullable): an #EWebDAVAccessControlEntry
545  *
546  * Frees an #EWebDAVAccessControlEntry previously created with e_webdav_access_control_entry_new()
547  * or e_webdav_access_control_entry_copy(). The function does nothing, if @ptr is %NULL.
548  *
549  * Since: 3.26
550  **/
551 void
e_webdav_access_control_entry_free(gpointer ptr)552 e_webdav_access_control_entry_free (gpointer ptr)
553 {
554 	EWebDAVAccessControlEntry *ace = ptr;
555 
556 	if (ace) {
557 		g_free (ace->principal_href);
558 		g_free (ace->inherited_href);
559 		g_slist_free_full (ace->privileges, e_webdav_privilege_free);
560 		g_slice_free (EWebDAVAccessControlEntry, ace);
561 	}
562 }
563 
564 /**
565  * e_webdav_access_control_entry_append_privilege:
566  * @ace: an #EWebDAVAccessControlEntry
567  * @privilege: (transfer full): an #EWebDAVPrivilege
568  *
569  * Appends a new @privilege to the list of privileges for the @ace.
570  * The function assumes ownership of the @privilege, which is freed
571  * together with the @ace.
572  *
573  * Since: 3.26
574  **/
575 void
e_webdav_access_control_entry_append_privilege(EWebDAVAccessControlEntry * ace,EWebDAVPrivilege * privilege)576 e_webdav_access_control_entry_append_privilege (EWebDAVAccessControlEntry *ace,
577 						EWebDAVPrivilege *privilege)
578 {
579 	g_return_if_fail (ace != NULL);
580 	g_return_if_fail (privilege != NULL);
581 
582 	ace->privileges = g_slist_append (ace->privileges, privilege);
583 }
584 
585 /**
586  * e_webdav_access_control_entry_get_privileges:
587  * @ace: an #EWebDAVAccessControlEntry
588  *
589  * Returns: (element-type EWebDAVPrivilege) (transfer none): A #GSList of #EWebDAVPrivilege
590  *    with the list of privileges for the @ace. The reurned #GSList, together with its data
591  *    is owned by the @ace.
592  *
593  * Since: 3.26
594  **/
595 GSList *
e_webdav_access_control_entry_get_privileges(EWebDAVAccessControlEntry * ace)596 e_webdav_access_control_entry_get_privileges (EWebDAVAccessControlEntry *ace)
597 {
598 	g_return_val_if_fail (ace != NULL, NULL);
599 
600 	return ace->privileges;
601 }
602 
603 static void
e_webdav_session_finalize(GObject * object)604 e_webdav_session_finalize (GObject *object)
605 {
606 	EWebDAVSession *webdav = E_WEBDAV_SESSION (object);
607 
608 	g_clear_pointer (&webdav->priv->last_dav_error_code, g_free);
609 
610 	/* Chain up to parent's method. */
611 	G_OBJECT_CLASS (e_webdav_session_parent_class)->finalize (object);
612 }
613 
614 static void
e_webdav_session_class_init(EWebDAVSessionClass * klass)615 e_webdav_session_class_init (EWebDAVSessionClass *klass)
616 {
617 	GObjectClass *object_class;
618 
619 	object_class = G_OBJECT_CLASS (klass);
620 	object_class->finalize = e_webdav_session_finalize;
621 }
622 
623 static void
e_webdav_session_init(EWebDAVSession * webdav)624 e_webdav_session_init (EWebDAVSession *webdav)
625 {
626 	webdav->priv = e_webdav_session_get_instance_private (webdav);
627 }
628 
629 /**
630  * e_webdav_session_new:
631  * @source: an #ESource
632  *
633  * Creates a new #EWebDAVSession associated with given @source.
634  * The #EWebDAVSession uses an #ESourceWebdav extension on certain
635  * places when it's defined for the @source.
636  *
637  * Returns: (transfer full): a new #EWebDAVSession; free it with g_object_unref(),
638  *    when no longer needed.
639  *
640  * Since: 3.26
641  **/
642 EWebDAVSession *
e_webdav_session_new(ESource * source)643 e_webdav_session_new (ESource *source)
644 {
645 	g_return_val_if_fail (E_IS_SOURCE (source), NULL);
646 
647 	return g_object_new (E_TYPE_WEBDAV_SESSION,
648 		"source", source,
649 		NULL);
650 }
651 
652 /**
653  * e_webdav_session_get_last_dav_error_code:
654  * @webdav: an #EWebDAVSession
655  *
656  * Returns last DAV error code as returned by the server. Each recognized code
657  * is enclosed in "[]" in the returned string, to be able to distinguish between
658  * them, in case the server returned multiple codes.
659  *
660  * The string is valid until the next request is executed.
661  *
662  * Returns: (transfer none): a DAV error from the last request, or %NULL, when
663  *    no error had been recognized.
664  *
665  * Since: 3.36
666  **/
667 const gchar *
e_webdav_session_get_last_dav_error_code(EWebDAVSession * webdav)668 e_webdav_session_get_last_dav_error_code (EWebDAVSession *webdav)
669 {
670 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), NULL);
671 
672 	return webdav->priv->last_dav_error_code;
673 }
674 
675 /**
676  * e_webdav_session_get_last_dav_error_is_permission:
677  * @webdav: an #EWebDAVSession
678  *
679  * Returns: whether the last recognized DAV error code contains an error
680  *    which means that user doesn't have permission for the operation. If there
681  *    is no DAV error stored, then returns %FALSE.
682  *
683  * Since: 3.36
684  **/
685 gboolean
e_webdav_session_get_last_dav_error_is_permission(EWebDAVSession * webdav)686 e_webdav_session_get_last_dav_error_is_permission (EWebDAVSession *webdav)
687 {
688 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
689 
690 	return webdav->priv->last_dav_error_code &&
691 		strstr (webdav->priv->last_dav_error_code, "[need-privileges]");
692 }
693 
694 /**
695  * e_webdav_session_new_request:
696  * @webdav: an #EWebDAVSession
697  * @method: an HTTP method
698  * @uri: (nullable): URI to create the request for, or %NULL to read from #ESource
699  * @error: return location for a #GError, or %NULL
700  *
701  * Returns: (transfer full): A new #SoupRequestHTTP for the given @uri, or, when %NULL,
702  *    for the URI stored in the associated #ESource. Free the returned structure
703  *    with g_object_unref(), when no longer needed.
704  *
705  * Since: 3.26
706  **/
707 SoupRequestHTTP *
e_webdav_session_new_request(EWebDAVSession * webdav,const gchar * method,const gchar * uri,GError ** error)708 e_webdav_session_new_request (EWebDAVSession *webdav,
709 			      const gchar *method,
710 			      const gchar *uri,
711 			      GError **error)
712 {
713 	ESoupSession *session;
714 	SoupRequestHTTP *request;
715 	SoupURI *soup_uri;
716 	ESource *source;
717 	ESourceWebdav *webdav_extension;
718 	const gchar *path;
719 
720 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), NULL);
721 
722 	session = E_SOUP_SESSION (webdav);
723 	if (uri && *uri)
724 		return e_soup_session_new_request (session, method, uri, error);
725 
726 	source = e_soup_session_get_source (session);
727 	g_return_val_if_fail (E_IS_SOURCE (source), NULL);
728 
729 	if (!e_source_has_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND)) {
730 		g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
731 			_("Cannot determine destination URL without WebDAV extension"));
732 		return NULL;
733 	}
734 
735 	webdav_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
736 	soup_uri = e_source_webdav_dup_soup_uri (webdav_extension);
737 
738 	g_return_val_if_fail (soup_uri != NULL, NULL);
739 
740 	/* The URI in the ESource should be to a collection, with an ending
741 	   forward slash, thus ensure it's there. */
742 	path = soup_uri_get_path (soup_uri);
743 	if (!path || !*path || !g_str_has_suffix (path, "/")) {
744 		gchar *new_path;
745 
746 		new_path = g_strconcat (path ? path : "", "/", NULL);
747 		soup_uri_set_path (soup_uri, new_path);
748 		g_free (new_path);
749 	}
750 
751 	request = e_soup_session_new_request_uri (session, method, soup_uri, error);
752 
753 	soup_uri_free (soup_uri);
754 
755 	return request;
756 }
757 
758 static gboolean
e_webdav_session_extract_propstat_error_cb(EWebDAVSession * webdav,xmlNodePtr prop_node,const SoupURI * request_uri,const gchar * href,guint status_code,gpointer user_data)759 e_webdav_session_extract_propstat_error_cb (EWebDAVSession *webdav,
760 					    xmlNodePtr prop_node,
761 					    const SoupURI *request_uri,
762 					    const gchar *href,
763 					    guint status_code,
764 					    gpointer user_data)
765 {
766 	GError **error = user_data;
767 
768 	g_return_val_if_fail (error != NULL, FALSE);
769 
770 	if (status_code != SOUP_STATUS_OK && (
771 	    status_code != SOUP_STATUS_FAILED_DEPENDENCY ||
772 	    !*error)) {
773 		xmlNodePtr parent;
774 		const xmlChar *description = NULL;
775 
776 		parent = prop_node->parent;
777 		if (parent) {
778 			description = e_xml_find_child_and_get_text (parent, E_WEBDAV_NS_DAV, "responsedescription");
779 
780 			if (!description || !*description) {
781 				description = NULL;
782 				parent = parent->parent;
783 				if (parent) {
784 					description = e_xml_find_child_and_get_text (parent, E_WEBDAV_NS_DAV, "responsedescription");
785 
786 					if (!description || !*description)
787 						description = NULL;
788 				}
789 			}
790 		}
791 
792 		g_clear_error (error);
793 		g_set_error_literal (error, SOUP_HTTP_ERROR, status_code,
794 			e_soup_session_util_status_to_string (status_code, (const gchar *) description));
795 	}
796 
797 	return TRUE;
798 }
799 
800 static gboolean
e_webdav_session_extract_dav_error(EWebDAVSession * webdav,xmlXPathContextPtr xpath_ctx,const gchar * xpath_prefix,gchar ** out_detail_text,gboolean can_change_last_dav_error_code)801 e_webdav_session_extract_dav_error (EWebDAVSession *webdav,
802 				    xmlXPathContextPtr xpath_ctx,
803 				    const gchar *xpath_prefix,
804 				    gchar **out_detail_text,
805 				    gboolean can_change_last_dav_error_code)
806 {
807 	xmlXPathObjectPtr xpath_obj;
808 	gchar *detail_text;
809 
810 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
811 	g_return_val_if_fail (xpath_ctx != NULL, FALSE);
812 	g_return_val_if_fail (xpath_prefix != NULL, FALSE);
813 	g_return_val_if_fail (out_detail_text != NULL, FALSE);
814 
815 	if (!e_xml_xpath_eval_exists (xpath_ctx, "%s/D:error", xpath_prefix))
816 		return FALSE;
817 
818 	detail_text = e_xml_xpath_eval_as_string (xpath_ctx, "%s/D:error", xpath_prefix);
819 
820 	xpath_obj = e_xml_xpath_eval (xpath_ctx, "%s/D:error", xpath_prefix);
821 	if (xpath_obj) {
822 		if (xpath_obj->type == XPATH_NODESET &&
823 		    xpath_obj->nodesetval &&
824 		    xpath_obj->nodesetval->nodeNr == 1 &&
825 		    xpath_obj->nodesetval->nodeTab &&
826 		    xpath_obj->nodesetval->nodeTab[0] &&
827 		    xpath_obj->nodesetval->nodeTab[0]->children) {
828 			GString *text = g_string_new ("");
829 			xmlNodePtr node;
830 
831 			for (node = xpath_obj->nodesetval->nodeTab[0]->children; node; node = node->next) {
832 				if (node->type == XML_ELEMENT_NODE &&
833 				    node->name && *(node->name)) {
834 					g_string_append_printf (text, "[%s]", (const gchar *) node->name);
835 				}
836 			}
837 
838 			if (text->len > 0) {
839 				if (can_change_last_dav_error_code) {
840 					g_clear_pointer (&webdav->priv->last_dav_error_code, g_free);
841 					webdav->priv->last_dav_error_code = g_strdup (text->str);
842 				}
843 
844 				if (detail_text) {
845 					g_strstrip (detail_text);
846 					if (*detail_text)
847 						g_string_prepend (text, detail_text);
848 					g_free (detail_text);
849 				}
850 
851 				detail_text = g_string_free (text, FALSE);
852 			} else {
853 				g_string_free (text, TRUE);
854 			}
855 		}
856 
857 		xmlXPathFreeObject (xpath_obj);
858 	}
859 
860 	*out_detail_text = detail_text;
861 
862 	return detail_text != NULL;
863 }
864 
865 static gboolean
e_webdav_session_replace_with_detailed_error_internal(EWebDAVSession * webdav,SoupRequestHTTP * request,const GByteArray * response_data,gboolean ignore_multistatus,const gchar * prefix,GError ** inout_error,gboolean can_change_last_dav_error_code,gboolean skip_text_on_success)866 e_webdav_session_replace_with_detailed_error_internal (EWebDAVSession *webdav,
867 						       SoupRequestHTTP *request,
868 						       const GByteArray *response_data,
869 						       gboolean ignore_multistatus,
870 						       const gchar *prefix,
871 						       GError **inout_error,
872 						       gboolean can_change_last_dav_error_code,
873 						       gboolean skip_text_on_success)
874 {
875 	SoupMessage *message;
876 	GByteArray byte_array = { 0 };
877 	const gchar *content_type, *reason_phrase;
878 	gchar *detail_text = NULL;
879 	gchar *reason_phrase_copy = NULL;
880 	gboolean error_set = FALSE;
881 	guint status_code;
882 	GError *local_error = NULL;
883 
884 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
885 	g_return_val_if_fail (SOUP_IS_REQUEST_HTTP (request), FALSE);
886 
887 	message = soup_request_http_get_message (request);
888 	if (!message)
889 		return FALSE;
890 
891 	status_code = message->status_code;
892 	reason_phrase = message->reason_phrase;
893 	byte_array.data = NULL;
894 	byte_array.len = 0;
895 
896 	if (response_data && response_data->len) {
897 		byte_array.data = (gpointer) response_data->data;
898 		byte_array.len = response_data->len;
899 	} else if (message->response_body && message->response_body->length) {
900 		byte_array.data = (gpointer) message->response_body->data;
901 		byte_array.len = message->response_body->length;
902 	}
903 
904 	if (!byte_array.data || !byte_array.len)
905 		goto out;
906 
907 	if (status_code == SOUP_STATUS_MULTI_STATUS &&
908 	    !ignore_multistatus &&
909 	    !e_webdav_session_traverse_multistatus_response (webdav, message, &byte_array,
910 		e_webdav_session_extract_propstat_error_cb, &local_error, NULL)) {
911 		g_clear_error (&local_error);
912 	}
913 
914 	if (local_error) {
915 		if (prefix)
916 			g_prefix_error (&local_error, "%s: ", prefix);
917 		g_propagate_error (inout_error, local_error);
918 
919 		g_object_unref (message);
920 
921 		return TRUE;
922 	}
923 
924 	content_type = soup_message_headers_get_content_type (message->response_headers, NULL);
925 	if (content_type && (!skip_text_on_success || (status_code && !SOUP_STATUS_IS_SUCCESSFUL (status_code))) && (
926 	    (g_ascii_strcasecmp (content_type, "application/xml") == 0 ||
927 	     g_ascii_strcasecmp (content_type, "text/xml") == 0))) {
928 		xmlDocPtr doc;
929 
930 		if (status_code == SOUP_STATUS_MULTI_STATUS && ignore_multistatus)
931 			doc = NULL;
932 		else
933 			doc = e_xml_parse_data (byte_array.data, byte_array.len);
934 
935 		if (doc) {
936 			xmlXPathContextPtr xpath_ctx;
937 
938 			xpath_ctx = e_xml_new_xpath_context_with_namespaces (doc,
939 				"D", E_WEBDAV_NS_DAV,
940 				"C", E_WEBDAV_NS_CALDAV,
941 				"A", E_WEBDAV_NS_CARDDAV,
942 				NULL);
943 
944 			if (xpath_ctx &&
945 			    e_webdav_session_extract_dav_error (webdav, xpath_ctx, "", &detail_text, can_change_last_dav_error_code)) {
946 				/* do nothing, detail_text is set */
947 			} else if (xpath_ctx) {
948 				const gchar *path_prefix = NULL;
949 
950 				if (e_xml_xpath_eval_exists (xpath_ctx, "/D:multistatus/D:response/D:status"))
951 					path_prefix = "/D:multistatus/D:response";
952 				else if (e_xml_xpath_eval_exists (xpath_ctx, "/C:mkcalendar-response/D:status"))
953 					path_prefix = "/C:mkcalendar-response";
954 				else if (e_xml_xpath_eval_exists (xpath_ctx, "/D:mkcol-response/D:status"))
955 					path_prefix = "/D:mkcol-response";
956 
957 				if (path_prefix) {
958 					guint parsed_status = 0;
959 					gchar *status;
960 
961 					status = e_xml_xpath_eval_as_string (xpath_ctx, "%s/D:status", path_prefix);
962 					if (status && soup_headers_parse_status_line (status, NULL, &parsed_status, &reason_phrase_copy) &&
963 					    !SOUP_STATUS_IS_SUCCESSFUL (parsed_status)) {
964 						status_code = parsed_status;
965 						reason_phrase = reason_phrase_copy;
966 						detail_text = e_xml_xpath_eval_as_string (xpath_ctx, "%s/D:responsedescription", path_prefix);
967 
968 						if (!detail_text)
969 							e_webdav_session_extract_dav_error (webdav, xpath_ctx, path_prefix, &detail_text, can_change_last_dav_error_code);
970 					} else {
971 						e_webdav_session_extract_dav_error (webdav, xpath_ctx, path_prefix, &detail_text, can_change_last_dav_error_code);
972 					}
973 
974 					g_free (status);
975 				}
976 			}
977 
978 			if (xpath_ctx)
979 				xmlXPathFreeContext (xpath_ctx);
980 			xmlFreeDoc (doc);
981 		}
982 	} else if (content_type && (!skip_text_on_success || (status_code && !SOUP_STATUS_IS_SUCCESSFUL (status_code))) &&
983 	     g_ascii_strcasecmp (content_type, "text/plain") == 0) {
984 		detail_text = g_strndup ((const gchar *) byte_array.data, byte_array.len);
985 	} else if (content_type && (!skip_text_on_success || (status_code && !SOUP_STATUS_IS_SUCCESSFUL (status_code))) &&
986 	     g_ascii_strcasecmp (content_type, "text/html") == 0) {
987 		SoupURI *soup_uri;
988 		gchar *uri = NULL;
989 
990 		soup_uri = soup_message_get_uri (message);
991 		if (soup_uri) {
992 			soup_uri = soup_uri_copy (soup_uri);
993 			soup_uri_set_password (soup_uri, NULL);
994 
995 			uri = soup_uri_to_string (soup_uri, FALSE);
996 
997 			soup_uri_free (soup_uri);
998 		}
999 
1000 		if (uri && *uri)
1001 			detail_text = g_strdup_printf (_("The server responded with an HTML page, which can mean there’s an error on the server or with the client request. The used URI was: %s"), uri);
1002 		else
1003 			detail_text = g_strdup_printf (_("The server responded with an HTML page, which can mean there’s an error on the server or with the client request."));
1004 
1005 		g_free (uri);
1006 	}
1007 
1008  out:
1009 	if (detail_text)
1010 		g_strstrip (detail_text);
1011 
1012 	if (detail_text && *detail_text) {
1013 		error_set = TRUE;
1014 
1015 		g_clear_error (inout_error);
1016 
1017 		if (prefix) {
1018 			g_set_error (inout_error, SOUP_HTTP_ERROR, status_code,
1019 				/* Translators: The first '%s' is replaced with error prefix, as provided
1020 				   by the caller, which can be in a form: "Failed with something".
1021 				   The '%d' is replaced with actual HTTP status code.
1022 				   The second '%s' is replaced with a reason phrase of the error (user readable text).
1023 				   The last '%s' is replaced with detailed error text, as returned by the server. */
1024 				_("%s: HTTP error code %d (%s): %s"), prefix, status_code,
1025 				e_soup_session_util_status_to_string (status_code, reason_phrase),
1026 				detail_text);
1027 		} else {
1028 			g_set_error (inout_error, SOUP_HTTP_ERROR, status_code,
1029 				/* Translators: The '%d' is replaced with actual HTTP status code.
1030 				   The '%s' is replaced with a reason phrase of the error (user readable text).
1031 				   The last '%s' is replaced with detailed error text, as returned by the server. */
1032 				_("Failed with HTTP error code %d (%s): %s"), status_code,
1033 				e_soup_session_util_status_to_string (status_code, reason_phrase),
1034 				detail_text);
1035 		}
1036 	} else if (status_code && !SOUP_STATUS_IS_SUCCESSFUL (status_code)) {
1037 		error_set = TRUE;
1038 
1039 		g_clear_error (inout_error);
1040 
1041 		if (prefix) {
1042 			g_set_error (inout_error, SOUP_HTTP_ERROR, status_code,
1043 				/* Translators: The first '%s' is replaced with error prefix, as provided
1044 				   by the caller, which can be in a form: "Failed with something".
1045 				   The '%d' is replaced with actual HTTP status code.
1046 				   The second '%s' is replaced with a reason phrase of the error (user readable text). */
1047 				_("%s: HTTP error code %d (%s)"), prefix, status_code,
1048 				e_soup_session_util_status_to_string (status_code, reason_phrase));
1049 		} else {
1050 			g_set_error (inout_error, SOUP_HTTP_ERROR, status_code,
1051 				/* Translators: The '%d' is replaced with actual HTTP status code.
1052 				   The '%s' is replaced with a reason phrase of the error (user readable text). */
1053 				_("Failed with HTTP error code %d (%s)"), status_code,
1054 				e_soup_session_util_status_to_string (status_code, reason_phrase));
1055 		}
1056 	}
1057 
1058 	g_object_unref (message);
1059 	g_free (reason_phrase_copy);
1060 	g_free (detail_text);
1061 
1062 	return error_set;
1063 }
1064 
1065 /**
1066  * e_webdav_session_replace_with_detailed_error:
1067  * @webdav: an #EWebDAVSession
1068  * @request: a #SoupRequestHTTP
1069  * @response_data: (nullable): received response data, or %NULL
1070  * @ignore_multistatus: whether to ignore multistatus responses
1071  * @prefix: (nullable): error message prefix, used when replacing, or %NULL
1072  * @inout_error: (inout) (nullable) (transfer full): a #GError variable to replace content to, or %NULL
1073  *
1074  * Tries to read detailed error information from @response_data,
1075  * if not provided, then from @request's response_body. If the detailed
1076  * error cannot be found, then does nothing, otherwise frees the content
1077  * of @inout_error, if any, and then populates it with an error message
1078  * prefixed with @prefix.
1079  *
1080  * The @prefix might be of form "Failed to something", because the resulting
1081  * error message will be:
1082  * "Failed to something: HTTP error code XXX (reason_phrase): detailed_error".
1083  * When @prefix is %NULL, the error message will be:
1084  * "Failed with HTTP error code XXX (reason phrase): detailed_error".
1085  *
1086  * As the caller might not be interested in errors, also the @inout_error
1087  * can be %NULL, in which case the function does nothing.
1088  *
1089  * Returns: Whether any detailed error had been recognized.
1090  *
1091  * Since: 3.26
1092  **/
1093 gboolean
e_webdav_session_replace_with_detailed_error(EWebDAVSession * webdav,SoupRequestHTTP * request,const GByteArray * response_data,gboolean ignore_multistatus,const gchar * prefix,GError ** inout_error)1094 e_webdav_session_replace_with_detailed_error (EWebDAVSession *webdav,
1095 					      SoupRequestHTTP *request,
1096 					      const GByteArray *response_data,
1097 					      gboolean ignore_multistatus,
1098 					      const gchar *prefix,
1099 					      GError **inout_error)
1100 {
1101 	return e_webdav_session_replace_with_detailed_error_internal (webdav, request, response_data, ignore_multistatus, prefix, inout_error, FALSE, FALSE);
1102 }
1103 
1104 /**
1105  * e_webdav_session_ensure_full_uri:
1106  * @webdav: an #EWebDAVSession
1107  * @request_uri: (nullable): a #SoupURI to which the @href belongs, or %NULL
1108  * @href: a possibly path-only href
1109  *
1110  * Converts possibly path-only @href into a full URI under the @request_uri.
1111  * When the @request_uri is %NULL, the URI defined in associated #ESource is
1112  * used instead, taken from the #ESourceWebdav extension, if defined.
1113  *
1114  * Free the returned pointer with g_free(), when no longer needed.
1115  *
1116  * Returns: (transfer full): The @href as a full URI
1117  *
1118  * Since: 3.24
1119  **/
1120 gchar *
e_webdav_session_ensure_full_uri(EWebDAVSession * webdav,const SoupURI * request_uri,const gchar * href)1121 e_webdav_session_ensure_full_uri (EWebDAVSession *webdav,
1122 				  const SoupURI *request_uri,
1123 				  const gchar *href)
1124 {
1125 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), NULL);
1126 	g_return_val_if_fail (href != NULL, NULL);
1127 
1128 	if (*href == '/' || !strstr (href, "://")) {
1129 		SoupURI *soup_uri;
1130 		gchar *full_uri;
1131 
1132 		if (request_uri) {
1133 			soup_uri = soup_uri_copy ((SoupURI *) request_uri);
1134 		} else {
1135 			ESource *source;
1136 			ESourceWebdav *webdav_extension;
1137 
1138 			source = e_soup_session_get_source (E_SOUP_SESSION (webdav));
1139 			g_return_val_if_fail (E_IS_SOURCE (source), NULL);
1140 
1141 			if (!e_source_has_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND))
1142 				return g_strdup (href);
1143 
1144 			webdav_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
1145 			soup_uri = e_source_webdav_dup_soup_uri (webdav_extension);
1146 		}
1147 
1148 		g_return_val_if_fail (soup_uri != NULL, NULL);
1149 
1150 		soup_uri_set_path (soup_uri, href);
1151 		soup_uri_set_user (soup_uri, NULL);
1152 		soup_uri_set_password (soup_uri, NULL);
1153 
1154 		full_uri = soup_uri_to_string (soup_uri, FALSE);
1155 
1156 		soup_uri_free (soup_uri);
1157 
1158 		return full_uri;
1159 	}
1160 
1161 	return g_strdup (href);
1162 }
1163 
1164 static GHashTable *
e_webdav_session_comma_header_to_hashtable(SoupMessageHeaders * headers,const gchar * header_name)1165 e_webdav_session_comma_header_to_hashtable (SoupMessageHeaders *headers,
1166 					    const gchar *header_name)
1167 {
1168 	GHashTable *soup_params, *result;
1169 	GHashTableIter iter;
1170 	const gchar *value;
1171 	gpointer key;
1172 
1173 	g_return_val_if_fail (header_name != NULL, NULL);
1174 
1175 	if (!headers)
1176 		return NULL;
1177 
1178 	value = soup_message_headers_get_list (headers, header_name);
1179 	if (!value)
1180 		return NULL;
1181 
1182 	soup_params = soup_header_parse_param_list (value);
1183 	if (!soup_params)
1184 		return NULL;
1185 
1186 	result = g_hash_table_new_full (camel_strcase_hash, camel_strcase_equal, g_free, NULL);
1187 
1188 	g_hash_table_iter_init (&iter, soup_params);
1189 	while (g_hash_table_iter_next (&iter, &key, NULL)) {
1190 		value = key;
1191 
1192 		if (value && *value)
1193 			g_hash_table_insert (result, g_strdup (value), GINT_TO_POINTER (1));
1194 	}
1195 
1196 	soup_header_free_param_list (soup_params);
1197 
1198 	return result;
1199 }
1200 
1201 /**
1202  * e_webdav_session_options_sync:
1203  * @webdav: an #EWebDAVSession
1204  * @uri: (nullable): URI to issue the request for, or %NULL to read from #ESource
1205  * @out_capabilities: (out) (transfer full): return location for DAV capabilities
1206  * @out_allows: (out) (transfer full): return location for allowed operations
1207  * @cancellable: optional #GCancellable object, or %NULL
1208  * @error: return location for a #GError, or %NULL
1209  *
1210  * Issues OPTIONS request on the provided @uri, or, in case it's %NULL, on the URI
1211  * defined in associated #ESource.
1212  *
1213  * The @out_capabilities contains a set of returned capabilities. Some known are
1214  * defined as E_WEBDAV_CAPABILITY_CLASS_1, and so on. The 'value' of the #GHashTable
1215  * doesn't have any particular meaning and the strings are compared case insensitively.
1216  * Free the hash table with g_hash_table_destroy(), when no longer needed. The returned
1217  * value can be %NULL on success, it's when the server doesn't provide the information.
1218  *
1219  * The @out_allows contains a set of allowed methods returned by the server. Some known
1220  * are defined as %SOUP_METHOD_OPTIONS, and so on. The 'value' of the #GHashTable
1221  * doesn't have any particular meaning and the strings are compared case insensitively.
1222  * Free the hash table with g_hash_table_destroy(), when no longer needed. The returned
1223  * value can be %NULL on success, it's when the server doesn't provide the information.
1224  *
1225  * Returns: Whether succeeded.
1226  *
1227  * Since: 3.26
1228  **/
1229 gboolean
e_webdav_session_options_sync(EWebDAVSession * webdav,const gchar * uri,GHashTable ** out_capabilities,GHashTable ** out_allows,GCancellable * cancellable,GError ** error)1230 e_webdav_session_options_sync (EWebDAVSession *webdav,
1231 			       const gchar *uri,
1232 			       GHashTable **out_capabilities,
1233 			       GHashTable **out_allows,
1234 			       GCancellable *cancellable,
1235 			       GError **error)
1236 {
1237 	SoupRequestHTTP *request;
1238 	SoupMessage *message;
1239 	GByteArray *bytes;
1240 
1241 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
1242 	g_return_val_if_fail (out_capabilities != NULL, FALSE);
1243 	g_return_val_if_fail (out_allows != NULL, FALSE);
1244 
1245 	*out_capabilities = NULL;
1246 	*out_allows = NULL;
1247 
1248 	g_clear_pointer (&webdav->priv->last_dav_error_code, g_free);
1249 
1250 	request = e_webdav_session_new_request (webdav, SOUP_METHOD_OPTIONS, uri, error);
1251 	if (!request)
1252 		return FALSE;
1253 
1254 	bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, error);
1255 
1256 	if (!bytes) {
1257 		g_object_unref (request);
1258 		return FALSE;
1259 	}
1260 
1261 	message = soup_request_http_get_message (request);
1262 
1263 	g_byte_array_free (bytes, TRUE);
1264 	g_object_unref (request);
1265 
1266 	g_return_val_if_fail (message != NULL, FALSE);
1267 
1268 	*out_capabilities = e_webdav_session_comma_header_to_hashtable (message->response_headers, "DAV");
1269 	*out_allows = e_webdav_session_comma_header_to_hashtable (message->response_headers, "Allow");
1270 
1271 	g_object_unref (message);
1272 
1273 	return TRUE;
1274 }
1275 
1276 /**
1277  * e_webdav_session_post_with_content_type_sync:
1278  * @webdav: an #EWebDAVSession
1279  * @uri: (nullable): URI to issue the request for, or %NULL to read from #ESource
1280  * @data: data to post to the server
1281  * @data_length: length of @data, or -1, when @data is NUL-terminated
1282  * @in_content_type: (nullable): a Content-Type of the @data, or %NULL, to use application/xml
1283  * @out_content_type: (nullable) (transfer full): return location for response Content-Type, or %NULL
1284  * @out_content: (nullable) (transfer full): return location for response content, or %NULL
1285  * @cancellable: optional #GCancellable object, or %NULL
1286  * @error: return location for a #GError, or %NULL
1287  *
1288  * Issues POST request on the provided @uri, or, in case it's %NULL, on the URI
1289  * defined in associated #ESource.
1290  *
1291  * The optional @out_content_type can be used to get content type of the response.
1292  * Free it with g_free(), when no longer needed.
1293  *
1294  * The optional @out_content can be used to get actual result content. Free it
1295  * with g_byte_array_free(), when no longer needed.
1296  *
1297  * Returns: Whether succeeded.
1298  *
1299  * Since: 3.32
1300  **/
1301 gboolean
e_webdav_session_post_with_content_type_sync(EWebDAVSession * webdav,const gchar * uri,const gchar * data,gsize data_length,const gchar * in_content_type,gchar ** out_content_type,GByteArray ** out_content,GCancellable * cancellable,GError ** error)1302 e_webdav_session_post_with_content_type_sync (EWebDAVSession *webdav,
1303 					      const gchar *uri,
1304 					      const gchar *data,
1305 					      gsize data_length,
1306 					      const gchar *in_content_type,
1307 					      gchar **out_content_type,
1308 					      GByteArray **out_content,
1309 					      GCancellable *cancellable,
1310 					      GError **error)
1311 {
1312 	SoupRequestHTTP *request;
1313 	SoupMessage *message;
1314 	GByteArray *bytes;
1315 	gboolean success;
1316 
1317 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
1318 	g_return_val_if_fail (data != NULL, FALSE);
1319 
1320 	if (out_content_type)
1321 		*out_content_type = NULL;
1322 
1323 	if (out_content)
1324 		*out_content = NULL;
1325 
1326 	if (data_length == (gsize) -1)
1327 		data_length = strlen (data);
1328 
1329 	g_clear_pointer (&webdav->priv->last_dav_error_code, g_free);
1330 
1331 	request = e_webdav_session_new_request (webdav, SOUP_METHOD_POST, uri, error);
1332 	if (!request)
1333 		return FALSE;
1334 
1335 	message = soup_request_http_get_message (request);
1336 	if (!message) {
1337 		g_warn_if_fail (message != NULL);
1338 		g_object_unref (request);
1339 
1340 		return FALSE;
1341 	}
1342 
1343 	soup_message_set_request (message, (in_content_type && *in_content_type) ? in_content_type : E_WEBDAV_CONTENT_TYPE_XML,
1344 		SOUP_MEMORY_COPY, data, data_length);
1345 
1346 	bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, error);
1347 
1348 	success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, TRUE, _("Failed to post data"), error, TRUE, FALSE) &&
1349 		bytes != NULL;
1350 
1351 	if (success) {
1352 		if (out_content_type) {
1353 			*out_content_type = g_strdup (soup_message_headers_get_content_type (message->response_headers, NULL));
1354 		}
1355 
1356 		if (out_content) {
1357 			*out_content = bytes;
1358 			bytes = NULL;
1359 		}
1360 	}
1361 
1362 	if (bytes)
1363 		g_byte_array_free (bytes, TRUE);
1364 	g_object_unref (message);
1365 	g_object_unref (request);
1366 
1367 	return success;
1368 }
1369 
1370 /**
1371  * e_webdav_session_post_sync:
1372  * @webdav: an #EWebDAVSession
1373  * @uri: (nullable): URI to issue the request for, or %NULL to read from #ESource
1374  * @data: data to post to the server
1375  * @data_length: length of @data, or -1, when @data is NUL-terminated
1376  * @out_content_type: (nullable) (transfer full): return location for response Content-Type, or %NULL
1377  * @out_content: (nullable) (transfer full): return location for response content, or %NULL
1378  * @cancellable: optional #GCancellable object, or %NULL
1379  * @error: return location for a #GError, or %NULL
1380  *
1381  * Issues POST request on the provided @uri, or, in case it's %NULL, on the URI
1382  * defined in associated #ESource. The Content-Type of the @data is set to
1383  * application/xml. To POST the @data with a different Content-Type use
1384  * e_webdav_session_post_with_content_type_sync().
1385  *
1386  * The optional @out_content_type can be used to get content type of the response.
1387  * Free it with g_free(), when no longer needed.
1388  *
1389  * The optional @out_content can be used to get actual result content. Free it
1390  * with g_byte_array_free(), when no longer needed.
1391  *
1392  * Returns: Whether succeeded.
1393  *
1394  * Since: 3.26
1395  **/
1396 gboolean
e_webdav_session_post_sync(EWebDAVSession * webdav,const gchar * uri,const gchar * data,gsize data_length,gchar ** out_content_type,GByteArray ** out_content,GCancellable * cancellable,GError ** error)1397 e_webdav_session_post_sync (EWebDAVSession *webdav,
1398 			    const gchar *uri,
1399 			    const gchar *data,
1400 			    gsize data_length,
1401 			    gchar **out_content_type,
1402 			    GByteArray **out_content,
1403 			    GCancellable *cancellable,
1404 			    GError **error)
1405 {
1406 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
1407 	g_return_val_if_fail (data != NULL, FALSE);
1408 
1409 	return e_webdav_session_post_with_content_type_sync (webdav, uri, data, data_length, NULL, out_content_type, out_content, cancellable, error);
1410 }
1411 
1412 /**
1413  * e_webdav_session_propfind_sync:
1414  * @webdav: an #EWebDAVSession
1415  * @uri: (nullable): URI to issue the request for, or %NULL to read from #ESource
1416  * @depth: requested depth, can be one of %E_WEBDAV_DEPTH_THIS, %E_WEBDAV_DEPTH_THIS_AND_CHILDREN or %E_WEBDAV_DEPTH_INFINITY
1417  * @xml: (nullable): the request itself, as an #EXmlDocument, the root element should be DAV:propfind, or %NULL
1418  * @func: (scope call): an #EWebDAVPropstatTraverseFunc function to call for each DAV:propstat in the multistatus response
1419  * @func_user_data: (closure func): user data passed to @func
1420  * @cancellable: optional #GCancellable object, or %NULL
1421  * @error: return location for a #GError, or %NULL
1422  *
1423  * Issues PROPFIND request on the provided @uri, or, in case it's %NULL, on the URI
1424  * defined in associated #ESource. On success, calls @func for each returned
1425  * DAV:propstat.
1426  *
1427  * The @xml can be %NULL, in which case the server should behave like DAV:allprop request.
1428  *
1429  * Returns: Whether succeeded.
1430  *
1431  * Since: 3.26
1432  **/
1433 gboolean
e_webdav_session_propfind_sync(EWebDAVSession * webdav,const gchar * uri,const gchar * depth,const EXmlDocument * xml,EWebDAVPropstatTraverseFunc func,gpointer func_user_data,GCancellable * cancellable,GError ** error)1434 e_webdav_session_propfind_sync (EWebDAVSession *webdav,
1435 				const gchar *uri,
1436 				const gchar *depth,
1437 				const EXmlDocument *xml,
1438 				EWebDAVPropstatTraverseFunc func,
1439 				gpointer func_user_data,
1440 				GCancellable *cancellable,
1441 				GError **error)
1442 {
1443 	SoupRequestHTTP *request;
1444 	SoupMessage *message;
1445 	GByteArray *bytes;
1446 	gboolean success;
1447 
1448 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
1449 	g_return_val_if_fail (depth != NULL, FALSE);
1450 	if (xml)
1451 		g_return_val_if_fail (E_IS_XML_DOCUMENT (xml), FALSE);
1452 	g_return_val_if_fail (func != NULL, FALSE);
1453 
1454 	g_clear_pointer (&webdav->priv->last_dav_error_code, g_free);
1455 
1456 	request = e_webdav_session_new_request (webdav, SOUP_METHOD_PROPFIND, uri, error);
1457 	if (!request)
1458 		return FALSE;
1459 
1460 	message = soup_request_http_get_message (request);
1461 	if (!message) {
1462 		g_warn_if_fail (message != NULL);
1463 		g_object_unref (request);
1464 
1465 		return FALSE;
1466 	}
1467 
1468 	soup_message_headers_replace (message->request_headers, "Depth", depth);
1469 
1470 	if (xml) {
1471 		gchar *content;
1472 		gsize content_length;
1473 
1474 		content = e_xml_document_get_content (xml, &content_length);
1475 		if (!content) {
1476 			g_object_unref (message);
1477 			g_object_unref (request);
1478 
1479 			g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Failed to get input XML content"));
1480 
1481 			return FALSE;
1482 		}
1483 
1484 		soup_message_set_request (message, E_WEBDAV_CONTENT_TYPE_XML,
1485 			SOUP_MEMORY_TAKE, content, content_length);
1486 	}
1487 
1488 	bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, error);
1489 
1490 	success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, TRUE, _("Failed to get properties"), error, TRUE, FALSE) &&
1491 		bytes != NULL;
1492 
1493 	if (success)
1494 		success = e_webdav_session_traverse_multistatus_response (webdav, message, bytes, func, func_user_data, error);
1495 
1496 	if (bytes)
1497 		g_byte_array_free (bytes, TRUE);
1498 	g_object_unref (message);
1499 	g_object_unref (request);
1500 
1501 	return success;
1502 }
1503 
1504 /**
1505  * e_webdav_session_proppatch_sync:
1506  * @webdav: an #EWebDAVSession
1507  * @uri: (nullable): URI to issue the request for, or %NULL to read from #ESource
1508  * @xml: an #EXmlDocument with request changes, its root element should be DAV:propertyupdate
1509  * @cancellable: optional #GCancellable object, or %NULL
1510  * @error: return location for a #GError, or %NULL
1511  *
1512  * Issues PROPPATCH request on the provided @uri, or, in case it's %NULL, on the URI
1513  * defined in associated #ESource, with the @changes. The order of requested changes
1514  * inside @xml is significant, unlike on other places.
1515  *
1516  * Returns: Whether succeeded.
1517  *
1518  * Since: 3.26
1519  **/
1520 gboolean
e_webdav_session_proppatch_sync(EWebDAVSession * webdav,const gchar * uri,const EXmlDocument * xml,GCancellable * cancellable,GError ** error)1521 e_webdav_session_proppatch_sync (EWebDAVSession *webdav,
1522 				 const gchar *uri,
1523 				 const EXmlDocument *xml,
1524 				 GCancellable *cancellable,
1525 				 GError **error)
1526 {
1527 	SoupRequestHTTP *request;
1528 	SoupMessage *message;
1529 	GByteArray *bytes;
1530 	gchar *content;
1531 	gsize content_length;
1532 	gboolean success;
1533 
1534 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
1535 	g_return_val_if_fail (E_IS_XML_DOCUMENT (xml), FALSE);
1536 
1537 	g_clear_pointer (&webdav->priv->last_dav_error_code, g_free);
1538 
1539 	request = e_webdav_session_new_request (webdav, SOUP_METHOD_PROPPATCH, uri, error);
1540 	if (!request)
1541 		return FALSE;
1542 
1543 	message = soup_request_http_get_message (request);
1544 	if (!message) {
1545 		g_warn_if_fail (message != NULL);
1546 		g_object_unref (request);
1547 
1548 		return FALSE;
1549 	}
1550 
1551 	content = e_xml_document_get_content (xml, &content_length);
1552 	if (!content) {
1553 		g_object_unref (message);
1554 		g_object_unref (request);
1555 
1556 		g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Failed to get input XML content"));
1557 
1558 		return FALSE;
1559 	}
1560 
1561 	soup_message_set_request (message, E_WEBDAV_CONTENT_TYPE_XML,
1562 		SOUP_MEMORY_TAKE, content, content_length);
1563 
1564 	bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, error);
1565 
1566 	success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, FALSE, _("Failed to update properties"), error, TRUE, FALSE) &&
1567 		bytes != NULL;
1568 
1569 	if (bytes)
1570 		g_byte_array_free (bytes, TRUE);
1571 	g_object_unref (message);
1572 	g_object_unref (request);
1573 
1574 	return success;
1575 }
1576 
1577 /**
1578  * e_webdav_session_report_sync:
1579  * @webdav: an #EWebDAVSession
1580  * @uri: (nullable): URI to issue the request for, or %NULL to read from #ESource
1581  * @depth: (nullable): requested depth, can be %NULL, then no Depth header is sent
1582  * @xml: the request itself, as an #EXmlDocument
1583  * @func: (nullable) (scope call): an #EWebDAVPropstatTraverseFunc function to call for each DAV:propstat in the multistatus response, or %NULL
1584  * @func_user_data: (closure func): user data passed to @func
1585  * @out_content_type: (nullable) (transfer full): return location for response Content-Type, or %NULL
1586  * @out_content: (nullable) (transfer full): return location for response content, or %NULL
1587  * @cancellable: optional #GCancellable object, or %NULL
1588  * @error: return location for a #GError, or %NULL
1589  *
1590  * Issues REPORT request on the provided @uri, or, in case it's %NULL, on the URI
1591  * defined in associated #ESource. On success, calls @func for each returned
1592  * DAV:propstat.
1593  *
1594  * The report can result in a multistatus response, but also to raw data. In case
1595  * the @func is provided and the result is a multistatus response, then it is traversed
1596  * using this @func.
1597  *
1598  * The optional @out_content_type can be used to get content type of the response.
1599  * Free it with g_free(), when no longer needed.
1600  *
1601  * The optional @out_content can be used to get actual result content. Free it
1602  * with g_byte_array_free(), when no longer needed.
1603  *
1604  * Returns: Whether succeeded.
1605  *
1606  * Since: 3.26
1607  **/
1608 gboolean
e_webdav_session_report_sync(EWebDAVSession * webdav,const gchar * uri,const gchar * depth,const EXmlDocument * xml,EWebDAVPropstatTraverseFunc func,gpointer func_user_data,gchar ** out_content_type,GByteArray ** out_content,GCancellable * cancellable,GError ** error)1609 e_webdav_session_report_sync (EWebDAVSession *webdav,
1610 			      const gchar *uri,
1611 			      const gchar *depth,
1612 			      const EXmlDocument *xml,
1613 			      EWebDAVPropstatTraverseFunc func,
1614 			      gpointer func_user_data,
1615 			      gchar **out_content_type,
1616 			      GByteArray **out_content,
1617 			      GCancellable *cancellable,
1618 			      GError **error)
1619 {
1620 	SoupRequestHTTP *request;
1621 	SoupMessage *message;
1622 	GByteArray *bytes;
1623 	gchar *content;
1624 	gsize content_length;
1625 	gboolean success;
1626 
1627 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
1628 	g_return_val_if_fail (E_IS_XML_DOCUMENT (xml), FALSE);
1629 
1630 	if (out_content_type)
1631 		*out_content_type = NULL;
1632 
1633 	if (out_content)
1634 		*out_content = NULL;
1635 
1636 	g_clear_pointer (&webdav->priv->last_dav_error_code, g_free);
1637 
1638 	request = e_webdav_session_new_request (webdav, "REPORT", uri, error);
1639 	if (!request)
1640 		return FALSE;
1641 
1642 	message = soup_request_http_get_message (request);
1643 	if (!message) {
1644 		g_warn_if_fail (message != NULL);
1645 		g_object_unref (request);
1646 
1647 		return FALSE;
1648 	}
1649 
1650 	if (depth)
1651 		soup_message_headers_replace (message->request_headers, "Depth", depth);
1652 
1653 	content = e_xml_document_get_content (xml, &content_length);
1654 	if (!content) {
1655 		g_object_unref (message);
1656 		g_object_unref (request);
1657 
1658 		g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Failed to get input XML content"));
1659 
1660 		return FALSE;
1661 	}
1662 
1663 	soup_message_set_request (message, E_WEBDAV_CONTENT_TYPE_XML,
1664 		SOUP_MEMORY_TAKE, content, content_length);
1665 
1666 	bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, error);
1667 
1668 	success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, TRUE, _("Failed to issue REPORT"), error, TRUE, FALSE) &&
1669 		bytes != NULL;
1670 
1671 	if (success && func && message->status_code == SOUP_STATUS_MULTI_STATUS)
1672 		success = e_webdav_session_traverse_multistatus_response (webdav, message, bytes, func, func_user_data, error);
1673 
1674 	if (success) {
1675 		if (out_content_type) {
1676 			*out_content_type = g_strdup (soup_message_headers_get_content_type (message->response_headers, NULL));
1677 		}
1678 
1679 		if (out_content) {
1680 			*out_content = bytes;
1681 			bytes = NULL;
1682 		}
1683 	}
1684 
1685 	if (bytes)
1686 		g_byte_array_free (bytes, TRUE);
1687 	g_object_unref (message);
1688 	g_object_unref (request);
1689 
1690 	return success;
1691 }
1692 
1693 /**
1694  * e_webdav_session_mkcol_sync:
1695  * @webdav: an #EWebDAVSession
1696  * @uri: URI of the collection to create
1697  * @cancellable: optional #GCancellable object, or %NULL
1698  * @error: return location for a #GError, or %NULL
1699  *
1700  * Creates a new generic collection identified by @uri on the server.
1701  * To create specific collections use e_webdav_session_mkcalendar_sync()
1702  * or e_webdav_session_mkcol_addressbook_sync().
1703  *
1704  * Returns: Whether succeeded.
1705  *
1706  * Since: 3.26
1707  **/
1708 gboolean
e_webdav_session_mkcol_sync(EWebDAVSession * webdav,const gchar * uri,GCancellable * cancellable,GError ** error)1709 e_webdav_session_mkcol_sync (EWebDAVSession *webdav,
1710 			     const gchar *uri,
1711 			     GCancellable *cancellable,
1712 			     GError **error)
1713 {
1714 	SoupRequestHTTP *request;
1715 	GByteArray *bytes;
1716 	gboolean success;
1717 
1718 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
1719 	g_return_val_if_fail (uri != NULL, FALSE);
1720 
1721 	g_clear_pointer (&webdav->priv->last_dav_error_code, g_free);
1722 
1723 	request = e_webdav_session_new_request (webdav, SOUP_METHOD_MKCOL, uri, error);
1724 	if (!request)
1725 		return FALSE;
1726 
1727 	bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, error);
1728 
1729 	success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, FALSE, _("Failed to create collection"), error, TRUE, FALSE) &&
1730 		bytes != NULL;
1731 
1732 	if (bytes)
1733 		g_byte_array_free (bytes, TRUE);
1734 	g_object_unref (request);
1735 
1736 	return success;
1737 }
1738 
1739 /**
1740  * e_webdav_session_mkcol_addressbook_sync:
1741  * @webdav: an #EWebDAVSession
1742  * @uri: URI of the collection to create
1743  * @display_name: (nullable): a human-readable display name to set, or %NULL
1744  * @description: (nullable): a human-readable description of the address book, or %NULL
1745  * @cancellable: optional #GCancellable object, or %NULL
1746  * @error: return location for a #GError, or %NULL
1747  *
1748  * Creates a new address book collection identified by @uri on the server.
1749  *
1750  * Note that CardDAV RFC 6352 Section 5.2 forbids to create address book
1751  * resources under other address book resources (no nested address books
1752  * are allowed).
1753  *
1754  * Returns: Whether succeeded.
1755  *
1756  * Since: 3.26
1757  **/
1758 gboolean
e_webdav_session_mkcol_addressbook_sync(EWebDAVSession * webdav,const gchar * uri,const gchar * display_name,const gchar * description,GCancellable * cancellable,GError ** error)1759 e_webdav_session_mkcol_addressbook_sync (EWebDAVSession *webdav,
1760 					 const gchar *uri,
1761 					 const gchar *display_name,
1762 					 const gchar *description,
1763 					 GCancellable *cancellable,
1764 					 GError **error)
1765 {
1766 	SoupRequestHTTP *request;
1767 	SoupMessage *message;
1768 	EXmlDocument *xml;
1769 	gchar *content;
1770 	gsize content_length;
1771 	GByteArray *bytes;
1772 	gboolean success;
1773 
1774 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
1775 	g_return_val_if_fail (uri != NULL, FALSE);
1776 
1777 	g_clear_pointer (&webdav->priv->last_dav_error_code, g_free);
1778 
1779 	request = e_webdav_session_new_request (webdav, SOUP_METHOD_MKCOL, uri, error);
1780 	if (!request)
1781 		return FALSE;
1782 
1783 	message = soup_request_http_get_message (request);
1784 	if (!message) {
1785 		g_warn_if_fail (message != NULL);
1786 		g_object_unref (request);
1787 
1788 		return FALSE;
1789 	}
1790 
1791 	xml = e_xml_document_new (E_WEBDAV_NS_DAV, "mkcol");
1792 	e_xml_document_add_namespaces (xml, "A", E_WEBDAV_NS_CARDDAV, NULL);
1793 
1794 	e_xml_document_start_element (xml, E_WEBDAV_NS_DAV, "set");
1795 	e_xml_document_start_element (xml, E_WEBDAV_NS_DAV, "prop");
1796 	e_xml_document_start_element (xml, E_WEBDAV_NS_DAV, "resourcetype");
1797 	e_xml_document_add_empty_element (xml, E_WEBDAV_NS_DAV, "collection");
1798 	e_xml_document_add_empty_element (xml, E_WEBDAV_NS_CARDDAV, "addressbook");
1799 	e_xml_document_end_element (xml); /* resourcetype */
1800 
1801 	if (display_name && *display_name) {
1802 		e_xml_document_start_text_element (xml, E_WEBDAV_NS_DAV, "displayname");
1803 		e_xml_document_write_string (xml, display_name);
1804 		e_xml_document_end_element (xml);
1805 	}
1806 
1807 	if (description && *description) {
1808 		e_xml_document_start_text_element (xml, E_WEBDAV_NS_CARDDAV, "addressbook-description");
1809 		e_xml_document_write_string (xml, description);
1810 		e_xml_document_end_element (xml);
1811 	}
1812 
1813 	e_xml_document_end_element (xml); /* prop */
1814 	e_xml_document_end_element (xml); /* set */
1815 
1816 	content = e_xml_document_get_content (xml, &content_length);
1817 	if (!content) {
1818 		g_object_unref (message);
1819 		g_object_unref (request);
1820 		g_object_unref (xml);
1821 
1822 		g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Failed to get XML request content"));
1823 
1824 		return FALSE;
1825 	}
1826 
1827 	soup_message_set_request (message, E_WEBDAV_CONTENT_TYPE_XML,
1828 		SOUP_MEMORY_TAKE, content, content_length);
1829 
1830 	g_object_unref (xml);
1831 
1832 	bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, error);
1833 
1834 	success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, FALSE, _("Failed to create address book"), error, TRUE, FALSE) &&
1835 		bytes != NULL;
1836 
1837 	if (bytes)
1838 		g_byte_array_free (bytes, TRUE);
1839 	g_object_unref (message);
1840 	g_object_unref (request);
1841 
1842 	return success;
1843 }
1844 
1845 /**
1846  * e_webdav_session_mkcalendar_sync:
1847  * @webdav: an #EWebDAVSession
1848  * @uri: URI of the collection to create
1849  * @display_name: (nullable): a human-readable display name to set, or %NULL
1850  * @description: (nullable): a human-readable description of the calendar, or %NULL
1851  * @color: (nullable): a color to set, in format "&num;RRGGBB", or %NULL
1852  * @supports: a bit-or of EWebDAVResourceSupports values
1853  * @cancellable: optional #GCancellable object, or %NULL
1854  * @error: return location for a #GError, or %NULL
1855  *
1856  * Creates a new calendar collection identified by @uri on the server.
1857  * The @supports defines what component types can be stored into
1858  * the created calendar collection. Only %E_WEBDAV_RESOURCE_SUPPORTS_NONE
1859  * and values related to iCalendar content can be used here.
1860  * Using %E_WEBDAV_RESOURCE_SUPPORTS_NONE means that everything is supported.
1861  *
1862  * Note that CalDAV RFC 4791 Section 4.2 forbids to create calendar
1863  * resources under other calendar resources (no nested calendars
1864  * are allowed).
1865  *
1866  * Returns: Whether succeeded.
1867  *
1868  * Since: 3.26
1869  **/
1870 gboolean
e_webdav_session_mkcalendar_sync(EWebDAVSession * webdav,const gchar * uri,const gchar * display_name,const gchar * description,const gchar * color,guint32 supports,GCancellable * cancellable,GError ** error)1871 e_webdav_session_mkcalendar_sync (EWebDAVSession *webdav,
1872 				  const gchar *uri,
1873 				  const gchar *display_name,
1874 				  const gchar *description,
1875 				  const gchar *color,
1876 				  guint32 supports,
1877 				  GCancellable *cancellable,
1878 				  GError **error)
1879 {
1880 	SoupRequestHTTP *request;
1881 	SoupMessage *message;
1882 	GByteArray *bytes;
1883 	gboolean success;
1884 
1885 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
1886 	g_return_val_if_fail (uri != NULL, FALSE);
1887 
1888 	g_clear_pointer (&webdav->priv->last_dav_error_code, g_free);
1889 
1890 	request = e_webdav_session_new_request (webdav, "MKCALENDAR", uri, error);
1891 	if (!request)
1892 		return FALSE;
1893 
1894 	message = soup_request_http_get_message (request);
1895 	if (!message) {
1896 		g_warn_if_fail (message != NULL);
1897 		g_object_unref (request);
1898 
1899 		return FALSE;
1900 	}
1901 
1902 	supports = supports & (
1903 		E_WEBDAV_RESOURCE_SUPPORTS_EVENTS |
1904 		E_WEBDAV_RESOURCE_SUPPORTS_MEMOS |
1905 		E_WEBDAV_RESOURCE_SUPPORTS_TASKS |
1906 		E_WEBDAV_RESOURCE_SUPPORTS_FREEBUSY |
1907 		E_WEBDAV_RESOURCE_SUPPORTS_TIMEZONE);
1908 
1909 	if ((display_name && *display_name) ||
1910 	    (description && *description) ||
1911 	    (color && *color) ||
1912 	    (supports != 0)) {
1913 		EXmlDocument *xml;
1914 		gchar *content;
1915 		gsize content_length;
1916 
1917 		xml = e_xml_document_new (E_WEBDAV_NS_CALDAV, "mkcalendar");
1918 		e_xml_document_add_namespaces (xml, "D", E_WEBDAV_NS_DAV, NULL);
1919 
1920 		e_xml_document_start_element (xml, E_WEBDAV_NS_DAV, "set");
1921 		e_xml_document_start_element (xml, E_WEBDAV_NS_DAV, "prop");
1922 
1923 		if (display_name && *display_name) {
1924 			e_xml_document_start_text_element (xml, E_WEBDAV_NS_DAV, "displayname");
1925 			e_xml_document_write_string (xml, display_name);
1926 			e_xml_document_end_element (xml);
1927 		}
1928 
1929 		if (description && *description) {
1930 			e_xml_document_start_text_element (xml, E_WEBDAV_NS_CALDAV, "calendar-description");
1931 			e_xml_document_write_string (xml, description);
1932 			e_xml_document_end_element (xml);
1933 		}
1934 
1935 		if (color && *color) {
1936 			e_xml_document_add_namespaces (xml, "IC", E_WEBDAV_NS_ICAL, NULL);
1937 
1938 			e_xml_document_start_text_element (xml, E_WEBDAV_NS_ICAL, "calendar-color");
1939 			e_xml_document_write_string (xml, color);
1940 			e_xml_document_end_element (xml);
1941 		}
1942 
1943 		if (supports != 0 && supports != (E_WEBDAV_RESOURCE_SUPPORTS_EVENTS |
1944 		    E_WEBDAV_RESOURCE_SUPPORTS_TASKS | E_WEBDAV_RESOURCE_SUPPORTS_MEMOS)) {
1945 			/* If the user has selected Events, Tasks and Memos, all of them offered by
1946 			 * Evolution, then user actually wants the new collection to contain whatever
1947 			 * components the server supports, including VAVAILABILITY, or anything invented
1948 			 * in the future, for which EDS and Evolution have no knowledge. */
1949 			struct SupportValues {
1950 				guint32 mask;
1951 				const gchar *value;
1952 			} values[] = {
1953 				{ E_WEBDAV_RESOURCE_SUPPORTS_EVENTS, "VEVENT" },
1954 				{ E_WEBDAV_RESOURCE_SUPPORTS_MEMOS, "VJOURNAL" },
1955 				{ E_WEBDAV_RESOURCE_SUPPORTS_TASKS, "VTODO" },
1956 				{ E_WEBDAV_RESOURCE_SUPPORTS_FREEBUSY, "VFREEBUSY" },
1957 				{ E_WEBDAV_RESOURCE_SUPPORTS_TIMEZONE, "TIMEZONE" }
1958 			};
1959 			gint ii;
1960 
1961 			e_xml_document_start_text_element (xml, E_WEBDAV_NS_CALDAV, "supported-calendar-component-set");
1962 
1963 			for (ii = 0; ii < G_N_ELEMENTS (values); ii++) {
1964 				if ((supports & values[ii].mask) != 0) {
1965 					e_xml_document_start_text_element (xml, E_WEBDAV_NS_CALDAV, "comp");
1966 					e_xml_document_add_attribute (xml, NULL, "name", values[ii].value);
1967 					e_xml_document_end_element (xml); /* comp */
1968 				}
1969 			}
1970 
1971 			e_xml_document_end_element (xml); /* supported-calendar-component-set */
1972 		}
1973 
1974 		e_xml_document_end_element (xml); /* prop */
1975 		e_xml_document_end_element (xml); /* set */
1976 
1977 		content = e_xml_document_get_content (xml, &content_length);
1978 		if (!content) {
1979 			g_object_unref (message);
1980 			g_object_unref (request);
1981 			g_object_unref (xml);
1982 
1983 			g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Failed to get XML request content"));
1984 
1985 			return FALSE;
1986 		}
1987 
1988 		soup_message_set_request (message, E_WEBDAV_CONTENT_TYPE_XML,
1989 			SOUP_MEMORY_TAKE, content, content_length);
1990 
1991 		g_object_unref (xml);
1992 	}
1993 
1994 	bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, error);
1995 
1996 	success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, FALSE, _("Failed to create calendar"), error, TRUE, FALSE) &&
1997 		bytes != NULL;
1998 
1999 	if (bytes)
2000 		g_byte_array_free (bytes, TRUE);
2001 	g_object_unref (message);
2002 	g_object_unref (request);
2003 
2004 	return success;
2005 }
2006 
2007 static void
e_webdav_session_extract_href_and_etag(SoupMessage * message,gchar ** out_href,gchar ** out_etag)2008 e_webdav_session_extract_href_and_etag (SoupMessage *message,
2009 					gchar **out_href,
2010 					gchar **out_etag)
2011 {
2012 	g_return_if_fail (SOUP_IS_MESSAGE (message));
2013 
2014 	if (out_href) {
2015 		const gchar *header;
2016 
2017 		*out_href = NULL;
2018 
2019 		header = soup_message_headers_get_list (message->response_headers, "Location");
2020 		if (header) {
2021 			gchar *file = strrchr (header, '/');
2022 
2023 			if (file) {
2024 				gchar *decoded;
2025 
2026 				decoded = soup_uri_decode (file + 1);
2027 				*out_href = soup_uri_encode (decoded ? decoded : (file + 1), NULL);
2028 
2029 				g_free (decoded);
2030 			}
2031 		}
2032 
2033 		if (!*out_href)
2034 			*out_href = soup_uri_to_string (soup_message_get_uri (message), FALSE);
2035 	}
2036 
2037 	if (out_etag) {
2038 		const gchar *header;
2039 
2040 		*out_etag = NULL;
2041 
2042 		header = soup_message_headers_get_list (message->response_headers, "ETag");
2043 		if (header)
2044 			*out_etag = e_webdav_session_util_maybe_dequote (g_strdup (header));
2045 	}
2046 }
2047 
2048 /**
2049  * e_webdav_session_get_sync:
2050  * @webdav: an #EWebDAVSession
2051  * @uri: URI of the resource to read
2052  * @out_href: (out) (nullable) (transfer full): optional return location for href of the resource, or %NULL
2053  * @out_etag: (out) (nullable) (transfer full): optional return location for etag of the resource, or %NULL
2054  * @out_stream: (out caller-allocates): a #GOutputStream to write data to
2055  * @cancellable: optional #GCancellable object, or %NULL
2056  * @error: return location for a #GError, or %NULL
2057  *
2058  * Reads a resource identified by @uri from the server and writes it
2059  * to the @stream. The URI cannot reference a collection.
2060  *
2061  * Free returned pointer of @out_href and @out_etag, if not %NULL, with g_free(),
2062  * when no longer needed.
2063  *
2064  * The e_webdav_session_get_data_sync() can be used to read the resource data
2065  * directly to memory.
2066  *
2067  * Returns: Whether succeeded.
2068  *
2069  * Since: 3.26
2070  **/
2071 gboolean
e_webdav_session_get_sync(EWebDAVSession * webdav,const gchar * uri,gchar ** out_href,gchar ** out_etag,GOutputStream * out_stream,GCancellable * cancellable,GError ** error)2072 e_webdav_session_get_sync (EWebDAVSession *webdav,
2073 			   const gchar *uri,
2074 			   gchar **out_href,
2075 			   gchar **out_etag,
2076 			   GOutputStream *out_stream,
2077 			   GCancellable *cancellable,
2078 			   GError **error)
2079 {
2080 	SoupRequestHTTP *request;
2081 	SoupMessage *message;
2082 	GInputStream *input_stream;
2083 	gboolean success;
2084 
2085 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
2086 	g_return_val_if_fail (uri != NULL, FALSE);
2087 	g_return_val_if_fail (G_IS_OUTPUT_STREAM (out_stream), FALSE);
2088 
2089 	g_clear_pointer (&webdav->priv->last_dav_error_code, g_free);
2090 
2091 	request = e_webdav_session_new_request (webdav, SOUP_METHOD_GET, uri, error);
2092 	if (!request)
2093 		return FALSE;
2094 
2095 	message = soup_request_http_get_message (request);
2096 	if (!message) {
2097 		g_warn_if_fail (message != NULL);
2098 		g_object_unref (request);
2099 
2100 		return FALSE;
2101 	}
2102 
2103 	input_stream = e_soup_session_send_request_sync (E_SOUP_SESSION (webdav), request, cancellable, error);
2104 
2105 	success = input_stream != NULL;
2106 
2107 	if (success) {
2108 		gpointer buffer;
2109 		gsize nread = 0, nwritten;
2110 		gboolean first_chunk = TRUE;
2111 
2112 		buffer = g_malloc (BUFFER_SIZE);
2113 
2114 		while (success = g_input_stream_read_all (input_stream, buffer, BUFFER_SIZE, &nread, cancellable, error),
2115 		       success && nread > 0) {
2116 			if (first_chunk) {
2117 				GByteArray tmp_bytes;
2118 
2119 				first_chunk = FALSE;
2120 
2121 				tmp_bytes.data = buffer;
2122 				tmp_bytes.len = nread;
2123 
2124 				success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, &tmp_bytes, FALSE, _("Failed to read resource"), error, TRUE, TRUE);
2125 				if (!success)
2126 					break;
2127 			}
2128 
2129 			success = g_output_stream_write_all (out_stream, buffer, nread, &nwritten, cancellable, error);
2130 			if (!success)
2131 				break;
2132 		}
2133 
2134 		if (success && first_chunk) {
2135 			success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, NULL, FALSE, _("Failed to read resource"), error, TRUE, TRUE);
2136 		}
2137 
2138 		g_free (buffer);
2139 	}
2140 
2141 	if (success)
2142 		e_webdav_session_extract_href_and_etag (message, out_href, out_etag);
2143 
2144 	g_clear_object (&input_stream);
2145 	g_object_unref (message);
2146 	g_object_unref (request);
2147 
2148 	return success;
2149 }
2150 
2151 /**
2152  * e_webdav_session_get_data_sync:
2153  * @webdav: an #EWebDAVSession
2154  * @uri: URI of the resource to read
2155  * @out_href: (out) (nullable) (transfer full): optional return location for href of the resource, or %NULL
2156  * @out_etag: (out) (nullable) (transfer full): optional return location for etag of the resource, or %NULL
2157  * @out_bytes: (out) (transfer full): return location for bytes being read
2158  * @out_length: (out) (nullable): option return location for length of bytes being read, or %NULL
2159  * @cancellable: optional #GCancellable object, or %NULL
2160  * @error: return location for a #GError, or %NULL
2161  *
2162  * Reads a resource identified by @uri from the server. The URI cannot
2163  * reference a collection.
2164  *
2165  * The @out_bytes is filled by actual data being read. If not %NULL, @out_length
2166  * is populated with how many bytes had been read. The @out_bytes is always
2167  * NUL-terminated, while this termination byte is not part of @out_length.
2168  * Free the @out_bytes with g_free(), when no longer needed.
2169  *
2170  * Free returned pointer of @out_href and @out_etag, if not %NULL, with g_free(),
2171  * when no longer needed.
2172  *
2173  * To read large data use e_webdav_session_get_sync() instead.
2174  *
2175  * Returns: Whether succeeded.
2176  *
2177  * Since: 3.26
2178  **/
2179 gboolean
e_webdav_session_get_data_sync(EWebDAVSession * webdav,const gchar * uri,gchar ** out_href,gchar ** out_etag,gchar ** out_bytes,gsize * out_length,GCancellable * cancellable,GError ** error)2180 e_webdav_session_get_data_sync (EWebDAVSession *webdav,
2181 				const gchar *uri,
2182 				gchar **out_href,
2183 				gchar **out_etag,
2184 				gchar **out_bytes,
2185 				gsize *out_length,
2186 				GCancellable *cancellable,
2187 				GError **error)
2188 {
2189 	GOutputStream *output_stream;
2190 	gsize bytes_written = 0;
2191 	gboolean success;
2192 
2193 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
2194 	g_return_val_if_fail (uri != NULL, FALSE);
2195 	g_return_val_if_fail (out_bytes != NULL, FALSE);
2196 
2197 	*out_bytes = NULL;
2198 	if (out_length)
2199 		*out_length = 0;
2200 
2201 	output_stream = g_memory_output_stream_new_resizable ();
2202 
2203 	success = e_webdav_session_get_sync (webdav, uri, out_href, out_etag, output_stream, cancellable, error) &&
2204 		g_output_stream_write_all (output_stream, "", 1, &bytes_written, cancellable, error) &&
2205 		g_output_stream_close (output_stream, cancellable, error);
2206 
2207 	if (success) {
2208 		if (out_length)
2209 			*out_length = g_memory_output_stream_get_data_size (G_MEMORY_OUTPUT_STREAM (output_stream)) - 1;
2210 
2211 		*out_bytes = g_memory_output_stream_steal_data (G_MEMORY_OUTPUT_STREAM (output_stream));
2212 	}
2213 
2214 	g_object_unref (output_stream);
2215 
2216 	return success;
2217 }
2218 
2219 typedef struct _ChunkWriteData {
2220 	SoupSession *session;
2221 	GInputStream *stream;
2222 	goffset read_from;
2223 	gboolean wrote_any;
2224 	gsize buffer_size;
2225 	gpointer buffer;
2226 	GCancellable *cancellable;
2227 	GError *error;
2228 } ChunkWriteData;
2229 
2230 static void
e_webdav_session_write_next_chunk(SoupMessage * message,gpointer user_data)2231 e_webdav_session_write_next_chunk (SoupMessage *message,
2232 				   gpointer user_data)
2233 {
2234 	ChunkWriteData *cwd = user_data;
2235 	gsize nread;
2236 
2237 	g_return_if_fail (SOUP_IS_MESSAGE (message));
2238 	g_return_if_fail (cwd != NULL);
2239 
2240 	if (!g_input_stream_read_all (cwd->stream, cwd->buffer, cwd->buffer_size, &nread, cwd->cancellable, &cwd->error)) {
2241 		soup_session_cancel_message (cwd->session, message, SOUP_STATUS_CANCELLED);
2242 		return;
2243 	}
2244 
2245 	if (nread == 0) {
2246 		soup_message_body_complete (message->request_body);
2247 	} else {
2248 		cwd->wrote_any = TRUE;
2249 		soup_message_body_append (message->request_body, SOUP_MEMORY_TEMPORARY, cwd->buffer, nread);
2250 	}
2251 }
2252 
2253 static void
e_webdav_session_write_restarted(SoupMessage * message,gpointer user_data)2254 e_webdav_session_write_restarted (SoupMessage *message,
2255 				  gpointer user_data)
2256 {
2257 	ChunkWriteData *cwd = user_data;
2258 
2259 	g_return_if_fail (SOUP_IS_MESSAGE (message));
2260 	g_return_if_fail (cwd != NULL);
2261 
2262 	/* The 302 redirect will turn it into a GET request and
2263 	 * reset the body encoding back to "NONE". Fix that.
2264 	 */
2265 	soup_message_headers_set_encoding (message->request_headers, SOUP_ENCODING_CHUNKED);
2266 	message->method = SOUP_METHOD_PUT;
2267 
2268 	if (cwd->wrote_any) {
2269 		cwd->wrote_any = FALSE;
2270 
2271 		if (!G_IS_SEEKABLE (cwd->stream) || !g_seekable_can_seek (G_SEEKABLE (cwd->stream)) ||
2272 		    !g_seekable_seek (G_SEEKABLE (cwd->stream), cwd->read_from, G_SEEK_SET, cwd->cancellable, &cwd->error)) {
2273 			if (!cwd->error)
2274 				g_set_error_literal (&cwd->error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT,
2275 					_("Cannot rewind input stream: Not supported"));
2276 
2277 			soup_session_cancel_message (cwd->session, message, SOUP_STATUS_CANCELLED);
2278 		} else {
2279 			soup_message_body_truncate (message->request_body);
2280 		}
2281 	}
2282 }
2283 
2284 static void
e_webdav_session_set_if_match_header(SoupMessage * message,const gchar * etag)2285 e_webdav_session_set_if_match_header (SoupMessage *message,
2286 				      const gchar *etag)
2287 {
2288 	gint len;
2289 
2290 	g_return_if_fail (SOUP_IS_MESSAGE (message));
2291 	g_return_if_fail (etag != NULL);
2292 
2293 	len = strlen (etag);
2294 
2295 	if ((*etag == '\"' && len > 2 && etag[len - 1] == '\"') || strchr (etag, '\"')) {
2296 		soup_message_headers_replace (message->request_headers, "If-Match", etag);
2297 	} else {
2298 		gchar *quoted;
2299 
2300 		quoted = g_strconcat ("\"", etag, "\"", NULL);
2301 		soup_message_headers_replace (message->request_headers, "If-Match", quoted);
2302 		g_free (quoted);
2303 	}
2304 }
2305 
2306 /**
2307  * e_webdav_session_put_sync:
2308  * @webdav: an #EWebDAVSession
2309  * @uri: URI of the resource to write
2310  * @etag: (nullable): an ETag of the resource, if it's an existing resource, or %NULL
2311  * @content_type: Content-Type of the @bytes to be written
2312  * @stream: a #GInputStream with data to be written
2313  * @out_href: (out) (nullable) (transfer full): optional return location for href of the resource, or %NULL
2314  * @out_etag: (out) (nullable) (transfer full): optional return location for etag of the resource, or %NULL
2315  * @cancellable: optional #GCancellable object, or %NULL
2316  * @error: return location for a #GError, or %NULL
2317  *
2318  * Writes data from @stream to a resource identified by @uri to the server.
2319  * The URI cannot reference a collection.
2320  *
2321  * The @etag argument is used to avoid clashes when overwriting existing
2322  * resources. It can contain three values:
2323  *  - %NULL - to write completely new resource
2324  *  - empty string - write new resource or overwrite any existing, regardless changes on the server
2325  *  - valid ETag - overwrite existing resource only if it wasn't changed on the server.
2326  *
2327  * Note that the actual behaviour is also influenced by #ESourceWebdav:avoid-ifmatch
2328  * property of the associated #ESource.
2329  *
2330  * The @out_href, if provided, is filled with the resulting URI
2331  * of the written resource. It can be different from the @uri when the server
2332  * redirected to a different location.
2333  *
2334  * The @out_etag contains ETag of the resource after it had been saved.
2335  *
2336  * The @stream should support also #GSeekable interface, because the data
2337  * send can require restart of the send due to redirect or other reasons.
2338  *
2339  * This method uses Transfer-Encoding:chunked, in contrast to the
2340  * e_webdav_session_put_data_sync(), which writes data stored in memory
2341  * like any other request.
2342  *
2343  * Returns: Whether succeeded.
2344  *
2345  * Since: 3.26
2346  **/
2347 gboolean
e_webdav_session_put_sync(EWebDAVSession * webdav,const gchar * uri,const gchar * etag,const gchar * content_type,GInputStream * stream,gchar ** out_href,gchar ** out_etag,GCancellable * cancellable,GError ** error)2348 e_webdav_session_put_sync (EWebDAVSession *webdav,
2349 			   const gchar *uri,
2350 			   const gchar *etag,
2351 			   const gchar *content_type,
2352 			   GInputStream *stream,
2353 			   gchar **out_href,
2354 			   gchar **out_etag,
2355 			   GCancellable *cancellable,
2356 			   GError **error)
2357 {
2358 	ChunkWriteData cwd;
2359 	SoupRequestHTTP *request;
2360 	SoupMessage *message;
2361 	GByteArray *bytes;
2362 	gulong restarted_id, wrote_headers_id, wrote_chunk_id;
2363 	gboolean success;
2364 
2365 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
2366 	g_return_val_if_fail (uri != NULL, FALSE);
2367 	g_return_val_if_fail (content_type != NULL, FALSE);
2368 	g_return_val_if_fail (G_IS_INPUT_STREAM (stream), FALSE);
2369 
2370 	if (out_href)
2371 		*out_href = NULL;
2372 	if (out_etag)
2373 		*out_etag = NULL;
2374 
2375 	g_clear_pointer (&webdav->priv->last_dav_error_code, g_free);
2376 
2377 	request = e_webdav_session_new_request (webdav, SOUP_METHOD_PUT, uri, error);
2378 	if (!request)
2379 		return FALSE;
2380 
2381 	message = soup_request_http_get_message (request);
2382 	if (!message) {
2383 		g_warn_if_fail (message != NULL);
2384 		g_object_unref (request);
2385 
2386 		return FALSE;
2387 	}
2388 
2389 	if (!etag || *etag) {
2390 		ESource *source;
2391 		gboolean avoid_ifmatch = FALSE;
2392 
2393 		source = e_soup_session_get_source (E_SOUP_SESSION (webdav));
2394 		if (source && e_source_has_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND)) {
2395 			ESourceWebdav *webdav_extension;
2396 
2397 			webdav_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
2398 			avoid_ifmatch = e_source_webdav_get_avoid_ifmatch (webdav_extension);
2399 		}
2400 
2401 		if (!avoid_ifmatch) {
2402 			if (etag) {
2403 				e_webdav_session_set_if_match_header (message, etag);
2404 			} else {
2405 				soup_message_headers_replace (message->request_headers, "If-None-Match", "*");
2406 			}
2407 		}
2408 	}
2409 
2410 	cwd.session = SOUP_SESSION (webdav);
2411 	cwd.stream = stream;
2412 	cwd.read_from = 0;
2413 	cwd.wrote_any = FALSE;
2414 	cwd.buffer_size = BUFFER_SIZE;
2415 	cwd.buffer = g_malloc (cwd.buffer_size);
2416 	cwd.cancellable = cancellable;
2417 	cwd.error = NULL;
2418 
2419 	if (G_IS_SEEKABLE (stream) && g_seekable_can_seek (G_SEEKABLE (stream)))
2420 		cwd.read_from = g_seekable_tell (G_SEEKABLE (stream));
2421 
2422 	if (content_type && *content_type)
2423 		soup_message_headers_replace (message->request_headers, "Content-Type", content_type);
2424 
2425 	soup_message_headers_set_encoding (message->request_headers, SOUP_ENCODING_CHUNKED);
2426 	soup_message_body_set_accumulate (message->request_body, FALSE);
2427 	soup_message_set_flags (message, SOUP_MESSAGE_CAN_REBUILD);
2428 
2429 	restarted_id = g_signal_connect (message, "restarted", G_CALLBACK (e_webdav_session_write_restarted), &cwd);
2430 	wrote_headers_id = g_signal_connect (message, "wrote-headers", G_CALLBACK (e_webdav_session_write_next_chunk), &cwd);
2431 	wrote_chunk_id = g_signal_connect (message, "wrote-chunk", G_CALLBACK (e_webdav_session_write_next_chunk), &cwd);
2432 
2433 	bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, error);
2434 
2435 	g_signal_handler_disconnect (message, restarted_id);
2436 	g_signal_handler_disconnect (message, wrote_headers_id);
2437 	g_signal_handler_disconnect (message, wrote_chunk_id);
2438 
2439 	success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, FALSE, _("Failed to put data"), error, TRUE, TRUE) &&
2440 		bytes != NULL;
2441 
2442 	if (cwd.error) {
2443 		g_clear_error (error);
2444 		g_propagate_error (error, cwd.error);
2445 		success = FALSE;
2446 	}
2447 
2448 	if (success) {
2449 		if (success && !SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
2450 			success = FALSE;
2451 
2452 			g_set_error (error, SOUP_HTTP_ERROR, message->status_code,
2453 				_("Failed to put data to server, error code %d (%s)"), message->status_code,
2454 				e_soup_session_util_status_to_string (message->status_code, message->reason_phrase));
2455 		}
2456 	}
2457 
2458 	if (success)
2459 		e_webdav_session_extract_href_and_etag (message, out_href, out_etag);
2460 
2461 	if (bytes)
2462 		g_byte_array_free (bytes, TRUE);
2463 	g_object_unref (message);
2464 	g_object_unref (request);
2465 	g_free (cwd.buffer);
2466 
2467 	return success;
2468 }
2469 
2470 /**
2471  * e_webdav_session_put_data_sync:
2472  * @webdav: an #EWebDAVSession
2473  * @uri: URI of the resource to write
2474  * @etag: (nullable): an ETag of the resource, if it's an existing resource, or %NULL
2475  * @content_type: Content-Type of the @bytes to be written
2476  * @bytes: actual bytes to be written
2477  * @length: how many bytes to write, or -1, when the @bytes is NUL-terminated
2478  * @out_href: (out) (nullable) (transfer full): optional return location for href of the resource, or %NULL
2479  * @out_etag: (out) (nullable) (transfer full): optional return location for etag of the resource, or %NULL
2480  * @cancellable: optional #GCancellable object, or %NULL
2481  * @error: return location for a #GError, or %NULL
2482  *
2483  * Writes data to a resource identified by @uri to the server. The URI cannot
2484  * reference a collection.
2485  *
2486  * The @etag argument is used to avoid clashes when overwriting existing
2487  * resources. It can contain three values:
2488  *  - %NULL - to write completely new resource
2489  *  - empty string - write new resource or overwrite any existing, regardless changes on the server
2490  *  - valid ETag - overwrite existing resource only if it wasn't changed on the server.
2491  *
2492  * Note that the actual usage of @etag is also influenced by #ESourceWebdav:avoid-ifmatch
2493  * property of the associated #ESource.
2494  *
2495  * The @out_href, if provided, is filled with the resulting URI
2496  * of the written resource. It can be different from the @uri when the server
2497  * redirected to a different location.
2498  *
2499  * The @out_etag contains ETag of the resource after it had been saved.
2500  *
2501  * To write large data use e_webdav_session_put_sync() instead.
2502  *
2503  * Returns: Whether succeeded.
2504  *
2505  * Since: 3.26
2506  **/
2507 gboolean
e_webdav_session_put_data_sync(EWebDAVSession * webdav,const gchar * uri,const gchar * etag,const gchar * content_type,const gchar * bytes,gsize length,gchar ** out_href,gchar ** out_etag,GCancellable * cancellable,GError ** error)2508 e_webdav_session_put_data_sync (EWebDAVSession *webdav,
2509 				const gchar *uri,
2510 				const gchar *etag,
2511 				const gchar *content_type,
2512 				const gchar *bytes,
2513 				gsize length,
2514 				gchar **out_href,
2515 				gchar **out_etag,
2516 				GCancellable *cancellable,
2517 				GError **error)
2518 {
2519 	SoupRequestHTTP *request;
2520 	SoupMessage *message;
2521 	GByteArray *ret_bytes;
2522 	gboolean success;
2523 
2524 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
2525 	g_return_val_if_fail (uri != NULL, FALSE);
2526 	g_return_val_if_fail (content_type != NULL, FALSE);
2527 	g_return_val_if_fail (bytes != NULL, FALSE);
2528 
2529 	if (length == (gsize) -1)
2530 		length = strlen (bytes);
2531 	if (out_href)
2532 		*out_href = NULL;
2533 	if (out_etag)
2534 		*out_etag = NULL;
2535 
2536 	g_clear_pointer (&webdav->priv->last_dav_error_code, g_free);
2537 
2538 	request = e_webdav_session_new_request (webdav, SOUP_METHOD_PUT, uri, error);
2539 	if (!request)
2540 		return FALSE;
2541 
2542 	message = soup_request_http_get_message (request);
2543 	if (!message) {
2544 		g_warn_if_fail (message != NULL);
2545 		g_object_unref (request);
2546 
2547 		return FALSE;
2548 	}
2549 
2550 	if (!etag || *etag) {
2551 		ESource *source;
2552 		gboolean avoid_ifmatch = FALSE;
2553 
2554 		source = e_soup_session_get_source (E_SOUP_SESSION (webdav));
2555 		if (source && e_source_has_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND)) {
2556 			ESourceWebdav *webdav_extension;
2557 
2558 			webdav_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
2559 			avoid_ifmatch = e_source_webdav_get_avoid_ifmatch (webdav_extension);
2560 		}
2561 
2562 		if (!avoid_ifmatch) {
2563 			if (etag) {
2564 				e_webdav_session_set_if_match_header (message, etag);
2565 			} else {
2566 				soup_message_headers_replace (message->request_headers, "If-None-Match", "*");
2567 			}
2568 		}
2569 	}
2570 
2571 	if (content_type && *content_type)
2572 		soup_message_headers_replace (message->request_headers, "Content-Type", content_type);
2573 
2574 	soup_message_headers_replace (message->request_headers, "Prefer", "return=minimal");
2575 
2576 	soup_message_set_request (message, content_type, SOUP_MEMORY_TEMPORARY, bytes, length);
2577 
2578 	ret_bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, error);
2579 
2580 	success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, ret_bytes, FALSE, _("Failed to put data"), error, TRUE, TRUE) &&
2581 		ret_bytes != NULL;
2582 
2583 	if (success) {
2584 		if (success && !SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
2585 			success = FALSE;
2586 
2587 			g_set_error (error, SOUP_HTTP_ERROR, message->status_code,
2588 				_("Failed to put data to server, error code %d (%s)"), message->status_code,
2589 				e_soup_session_util_status_to_string (message->status_code, message->reason_phrase));
2590 		}
2591 	}
2592 
2593 	if (success)
2594 		e_webdav_session_extract_href_and_etag (message, out_href, out_etag);
2595 
2596 	if (ret_bytes)
2597 		g_byte_array_free (ret_bytes, TRUE);
2598 	g_object_unref (message);
2599 	g_object_unref (request);
2600 
2601 	return success;
2602 }
2603 
2604 /**
2605  * e_webdav_session_delete_sync:
2606  * @webdav: an #EWebDAVSession
2607  * @uri: URI of the resource to delete
2608  * @depth: (nullable): optional requested depth, can be one of %E_WEBDAV_DEPTH_THIS or %E_WEBDAV_DEPTH_INFINITY, or %NULL
2609  * @etag: (nullable): an optional ETag of the resource, or %NULL
2610  * @cancellable: optional #GCancellable object, or %NULL
2611  * @error: return location for a #GError, or %NULL
2612  *
2613  * Deletes a resource identified by @uri on the server. The URI can
2614  * reference a collection, in which case @depth should be %E_WEBDAV_DEPTH_INFINITY.
2615  * Use @depth %E_WEBDAV_DEPTH_THIS when deleting a regular resource, or %NULL,
2616  * to let the server use default Depth.
2617  *
2618  * The @etag argument is used to avoid clashes when overwriting existing resources.
2619  * Use %NULL @etag when deleting collection resources or to force the deletion,
2620  * otherwise provide a valid ETag of a non-collection resource to verify that
2621  * the version requested to delete is the same as on the server.
2622  *
2623  * Note that the actual usage of @etag is also influenced by #ESourceWebdav:avoid-ifmatch
2624  * property of the associated #ESource.
2625  *
2626  * Returns: Whether succeeded.
2627  *
2628  * Since: 3.26
2629  **/
2630 gboolean
e_webdav_session_delete_sync(EWebDAVSession * webdav,const gchar * uri,const gchar * depth,const gchar * etag,GCancellable * cancellable,GError ** error)2631 e_webdav_session_delete_sync (EWebDAVSession *webdav,
2632 			      const gchar *uri,
2633 			      const gchar *depth,
2634 			      const gchar *etag,
2635 			      GCancellable *cancellable,
2636 			      GError **error)
2637 {
2638 	SoupRequestHTTP *request;
2639 	SoupMessage *message;
2640 	GByteArray *bytes;
2641 	gboolean success;
2642 
2643 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
2644 	g_return_val_if_fail (uri != NULL, FALSE);
2645 
2646 	g_clear_pointer (&webdav->priv->last_dav_error_code, g_free);
2647 
2648 	request = e_webdav_session_new_request (webdav, SOUP_METHOD_DELETE, uri, error);
2649 	if (!request)
2650 		return FALSE;
2651 
2652 	message = soup_request_http_get_message (request);
2653 	if (!message) {
2654 		g_warn_if_fail (message != NULL);
2655 		g_object_unref (request);
2656 
2657 		return FALSE;
2658 	}
2659 
2660 	if (etag) {
2661 		ESource *source;
2662 		gboolean avoid_ifmatch = FALSE;
2663 
2664 		source = e_soup_session_get_source (E_SOUP_SESSION (webdav));
2665 		if (source && e_source_has_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND)) {
2666 			ESourceWebdav *webdav_extension;
2667 
2668 			webdav_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
2669 			avoid_ifmatch = e_source_webdav_get_avoid_ifmatch (webdav_extension);
2670 		}
2671 
2672 		if (!avoid_ifmatch) {
2673 			e_webdav_session_set_if_match_header (message, etag);
2674 		}
2675 	}
2676 
2677 	if (depth)
2678 		soup_message_headers_replace (message->request_headers, "Depth", depth);
2679 
2680 	bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, error);
2681 
2682 	success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, FALSE, _("Failed to delete resource"), error, TRUE, FALSE) &&
2683 		bytes != NULL;
2684 
2685 	if (bytes)
2686 		g_byte_array_free (bytes, TRUE);
2687 	g_object_unref (message);
2688 	g_object_unref (request);
2689 
2690 	return success;
2691 }
2692 
2693 /**
2694  * e_webdav_session_copy_sync:
2695  * @webdav: an #EWebDAVSession
2696  * @source_uri: URI of the resource or collection to copy
2697  * @destination_uri: URI of the destination
2698  * @depth: requested depth, can be one of %E_WEBDAV_DEPTH_THIS or %E_WEBDAV_DEPTH_INFINITY
2699  * @can_overwrite: whether can overwrite @destination_uri, when it exists
2700  * @cancellable: optional #GCancellable object, or %NULL
2701  * @error: return location for a #GError, or %NULL
2702  *
2703  * Copies a resource identified by @source_uri to @destination_uri on the server.
2704  * The @source_uri can reference also collections, in which case the @depth influences
2705  * whether only the collection itself is copied (%E_WEBDAV_DEPTH_THIS) or whether
2706  * the collection with all its children is copied (%E_WEBDAV_DEPTH_INFINITY).
2707  *
2708  * Returns: Whether succeeded.
2709  *
2710  * Since: 3.26
2711  **/
2712 gboolean
e_webdav_session_copy_sync(EWebDAVSession * webdav,const gchar * source_uri,const gchar * destination_uri,const gchar * depth,gboolean can_overwrite,GCancellable * cancellable,GError ** error)2713 e_webdav_session_copy_sync (EWebDAVSession *webdav,
2714 			    const gchar *source_uri,
2715 			    const gchar *destination_uri,
2716 			    const gchar *depth,
2717 			    gboolean can_overwrite,
2718 			    GCancellable *cancellable,
2719 			    GError **error)
2720 {
2721 	SoupRequestHTTP *request;
2722 	SoupMessage *message;
2723 	GByteArray *bytes;
2724 	gboolean success;
2725 
2726 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
2727 	g_return_val_if_fail (source_uri != NULL, FALSE);
2728 	g_return_val_if_fail (destination_uri != NULL, FALSE);
2729 	g_return_val_if_fail (depth != NULL, FALSE);
2730 
2731 	g_clear_pointer (&webdav->priv->last_dav_error_code, g_free);
2732 
2733 	request = e_webdav_session_new_request (webdav, SOUP_METHOD_COPY, source_uri, error);
2734 	if (!request)
2735 		return FALSE;
2736 
2737 	message = soup_request_http_get_message (request);
2738 	if (!message) {
2739 		g_warn_if_fail (message != NULL);
2740 		g_object_unref (request);
2741 
2742 		return FALSE;
2743 	}
2744 
2745 	soup_message_headers_replace (message->request_headers, "Depth", depth);
2746 	soup_message_headers_replace (message->request_headers, "Destination", destination_uri);
2747 	soup_message_headers_replace (message->request_headers, "Overwrite", can_overwrite ? "T" : "F");
2748 
2749 	bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, error);
2750 
2751 	success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, FALSE, _("Failed to copy resource"), error, TRUE, FALSE) &&
2752 		bytes != NULL;
2753 
2754 	if (bytes)
2755 		g_byte_array_free (bytes, TRUE);
2756 	g_object_unref (message);
2757 	g_object_unref (request);
2758 
2759 	return success;
2760 }
2761 
2762 /**
2763  * e_webdav_session_move_sync:
2764  * @webdav: an #EWebDAVSession
2765  * @source_uri: URI of the resource or collection to copy
2766  * @destination_uri: URI of the destination
2767  * @can_overwrite: whether can overwrite @destination_uri, when it exists
2768  * @cancellable: optional #GCancellable object, or %NULL
2769  * @error: return location for a #GError, or %NULL
2770  *
2771  * Moves a resource identified by @source_uri to @destination_uri on the server.
2772  * The @source_uri can reference also collections.
2773  *
2774  * Returns: Whether succeeded.
2775  *
2776  * Since: 3.26
2777  **/
2778 gboolean
e_webdav_session_move_sync(EWebDAVSession * webdav,const gchar * source_uri,const gchar * destination_uri,gboolean can_overwrite,GCancellable * cancellable,GError ** error)2779 e_webdav_session_move_sync (EWebDAVSession *webdav,
2780 			    const gchar *source_uri,
2781 			    const gchar *destination_uri,
2782 			    gboolean can_overwrite,
2783 			    GCancellable *cancellable,
2784 			    GError **error)
2785 {
2786 	SoupRequestHTTP *request;
2787 	SoupMessage *message;
2788 	GByteArray *bytes;
2789 	gboolean success;
2790 
2791 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
2792 	g_return_val_if_fail (source_uri != NULL, FALSE);
2793 	g_return_val_if_fail (destination_uri != NULL, FALSE);
2794 
2795 	g_clear_pointer (&webdav->priv->last_dav_error_code, g_free);
2796 
2797 	request = e_webdav_session_new_request (webdav, SOUP_METHOD_MOVE, source_uri, error);
2798 	if (!request)
2799 		return FALSE;
2800 
2801 	message = soup_request_http_get_message (request);
2802 	if (!message) {
2803 		g_warn_if_fail (message != NULL);
2804 		g_object_unref (request);
2805 
2806 		return FALSE;
2807 	}
2808 
2809 	soup_message_headers_replace (message->request_headers, "Depth", E_WEBDAV_DEPTH_INFINITY);
2810 	soup_message_headers_replace (message->request_headers, "Destination", destination_uri);
2811 	soup_message_headers_replace (message->request_headers, "Overwrite", can_overwrite ? "T" : "F");
2812 
2813 	bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, error);
2814 
2815 	success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, FALSE, _("Failed to move resource"), error, TRUE, FALSE) &&
2816 		bytes != NULL;
2817 
2818 	if (bytes)
2819 		g_byte_array_free (bytes, TRUE);
2820 	g_object_unref (message);
2821 	g_object_unref (request);
2822 
2823 	return success;
2824 }
2825 
2826 /**
2827  * e_webdav_session_lock_sync:
2828  * @webdav: an #EWebDAVSession
2829  * @uri: (nullable): URI to lock, or %NULL to read from #ESource
2830  * @depth: requested depth, can be one of %E_WEBDAV_DEPTH_THIS or %E_WEBDAV_DEPTH_INFINITY
2831  * @lock_timeout: timeout for the lock, in seconds, on 0 to infinity
2832  * @xml: an XML describing the lock request, with DAV:lockinfo root element
2833  * @out_lock_token: (out) (transfer full): return location of the obtained or refreshed lock token
2834  * @out_xml_response: (out) (nullable) (transfer full): optional return location for the server response as #xmlDocPtr
2835  * @cancellable: optional #GCancellable object, or %NULL
2836  * @error: return location for a #GError, or %NULL
2837  *
2838  * Locks a resource identified by @uri, or, in case it's %NULL, on the URI
2839  * defined in associated #ESource.
2840  *
2841  * The @out_lock_token can be refreshed with e_webdav_session_refresh_lock_sync().
2842  * Release the lock with e_webdav_session_unlock_sync().
2843  * Free the returned @out_lock_token with g_free(), when no longer needed.
2844  *
2845  * If provided, free the returned @out_xml_response with xmlFreeDoc(),
2846  * when no longer needed.
2847  *
2848  * Returns: Whether succeeded.
2849  *
2850  * Since: 3.26
2851  **/
2852 gboolean
e_webdav_session_lock_sync(EWebDAVSession * webdav,const gchar * uri,const gchar * depth,gint32 lock_timeout,const EXmlDocument * xml,gchar ** out_lock_token,xmlDoc ** out_xml_response,GCancellable * cancellable,GError ** error)2853 e_webdav_session_lock_sync (EWebDAVSession *webdav,
2854 			    const gchar *uri,
2855 			    const gchar *depth,
2856 			    gint32 lock_timeout,
2857 			    const EXmlDocument *xml,
2858 			    gchar **out_lock_token,
2859 			    xmlDoc **out_xml_response,
2860 			    GCancellable *cancellable,
2861 			    GError **error)
2862 {
2863 	SoupRequestHTTP *request;
2864 	SoupMessage *message;
2865 	GByteArray *bytes;
2866 	gboolean success;
2867 
2868 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
2869 	g_return_val_if_fail (depth != NULL, FALSE);
2870 	g_return_val_if_fail (E_IS_XML_DOCUMENT (xml), FALSE);
2871 	g_return_val_if_fail (out_lock_token != NULL, FALSE);
2872 
2873 	*out_lock_token = NULL;
2874 
2875 	g_clear_pointer (&webdav->priv->last_dav_error_code, g_free);
2876 
2877 	request = e_webdav_session_new_request (webdav, SOUP_METHOD_LOCK, uri, error);
2878 	if (!request)
2879 		return FALSE;
2880 
2881 	message = soup_request_http_get_message (request);
2882 	if (!message) {
2883 		g_warn_if_fail (message != NULL);
2884 		g_object_unref (request);
2885 
2886 		return FALSE;
2887 	}
2888 
2889 	if (depth)
2890 		soup_message_headers_replace (message->request_headers, "Depth", depth);
2891 
2892 	if (lock_timeout) {
2893 		gchar *value;
2894 
2895 		value = g_strdup_printf ("Second-%d", lock_timeout);
2896 		soup_message_headers_replace (message->request_headers, "Timeout", value);
2897 		g_free (value);
2898 	} else {
2899 		soup_message_headers_replace (message->request_headers, "Timeout", "Infinite");
2900 	}
2901 
2902 	if (xml) {
2903 		gchar *content;
2904 		gsize content_length;
2905 
2906 		content = e_xml_document_get_content (xml, &content_length);
2907 		if (!content) {
2908 			g_object_unref (message);
2909 			g_object_unref (request);
2910 
2911 			g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Failed to get input XML content"));
2912 
2913 			return FALSE;
2914 		}
2915 
2916 		soup_message_set_request (message, E_WEBDAV_CONTENT_TYPE_XML,
2917 			SOUP_MEMORY_TAKE, content, content_length);
2918 	}
2919 
2920 	bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, error);
2921 
2922 	success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, FALSE, _("Failed to lock resource"), error, TRUE, FALSE) &&
2923 		bytes != NULL;
2924 
2925 	if (success && out_xml_response) {
2926 		const gchar *content_type;
2927 
2928 		*out_xml_response = NULL;
2929 
2930 		content_type = soup_message_headers_get_content_type (message->response_headers, NULL);
2931 		if (!content_type ||
2932 		    (g_ascii_strcasecmp (content_type, "application/xml") != 0 &&
2933 		     g_ascii_strcasecmp (content_type, "text/xml") != 0)) {
2934 			if (!content_type) {
2935 				g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
2936 					_("Expected application/xml response, but none returned"));
2937 			} else {
2938 				g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
2939 					_("Expected application/xml response, but %s returned"), content_type);
2940 			}
2941 
2942 			success = FALSE;
2943 		}
2944 
2945 		if (success) {
2946 			xmlDocPtr doc;
2947 
2948 			doc = e_xml_parse_data (bytes->data, bytes->len);
2949 			if (!doc) {
2950 				g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
2951 					_("Failed to parse XML data"));
2952 
2953 				success = FALSE;
2954 			} else {
2955 				*out_xml_response = doc;
2956 			}
2957 		}
2958 	}
2959 
2960 	if (success)
2961 		*out_lock_token = g_strdup (soup_message_headers_get_list (message->response_headers, "Lock-Token"));
2962 
2963 	if (bytes)
2964 		g_byte_array_free (bytes, TRUE);
2965 	g_object_unref (message);
2966 	g_object_unref (request);
2967 
2968 	return success;
2969 }
2970 
2971 /**
2972  * e_webdav_session_refresh_lock_sync:
2973  * @webdav: an #EWebDAVSession
2974  * @uri: (nullable): URI to lock, or %NULL to read from #ESource
2975  * @lock_token: token of an existing lock
2976  * @lock_timeout: timeout for the lock, in seconds, on 0 to infinity
2977  * @cancellable: optional #GCancellable object, or %NULL
2978  * @error: return location for a #GError, or %NULL
2979  *
2980  * Refreshes existing lock @lock_token for a resource identified by @uri,
2981  * or, in case it's %NULL, on the URI defined in associated #ESource.
2982  * The @lock_token is returned from e_webdav_session_lock_sync() and
2983  * the @uri should be the same as that used with e_webdav_session_lock_sync().
2984  *
2985  * Returns: Whether succeeded.
2986  *
2987  * Since: 3.26
2988  **/
2989 gboolean
e_webdav_session_refresh_lock_sync(EWebDAVSession * webdav,const gchar * uri,const gchar * lock_token,gint32 lock_timeout,GCancellable * cancellable,GError ** error)2990 e_webdav_session_refresh_lock_sync (EWebDAVSession *webdav,
2991 				    const gchar *uri,
2992 				    const gchar *lock_token,
2993 				    gint32 lock_timeout,
2994 				    GCancellable *cancellable,
2995 				    GError **error)
2996 {
2997 	SoupRequestHTTP *request;
2998 	SoupMessage *message;
2999 	GByteArray *bytes;
3000 	gchar *value;
3001 	gboolean success;
3002 
3003 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
3004 	g_return_val_if_fail (lock_token != NULL, FALSE);
3005 
3006 	g_clear_pointer (&webdav->priv->last_dav_error_code, g_free);
3007 
3008 	request = e_webdav_session_new_request (webdav, SOUP_METHOD_LOCK, uri, error);
3009 	if (!request)
3010 		return FALSE;
3011 
3012 	message = soup_request_http_get_message (request);
3013 	if (!message) {
3014 		g_warn_if_fail (message != NULL);
3015 		g_object_unref (request);
3016 
3017 		return FALSE;
3018 	}
3019 
3020 	if (lock_timeout) {
3021 		value = g_strdup_printf ("Second-%d", lock_timeout);
3022 		soup_message_headers_replace (message->request_headers, "Timeout", value);
3023 		g_free (value);
3024 	} else {
3025 		soup_message_headers_replace (message->request_headers, "Timeout", "Infinite");
3026 	}
3027 
3028 	soup_message_headers_replace (message->request_headers, "Lock-Token", lock_token);
3029 
3030 	bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, error);
3031 
3032 	success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, FALSE, _("Failed to refresh lock"), error, TRUE, FALSE) &&
3033 		bytes != NULL;
3034 
3035 	if (bytes)
3036 		g_byte_array_free (bytes, TRUE);
3037 	g_object_unref (message);
3038 	g_object_unref (request);
3039 
3040 	return success;
3041 }
3042 
3043 /**
3044  * e_webdav_session_unlock_sync:
3045  * @webdav: an #EWebDAVSession
3046  * @uri: (nullable): URI to lock, or %NULL to read from #ESource
3047  * @lock_token: token of an existing lock
3048  * @cancellable: optional #GCancellable object, or %NULL
3049  * @error: return location for a #GError, or %NULL
3050  *
3051  * Releases (unlocks) existing lock @lock_token for a resource identified by @uri,
3052  * or, in case it's %NULL, on the URI defined in associated #ESource.
3053  * The @lock_token is returned from e_webdav_session_lock_sync() and
3054  * the @uri should be the same as that used with e_webdav_session_lock_sync().
3055  *
3056  * Returns: Whether succeeded.
3057  *
3058  * Since: 3.26
3059  **/
3060 gboolean
e_webdav_session_unlock_sync(EWebDAVSession * webdav,const gchar * uri,const gchar * lock_token,GCancellable * cancellable,GError ** error)3061 e_webdav_session_unlock_sync (EWebDAVSession *webdav,
3062 			      const gchar *uri,
3063 			      const gchar *lock_token,
3064 			      GCancellable *cancellable,
3065 			      GError **error)
3066 {
3067 	SoupRequestHTTP *request;
3068 	SoupMessage *message;
3069 	GByteArray *bytes;
3070 	gboolean success;
3071 
3072 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
3073 	g_return_val_if_fail (lock_token != NULL, FALSE);
3074 
3075 	g_clear_pointer (&webdav->priv->last_dav_error_code, g_free);
3076 
3077 	request = e_webdav_session_new_request (webdav, SOUP_METHOD_UNLOCK, uri, error);
3078 	if (!request)
3079 		return FALSE;
3080 
3081 	message = soup_request_http_get_message (request);
3082 	if (!message) {
3083 		g_warn_if_fail (message != NULL);
3084 		g_object_unref (request);
3085 
3086 		return FALSE;
3087 	}
3088 
3089 	soup_message_headers_replace (message->request_headers, "Lock-Token", lock_token);
3090 
3091 	bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, error);
3092 
3093 	success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, FALSE, _("Failed to unlock"), error, TRUE, FALSE) &&
3094 		bytes != NULL;
3095 
3096 	if (bytes)
3097 		g_byte_array_free (bytes, TRUE);
3098 	g_object_unref (message);
3099 	g_object_unref (request);
3100 
3101 	return success;
3102 }
3103 
3104 static gboolean
e_webdav_session_traverse_propstat_response(EWebDAVSession * webdav,const SoupMessage * message,const GByteArray * xml_data,gboolean require_multistatus,const gchar * top_path_ns_href1,const gchar * top_path_name1,const gchar * top_path_ns_href2,const gchar * top_path_name2,EWebDAVPropstatTraverseFunc func,gpointer func_user_data,GError ** error)3105 e_webdav_session_traverse_propstat_response (EWebDAVSession *webdav,
3106 					     const SoupMessage *message,
3107 					     const GByteArray *xml_data,
3108 					     gboolean require_multistatus,
3109 					     const gchar *top_path_ns_href1,
3110 					     const gchar *top_path_name1,
3111 					     const gchar *top_path_ns_href2,
3112 					     const gchar *top_path_name2,
3113 					     EWebDAVPropstatTraverseFunc func,
3114 					     gpointer func_user_data,
3115 					     GError **error)
3116 {
3117 	SoupURI *request_uri = NULL;
3118 	xmlDocPtr doc;
3119 	xmlNodePtr top_node, node;
3120 	gboolean do_stop = FALSE;
3121 
3122 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
3123 	g_return_val_if_fail (xml_data != NULL, FALSE);
3124 	g_return_val_if_fail (top_path_name1 != NULL, FALSE);
3125 	g_return_val_if_fail (func != NULL, FALSE);
3126 
3127 	if (message) {
3128 		const gchar *content_type;
3129 
3130 		if (require_multistatus && message->status_code != SOUP_STATUS_MULTI_STATUS) {
3131 			g_set_error (error, SOUP_HTTP_ERROR, message->status_code,
3132 				_("Expected multistatus response, but %d returned (%s)"), message->status_code,
3133 				e_soup_session_util_status_to_string (message->status_code, message->reason_phrase));
3134 
3135 			return FALSE;
3136 		}
3137 
3138 		content_type = soup_message_headers_get_content_type (message->response_headers, NULL);
3139 		if (!content_type ||
3140 		    (g_ascii_strcasecmp (content_type, "application/xml") != 0 &&
3141 		     g_ascii_strcasecmp (content_type, "text/xml") != 0)) {
3142 			if (!content_type) {
3143 				g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
3144 					_("Expected application/xml response, but none returned"));
3145 			} else {
3146 				g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
3147 					_("Expected application/xml response, but %s returned"), content_type);
3148 			}
3149 
3150 			return FALSE;
3151 		}
3152 
3153 		request_uri = soup_message_get_uri ((SoupMessage *) message);
3154 	}
3155 
3156 	doc = e_xml_parse_data (xml_data->data, xml_data->len);
3157 
3158 	if (!doc) {
3159 		g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
3160 			_("Failed to parse XML data"));
3161 
3162 		return FALSE;
3163 	}
3164 
3165 	top_node = xmlDocGetRootElement (doc);
3166 
3167 	if (!top_node) {
3168 		g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
3169 			_("XML data does not have root node"));
3170 
3171 		xmlFreeDoc (doc);
3172 
3173 		return FALSE;
3174 	}
3175 
3176 	top_node = e_xml_find_sibling (top_node, top_path_ns_href1, top_path_name1);
3177 
3178 	if (!top_node) {
3179 		gchar *tmp;
3180 
3181 		tmp = g_strconcat (
3182 			top_path_ns_href1 ? top_path_ns_href1 : "",
3183 			top_path_ns_href1 ? ":" : "",
3184 			top_path_name1,
3185 			top_path_name2 ? "/" : "",
3186 			(top_path_name2 && top_path_ns_href2) ? top_path_ns_href2 : "",
3187 			(top_path_name2 && top_path_ns_href2) ? ":" : "",
3188 			top_path_name2 ? top_path_name2 : "",
3189 			NULL);
3190 
3191 		g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
3192 			_("XML data doesn't have required structure (%s)"), tmp);
3193 
3194 		xmlFreeDoc (doc);
3195 		g_free (tmp);
3196 
3197 		return FALSE;
3198 	}
3199 
3200 	/* The server can return 'multistatus' with no 'response' children, which is not a problem */
3201 	if (top_path_name2)
3202 		top_node = e_xml_find_child (top_node, top_path_ns_href2, top_path_name2);
3203 
3204 	for (node = top_node; node && !do_stop; node = xmlNextElementSibling (node)) {
3205 		xmlNodePtr href_node = NULL, propstat_node = NULL;
3206 		xmlNodePtr status_node = NULL, prop_node = NULL;
3207 		const xmlChar *href_content, *status_content;
3208 		guint status_code;
3209 		gchar *full_uri;
3210 
3211 		e_xml_find_children_nodes (node, 2,
3212 			E_WEBDAV_NS_DAV, "href", &href_node,
3213 			E_WEBDAV_NS_DAV, "propstat", &propstat_node);
3214 
3215 		if (!href_node || !propstat_node)
3216 			continue;
3217 
3218 		href_content = e_xml_get_node_text (href_node);
3219 		g_warn_if_fail (href_content != NULL);
3220 
3221 		if (!href_content)
3222 			continue;
3223 
3224 		full_uri = e_webdav_session_ensure_full_uri (webdav, request_uri, (const gchar *) href_content);
3225 
3226 		while (propstat_node && !do_stop) {
3227 			e_xml_find_children_nodes (propstat_node, 2,
3228 				E_WEBDAV_NS_DAV, "status", &status_node,
3229 				E_WEBDAV_NS_DAV, "prop", &prop_node);
3230 
3231 			status_content = e_xml_get_node_text (status_node);
3232 
3233 			if (!status_content || !soup_headers_parse_status_line ((const gchar *) status_content, NULL, &status_code, NULL))
3234 				status_code = 0;
3235 
3236 			if (prop_node && prop_node->children)
3237 				do_stop = !func (webdav, prop_node, request_uri, full_uri ? full_uri : (const gchar *) href_content, status_code, func_user_data);
3238 
3239 			propstat_node = e_xml_find_next_sibling (propstat_node, E_WEBDAV_NS_DAV, "propstat");
3240 		}
3241 
3242 		g_free (full_uri);
3243 	}
3244 
3245 	xmlFreeDoc (doc);
3246 
3247 	return TRUE;
3248 }
3249 
3250 /**
3251  * e_webdav_session_traverse_multistatus_response:
3252  * @webdav: an #EWebDAVSession
3253  * @message: (nullable): an optional #SoupMessage corresponding to the response, or %NULL
3254  * @xml_data: a #GByteArray containing DAV:multistatus response
3255  * @func: (scope call): an #EWebDAVPropstatTraverseFunc function to call for each DAV:propstat in the multistatus response
3256  * @func_user_data: (closure func): user data passed to @func
3257  * @error: return location for a #GError, or %NULL
3258  *
3259  * Traverses a DAV:multistatus response and calls @func for each returned DAV:propstat.
3260  *
3261  * The @message, if provided, is used to verify that the response is a multi-status
3262  * and that the Content-Type is properly set. It's used to get a request URI as well.
3263  *
3264  * Returns: Whether succeeded.
3265  *
3266  * Since: 3.26
3267  **/
3268 gboolean
e_webdav_session_traverse_multistatus_response(EWebDAVSession * webdav,const SoupMessage * message,const GByteArray * xml_data,EWebDAVPropstatTraverseFunc func,gpointer func_user_data,GError ** error)3269 e_webdav_session_traverse_multistatus_response (EWebDAVSession *webdav,
3270 						const SoupMessage *message,
3271 						const GByteArray *xml_data,
3272 						EWebDAVPropstatTraverseFunc func,
3273 						gpointer func_user_data,
3274 						GError **error)
3275 {
3276 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
3277 	g_return_val_if_fail (xml_data != NULL, FALSE);
3278 	g_return_val_if_fail (func != NULL, FALSE);
3279 
3280 	return e_webdav_session_traverse_propstat_response (webdav, message, xml_data, TRUE,
3281 		E_WEBDAV_NS_DAV, "multistatus",
3282 		E_WEBDAV_NS_DAV, "response",
3283 		func, func_user_data, error);
3284 }
3285 
3286 /**
3287  * e_webdav_session_traverse_mkcol_response:
3288  * @webdav: an #EWebDAVSession
3289  * @message: (nullable): an optional #SoupMessage corresponding to the response, or %NULL
3290  * @xml_data: a #GByteArray containing DAV:mkcol-response response
3291  * @func: (scope call): an #EWebDAVPropstatTraverseFunc function to call for each DAV:propstat in the response
3292  * @func_user_data: (closure func): user data passed to @func
3293  * @error: return location for a #GError, or %NULL
3294  *
3295  * Traverses a DAV:mkcol-response response and calls @func for each returned DAV:propstat.
3296  *
3297  * The @message, if provided, is used to verify that the response is an XML Content-Type.
3298  * It's used to get the request URI as well.
3299  *
3300  * Returns: Whether succeeded.
3301  *
3302  * Since: 3.26
3303  **/
3304 gboolean
e_webdav_session_traverse_mkcol_response(EWebDAVSession * webdav,const SoupMessage * message,const GByteArray * xml_data,EWebDAVPropstatTraverseFunc func,gpointer func_user_data,GError ** error)3305 e_webdav_session_traverse_mkcol_response (EWebDAVSession *webdav,
3306 					  const SoupMessage *message,
3307 					  const GByteArray *xml_data,
3308 					  EWebDAVPropstatTraverseFunc func,
3309 					  gpointer func_user_data,
3310 					  GError **error)
3311 {
3312 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
3313 	g_return_val_if_fail (xml_data != NULL, FALSE);
3314 	g_return_val_if_fail (func != NULL, FALSE);
3315 
3316 	return e_webdav_session_traverse_propstat_response (webdav, message, xml_data, FALSE,
3317 		E_WEBDAV_NS_DAV, "mkcol-response",
3318 		NULL, NULL,
3319 		func, func_user_data, error);
3320 }
3321 
3322 /**
3323  * e_webdav_session_traverse_mkcalendar_response:
3324  * @webdav: an #EWebDAVSession
3325  * @message: (nullable): an optional #SoupMessage corresponding to the response, or %NULL
3326  * @xml_data: a #GByteArray containing CALDAV:mkcalendar-response response
3327  * @func: (scope call): an #EWebDAVPropstatTraverseFunc function to call for each DAV:propstat in the response
3328  * @func_user_data: (closure func): user data passed to @func
3329  * @error: return location for a #GError, or %NULL
3330  *
3331  * Traverses a CALDAV:mkcalendar-response response and calls @func for each returned DAV:propstat.
3332  *
3333  * The @message, if provided, is used to verify that the response is an XML Content-Type.
3334  * It's used to get the request URI as well.
3335  *
3336  * Returns: Whether succeeded.
3337  *
3338  * Since: 3.26
3339  **/
3340 gboolean
e_webdav_session_traverse_mkcalendar_response(EWebDAVSession * webdav,const SoupMessage * message,const GByteArray * xml_data,EWebDAVPropstatTraverseFunc func,gpointer func_user_data,GError ** error)3341 e_webdav_session_traverse_mkcalendar_response (EWebDAVSession *webdav,
3342 					       const SoupMessage *message,
3343 					       const GByteArray *xml_data,
3344 					       EWebDAVPropstatTraverseFunc func,
3345 					       gpointer func_user_data,
3346 					       GError **error)
3347 {
3348 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
3349 	g_return_val_if_fail (xml_data != NULL, FALSE);
3350 	g_return_val_if_fail (func != NULL, FALSE);
3351 
3352 	return e_webdav_session_traverse_propstat_response (webdav, message, xml_data, FALSE,
3353 		E_WEBDAV_NS_CALDAV, "mkcalendar-response",
3354 		NULL, NULL,
3355 		func, func_user_data, error);
3356 }
3357 
3358 static gboolean
e_webdav_session_getctag_cb(EWebDAVSession * webdav,xmlNodePtr prop_node,const SoupURI * request_uri,const gchar * href,guint status_code,gpointer user_data)3359 e_webdav_session_getctag_cb (EWebDAVSession *webdav,
3360 			     xmlNodePtr prop_node,
3361 			     const SoupURI *request_uri,
3362 			     const gchar *href,
3363 			     guint status_code,
3364 			     gpointer user_data)
3365 {
3366 	if (status_code == SOUP_STATUS_OK) {
3367 		const xmlChar *ctag_content;
3368 		gchar **out_ctag = user_data;
3369 
3370 		g_return_val_if_fail (out_ctag != NULL, FALSE);
3371 
3372 		ctag_content = e_xml_find_child_and_get_text (prop_node, E_WEBDAV_NS_CALENDARSERVER, "getctag");
3373 
3374 		if (ctag_content && *ctag_content)
3375 			*out_ctag = e_webdav_session_util_maybe_dequote (g_strdup ((const gchar *) ctag_content));
3376 	}
3377 
3378 	return FALSE;
3379 }
3380 
3381 /**
3382  * e_webdav_session_getctag_sync:
3383  * @webdav: an #EWebDAVSession
3384  * @uri: (nullable): URI to issue the request for, or %NULL to read from #ESource
3385  * @out_ctag: (out) (transfer full): return location for the ctag
3386  * @cancellable: optional #GCancellable object, or %NULL
3387  * @error: return location for a #GError, or %NULL
3388  *
3389  * Issues a getctag property request for a collection identified by @uri, or,
3390  * in case it's %NULL, on the URI defined in associated #ESource. The ctag is
3391  * a collection tag, which changes whenever the collection changes (similar
3392  * to etag). The getctag is an extension, thus the function can fail when
3393  * the server doesn't support it.
3394  *
3395  * Free the returned @out_ctag with g_free(), when no longer needed.
3396  *
3397  * Returns: Whether succeeded.
3398  *
3399  * Since: 3.26
3400  **/
3401 gboolean
e_webdav_session_getctag_sync(EWebDAVSession * webdav,const gchar * uri,gchar ** out_ctag,GCancellable * cancellable,GError ** error)3402 e_webdav_session_getctag_sync (EWebDAVSession *webdav,
3403 			       const gchar *uri,
3404 			       gchar **out_ctag,
3405 			       GCancellable *cancellable,
3406 			       GError **error)
3407 {
3408 	EXmlDocument *xml;
3409 	gboolean success;
3410 
3411 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
3412 	g_return_val_if_fail (out_ctag != NULL, FALSE);
3413 
3414 	*out_ctag = NULL;
3415 
3416 	xml = e_xml_document_new (E_WEBDAV_NS_DAV, "propfind");
3417 	g_return_val_if_fail (xml != NULL, FALSE);
3418 
3419 	e_xml_document_add_namespaces (xml, "CS", E_WEBDAV_NS_CALENDARSERVER, NULL);
3420 
3421 	e_xml_document_start_element (xml, NULL, "prop");
3422 	e_xml_document_add_empty_element (xml, E_WEBDAV_NS_CALENDARSERVER, "getctag");
3423 	e_xml_document_end_element (xml); /* prop */
3424 
3425 	success = e_webdav_session_propfind_sync (webdav, uri, E_WEBDAV_DEPTH_THIS, xml,
3426 		e_webdav_session_getctag_cb, out_ctag, cancellable, error);
3427 
3428 	g_object_unref (xml);
3429 
3430 	return success && *out_ctag != NULL;
3431 }
3432 
3433 static EWebDAVResourceKind
e_webdav_session_extract_kind(xmlNodePtr parent_node)3434 e_webdav_session_extract_kind (xmlNodePtr parent_node)
3435 {
3436 	xmlNodePtr resourcetype;
3437 
3438 	g_return_val_if_fail (parent_node != NULL, E_WEBDAV_RESOURCE_KIND_UNKNOWN);
3439 
3440 	resourcetype = e_xml_find_child (parent_node, E_WEBDAV_NS_DAV, "resourcetype");
3441 
3442 	if (e_xml_find_child (resourcetype, E_WEBDAV_NS_CARDDAV, "addressbook"))
3443 		return E_WEBDAV_RESOURCE_KIND_ADDRESSBOOK;
3444 
3445 	if (e_xml_find_child (resourcetype, E_WEBDAV_NS_CALDAV, "calendar"))
3446 		return E_WEBDAV_RESOURCE_KIND_CALENDAR;
3447 
3448 	/* These are subscribed iCalendar files, aka 'On The Web' calendars */
3449 	if (e_xml_find_child (resourcetype, E_WEBDAV_NS_DAV, "collection") &&
3450 	    e_xml_find_child (resourcetype, E_WEBDAV_NS_CALENDARSERVER, "subscribed") &&
3451 	    e_xml_find_in_hierarchy (parent_node, E_WEBDAV_NS_CALENDARSERVER, "source", E_WEBDAV_NS_DAV, "href", NULL, NULL))
3452 		return E_WEBDAV_RESOURCE_KIND_SUBSCRIBED_ICALENDAR;
3453 
3454 	if (e_xml_find_child (resourcetype, E_WEBDAV_NS_DAV, "principal"))
3455 		return E_WEBDAV_RESOURCE_KIND_PRINCIPAL;
3456 
3457 	if (e_xml_find_child (resourcetype, E_WEBDAV_NS_DAV, "collection"))
3458 		return E_WEBDAV_RESOURCE_KIND_COLLECTION;
3459 
3460 	return E_WEBDAV_RESOURCE_KIND_RESOURCE;
3461 }
3462 
3463 static guint32
e_webdav_session_extract_supports(xmlNodePtr prop_node)3464 e_webdav_session_extract_supports (xmlNodePtr prop_node)
3465 {
3466 	xmlNodePtr calendar_components;
3467 	guint32 supports = E_WEBDAV_RESOURCE_SUPPORTS_NONE;
3468 
3469 	g_return_val_if_fail (prop_node != NULL, E_WEBDAV_RESOURCE_SUPPORTS_NONE);
3470 
3471 	if (e_xml_find_in_hierarchy (prop_node, E_WEBDAV_NS_DAV, "resourcetype", E_WEBDAV_NS_CARDDAV, "addressbook", NULL, NULL))
3472 		supports = supports | E_WEBDAV_RESOURCE_SUPPORTS_CONTACTS;
3473 
3474 	calendar_components = e_xml_find_child (prop_node, E_WEBDAV_NS_CALDAV, "supported-calendar-component-set");
3475 
3476 	if (calendar_components) {
3477 		xmlNodePtr node;
3478 		gint found_comps = 0;
3479 
3480 		for (node = calendar_components->children; node; node = xmlNextElementSibling (node)) {
3481 			if (e_xml_is_element_name (node, E_WEBDAV_NS_CALDAV, "comp")) {
3482 				xmlChar *name;
3483 
3484 				found_comps++;
3485 
3486 				name = xmlGetProp (node, (const xmlChar *) "name");
3487 
3488 				if (!name)
3489 					continue;
3490 
3491 				if (g_ascii_strcasecmp ((const gchar *) name, "VEVENT") == 0)
3492 					supports |= E_WEBDAV_RESOURCE_SUPPORTS_EVENTS;
3493 				else if (g_ascii_strcasecmp ((const gchar *) name, "VJOURNAL") == 0)
3494 					supports |= E_WEBDAV_RESOURCE_SUPPORTS_MEMOS;
3495 				else if (g_ascii_strcasecmp ((const gchar *) name, "VTODO") == 0)
3496 					supports |= E_WEBDAV_RESOURCE_SUPPORTS_TASKS;
3497 				else if (g_ascii_strcasecmp ((const gchar *) name, "VFREEBUSY") == 0)
3498 					supports |= E_WEBDAV_RESOURCE_SUPPORTS_FREEBUSY;
3499 				else if (g_ascii_strcasecmp ((const gchar *) name, "VTIMEZONE") == 0)
3500 					supports |= E_WEBDAV_RESOURCE_SUPPORTS_TIMEZONE;
3501 
3502 				xmlFree (name);
3503 			}
3504 		}
3505 
3506 		if (!found_comps) {
3507 			/* If the property is not present, assume all component
3508 			 * types are supported.  (RFC 4791, Section 5.2.3) */
3509 			supports = supports |
3510 				E_WEBDAV_RESOURCE_SUPPORTS_EVENTS |
3511 				E_WEBDAV_RESOURCE_SUPPORTS_MEMOS |
3512 				E_WEBDAV_RESOURCE_SUPPORTS_TASKS |
3513 				E_WEBDAV_RESOURCE_SUPPORTS_FREEBUSY |
3514 				E_WEBDAV_RESOURCE_SUPPORTS_TIMEZONE;
3515 		}
3516 	}
3517 
3518 	return supports;
3519 }
3520 
3521 static gchar *
e_webdav_session_extract_nonempty(xmlNodePtr parent,const gchar * prop_ns_href,const gchar * prop_name,const gchar * alternative_prop_ns_href,const gchar * alternative_prop_name)3522 e_webdav_session_extract_nonempty (xmlNodePtr parent,
3523 				   const gchar *prop_ns_href,
3524 				   const gchar *prop_name,
3525 				   const gchar *alternative_prop_ns_href,
3526 				   const gchar *alternative_prop_name)
3527 {
3528 	const xmlChar *x_value;
3529 	gchar *value = NULL;
3530 
3531 	g_return_val_if_fail (parent != NULL, NULL);
3532 	g_return_val_if_fail (prop_name != NULL, NULL);
3533 
3534 	x_value = e_xml_find_child_and_get_text (parent, prop_ns_href, prop_name);
3535 
3536 	if (x_value && *x_value)
3537 		value = g_strdup ((const gchar *) x_value);
3538 
3539 	if (!value && alternative_prop_name) {
3540 		x_value = e_xml_find_child_and_get_text (parent, alternative_prop_ns_href, alternative_prop_name);
3541 
3542 		if (x_value && *x_value)
3543 			value = g_strdup ((const gchar *) x_value);
3544 	}
3545 
3546 	if (!value)
3547 		return NULL;
3548 
3549 	if (!*value) {
3550 		g_free (value);
3551 		return NULL;
3552 	}
3553 
3554 	return e_webdav_session_util_maybe_dequote (value);
3555 }
3556 
3557 static gsize
e_webdav_session_extract_content_length(xmlNodePtr parent)3558 e_webdav_session_extract_content_length (xmlNodePtr parent)
3559 {
3560 	gchar *value;
3561 	gsize length;
3562 
3563 	g_return_val_if_fail (parent != NULL, 0);
3564 
3565 	value = e_webdav_session_extract_nonempty (parent, E_WEBDAV_NS_DAV, "getcontentlength", NULL, NULL);
3566 	if (!value)
3567 		return 0;
3568 
3569 	length = g_ascii_strtoll (value, NULL, 10);
3570 
3571 	g_free (value);
3572 
3573 	return length;
3574 }
3575 
3576 static guint
e_webdav_session_extract_uint(xmlNodePtr parent,const gchar * prop_ns_href,const gchar * prop_name)3577 e_webdav_session_extract_uint (xmlNodePtr parent,
3578 			       const gchar *prop_ns_href,
3579 			       const gchar *prop_name)
3580 {
3581 	gchar *value_str, *end_ptr = NULL;
3582 	guint64 value;
3583 
3584 	g_return_val_if_fail (parent != NULL, (guint) -1);
3585 
3586 	value_str = e_webdav_session_extract_nonempty (parent, prop_ns_href, prop_name, NULL, NULL);
3587 	if (!value_str)
3588 		return (guint) -1;
3589 
3590 	value = g_ascii_strtoull (value_str, &end_ptr, 10);
3591 
3592 	g_free (value_str);
3593 
3594 	if (end_ptr == value_str)
3595 		return (guint) -1;
3596 
3597 	return (guint) value;
3598 }
3599 
3600 static glong
e_webdav_session_extract_datetime(xmlNodePtr parent,const gchar * ns_href,const gchar * prop,gboolean is_iso_property)3601 e_webdav_session_extract_datetime (xmlNodePtr parent,
3602 				   const gchar *ns_href,
3603 				   const gchar *prop,
3604 				   gboolean is_iso_property)
3605 {
3606 	gchar *value;
3607 	GTimeVal tv;
3608 
3609 	g_return_val_if_fail (parent != NULL, -1);
3610 	g_return_val_if_fail (prop != NULL, -1);
3611 
3612 	value = e_webdav_session_extract_nonempty (parent, ns_href, prop, NULL, NULL);
3613 	if (!value)
3614 		return -1;
3615 
3616 	if (is_iso_property && !g_time_val_from_iso8601 (value, &tv)) {
3617 		tv.tv_sec = -1;
3618 	} else if (!is_iso_property) {
3619 		tv.tv_sec = camel_header_decode_date (value, NULL);
3620 	}
3621 
3622 	g_free (value);
3623 
3624 	return tv.tv_sec;
3625 }
3626 
3627 static gboolean
e_webdav_session_list_cb(EWebDAVSession * webdav,xmlNodePtr prop_node,const SoupURI * request_uri,const gchar * href,guint status_code,gpointer user_data)3628 e_webdav_session_list_cb (EWebDAVSession *webdav,
3629 			  xmlNodePtr prop_node,
3630 			  const SoupURI *request_uri,
3631 			  const gchar *href,
3632 			  guint status_code,
3633 			  gpointer user_data)
3634 {
3635 	GSList **out_resources = user_data;
3636 
3637 	g_return_val_if_fail (out_resources != NULL, FALSE);
3638 	g_return_val_if_fail (request_uri != NULL, FALSE);
3639 
3640 	if (status_code == SOUP_STATUS_OK) {
3641 		EWebDAVResource *resource;
3642 		EWebDAVResourceKind kind;
3643 		guint32 supports;
3644 		gchar *etag;
3645 		gchar *display_name;
3646 		gchar *content_type;
3647 		gsize content_length;
3648 		glong creation_date;
3649 		glong last_modified;
3650 		gchar *description;
3651 		gchar *color;
3652 		gchar *source_href = NULL;
3653 		guint order;
3654 
3655 		kind = e_webdav_session_extract_kind (prop_node);
3656 		if (kind == E_WEBDAV_RESOURCE_KIND_UNKNOWN)
3657 			return TRUE;
3658 
3659 		if (kind == E_WEBDAV_RESOURCE_KIND_SUBSCRIBED_ICALENDAR) {
3660 			xmlNodePtr source_href_node;
3661 			const xmlChar *x_source_href = NULL;
3662 
3663 
3664 			source_href_node = e_xml_find_in_hierarchy (prop_node, E_WEBDAV_NS_CALENDARSERVER, "source", E_WEBDAV_NS_DAV, "href", NULL, NULL);
3665 
3666 			if (!source_href_node)
3667 				return TRUE;
3668 
3669 			x_source_href = e_xml_get_node_text (source_href_node);
3670 
3671 			if (!x_source_href || !*x_source_href)
3672 				return TRUE;
3673 
3674 			source_href = e_webdav_session_util_maybe_dequote (g_strdup ((const gchar *) x_source_href));
3675 		}
3676 
3677 		supports = e_webdav_session_extract_supports (prop_node);
3678 		etag = e_webdav_session_extract_nonempty (prop_node, E_WEBDAV_NS_DAV, "getetag", E_WEBDAV_NS_CALENDARSERVER, "getctag");
3679 		display_name = e_webdav_session_extract_nonempty (prop_node, E_WEBDAV_NS_DAV, "displayname", NULL, NULL);
3680 		content_type = e_webdav_session_extract_nonempty (prop_node, E_WEBDAV_NS_DAV, "getcontenttype", NULL, NULL);
3681 		content_length = e_webdav_session_extract_content_length (prop_node);
3682 		creation_date = e_webdav_session_extract_datetime (prop_node, E_WEBDAV_NS_DAV, "creationdate", TRUE);
3683 		last_modified = e_webdav_session_extract_datetime (prop_node, E_WEBDAV_NS_DAV, "getlastmodified", FALSE);
3684 		description = e_webdav_session_extract_nonempty (prop_node, E_WEBDAV_NS_CALDAV, "calendar-description",
3685 			E_WEBDAV_NS_CARDDAV, "addressbook-description");
3686 		color = e_webdav_session_extract_nonempty (prop_node, E_WEBDAV_NS_ICAL, "calendar-color", NULL, NULL);
3687 		order = e_webdav_session_extract_uint (prop_node, E_WEBDAV_NS_ICAL, "calendar-order");
3688 
3689 		resource = e_webdav_resource_new (kind, supports,
3690 			source_href ? source_href : href,
3691 			NULL, /* etag */
3692 			NULL, /* display_name */
3693 			NULL, /* content_type */
3694 			content_length,
3695 			creation_date,
3696 			last_modified,
3697 			NULL, /* description */
3698 			NULL, /* color */
3699 			order);
3700 		resource->etag = etag;
3701 		resource->display_name = display_name;
3702 		resource->content_type = content_type;
3703 		resource->description = description;
3704 		resource->color = color;
3705 
3706 		*out_resources = g_slist_prepend (*out_resources, resource);
3707 
3708 		g_free (source_href);
3709 	}
3710 
3711 	return TRUE;
3712 }
3713 
3714 /**
3715  * e_webdav_session_list_sync:
3716  * @webdav: an #EWebDAVSession
3717  * @uri: (nullable): URI to issue the request for, or %NULL to read from #ESource
3718  * @depth: requested depth, can be one of %E_WEBDAV_DEPTH_THIS, %E_WEBDAV_DEPTH_THIS_AND_CHILDREN or %E_WEBDAV_DEPTH_INFINITY
3719  * @flags: a bit-or of #EWebDAVListFlags, claiming what properties to read
3720  * @out_resources: (out) (transfer full) (element-type EWebDAVResource): return location for the resources
3721  * @cancellable: optional #GCancellable object, or %NULL
3722  * @error: return location for a #GError, or %NULL
3723  *
3724  * Lists content of the @uri, or, in case it's %NULL, of the URI defined
3725  * in associated #ESource, which should point to a collection. The @flags
3726  * influences which properties are read for the resources.
3727  *
3728  * The @out_resources is in no particular order.
3729  *
3730  * Free the returned @out_resources with
3731  * g_slist_free_full (resources, e_webdav_resource_free);
3732  * when no longer needed.
3733  *
3734  * Returns: Whether succeeded.
3735  *
3736  * Since: 3.26
3737  **/
3738 gboolean
e_webdav_session_list_sync(EWebDAVSession * webdav,const gchar * uri,const gchar * depth,guint32 flags,GSList ** out_resources,GCancellable * cancellable,GError ** error)3739 e_webdav_session_list_sync (EWebDAVSession *webdav,
3740 			    const gchar *uri,
3741 			    const gchar *depth,
3742 			    guint32 flags,
3743 			    GSList **out_resources,
3744 			    GCancellable *cancellable,
3745 			    GError **error)
3746 {
3747 	EXmlDocument *xml;
3748 	gboolean calendar_props, addressbook_props;
3749 	gboolean success;
3750 
3751 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
3752 	g_return_val_if_fail (out_resources != NULL, FALSE);
3753 
3754 	*out_resources = NULL;
3755 
3756 	if (!(flags & (E_WEBDAV_LIST_ONLY_CALENDAR | E_WEBDAV_LIST_ONLY_ADDRESSBOOK))) {
3757 		calendar_props = TRUE;
3758 		addressbook_props = TRUE;
3759 	} else {
3760 		calendar_props = (flags & E_WEBDAV_LIST_ONLY_CALENDAR) != 0;
3761 		addressbook_props = (flags & E_WEBDAV_LIST_ONLY_ADDRESSBOOK) != 0;
3762 	}
3763 
3764 	xml = e_xml_document_new (E_WEBDAV_NS_DAV, "propfind");
3765 	g_return_val_if_fail (xml != NULL, FALSE);
3766 
3767 	e_xml_document_start_element (xml, NULL, "prop");
3768 
3769 	e_xml_document_add_empty_element (xml, NULL, "resourcetype");
3770 
3771 	if (calendar_props) {
3772 		e_xml_document_add_namespaces (xml, "CS", E_WEBDAV_NS_CALENDARSERVER, NULL);
3773 
3774 		e_xml_document_add_empty_element (xml, E_WEBDAV_NS_CALENDARSERVER, "source");
3775 	}
3776 
3777 	if (calendar_props && (
3778 	    (flags & E_WEBDAV_LIST_SUPPORTS) != 0 ||
3779 	    (flags & E_WEBDAV_LIST_DESCRIPTION) != 0)) {
3780 		e_xml_document_add_namespaces (xml, "C", E_WEBDAV_NS_CALDAV, NULL);
3781 	}
3782 
3783 	if (calendar_props && (flags & E_WEBDAV_LIST_SUPPORTS) != 0) {
3784 		e_xml_document_add_empty_element (xml, E_WEBDAV_NS_CALDAV, "supported-calendar-component-set");
3785 	}
3786 
3787 	if ((flags & E_WEBDAV_LIST_DISPLAY_NAME) != 0) {
3788 		e_xml_document_add_empty_element (xml, NULL, "displayname");
3789 	}
3790 
3791 	if ((flags & E_WEBDAV_LIST_ETAG) != 0) {
3792 		e_xml_document_add_empty_element (xml, NULL, "getetag");
3793 
3794 		if (calendar_props)
3795 			e_xml_document_add_empty_element (xml, E_WEBDAV_NS_CALENDARSERVER, "getctag");
3796 	}
3797 
3798 	if ((flags & E_WEBDAV_LIST_CONTENT_TYPE) != 0) {
3799 		e_xml_document_add_empty_element (xml, NULL, "getcontenttype");
3800 	}
3801 
3802 	if ((flags & E_WEBDAV_LIST_CONTENT_LENGTH) != 0) {
3803 		e_xml_document_add_empty_element (xml, NULL, "getcontentlength");
3804 	}
3805 
3806 	if ((flags & E_WEBDAV_LIST_CREATION_DATE) != 0) {
3807 		e_xml_document_add_empty_element (xml, NULL, "creationdate");
3808 	}
3809 
3810 	if ((flags & E_WEBDAV_LIST_LAST_MODIFIED) != 0) {
3811 		e_xml_document_add_empty_element (xml, NULL, "getlastmodified");
3812 	}
3813 
3814 	if ((flags & E_WEBDAV_LIST_DESCRIPTION) != 0) {
3815 		if (calendar_props)
3816 			e_xml_document_add_empty_element (xml, E_WEBDAV_NS_CALDAV, "calendar-description");
3817 
3818 		if (addressbook_props) {
3819 			e_xml_document_add_namespaces (xml, "A", E_WEBDAV_NS_CARDDAV, NULL);
3820 
3821 			e_xml_document_add_empty_element (xml, E_WEBDAV_NS_CARDDAV, "addressbook-description");
3822 		}
3823 	}
3824 
3825 	if (calendar_props && ((flags & E_WEBDAV_LIST_COLOR) != 0 || (flags & E_WEBDAV_LIST_ORDER) != 0)) {
3826 		e_xml_document_add_namespaces (xml, "IC", E_WEBDAV_NS_ICAL, NULL);
3827 
3828 		if ((flags & E_WEBDAV_LIST_COLOR) != 0)
3829 			e_xml_document_add_empty_element (xml, E_WEBDAV_NS_ICAL, "calendar-color");
3830 
3831 		if ((flags & E_WEBDAV_LIST_ORDER) != 0)
3832 			e_xml_document_add_empty_element (xml, E_WEBDAV_NS_ICAL, "calendar-order");
3833 	}
3834 
3835 	e_xml_document_end_element (xml); /* prop */
3836 
3837 	success = e_webdav_session_propfind_sync (webdav, uri, depth, xml,
3838 		e_webdav_session_list_cb, out_resources, cancellable, error);
3839 
3840 	g_object_unref (xml);
3841 
3842 	/* Ensure display name in case the resource doesn't have any */
3843 	if (success && (flags & E_WEBDAV_LIST_DISPLAY_NAME) != 0) {
3844 		GSList *link;
3845 
3846 		for (link = *out_resources; link; link = g_slist_next (link)) {
3847 			EWebDAVResource *resource = link->data;
3848 
3849 			if (resource && !resource->display_name && resource->href) {
3850 				gchar *href_decoded = soup_uri_decode (resource->href);
3851 
3852 				if (href_decoded) {
3853 					gchar *cp;
3854 
3855 					/* Use the last non-empty path segment. */
3856 					while ((cp = strrchr (href_decoded, '/')) != NULL) {
3857 						if (*(cp + 1) == '\0')
3858 							*cp = '\0';
3859 						else {
3860 							resource->display_name = g_strdup (cp + 1);
3861 							break;
3862 						}
3863 					}
3864 				}
3865 
3866 				g_free (href_decoded);
3867 			}
3868 		}
3869 	}
3870 
3871 	if (success) {
3872 		/* Honour order returned by the server, even it's not significant. */
3873 		*out_resources = g_slist_reverse (*out_resources);
3874 	}
3875 
3876 	return success;
3877 }
3878 
3879 /**
3880  * e_webdav_session_update_properties_sync:
3881  * @webdav: an #EWebDAVSession
3882  * @uri: (nullable): URI to issue the request for, or %NULL to read from #ESource
3883  * @changes: (element-type EWebDAVPropertyChange): a #GSList with request changes
3884  * @cancellable: optional #GCancellable object, or %NULL
3885  * @error: return location for a #GError, or %NULL
3886  *
3887  * Updates properties (set/remove) on the provided @uri, or, in case it's %NULL,
3888  * on the URI defined in associated #ESource, with the @changes. The order
3889  * of @changes is significant, unlike on other places.
3890  *
3891  * This function supports only flat properties, those not under other element.
3892  * To support more complex property tries use e_webdav_session_proppatch_sync()
3893  * directly.
3894  *
3895  * Returns: Whether succeeded.
3896  *
3897  * Since: 3.26
3898  **/
3899 gboolean
e_webdav_session_update_properties_sync(EWebDAVSession * webdav,const gchar * uri,const GSList * changes,GCancellable * cancellable,GError ** error)3900 e_webdav_session_update_properties_sync (EWebDAVSession *webdav,
3901 					 const gchar *uri,
3902 					 const GSList *changes,
3903 					 GCancellable *cancellable,
3904 					 GError **error)
3905 {
3906 	EXmlDocument *xml;
3907 	GSList *link;
3908 	gboolean success;
3909 
3910 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
3911 	g_return_val_if_fail (changes != NULL, FALSE);
3912 
3913 	xml = e_xml_document_new (E_WEBDAV_NS_DAV, "propertyupdate");
3914 	g_return_val_if_fail (xml != NULL, FALSE);
3915 
3916 	for (link = (GSList *) changes; link; link = g_slist_next (link)) {
3917 		EWebDAVPropertyChange *change = link->data;
3918 
3919 		if (!change)
3920 			continue;
3921 
3922 		switch (change->kind) {
3923 		case E_WEBDAV_PROPERTY_SET:
3924 			e_xml_document_start_element (xml, NULL, "set");
3925 			e_xml_document_start_element (xml, NULL, "prop");
3926 			e_xml_document_start_text_element (xml, change->ns_uri, change->name);
3927 			if (change->value) {
3928 				e_xml_document_write_string (xml, change->value);
3929 			}
3930 			e_xml_document_end_element (xml); /* change->name */
3931 			e_xml_document_end_element (xml); /* prop */
3932 			e_xml_document_end_element (xml); /* set */
3933 			break;
3934 		case E_WEBDAV_PROPERTY_REMOVE:
3935 			e_xml_document_start_element (xml, NULL, "remove");
3936 			e_xml_document_start_element (xml, NULL, "prop");
3937 			e_xml_document_add_empty_element (xml, change->ns_uri, change->name);
3938 			e_xml_document_end_element (xml); /* prop */
3939 			e_xml_document_end_element (xml); /* remove */
3940 			break;
3941 		}
3942 	}
3943 
3944 	success = e_webdav_session_proppatch_sync (webdav, uri, xml, cancellable, error);
3945 
3946 	g_object_unref (xml);
3947 
3948 	return success;
3949 }
3950 
3951 /**
3952  * e_webdav_session_lock_resource_sync:
3953  * @webdav: an #EWebDAVSession
3954  * @uri: (nullable): URI to lock, or %NULL to read from #ESource
3955  * @lock_scope: an #EWebDAVLockScope to define the scope of the lock
3956  * @lock_timeout: timeout for the lock, in seconds, on 0 to infinity
3957  * @owner: (nullable): optional identificator of the owner of the lock, or %NULL
3958  * @out_lock_token: (out) (transfer full): return location of the obtained or refreshed lock token
3959  * @cancellable: optional #GCancellable object, or %NULL
3960  * @error: return location for a #GError, or %NULL
3961  *
3962  * Locks a resource identified by @uri, or, in case it's %NULL, by the URI defined
3963  * in associated #ESource. It obtains a write lock with the given @lock_scope.
3964  *
3965  * The @owner is used to identify the lock owner. When it's an http:// or https://,
3966  * then it's referenced as DAV:href, otherwise the value is treated as plain text.
3967  * If it's %NULL, then the user name from the associated #ESource is used.
3968  *
3969  * The @out_lock_token can be refreshed with e_webdav_session_refresh_lock_sync().
3970  * Release the lock with e_webdav_session_unlock_sync().
3971  * Free the returned @out_lock_token with g_free(), when no longer needed.
3972  *
3973  * Returns: Whether succeeded.
3974  *
3975  * Since: 3.26
3976  **/
3977 gboolean
e_webdav_session_lock_resource_sync(EWebDAVSession * webdav,const gchar * uri,EWebDAVLockScope lock_scope,gint32 lock_timeout,const gchar * owner,gchar ** out_lock_token,GCancellable * cancellable,GError ** error)3978 e_webdav_session_lock_resource_sync (EWebDAVSession *webdav,
3979 				     const gchar *uri,
3980 				     EWebDAVLockScope lock_scope,
3981 				     gint32 lock_timeout,
3982 				     const gchar *owner,
3983 				     gchar **out_lock_token,
3984 				     GCancellable *cancellable,
3985 				     GError **error)
3986 {
3987 	EXmlDocument *xml;
3988 	gchar *owner_ref;
3989 	gboolean success;
3990 
3991 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
3992 	g_return_val_if_fail (out_lock_token != NULL, FALSE);
3993 
3994 	xml = e_xml_document_new (E_WEBDAV_NS_DAV, "lockinfo");
3995 	g_return_val_if_fail (xml != NULL, FALSE);
3996 
3997 	e_xml_document_start_element (xml, NULL, "lockscope");
3998 	switch (lock_scope) {
3999 	case E_WEBDAV_LOCK_EXCLUSIVE:
4000 		e_xml_document_add_empty_element (xml, NULL, "exclusive");
4001 		break;
4002 	case E_WEBDAV_LOCK_SHARED:
4003 		e_xml_document_add_empty_element (xml, NULL, "shared");
4004 		break;
4005 	}
4006 	e_xml_document_end_element (xml); /* lockscope */
4007 
4008 	e_xml_document_start_element (xml, NULL, "locktype");
4009 	e_xml_document_add_empty_element (xml, NULL, "write");
4010 	e_xml_document_end_element (xml); /* locktype */
4011 
4012 	e_xml_document_start_text_element (xml, NULL, "owner");
4013 	if (owner) {
4014 		owner_ref = g_strdup (owner);
4015 	} else {
4016 		ESource *source = e_soup_session_get_source (E_SOUP_SESSION (webdav));
4017 
4018 		owner_ref = NULL;
4019 
4020 		if (e_source_has_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION)) {
4021 			owner_ref = e_source_authentication_dup_user (
4022 				e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION));
4023 
4024 			if (owner_ref && !*owner_ref)
4025 				g_clear_pointer (&owner_ref, g_free);
4026 		}
4027 
4028 		if (!owner_ref && e_source_has_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND)) {
4029 			owner_ref = e_source_webdav_dup_email_address (
4030 				e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND));
4031 
4032 			if (owner_ref && !*owner_ref)
4033 				g_clear_pointer (&owner_ref, g_free);
4034 		}
4035 	}
4036 
4037 	if (!owner_ref)
4038 		owner_ref = g_strconcat (g_get_host_name (), " / ", g_get_user_name (), NULL);
4039 
4040 	if (owner_ref) {
4041 		if (g_str_has_prefix (owner_ref, "http://") ||
4042 		    g_str_has_prefix (owner_ref, "https://")) {
4043 			e_xml_document_start_element (xml, NULL, "href");
4044 			e_xml_document_write_string (xml, owner_ref);
4045 			e_xml_document_end_element (xml); /* href */
4046 		} else {
4047 			e_xml_document_write_string (xml, owner_ref);
4048 		}
4049 	}
4050 
4051 	g_free (owner_ref);
4052 	e_xml_document_end_element (xml); /* owner */
4053 
4054 	success = e_webdav_session_lock_sync (webdav, uri, E_WEBDAV_DEPTH_INFINITY, lock_timeout, xml,
4055 		out_lock_token, NULL, cancellable, error);
4056 
4057 	g_object_unref (xml);
4058 
4059 	return success;
4060 }
4061 
4062 static void
e_webdav_session_traverse_privilege_level(xmlNodePtr parent_node,GNode * parent)4063 e_webdav_session_traverse_privilege_level (xmlNodePtr parent_node,
4064 					   GNode *parent)
4065 {
4066 	xmlNodePtr node;
4067 
4068 	g_return_if_fail (parent_node != NULL);
4069 	g_return_if_fail (parent != NULL);
4070 
4071 	for (node = e_xml_find_child (parent_node, E_WEBDAV_NS_DAV, "supported-privilege");
4072 	     node;
4073 	     node = e_xml_find_next_sibling (node, E_WEBDAV_NS_DAV, "supported-privilege")) {
4074 		xmlNodePtr privilege_node;
4075 
4076 		privilege_node = e_xml_find_child (node, E_WEBDAV_NS_DAV, "privilege");
4077 
4078 		if (privilege_node) {
4079 			GNode *child;
4080 			const xmlChar *description;
4081 			EWebDAVPrivilegeKind kind = E_WEBDAV_PRIVILEGE_KIND_COMMON;
4082 			EWebDAVPrivilegeHint hint = E_WEBDAV_PRIVILEGE_HINT_UNKNOWN;
4083 			EWebDAVPrivilege *privilege;
4084 
4085 			if (e_xml_find_child (privilege_node, E_WEBDAV_NS_DAV, "abstract"))
4086 				kind = E_WEBDAV_PRIVILEGE_KIND_ABSTRACT;
4087 			else if (e_xml_find_child (privilege_node, E_WEBDAV_NS_DAV, "aggregate"))
4088 				kind = E_WEBDAV_PRIVILEGE_KIND_AGGREGATE;
4089 
4090 			description = e_xml_find_child_and_get_text (privilege_node, E_WEBDAV_NS_DAV, "description");
4091 			privilege = e_webdav_privilege_new ((const gchar *) ((privilege_node->ns && privilege_node->ns->href) ? privilege_node->ns->href : NULL),
4092 				(const gchar *) privilege_node->name,
4093 				(const gchar *) description,
4094 				kind,
4095 				hint);
4096 			child = g_node_new (privilege);
4097 			g_node_append (parent, child);
4098 
4099 			privilege_node = e_xml_find_child (privilege_node, E_WEBDAV_NS_DAV, "supported-privilege");
4100 
4101 			if (privilege_node)
4102 				e_webdav_session_traverse_privilege_level (privilege_node, child);
4103 		}
4104 	}
4105 }
4106 
4107 static gboolean
e_webdav_session_supported_privilege_set_cb(EWebDAVSession * webdav,xmlNodePtr prop_node,const SoupURI * request_uri,const gchar * href,guint status_code,gpointer user_data)4108 e_webdav_session_supported_privilege_set_cb (EWebDAVSession *webdav,
4109 					     xmlNodePtr prop_node,
4110 					     const SoupURI *request_uri,
4111 					     const gchar *href,
4112 					     guint status_code,
4113 					     gpointer user_data)
4114 {
4115 	GNode **out_privileges = user_data;
4116 
4117 	g_return_val_if_fail (out_privileges != NULL, FALSE);
4118 
4119 	if (status_code == SOUP_STATUS_OK &&
4120 	    e_xml_find_in_hierarchy (prop_node, E_WEBDAV_NS_DAV, "supported-privilege-set", E_WEBDAV_NS_DAV, "supported-privilege", NULL, NULL)) {
4121 		GNode *root;
4122 
4123 		root = g_node_new (NULL);
4124 
4125 		e_webdav_session_traverse_privilege_level (prop_node, root);
4126 
4127 		*out_privileges = root;
4128 	}
4129 
4130 	return TRUE;
4131 }
4132 
4133 /**
4134  * e_webdav_session_acl_sync:
4135  * @webdav: an #EWebDAVSession
4136  * @uri: (nullable): URI to issue the request for, or %NULL to read from #ESource
4137  * @xml:the request itself, as an #EXmlDocument, the root element should be DAV:acl
4138  * @cancellable: optional #GCancellable object, or %NULL
4139  * @error: return location for a #GError, or %NULL
4140  *
4141  * Issues ACL request on the provided @uri, or, in case it's %NULL, on the URI
4142  * defined in associated #ESource.
4143  *
4144  * Returns: Whether succeeded.
4145  *
4146  * Since: 3.26
4147  **/
4148 gboolean
e_webdav_session_acl_sync(EWebDAVSession * webdav,const gchar * uri,const EXmlDocument * xml,GCancellable * cancellable,GError ** error)4149 e_webdav_session_acl_sync (EWebDAVSession *webdav,
4150 			   const gchar *uri,
4151 			   const EXmlDocument *xml,
4152 			   GCancellable *cancellable,
4153 			   GError **error)
4154 {
4155 	SoupRequestHTTP *request;
4156 	SoupMessage *message;
4157 	GByteArray *bytes;
4158 	gchar *content;
4159 	gsize content_length;
4160 	gboolean success;
4161 
4162 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
4163 	g_return_val_if_fail (E_IS_XML_DOCUMENT (xml), FALSE);
4164 
4165 	g_clear_pointer (&webdav->priv->last_dav_error_code, g_free);
4166 
4167 	request = e_webdav_session_new_request (webdav, "ACL", uri, error);
4168 	if (!request)
4169 		return FALSE;
4170 
4171 	message = soup_request_http_get_message (request);
4172 	if (!message) {
4173 		g_warn_if_fail (message != NULL);
4174 		g_object_unref (request);
4175 
4176 		return FALSE;
4177 	}
4178 
4179 	content = e_xml_document_get_content (xml, &content_length);
4180 	if (!content) {
4181 		g_object_unref (message);
4182 		g_object_unref (request);
4183 
4184 		g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Failed to get input XML content"));
4185 
4186 		return FALSE;
4187 	}
4188 
4189 	soup_message_set_request (message, E_WEBDAV_CONTENT_TYPE_XML,
4190 		SOUP_MEMORY_TAKE, content, content_length);
4191 
4192 	bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, error);
4193 
4194 	success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, TRUE, _("Failed to get access control list"), error, TRUE, FALSE) &&
4195 		bytes != NULL;
4196 
4197 	if (bytes)
4198 		g_byte_array_free (bytes, TRUE);
4199 	g_object_unref (message);
4200 	g_object_unref (request);
4201 
4202 	return success;
4203 }
4204 
4205 /**
4206  * e_webdav_session_get_supported_privilege_set_sync:
4207  * @webdav: an #EWebDAVSession
4208  * @uri: (nullable): URI to issue the request for, or %NULL to read from #ESource
4209  * @out_privileges: (out) (transfer full) (element-type EWebDAVPrivilege): return location for the tree of supported privileges
4210  * @cancellable: optional #GCancellable object, or %NULL
4211  * @error: return location for a #GError, or %NULL
4212  *
4213  * Gets supported privileges for the @uri, or, in case it's %NULL, for the URI
4214  * defined in associated #ESource.
4215  *
4216  * The root node of @out_privileges has always %NULL data.
4217  *
4218  * Free the returned @out_privileges with e_webdav_session_util_free_privileges()
4219  * when no longer needed.
4220  *
4221  * Returns: Whether succeeded.
4222  *
4223  * Since: 3.26
4224  **/
4225 gboolean
e_webdav_session_get_supported_privilege_set_sync(EWebDAVSession * webdav,const gchar * uri,GNode ** out_privileges,GCancellable * cancellable,GError ** error)4226 e_webdav_session_get_supported_privilege_set_sync (EWebDAVSession *webdav,
4227 						   const gchar *uri,
4228 						   GNode **out_privileges,
4229 						   GCancellable *cancellable,
4230 						   GError **error)
4231 {
4232 	EXmlDocument *xml;
4233 	gboolean success;
4234 
4235 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
4236 	g_return_val_if_fail (out_privileges != NULL, FALSE);
4237 
4238 	*out_privileges = NULL;
4239 
4240 	xml = e_xml_document_new (E_WEBDAV_NS_DAV, "propfind");
4241 	g_return_val_if_fail (xml != NULL, FALSE);
4242 
4243 	e_xml_document_start_element (xml, NULL, "prop");
4244 	e_xml_document_add_empty_element (xml, NULL, "supported-privilege-set");
4245 	e_xml_document_end_element (xml); /* prop */
4246 
4247 	success = e_webdav_session_propfind_sync (webdav, uri, E_WEBDAV_DEPTH_THIS, xml,
4248 		e_webdav_session_supported_privilege_set_cb, out_privileges, cancellable, error);
4249 
4250 	g_object_unref (xml);
4251 
4252 	return success;
4253 }
4254 
4255 static void
e_webdav_session_extract_privilege_simple(xmlNodePtr privilege_node,GSList ** out_privileges)4256 e_webdav_session_extract_privilege_simple (xmlNodePtr privilege_node,
4257 					   GSList **out_privileges)
4258 {
4259 	if (privilege_node) {
4260 		xmlNodePtr node;
4261 
4262 		for (node = privilege_node->children; node; node = node->next) {
4263 			if (node->type == XML_ELEMENT_NODE &&
4264 			    node->name && *(node->name) &&
4265 			    node->ns && node->ns->href && *(node->ns->href)) {
4266 				EWebDAVPrivilege *privilege;
4267 
4268 				privilege = e_webdav_privilege_new ((const gchar *) node->ns->href, (const gchar *) node->name,
4269 					NULL, E_WEBDAV_PRIVILEGE_KIND_COMMON, E_WEBDAV_PRIVILEGE_HINT_UNKNOWN);
4270 
4271 				if (privilege)
4272 					*out_privileges = g_slist_prepend (*out_privileges, privilege);
4273 			}
4274 		}
4275 	}
4276 }
4277 
4278 typedef struct _PrivilegeSetData {
4279 	gboolean any_found;
4280 	GSList **out_privileges;
4281 } PrivilegeSetData;
4282 
4283 static gboolean
e_webdav_session_current_user_privilege_set_cb(EWebDAVSession * webdav,xmlNodePtr prop_node,const SoupURI * request_uri,const gchar * href,guint status_code,gpointer user_data)4284 e_webdav_session_current_user_privilege_set_cb (EWebDAVSession *webdav,
4285 						xmlNodePtr prop_node,
4286 						const SoupURI *request_uri,
4287 						const gchar *href,
4288 						guint status_code,
4289 						gpointer user_data)
4290 {
4291 	PrivilegeSetData *psd = user_data;
4292 
4293 	g_return_val_if_fail (prop_node != NULL, FALSE);
4294 	g_return_val_if_fail (psd != NULL, FALSE);
4295 
4296 	if (status_code == SOUP_STATUS_OK) {
4297 		xmlNodePtr privilege_set_node;
4298 
4299 		privilege_set_node = e_xml_find_child (prop_node, E_WEBDAV_NS_DAV, "current-user-privilege-set");
4300 
4301 		if (privilege_set_node) {
4302 			xmlNodePtr privilege_node;
4303 
4304 			psd->any_found = TRUE;
4305 
4306 			for (privilege_node = e_xml_find_child (privilege_set_node, E_WEBDAV_NS_DAV, "privilege");
4307 			     privilege_node;
4308 			     privilege_node = e_xml_find_next_sibling (privilege_node, E_WEBDAV_NS_DAV, "privilege")) {
4309 				e_webdav_session_extract_privilege_simple (privilege_node, psd->out_privileges);
4310 			}
4311 		}
4312 	}
4313 
4314 	return TRUE;
4315 }
4316 
4317 /**
4318  * e_webdav_session_get_current_user_privilege_set_sync:
4319  * @webdav: an #EWebDAVSession
4320  * @uri: (nullable): URI to issue the request for, or %NULL to read from #ESource
4321  * @out_privileges: (out) (transfer full) (element-type EWebDAVPrivilege): return location for a %GSList of #EWebDAVPrivilege
4322  * @cancellable: optional #GCancellable object, or %NULL
4323  * @error: return location for a #GError, or %NULL
4324  *
4325  * Gets current user privileges for the @uri, or, in case it's %NULL, for the URI
4326  * defined in associated #ESource.
4327  *
4328  * Free the returned @out_privileges with
4329  * g_slist_free_full (privileges, e_webdav_privilege_free);
4330  * when no longer needed.
4331  *
4332  * Returns: Whether succeeded.
4333  *
4334  * Since: 3.26
4335  **/
4336 gboolean
e_webdav_session_get_current_user_privilege_set_sync(EWebDAVSession * webdav,const gchar * uri,GSList ** out_privileges,GCancellable * cancellable,GError ** error)4337 e_webdav_session_get_current_user_privilege_set_sync (EWebDAVSession *webdav,
4338 						      const gchar *uri,
4339 						      GSList **out_privileges,
4340 						      GCancellable *cancellable,
4341 						      GError **error)
4342 {
4343 	EXmlDocument *xml;
4344 	PrivilegeSetData psd;
4345 	gboolean success;
4346 
4347 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
4348 	g_return_val_if_fail (out_privileges != NULL, FALSE);
4349 
4350 	*out_privileges = NULL;
4351 
4352 	xml = e_xml_document_new (E_WEBDAV_NS_DAV, "propfind");
4353 	g_return_val_if_fail (xml != NULL, FALSE);
4354 
4355 	e_xml_document_start_element (xml, NULL, "prop");
4356 	e_xml_document_add_empty_element (xml, NULL, "current-user-privilege-set");
4357 	e_xml_document_end_element (xml); /* prop */
4358 
4359 	psd.any_found = FALSE;
4360 	psd.out_privileges = out_privileges;
4361 
4362 	success = e_webdav_session_propfind_sync (webdav, uri, E_WEBDAV_DEPTH_THIS, xml,
4363 		e_webdav_session_current_user_privilege_set_cb, &psd, cancellable, error);
4364 
4365 	g_object_unref (xml);
4366 
4367 	if (success && !psd.any_found) {
4368 		success = FALSE;
4369 		g_set_error_literal (error, SOUP_HTTP_ERROR, SOUP_STATUS_NOT_FOUND, soup_status_get_phrase (SOUP_STATUS_NOT_FOUND));
4370 	} else if (success) {
4371 		*out_privileges = g_slist_reverse (*out_privileges);
4372 	}
4373 
4374 	return success;
4375 }
4376 
4377 static gboolean
e_webdav_session_has_one_children(xmlNodePtr parent_node)4378 e_webdav_session_has_one_children (xmlNodePtr parent_node)
4379 {
4380 	xmlNodePtr node;
4381 	gint subelements = 0;
4382 
4383 	if (!parent_node)
4384 		return FALSE;
4385 
4386 	for (node = parent_node->children; node && subelements <= 1; node = node->next) {
4387 		if (node->type == XML_ELEMENT_NODE)
4388 			subelements++;
4389 	}
4390 
4391 	return subelements == 1;
4392 }
4393 
4394 static EWebDAVACEPrincipalKind
e_webdav_session_extract_acl_principal(xmlNodePtr principal_node,gchar ** out_principal_href,GSList ** out_principal_hrefs)4395 e_webdav_session_extract_acl_principal (xmlNodePtr principal_node,
4396 					gchar **out_principal_href,
4397 					GSList **out_principal_hrefs)
4398 {
4399 	xmlNodePtr node;
4400 
4401 	if (!principal_node)
4402 		return E_WEBDAV_ACE_PRINCIPAL_UNKNOWN;
4403 
4404 	g_return_val_if_fail (out_principal_href != NULL || out_principal_hrefs != NULL, E_WEBDAV_ACE_PRINCIPAL_UNKNOWN);
4405 
4406 	if (out_principal_href)
4407 		*out_principal_href = NULL;
4408 
4409 	if (out_principal_hrefs)
4410 		*out_principal_hrefs = NULL;
4411 
4412 	node = e_xml_find_child (principal_node, E_WEBDAV_NS_DAV, "href");
4413 
4414 	if (node) {
4415 		const xmlChar *href;
4416 
4417 		href = e_xml_get_node_text (node);
4418 
4419 		if (out_principal_href) {
4420 			*out_principal_href = (href && *href) ? g_strdup ((const gchar *) href) : NULL;
4421 		} else {
4422 			for (; node; node = e_xml_find_next_sibling (node, E_WEBDAV_NS_DAV, "href")) {
4423 
4424 				href = e_xml_get_node_text (node);
4425 
4426 				if (href && *href)
4427 					*out_principal_hrefs = g_slist_prepend (*out_principal_hrefs, g_strdup ((const gchar *) href));
4428 			}
4429 
4430 			*out_principal_hrefs = g_slist_reverse (*out_principal_hrefs);
4431 		}
4432 
4433 		return E_WEBDAV_ACE_PRINCIPAL_HREF;
4434 	}
4435 
4436 	if (e_xml_find_child (principal_node, E_WEBDAV_NS_DAV, "all"))
4437 		return E_WEBDAV_ACE_PRINCIPAL_ALL;
4438 
4439 	if (e_xml_find_child (principal_node, E_WEBDAV_NS_DAV, "authenticated"))
4440 		return E_WEBDAV_ACE_PRINCIPAL_AUTHENTICATED;
4441 
4442 	if (e_xml_find_child (principal_node, E_WEBDAV_NS_DAV, "unauthenticated"))
4443 		return E_WEBDAV_ACE_PRINCIPAL_UNAUTHENTICATED;
4444 
4445 	if (e_xml_find_child (principal_node, E_WEBDAV_NS_DAV, "self"))
4446 		return E_WEBDAV_ACE_PRINCIPAL_SELF;
4447 
4448 	node = e_xml_find_child (principal_node, E_WEBDAV_NS_DAV, "property");
4449 
4450 	if (node) {
4451 		/* No details read about what properties */
4452 		EWebDAVACEPrincipalKind kind = E_WEBDAV_ACE_PRINCIPAL_PROPERTY;
4453 
4454 		/* Special-case owner */
4455 		if (e_xml_find_child (node, E_WEBDAV_NS_DAV, "owner")) {
4456 			/* DAV:owner is the only child and there is only one DAV:property child of the DAV:principal */
4457 			if (e_webdav_session_has_one_children (node) &&
4458 			    e_webdav_session_has_one_children (principal_node)) {
4459 				kind = E_WEBDAV_ACE_PRINCIPAL_OWNER;
4460 			}
4461 		}
4462 
4463 		return kind;
4464 	}
4465 
4466 	return E_WEBDAV_ACE_PRINCIPAL_UNKNOWN;
4467 }
4468 
4469 static gboolean
e_webdav_session_acl_cb(EWebDAVSession * webdav,xmlNodePtr prop_node,const SoupURI * request_uri,const gchar * href,guint status_code,gpointer user_data)4470 e_webdav_session_acl_cb (EWebDAVSession *webdav,
4471 			 xmlNodePtr prop_node,
4472 			 const SoupURI *request_uri,
4473 			 const gchar *href,
4474 			 guint status_code,
4475 			 gpointer user_data)
4476 {
4477 	GSList **out_entries = user_data;
4478 	xmlNodePtr acl_node, ace_node;
4479 
4480 	g_return_val_if_fail (prop_node != NULL, FALSE);
4481 	g_return_val_if_fail (out_entries != NULL, FALSE);
4482 
4483 	if (status_code != SOUP_STATUS_OK)
4484 		return TRUE;
4485 
4486 
4487 	acl_node = e_xml_find_child (prop_node, E_WEBDAV_NS_DAV, "acl");
4488 
4489 	if (acl_node) {
4490 		for (ace_node = e_xml_find_child (acl_node, E_WEBDAV_NS_DAV, "ace");
4491 		     ace_node;
4492 		     ace_node = e_xml_find_next_sibling (ace_node, E_WEBDAV_NS_DAV, "ace")) {
4493 			EWebDAVACEPrincipalKind principal_kind = E_WEBDAV_ACE_PRINCIPAL_UNKNOWN;
4494 			xmlNodePtr node;
4495 			gchar *principal_href = NULL;
4496 			guint32 flags = E_WEBDAV_ACE_FLAG_UNKNOWN;
4497 			gchar *inherited_href = NULL;
4498 
4499 			node = e_xml_find_child (ace_node, E_WEBDAV_NS_DAV, "invert");
4500 
4501 			if (node) {
4502 				flags |= E_WEBDAV_ACE_FLAG_INVERT;
4503 
4504 				principal_kind = e_webdav_session_extract_acl_principal (e_xml_find_child (node, E_WEBDAV_NS_DAV, "principal"), &principal_href, NULL);
4505 			} else {
4506 				principal_kind = e_webdav_session_extract_acl_principal (e_xml_find_child (ace_node, E_WEBDAV_NS_DAV, "principal"), &principal_href, NULL);
4507 			}
4508 
4509 			if (principal_kind == E_WEBDAV_ACE_PRINCIPAL_UNKNOWN)
4510 				continue;
4511 
4512 			if (e_xml_find_child (ace_node, E_WEBDAV_NS_DAV, "protected"))
4513 				flags |= E_WEBDAV_ACE_FLAG_PROTECTED;
4514 
4515 			node = e_xml_find_in_hierarchy (ace_node, E_WEBDAV_NS_DAV, "inherited", E_WEBDAV_NS_DAV, "href", NULL, NULL);
4516 
4517 			if (node) {
4518 				flags |= E_WEBDAV_ACE_FLAG_INHERITED;
4519 				inherited_href = g_strdup ((const gchar *) e_xml_get_node_text (node));
4520 			}
4521 
4522 			node = e_xml_find_child (ace_node, E_WEBDAV_NS_DAV, "grant");
4523 
4524 			if (node) {
4525 				flags |= E_WEBDAV_ACE_FLAG_GRANT;
4526 			} else {
4527 				node = e_xml_find_child (ace_node, E_WEBDAV_NS_DAV, "deny");
4528 
4529 				if (node)
4530 					flags |= E_WEBDAV_ACE_FLAG_DENY;
4531 			}
4532 
4533 			if (node) {
4534 				EWebDAVAccessControlEntry *ace;
4535 
4536 				ace = e_webdav_access_control_entry_new	(principal_kind, principal_href, flags, inherited_href);
4537 
4538 				if (ace) {
4539 					for (node = e_xml_find_child (node, E_WEBDAV_NS_DAV, "privilege");
4540 					     node;
4541 					     node = e_xml_find_next_sibling (node, E_WEBDAV_NS_DAV, "privilege")) {
4542 						e_webdav_session_extract_privilege_simple (node, &ace->privileges);
4543 					}
4544 
4545 					ace->privileges = g_slist_reverse (ace->privileges);
4546 
4547 					*out_entries = g_slist_prepend (*out_entries, ace);
4548 				}
4549 			}
4550 
4551 			g_free (principal_href);
4552 			g_free (inherited_href);
4553 		}
4554 	}
4555 
4556 	return TRUE;
4557 }
4558 
4559 /**
4560  * e_webdav_session_get_acl_sync:
4561  * @webdav: an #EWebDAVSession
4562  * @uri: (nullable): URI to issue the request for, or %NULL to read from #ESource
4563  * @out_entries: (out) (transfer full) (element-type EWebDAVAccessControlEntry): return location for a #GSList of #EWebDAVAccessControlEntry
4564  * @cancellable: optional #GCancellable object, or %NULL
4565  * @error: return location for a #GError, or %NULL
4566  *
4567  * Gets Access Control List (ACL) for the @uri, or, in case it's %NULL, for the URI
4568  * defined in associated #ESource.
4569  *
4570  * This function doesn't read general #E_WEBDAV_ACE_PRINCIPAL_PROPERTY.
4571  *
4572  * Free the returned @out_entries with
4573  * g_slist_free_full (entries, e_webdav_access_control_entry_free);
4574  * when no longer needed.
4575  *
4576  * Returns: Whether succeeded.
4577  *
4578  * Since: 3.26
4579  **/
4580 gboolean
e_webdav_session_get_acl_sync(EWebDAVSession * webdav,const gchar * uri,GSList ** out_entries,GCancellable * cancellable,GError ** error)4581 e_webdav_session_get_acl_sync (EWebDAVSession *webdav,
4582 			       const gchar *uri,
4583 			       GSList **out_entries,
4584 			       GCancellable *cancellable,
4585 			       GError **error)
4586 {
4587 	EXmlDocument *xml;
4588 	gboolean success;
4589 
4590 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
4591 	g_return_val_if_fail (out_entries != NULL, FALSE);
4592 
4593 	*out_entries = NULL;
4594 
4595 	xml = e_xml_document_new (E_WEBDAV_NS_DAV, "propfind");
4596 	g_return_val_if_fail (xml != NULL, FALSE);
4597 
4598 	e_xml_document_start_element (xml, NULL, "prop");
4599 	e_xml_document_add_empty_element (xml, NULL, "acl");
4600 	e_xml_document_end_element (xml); /* prop */
4601 
4602 	success = e_webdav_session_propfind_sync (webdav, uri, E_WEBDAV_DEPTH_THIS, xml,
4603 		e_webdav_session_acl_cb, out_entries, cancellable, error);
4604 
4605 	g_object_unref (xml);
4606 
4607 	if (success)
4608 		*out_entries = g_slist_reverse (*out_entries);
4609 
4610 	return success;
4611 }
4612 
4613 typedef struct _ACLRestrictionsData {
4614 	guint32 *out_restrictions;
4615 	EWebDAVACEPrincipalKind *out_principal_kind;
4616 	GSList **out_principal_hrefs;
4617 } ACLRestrictionsData;
4618 
4619 static gboolean
e_webdav_session_acl_restrictions_cb(EWebDAVSession * webdav,xmlNodePtr prop_node,const SoupURI * request_uri,const gchar * href,guint status_code,gpointer user_data)4620 e_webdav_session_acl_restrictions_cb (EWebDAVSession *webdav,
4621 				      xmlNodePtr prop_node,
4622 				      const SoupURI *request_uri,
4623 				      const gchar *href,
4624 				      guint status_code,
4625 				      gpointer user_data)
4626 {
4627 	ACLRestrictionsData *ard = user_data;
4628 	xmlNodePtr acl_restrictions_node;
4629 
4630 	g_return_val_if_fail (prop_node != NULL, FALSE);
4631 	g_return_val_if_fail (ard != NULL, FALSE);
4632 
4633 	if (status_code != SOUP_STATUS_OK)
4634 		return TRUE;
4635 
4636 	acl_restrictions_node = e_xml_find_child (prop_node, E_WEBDAV_NS_DAV, "acl-restrictions");
4637 
4638 	if (acl_restrictions_node) {
4639 		xmlNodePtr required_principal;
4640 
4641 		if (e_xml_find_child (acl_restrictions_node, E_WEBDAV_NS_DAV, "grant-only"))
4642 			*ard->out_restrictions |= E_WEBDAV_ACL_RESTRICTION_GRANT_ONLY;
4643 
4644 		if (e_xml_find_child (acl_restrictions_node, E_WEBDAV_NS_DAV, "no-invert"))
4645 			*ard->out_restrictions |= E_WEBDAV_ACL_RESTRICTION_NO_INVERT;
4646 
4647 		if (e_xml_find_child (acl_restrictions_node, E_WEBDAV_NS_DAV, "deny-before-grant"))
4648 			*ard->out_restrictions |= E_WEBDAV_ACL_RESTRICTION_DENY_BEFORE_GRANT;
4649 
4650 		required_principal = e_xml_find_child (acl_restrictions_node, E_WEBDAV_NS_DAV, "required-principal");
4651 
4652 		if (required_principal) {
4653 			*ard->out_restrictions |= E_WEBDAV_ACL_RESTRICTION_REQUIRED_PRINCIPAL;
4654 			*ard->out_principal_kind = e_webdav_session_extract_acl_principal (required_principal, NULL, ard->out_principal_hrefs);
4655 		}
4656 	}
4657 
4658 	return TRUE;
4659 }
4660 
4661 /**
4662  * e_webdav_session_get_acl_restrictions_sync:
4663  * @webdav: an #EWebDAVSession
4664  * @uri: (nullable): URI to issue the request for, or %NULL to read from #ESource
4665  * @out_restrictions: (out): return location for bit-or of #EWebDAVACLRestrictions
4666  * @out_principal_kind: (out): return location for principal kind
4667  * @out_principal_hrefs: (out) (transfer full) (element-type utf8): return location for a #GSList of principal href-s
4668  * @cancellable: optional #GCancellable object, or %NULL
4669  * @error: return location for a #GError, or %NULL
4670  *
4671  * Gets Access Control List (ACL) restrictions for the @uri, or, in case it's %NULL,
4672  * for the URI defined in associated #ESource. The @out_principal_kind is valid only
4673  * if the @out_restrictions contains #E_WEBDAV_ACL_RESTRICTION_REQUIRED_PRINCIPAL.
4674  * The @out_principal_hrefs is valid only if the @out_principal_kind is valid and when
4675  * it is #E_WEBDAV_ACE_PRINCIPAL_HREF.
4676  *
4677  * Free the returned @out_principal_hrefs with
4678  * g_slist_free_full (entries, g_free);
4679  * when no longer needed.
4680  *
4681  * Returns: Whether succeeded.
4682  *
4683  * Since: 3.26
4684  **/
4685 gboolean
e_webdav_session_get_acl_restrictions_sync(EWebDAVSession * webdav,const gchar * uri,guint32 * out_restrictions,EWebDAVACEPrincipalKind * out_principal_kind,GSList ** out_principal_hrefs,GCancellable * cancellable,GError ** error)4686 e_webdav_session_get_acl_restrictions_sync (EWebDAVSession *webdav,
4687 					    const gchar *uri,
4688 					    guint32 *out_restrictions,
4689 					    EWebDAVACEPrincipalKind *out_principal_kind,
4690 					    GSList **out_principal_hrefs,
4691 					    GCancellable *cancellable,
4692 					    GError **error)
4693 {
4694 	ACLRestrictionsData ard;
4695 	EXmlDocument *xml;
4696 	gboolean success;
4697 
4698 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
4699 	g_return_val_if_fail (out_restrictions != NULL, FALSE);
4700 	g_return_val_if_fail (out_principal_kind != NULL, FALSE);
4701 	g_return_val_if_fail (out_principal_hrefs != NULL, FALSE);
4702 
4703 	*out_restrictions = E_WEBDAV_ACL_RESTRICTION_NONE;
4704 	*out_principal_kind = E_WEBDAV_ACE_PRINCIPAL_UNKNOWN;
4705 	*out_principal_hrefs = NULL;
4706 
4707 	xml = e_xml_document_new (E_WEBDAV_NS_DAV, "propfind");
4708 	g_return_val_if_fail (xml != NULL, FALSE);
4709 
4710 	e_xml_document_start_element (xml, NULL, "prop");
4711 	e_xml_document_add_empty_element (xml, NULL, "acl-restrictions");
4712 	e_xml_document_end_element (xml); /* prop */
4713 
4714 	ard.out_restrictions = out_restrictions;
4715 	ard.out_principal_kind = out_principal_kind;
4716 	ard.out_principal_hrefs = out_principal_hrefs;
4717 
4718 	success = e_webdav_session_propfind_sync (webdav, uri, E_WEBDAV_DEPTH_THIS, xml,
4719 		e_webdav_session_acl_restrictions_cb, &ard, cancellable, error);
4720 
4721 	g_object_unref (xml);
4722 
4723 	return success;
4724 }
4725 
4726 static gboolean
e_webdav_session_principal_collection_set_cb(EWebDAVSession * webdav,xmlNodePtr prop_node,const SoupURI * request_uri,const gchar * href,guint status_code,gpointer user_data)4727 e_webdav_session_principal_collection_set_cb (EWebDAVSession *webdav,
4728 					      xmlNodePtr prop_node,
4729 					      const SoupURI *request_uri,
4730 					      const gchar *href,
4731 					      guint status_code,
4732 					      gpointer user_data)
4733 {
4734 	GSList **out_principal_hrefs = user_data;
4735 	xmlNodePtr principal_collection_set;
4736 
4737 	g_return_val_if_fail (prop_node != NULL, FALSE);
4738 	g_return_val_if_fail (out_principal_hrefs != NULL, FALSE);
4739 
4740 	if (status_code != SOUP_STATUS_OK)
4741 		return TRUE;
4742 
4743 	principal_collection_set = e_xml_find_child (prop_node, E_WEBDAV_NS_DAV, "principal-collection-set");
4744 
4745 	if (principal_collection_set) {
4746 		xmlNodePtr node;
4747 
4748 		for (node = e_xml_find_child (principal_collection_set, E_WEBDAV_NS_DAV, "href");
4749 		     node;
4750 		     node = e_xml_find_next_sibling (node, E_WEBDAV_NS_DAV, "href")) {
4751 			const xmlChar *got_href;
4752 
4753 			got_href = e_xml_get_node_text (node);
4754 
4755 			if (got_href && *got_href)
4756 				*out_principal_hrefs = g_slist_prepend (*out_principal_hrefs, g_strdup ((const gchar *) got_href));
4757 		}
4758 	}
4759 
4760 	return TRUE;
4761 }
4762 
4763 /**
4764  * e_webdav_session_get_principal_collection_set_sync:
4765  * @webdav: an #EWebDAVSession
4766  * @uri: (nullable): URI to issue the request for, or %NULL to read from #ESource
4767  * @out_principal_hrefs: (out) (transfer full) (element-type utf8): return location for a #GSList of principal href-s
4768  * @cancellable: optional #GCancellable object, or %NULL
4769  * @error: return location for a #GError, or %NULL
4770  *
4771  * Gets list of principal collection href for the @uri, or, in case it's %NULL,
4772  * for the URI defined in associated #ESource. The @out_principal_hrefs are root
4773  * collections that contain the principals that are available on the server that
4774  * implements this resource.
4775  *
4776  * Free the returned @out_principal_hrefs with
4777  * g_slist_free_full (entries, g_free);
4778  * when no longer needed.
4779  *
4780  * Returns: Whether succeeded.
4781  *
4782  * Since: 3.26
4783  **/
4784 gboolean
e_webdav_session_get_principal_collection_set_sync(EWebDAVSession * webdav,const gchar * uri,GSList ** out_principal_hrefs,GCancellable * cancellable,GError ** error)4785 e_webdav_session_get_principal_collection_set_sync (EWebDAVSession *webdav,
4786 						    const gchar *uri,
4787 						    GSList **out_principal_hrefs, /* gchar * */
4788 						    GCancellable *cancellable,
4789 						    GError **error)
4790 {
4791 	EXmlDocument *xml;
4792 	gboolean success;
4793 
4794 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
4795 	g_return_val_if_fail (out_principal_hrefs != NULL, FALSE);
4796 
4797 	*out_principal_hrefs = NULL;
4798 
4799 	xml = e_xml_document_new (E_WEBDAV_NS_DAV, "propfind");
4800 	g_return_val_if_fail (xml != NULL, FALSE);
4801 
4802 	e_xml_document_start_element (xml, NULL, "prop");
4803 	e_xml_document_add_empty_element (xml, NULL, "principal-collection-set");
4804 	e_xml_document_end_element (xml); /* prop */
4805 
4806 	success = e_webdav_session_propfind_sync (webdav, uri, E_WEBDAV_DEPTH_THIS, xml,
4807 		e_webdav_session_principal_collection_set_cb, out_principal_hrefs, cancellable, error);
4808 
4809 	g_object_unref (xml);
4810 
4811 	if (success)
4812 		*out_principal_hrefs = g_slist_reverse (*out_principal_hrefs);
4813 
4814 	return success;
4815 }
4816 
4817 /**
4818  * e_webdav_session_set_acl_sync:
4819  * @webdav: an #EWebDAVSession
4820  * @uri: (nullable): URI to issue the request for, or %NULL to read from #ESource
4821  * @entries: (element-type EWebDAVAccessControlEntry): entries to write
4822  * @cancellable: optional #GCancellable object, or %NULL
4823  * @error: return location for a #GError, or %NULL
4824  *
4825  * Changes Access Control List (ACL) for the @uri, or, in case it's %NULL,
4826  * for the URI defined in associated #ESource.
4827  *
4828  * Make sure that the @entries satisfy ACL restrictions, as returned
4829  * by e_webdav_session_get_acl_restrictions_sync(). The order in the @entries
4830  * is preserved. It cannot contain any %E_WEBDAV_ACE_FLAG_PROTECTED,
4831  * nor @E_WEBDAV_ACE_FLAG_INHERITED, items.
4832  *
4833  * Use e_webdav_session_get_acl_sync() to read currently known ACL entries,
4834  * remove from the list those protected and inherited, and then modify
4835  * the rest with the required changed.
4836  *
4837  * Note this function doesn't support general %E_WEBDAV_ACE_PRINCIPAL_PROPERTY and
4838  * returns %G_IO_ERROR_NOT_SUPPORTED error when any such is tried to be written.
4839  *
4840  * In case the returned entries contain any %E_WEBDAV_ACE_PRINCIPAL_PROPERTY,
4841  * or there's a need to write such Access Control Entry, then do not use
4842  * e_webdav_session_get_acl_sync(), neither e_webdav_session_set_acl_sync(),
4843  * and write more generic implementation.
4844  *
4845  * Returns: Whether succeeded.
4846  *
4847  * Since: 3.26
4848  **/
4849 gboolean
e_webdav_session_set_acl_sync(EWebDAVSession * webdav,const gchar * uri,const GSList * entries,GCancellable * cancellable,GError ** error)4850 e_webdav_session_set_acl_sync (EWebDAVSession *webdav,
4851 			       const gchar *uri,
4852 			       const GSList *entries,
4853 			       GCancellable *cancellable,
4854 			       GError **error)
4855 {
4856 	EXmlDocument *xml;
4857 	GSList *link, *plink;
4858 	gboolean success;
4859 
4860 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
4861 	g_return_val_if_fail (entries != NULL, FALSE);
4862 
4863 	xml = e_xml_document_new (E_WEBDAV_NS_DAV, "acl");
4864 	g_return_val_if_fail (xml != NULL, FALSE);
4865 
4866 	for (link = (GSList *) entries; link; link = g_slist_next (link)) {
4867 		EWebDAVAccessControlEntry *ace = link->data;
4868 
4869 		if (!ace) {
4870 			g_warn_if_fail (ace != NULL);
4871 			g_object_unref (xml);
4872 			return FALSE;
4873 		}
4874 
4875 		if ((ace->flags & E_WEBDAV_ACE_FLAG_PROTECTED) != 0 ||
4876 		    (ace->flags & E_WEBDAV_ACE_FLAG_INHERITED) != 0) {
4877 			g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
4878 				_("Cannot store protected nor inherited Access Control Entry."));
4879 			g_object_unref (xml);
4880 			return FALSE;
4881 		}
4882 
4883 		if (ace->principal_kind == E_WEBDAV_ACE_PRINCIPAL_UNKNOWN) {
4884 			g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
4885 				_("Provided invalid principal kind for Access Control Entry."));
4886 			g_object_unref (xml);
4887 			return FALSE;
4888 		}
4889 
4890 		if (ace->principal_kind == E_WEBDAV_ACE_PRINCIPAL_PROPERTY) {
4891 			g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
4892 				_("Cannot store property-based Access Control Entry."));
4893 			g_object_unref (xml);
4894 			return FALSE;
4895 		}
4896 
4897 		if ((ace->flags & (E_WEBDAV_ACE_FLAG_GRANT | E_WEBDAV_ACE_FLAG_DENY)) == 0) {
4898 			g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
4899 				_("Access Control Entry can be only to Grant or Deny, but not None."));
4900 			g_object_unref (xml);
4901 			return FALSE;
4902 		}
4903 
4904 		if ((ace->flags & E_WEBDAV_ACE_FLAG_GRANT) != 0 &&
4905 		    (ace->flags & E_WEBDAV_ACE_FLAG_DENY) != 0) {
4906 			g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
4907 				_("Access Control Entry can be only to Grant or Deny, but not both."));
4908 			g_object_unref (xml);
4909 			return FALSE;
4910 		}
4911 
4912 		e_xml_document_start_element (xml, NULL, "ace");
4913 
4914 		if ((ace->flags & E_WEBDAV_ACE_FLAG_INVERT) != 0)
4915 			e_xml_document_start_element (xml, NULL, "invert");
4916 
4917 		e_xml_document_start_element (xml, NULL, "principal");
4918 		switch (ace->principal_kind) {
4919 		case E_WEBDAV_ACE_PRINCIPAL_UNKNOWN:
4920 			g_warn_if_reached ();
4921 			break;
4922 		case E_WEBDAV_ACE_PRINCIPAL_HREF:
4923 			e_xml_document_start_text_element (xml, NULL, "href");
4924 			e_xml_document_write_string (xml, ace->principal_href);
4925 			e_xml_document_end_element (xml);
4926 			break;
4927 		case E_WEBDAV_ACE_PRINCIPAL_ALL:
4928 			e_xml_document_add_empty_element (xml, NULL, "all");
4929 			break;
4930 		case E_WEBDAV_ACE_PRINCIPAL_AUTHENTICATED:
4931 			e_xml_document_add_empty_element (xml, NULL, "authenticated");
4932 			break;
4933 		case E_WEBDAV_ACE_PRINCIPAL_UNAUTHENTICATED:
4934 			e_xml_document_add_empty_element (xml, NULL, "unauthenticated");
4935 			break;
4936 		case E_WEBDAV_ACE_PRINCIPAL_PROPERTY:
4937 			g_warn_if_reached ();
4938 			break;
4939 		case E_WEBDAV_ACE_PRINCIPAL_SELF:
4940 			e_xml_document_add_empty_element (xml, NULL, "self");
4941 			break;
4942 		case E_WEBDAV_ACE_PRINCIPAL_OWNER:
4943 			e_xml_document_start_element (xml, NULL, "property");
4944 			e_xml_document_add_empty_element (xml, NULL, "owner");
4945 			e_xml_document_end_element (xml);
4946 			break;
4947 
4948 		}
4949 		e_xml_document_end_element (xml); /* principal */
4950 
4951 		if ((ace->flags & E_WEBDAV_ACE_FLAG_INVERT) != 0)
4952 			e_xml_document_end_element (xml); /* invert */
4953 
4954 		if ((ace->flags & E_WEBDAV_ACE_FLAG_GRANT) != 0)
4955 			e_xml_document_start_element (xml, NULL, "grant");
4956 		else if ((ace->flags & E_WEBDAV_ACE_FLAG_DENY) != 0)
4957 			e_xml_document_start_element (xml, NULL, "deny");
4958 		else
4959 			g_warn_if_reached ();
4960 
4961 		for (plink = ace->privileges; plink; plink = g_slist_next (plink)) {
4962 			EWebDAVPrivilege *privilege = plink->data;
4963 
4964 			if (!privilege) {
4965 				g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
4966 					_("Access Control Entry privilege cannot be NULL."));
4967 				g_object_unref (xml);
4968 				return FALSE;
4969 			}
4970 
4971 			e_xml_document_start_element (xml, NULL, "privilege");
4972 			e_xml_document_add_empty_element (xml, privilege->ns_uri, privilege->name);
4973 			e_xml_document_end_element (xml); /* privilege */
4974 		}
4975 
4976 		e_xml_document_end_element (xml); /* grant or deny */
4977 
4978 		e_xml_document_end_element (xml); /* ace */
4979 	}
4980 
4981 	success = e_webdav_session_acl_sync (webdav, uri, xml, cancellable, error);
4982 
4983 	g_object_unref (xml);
4984 
4985 	return success;
4986 }
4987 
4988 static gboolean
e_webdav_session_principal_property_search_cb(EWebDAVSession * webdav,xmlNodePtr prop_node,const SoupURI * request_uri,const gchar * href,guint status_code,gpointer user_data)4989 e_webdav_session_principal_property_search_cb (EWebDAVSession *webdav,
4990 					       xmlNodePtr prop_node,
4991 					       const SoupURI *request_uri,
4992 					       const gchar *href,
4993 					       guint status_code,
4994 					       gpointer user_data)
4995 {
4996 	GSList **out_principals = user_data;
4997 	EWebDAVResource *resource;
4998 	gchar *display_name;
4999 
5000 	g_return_val_if_fail (out_principals != NULL, FALSE);
5001 
5002 	if (status_code != SOUP_STATUS_OK)
5003 		return TRUE;
5004 
5005 	display_name = e_webdav_session_extract_nonempty (prop_node, E_WEBDAV_NS_DAV, "displayname", NULL, NULL);
5006 
5007 	resource = e_webdav_resource_new (
5008 		E_WEBDAV_RESOURCE_KIND_PRINCIPAL,
5009 		0, /* supports */
5010 		href,
5011 		NULL, /* etag */
5012 		NULL, /* display_name */
5013 		NULL, /* content_type */
5014 		0, /* content_length */
5015 		0, /* creation_date */
5016 		0, /* last_modified */
5017 		NULL, /* description */
5018 		NULL, /* color */
5019 		(guint) -1); /* order */
5020 	resource->display_name = display_name;
5021 
5022 	*out_principals = g_slist_prepend (*out_principals, resource);
5023 
5024 	return TRUE;
5025 }
5026 
5027 /**
5028  * e_webdav_session_principal_property_search_sync:
5029  * @webdav: an #EWebDAVSession
5030  * @uri: (nullable): URI to issue the request for, or %NULL to read from #ESource
5031  * @apply_to_principal_collection_set: whether to apply to principal-collection-set
5032  * @match_ns_uri: (nullable): namespace URI of the property to search in, or %NULL for %E_WEBDAV_NS_DAV
5033  * @match_property: name of the property to search in
5034  * @match_value: a string value to search for
5035  * @out_principals: (out) (transfer full) (element-type EWebDAVResource): return location for matching principals
5036  * @cancellable: optional #GCancellable object, or %NULL
5037  * @error: return location for a #GError, or %NULL
5038  *
5039  * Issues a DAV:principal-property-search for the @uri, or, in case it's %NULL,
5040  * for the URI defined in associated #ESource. The DAV:principal-property-search
5041  * performs a search for all principals whose properties contain character data
5042  * that matches the search criteria @match_value in @match_property property
5043  * of namespace @match_ns_uri.
5044  *
5045  * By default, the function searches all members (at any depth) of the collection
5046  * identified by the @uri. If @apply_to_principal_collection_set is set to %TRUE,
5047  * the search is applied instead to each collection returned by
5048  * e_webdav_session_get_principal_collection_set_sync() for the @uri.
5049  *
5050  * The @out_principals is a #GSList of #EWebDAVResource, where the kind
5051  * is set to %E_WEBDAV_RESOURCE_KIND_PRINCIPAL and only href with displayname
5052  * are filled. All other members of #EWebDAVResource are not set.
5053  *
5054  * Free the returned @out_principals with
5055  * g_slist_free_full (principals, e_webdav_resource_free);
5056  * when no longer needed.
5057  *
5058  * Returns: Whether succeeded. Note it can report success also when no matching
5059  *    principal had been found.
5060  *
5061  * Since: 3.26
5062  **/
5063 gboolean
e_webdav_session_principal_property_search_sync(EWebDAVSession * webdav,const gchar * uri,gboolean apply_to_principal_collection_set,const gchar * match_ns_uri,const gchar * match_property,const gchar * match_value,GSList ** out_principals,GCancellable * cancellable,GError ** error)5064 e_webdav_session_principal_property_search_sync (EWebDAVSession *webdav,
5065 						 const gchar *uri,
5066 						 gboolean apply_to_principal_collection_set,
5067 						 const gchar *match_ns_uri,
5068 						 const gchar *match_property,
5069 						 const gchar *match_value,
5070 						 GSList **out_principals,
5071 						 GCancellable *cancellable,
5072 						 GError **error)
5073 {
5074 	EXmlDocument *xml;
5075 	gboolean success;
5076 
5077 	g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
5078 	g_return_val_if_fail (match_property != NULL, FALSE);
5079 	g_return_val_if_fail (match_value != NULL, FALSE);
5080 	g_return_val_if_fail (out_principals != NULL, FALSE);
5081 
5082 	*out_principals = NULL;
5083 
5084 	xml = e_xml_document_new (E_WEBDAV_NS_DAV, "principal-property-search");
5085 	g_return_val_if_fail (xml != NULL, FALSE);
5086 
5087 	if (apply_to_principal_collection_set) {
5088 		e_xml_document_add_empty_element (xml, NULL, "apply-to-principal-collection-set");
5089 	}
5090 
5091 	e_xml_document_start_element (xml, NULL, "property-search");
5092 	e_xml_document_start_element (xml, NULL, "prop");
5093 	e_xml_document_add_empty_element (xml, match_ns_uri, match_property);
5094 	e_xml_document_end_element (xml); /* prop */
5095 	e_xml_document_start_text_element (xml, NULL, "match");
5096 	e_xml_document_write_string (xml, match_value);
5097 	e_xml_document_end_element (xml); /* match */
5098 	e_xml_document_end_element (xml); /* property-search */
5099 
5100 	e_xml_document_start_element (xml, NULL, "prop");
5101 	e_xml_document_add_empty_element (xml, NULL, "displayname");
5102 	e_xml_document_end_element (xml); /* prop */
5103 
5104 	success = e_webdav_session_report_sync (webdav, uri, E_WEBDAV_DEPTH_THIS, xml,
5105 		e_webdav_session_principal_property_search_cb, out_principals, NULL, NULL, cancellable, error);
5106 
5107 	g_object_unref (xml);
5108 
5109 	if (success)
5110 		*out_principals = g_slist_reverse (*out_principals);
5111 
5112 	return success;
5113 }
5114 
5115 /**
5116  * e_webdav_session_util_maybe_dequote:
5117  * @text: (inout) (nullable): text to dequote
5118  *
5119  * Dequotes @text, if it's enclosed in double-quotes. The function
5120  * changes @text, it doesn't allocate new string. The function does
5121  * nothing when the @text is not enclosed in double-quotes.
5122  *
5123  * Returns: possibly dequoted @text
5124  *
5125  * Since: 3.26
5126  **/
5127 gchar *
e_webdav_session_util_maybe_dequote(gchar * text)5128 e_webdav_session_util_maybe_dequote (gchar *text)
5129 {
5130 	gint len;
5131 
5132 	if (!text || *text != '\"')
5133 		return text;
5134 
5135 	len = strlen (text);
5136 
5137 	if (len < 2 || text[len - 1] != '\"')
5138 		return text;
5139 
5140 	memmove (text, text + 1, len - 2);
5141 	text[len - 2] = '\0';
5142 
5143 	return text;
5144 }
5145 
5146 static gboolean
e_webdav_session_free_in_traverse_cb(GNode * node,gpointer user_data)5147 e_webdav_session_free_in_traverse_cb (GNode *node,
5148 				      gpointer user_data)
5149 {
5150 	if (node) {
5151 		e_webdav_privilege_free (node->data);
5152 		node->data = NULL;
5153 	}
5154 
5155 	return FALSE;
5156 }
5157 
5158 /**
5159  * e_webdav_session_util_free_privileges:
5160  * @privileges: (nullable): a tree of #EWebDAVPrivilege structures
5161  *
5162  * Frees @privileges returned by e_webdav_session_get_supported_privilege_set_sync().
5163  * The function does nothing, if @privileges is %NULL.
5164  *
5165  * Since: 3.26
5166  **/
5167 void
e_webdav_session_util_free_privileges(GNode * privileges)5168 e_webdav_session_util_free_privileges (GNode *privileges)
5169 {
5170 	if (!privileges)
5171 		return;
5172 
5173 	g_node_traverse (privileges, G_PRE_ORDER, G_TRAVERSE_ALL, -1, e_webdav_session_free_in_traverse_cb, NULL);
5174 	g_node_destroy (privileges);
5175 }
5176 
5177 static gint
e_webdav_session_uricmp(const gchar * str1,gint len1,const gchar * str2,gint len2,gboolean case_sensitive)5178 e_webdav_session_uricmp (const gchar *str1,
5179 			 gint len1,
5180 			 const gchar *str2,
5181 			 gint len2,
5182 			 gboolean case_sensitive)
5183 {
5184 	const gchar *p1, *p2;
5185 	gchar c1, c2;
5186 
5187 	g_return_val_if_fail (str1 != NULL, -1);
5188 	g_return_val_if_fail (len1 >= 0, -1);
5189 	g_return_val_if_fail (str2 != NULL, -1);
5190 	g_return_val_if_fail (len2 >= 0, -1);
5191 
5192 	if (!len1 && !len2)
5193 		return 0;
5194 
5195 	/* Decode %-encoded letters, if needed */
5196 	#define get_next_char(str, ll, cc) G_STMT_START { \
5197 		if (!*str) { \
5198 			cc = 0; \
5199 		} else if (*str == '%' && ll >= 2 && g_ascii_isxdigit (str[1]) && g_ascii_isxdigit (str[2])) { \
5200 			cc = ((str[1] >= '0' && str[1] <= '9') ? (str[1] - '0') : \
5201 			      (str[1] >= 'a' && str[1] <= 'f') ? (str[1] - 'a' + 10) : \
5202 			      (str[1] >= 'A' && str[1] <= 'F') ? (str[1] - 'A' + 10) : 0) * 16 + \
5203 			     ((str[2] >= '0' && str[2] <= '9') ? (str[2] - '0') : \
5204 			      (str[2] >= 'a' && str[2] <= 'f') ? (str[2] - 'a' + 10) : \
5205 			      (str[2] >= 'A' && str[2] <= 'F') ? (str[2] - 'A' + 10) : 0); \
5206 			str += 3; \
5207 			ll -= 3; \
5208 		} else { \
5209 			cc = *str; \
5210 			str++; \
5211 			ll--; \
5212 		} \
5213 	} G_STMT_END
5214 
5215 	p1 = str1;
5216 	p2 = str2;
5217 
5218 	c1 = *p1;
5219 	c2 = *p2;
5220 
5221 	while (len1 > 0 && len2 > 0 && *p1 && *p2) {
5222 		get_next_char (p1, len1, c1);
5223 		get_next_char (p2, len2, c2);
5224 
5225 		if ((case_sensitive && c1 != c2) || (!case_sensitive && g_ascii_tolower (c1) != g_ascii_tolower (c2)))
5226 			return c1 - c2;
5227 	}
5228 
5229 	#undef get_next_char
5230 
5231 	if (!len1 || !*p1)
5232 		c1 = 0;
5233 
5234 	if (!len2 || !*p2)
5235 		c2 = 0;
5236 
5237 	return c1 - c2;
5238 }
5239 
5240 /**
5241  * e_webdav_session_util_item_href_equal:
5242  * @href1: the first href
5243  * @href2: the second href
5244  *
5245  * Compares two hrefs and return whether they reference
5246  * the same item on the server. The comparison is done in
5247  * a relaxed way, not considering scheme part and comparing
5248  * the host name case insensitively, while the path
5249  * case sensitively. It also ignores the username/password
5250  * information in the hostname part, if it's included.
5251  * The function doesn't decode any URI-encoded characters.
5252  *
5253  * Returns: whether the two href-s reference the same item
5254  *
5255  * Since: 3.40
5256  **/
5257 gboolean
e_webdav_session_util_item_href_equal(const gchar * href1,const gchar * href2)5258 e_webdav_session_util_item_href_equal (const gchar *href1,
5259 				       const gchar *href2)
5260 {
5261 	const gchar *ptr, *from1, *from2, *next1, *next2;
5262 
5263 	if (!href1 || !href2)
5264 		return href1 == href2;
5265 
5266 	if (g_strcmp0 (href1, href2) == 0)
5267 		return TRUE;
5268 
5269 	/* skip the scheme part */
5270 	ptr = strstr (href1, "://");
5271 	if (ptr)
5272 		href1 = ptr + 3;
5273 
5274 	ptr = strstr (href2, "://");
5275 	if (ptr)
5276 		href2 = ptr + 3;
5277 
5278 	for (from1 = href1, from2 = href2; from1 && from2; from1 = next1, from2 = next2) {
5279 		gint len1, len2;
5280 
5281 		ptr = strchr (from1, '/');
5282 		if (ptr)
5283 			ptr++;
5284 		next1 = ptr;
5285 
5286 		ptr = strchr (from2, '/');
5287 		if (ptr)
5288 			ptr++;
5289 		next2 = ptr;
5290 
5291 		if ((!next1 && next2) || (next1 && !next2))
5292 			break;
5293 
5294 		len1 = next1 ? next1 - from1 : strlen (from1);
5295 		len2 = next2 ? next2 - from2 : strlen (from2);
5296 
5297 		/* it's the hostname part */
5298 		if (from1 == href1) {
5299 			const gchar *dash;
5300 
5301 			/* ignore the username/password part */
5302 			ptr = strchr (from1, '@');
5303 			dash = strchr (from1, '/');
5304 			if (ptr && (!dash || dash > ptr)) {
5305 				len1 = len1 - (ptr - from1 + 1);
5306 				from1 = ptr + 1;
5307 			}
5308 
5309 			ptr = strchr (from2, '@');
5310 			dash = strchr (from2, '/');
5311 			if (ptr && (!dash || dash > ptr)) {
5312 				len2 = len2 - (ptr - from2 + 1);
5313 				from2 = ptr + 1;
5314 			}
5315 
5316 			if (e_webdav_session_uricmp (from1, len1, from2, len2, FALSE) != 0)
5317 				return FALSE;
5318 		} else if (e_webdav_session_uricmp (from1, len1, from2, len2, TRUE) != 0) {
5319 			return FALSE;
5320 		}
5321 	}
5322 
5323 	return !from1 && !from2;
5324 }
5325