I#27 - [IMAPx] Ignore DavMail's CR/LF in BODYSTRUCTURE response
[evolution-data-server.git] / src / libedataserver / e-webdav-session.c
blob4cb8e96ff80c0f5837858bccb72e913a69925c45
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)
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.
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.
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/>.
18 /**
19 * SECTION: e-webdav-session
20 * @include: libedataserver/libedataserver.h
21 * @short_description: A WebDAV, CalDAV and CardDAV session
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 **/
29 #include "evolution-data-server-config.h"
31 #include <stdio.h>
32 #include <glib/gi18n-lib.h>
34 #include "camel/camel.h"
36 #include "e-source-authentication.h"
37 #include "e-source-webdav.h"
38 #include "e-xml-utils.h"
40 #include "e-webdav-session.h"
42 #define BUFFER_SIZE 16384
44 struct _EWebDAVSessionPrivate {
45 gboolean dummy;
48 G_DEFINE_TYPE (EWebDAVSession, e_webdav_session, E_TYPE_SOUP_SESSION)
50 G_DEFINE_BOXED_TYPE (EWebDAVResource, e_webdav_resource, e_webdav_resource_copy, e_webdav_resource_free)
51 G_DEFINE_BOXED_TYPE (EWebDAVPropertyChange, e_webdav_property_change, e_webdav_property_change_copy, e_webdav_property_change_free)
52 G_DEFINE_BOXED_TYPE (EWebDAVPrivilege, e_webdav_privilege, e_webdav_privilege_copy, e_webdav_privilege_free)
53 G_DEFINE_BOXED_TYPE (EWebDAVAccessControlEntry, e_webdav_access_control_entry, e_webdav_access_control_entry_copy, e_webdav_access_control_entry_free)
55 /**
56 * e_webdav_resource_new:
57 * @kind: an #EWebDAVResourceKind of the resource
58 * @supports: bit-or of #EWebDAVResourceSupports values
59 * @href: href of the resource
60 * @etag: (nullable): optional ETag of the resource, or %NULL
61 * @display_name: (nullable): optional display name of the resource, or %NULL
62 * @content_type: (nullable): optional Content-Type of the resource, or %NULL
63 * @content_length: optional Content-Length of the resource, or 0
64 * @creation_date: optional date of creation of the resource, or 0
65 * @last_modified: optional last modified time of the resource, or 0
66 * @description: (nullable): optional description of the resource, or %NULL
67 * @color: (nullable): optional color of the resource, or %NULL
69 * Some values of the resource are not always valid, depending on the @kind,
70 * but also whether server stores such values and whether it had been asked
71 * for them to be fetched.
73 * The @etag for %E_WEBDAV_RESOURCE_KIND_COLLECTION can be a change tag instead.
75 * Returns: (transfer full): A newly created #EWebDAVResource, prefilled with
76 * given values. Free it with e_webdav_resource_free(), when no longer needed.
78 * Since: 3.26
79 **/
80 EWebDAVResource *
81 e_webdav_resource_new (EWebDAVResourceKind kind,
82 guint32 supports,
83 const gchar *href,
84 const gchar *etag,
85 const gchar *display_name,
86 const gchar *content_type,
87 gsize content_length,
88 glong creation_date,
89 glong last_modified,
90 const gchar *description,
91 const gchar *color)
93 EWebDAVResource *resource;
95 resource = g_new0 (EWebDAVResource, 1);
96 resource->kind = kind;
97 resource->supports = supports;
98 resource->href = g_strdup (href);
99 resource->etag = g_strdup (etag);
100 resource->display_name = g_strdup (display_name);
101 resource->content_type = g_strdup (content_type);
102 resource->content_length = content_length;
103 resource->creation_date = creation_date;
104 resource->last_modified = last_modified;
105 resource->description = g_strdup (description);
106 resource->color = g_strdup (color);
108 return resource;
112 * e_webdav_resource_copy:
113 * @src: (nullable): an #EWebDAVResource to make a copy of
115 * Returns: (transfer full): A new #EWebDAVResource prefilled with
116 * the same values as @src, or %NULL, when @src is %NULL.
117 * Free it with e_webdav_resource_free(), when no longer needed.
119 * Since: 3.26
121 EWebDAVResource *
122 e_webdav_resource_copy (const EWebDAVResource *src)
124 if (!src)
125 return NULL;
127 return e_webdav_resource_new (src->kind,
128 src->supports,
129 src->href,
130 src->etag,
131 src->display_name,
132 src->content_type,
133 src->content_length,
134 src->creation_date,
135 src->last_modified,
136 src->description,
137 src->color);
141 * e_webdav_resource_free:
142 * @ptr: (nullable): an #EWebDAVResource
144 * Frees an #EWebDAVResource previously created with e_webdav_resource_new()
145 * or e_webdav_resource_copy(). The function does nothing, if @ptr is %NULL.
147 * Since: 3.26
149 void
150 e_webdav_resource_free (gpointer ptr)
152 EWebDAVResource *resource = ptr;
154 if (resource) {
155 g_free (resource->href);
156 g_free (resource->etag);
157 g_free (resource->display_name);
158 g_free (resource->content_type);
159 g_free (resource->description);
160 g_free (resource->color);
161 g_free (resource);
165 static EWebDAVPropertyChange *
166 e_webdav_property_change_new (EWebDAVPropertyChangeKind kind,
167 const gchar *ns_uri,
168 const gchar *name,
169 const gchar *value)
171 EWebDAVPropertyChange *change;
173 change = g_new0 (EWebDAVPropertyChange, 1);
174 change->kind = kind;
175 change->ns_uri = g_strdup (ns_uri);
176 change->name = g_strdup (name);
177 change->value = g_strdup (value);
179 return change;
183 * e_webdav_property_change_new_set:
184 * @ns_uri: namespace URI of the property
185 * @name: name of the property
186 * @value: (nullable): value of the property, or %NULL for empty value
188 * Creates a new #EWebDAVPropertyChange of kind %E_WEBDAV_PROPERTY_SET,
189 * which is used to modify or set the property value. The @value is a string
190 * representation of the value to store. It can be %NULL, but it means
191 * an empty value, not to remove it. To remove property use
192 * e_webdav_property_change_new_remove() instead.
194 * Returns: (transfer full): A new #EWebDAVPropertyChange. Free it with
195 * e_webdav_property_change_free(), when no longer needed.
197 * Since: 3.26
199 EWebDAVPropertyChange *
200 e_webdav_property_change_new_set (const gchar *ns_uri,
201 const gchar *name,
202 const gchar *value)
204 g_return_val_if_fail (ns_uri != NULL, NULL);
205 g_return_val_if_fail (name != NULL, NULL);
207 return e_webdav_property_change_new (E_WEBDAV_PROPERTY_SET, ns_uri, name, value);
211 * e_webdav_property_change_new_remove:
212 * @ns_uri: namespace URI of the property
213 * @name: name of the property
215 * Creates a new #EWebDAVPropertyChange of kind %E_WEBDAV_PROPERTY_REMOVE,
216 * which is used to remove the given property. To change property value
217 * use e_webdav_property_change_new_set() instead.
219 * Returns: (transfer full): A new #EWebDAVPropertyChange. Free it with
220 * e_webdav_property_change_free(), when no longer needed.
222 * Since: 3.26
224 EWebDAVPropertyChange *
225 e_webdav_property_change_new_remove (const gchar *ns_uri,
226 const gchar *name)
228 g_return_val_if_fail (ns_uri != NULL, NULL);
229 g_return_val_if_fail (name != NULL, NULL);
231 return e_webdav_property_change_new (E_WEBDAV_PROPERTY_REMOVE, ns_uri, name, NULL);
235 * e_webdav_property_change_copy:
236 * @src: (nullable): an #EWebDAVPropertyChange to make a copy of
238 * Returns: (transfer full): A new #EWebDAVPropertyChange prefilled with
239 * the same values as @src, or %NULL, when @src is %NULL.
240 * Free it with e_webdav_property_change_free(), when no longer needed.
242 * Since: 3.26
244 EWebDAVPropertyChange *
245 e_webdav_property_change_copy (const EWebDAVPropertyChange *src)
247 if (!src)
248 return NULL;
250 return e_webdav_property_change_new (
251 src->kind,
252 src->ns_uri,
253 src->name,
254 src->value);
258 * e_webdav_property_change_free:
259 * @ptr: (nullable): an #EWebDAVPropertyChange
261 * Frees an #EWebDAVPropertyChange previously created with e_webdav_property_change_new_set(),
262 * e_webdav_property_change_new_remove() or or e_webdav_property_change_copy().
263 * The function does nothing, if @ptr is %NULL.
265 * Since: 3.26
267 void
268 e_webdav_property_change_free (gpointer ptr)
270 EWebDAVPropertyChange *change = ptr;
272 if (change) {
273 g_free (change->ns_uri);
274 g_free (change->name);
275 g_free (change->value);
276 g_free (change);
281 * e_webdav_privilege_new:
282 * @ns_uri: (nullable): a namespace URI
283 * @name: (nullable): element name
284 * @description: (nullable): human read-able description, or %NULL
285 * @kind: an #EWebDAVPrivilegeKind
286 * @hint: an #EWebDAVPrivilegeHint
288 * Describes one privilege entry. The @hint can be %E_WEBDAV_PRIVILEGE_HINT_UNKNOWN
289 * for privileges which are not known to the #EWebDAVSession. It's possible, because
290 * the servers can define their own privileges. The hint is also tried to pair with
291 * known hnts when it's %E_WEBDAV_PRIVILEGE_HINT_UNKNOWN.
293 * The @ns_uri and @name can be %NULL only if the @hint is one of the known
294 * privileges. Otherwise it's an error to pass either of the two as %NULL.
296 * Returns: (transfer full): A newly created #EWebDAVPrivilege, prefilled with
297 * given values. Free it with e_webdav_privilege_free(), when no longer needed.
299 * Since: 3.26
301 EWebDAVPrivilege *
302 e_webdav_privilege_new (const gchar *ns_uri,
303 const gchar *name,
304 const gchar *description,
305 EWebDAVPrivilegeKind kind,
306 EWebDAVPrivilegeHint hint)
308 EWebDAVPrivilege *privilege;
310 if ((!ns_uri || !name) && hint != E_WEBDAV_PRIVILEGE_HINT_UNKNOWN) {
311 const gchar *use_ns_uri = NULL, *use_name = NULL;
313 switch (hint) {
314 case E_WEBDAV_PRIVILEGE_HINT_UNKNOWN:
315 break;
316 case E_WEBDAV_PRIVILEGE_HINT_READ:
317 use_name = "read";
318 break;
319 case E_WEBDAV_PRIVILEGE_HINT_WRITE:
320 use_name = "write";
321 break;
322 case E_WEBDAV_PRIVILEGE_HINT_WRITE_PROPERTIES:
323 use_name = "write-properties";
324 break;
325 case E_WEBDAV_PRIVILEGE_HINT_WRITE_CONTENT:
326 use_name = "write-content";
327 break;
328 case E_WEBDAV_PRIVILEGE_HINT_UNLOCK:
329 use_name = "unlock";
330 break;
331 case E_WEBDAV_PRIVILEGE_HINT_READ_ACL:
332 use_name = "read-acl";
333 break;
334 case E_WEBDAV_PRIVILEGE_HINT_WRITE_ACL:
335 use_name = "write-acl";
336 break;
337 case E_WEBDAV_PRIVILEGE_HINT_READ_CURRENT_USER_PRIVILEGE_SET:
338 use_name = "read-current-user-privilege-set";
339 break;
340 case E_WEBDAV_PRIVILEGE_HINT_BIND:
341 use_name = "bind";
342 break;
343 case E_WEBDAV_PRIVILEGE_HINT_UNBIND:
344 use_name = "unbind";
345 break;
346 case E_WEBDAV_PRIVILEGE_HINT_ALL:
347 use_name = "all";
348 break;
349 case E_WEBDAV_PRIVILEGE_HINT_CALDAV_READ_FREE_BUSY:
350 use_ns_uri = E_WEBDAV_NS_CALDAV;
351 use_name = "read-free-busy";
352 break;
355 if (use_name) {
356 ns_uri = use_ns_uri ? use_ns_uri : E_WEBDAV_NS_DAV;
357 name = use_name;
361 g_return_val_if_fail (ns_uri != NULL, NULL);
362 g_return_val_if_fail (name != NULL, NULL);
364 if (hint == E_WEBDAV_PRIVILEGE_HINT_UNKNOWN) {
365 if (g_str_equal (ns_uri, E_WEBDAV_NS_DAV)) {
366 if (g_str_equal (name, "read"))
367 hint = E_WEBDAV_PRIVILEGE_HINT_READ;
368 else if (g_str_equal (name, "write"))
369 hint = E_WEBDAV_PRIVILEGE_HINT_WRITE;
370 else if (g_str_equal (name, "write-properties"))
371 hint = E_WEBDAV_PRIVILEGE_HINT_WRITE_PROPERTIES;
372 else if (g_str_equal (name, "write-content"))
373 hint = E_WEBDAV_PRIVILEGE_HINT_WRITE_CONTENT;
374 else if (g_str_equal (name, "unlock"))
375 hint = E_WEBDAV_PRIVILEGE_HINT_UNLOCK;
376 else if (g_str_equal (name, "read-acl"))
377 hint = E_WEBDAV_PRIVILEGE_HINT_READ_ACL;
378 else if (g_str_equal (name, "write-acl"))
379 hint = E_WEBDAV_PRIVILEGE_HINT_WRITE_ACL;
380 else if (g_str_equal (name, "read-current-user-privilege-set"))
381 hint = E_WEBDAV_PRIVILEGE_HINT_READ_CURRENT_USER_PRIVILEGE_SET;
382 else if (g_str_equal (name, "bind"))
383 hint = E_WEBDAV_PRIVILEGE_HINT_BIND;
384 else if (g_str_equal (name, "unbind"))
385 hint = E_WEBDAV_PRIVILEGE_HINT_UNBIND;
386 else if (g_str_equal (name, "all"))
387 hint = E_WEBDAV_PRIVILEGE_HINT_ALL;
388 } else if (g_str_equal (ns_uri, E_WEBDAV_NS_CALDAV)) {
389 if (g_str_equal (name, "read-free-busy"))
390 hint = E_WEBDAV_PRIVILEGE_HINT_CALDAV_READ_FREE_BUSY;
394 privilege = g_new (EWebDAVPrivilege, 1);
395 privilege->ns_uri = g_strdup (ns_uri);
396 privilege->name = g_strdup (name);
397 privilege->description = g_strdup (description);
398 privilege->kind = kind;
399 privilege->hint = hint;
401 return privilege;
405 * e_webdav_privilege_copy:
406 * @src: (nullable): an #EWebDAVPrivilege to make a copy of
408 * Returns: (transfer full): A new #EWebDAVPrivilege prefilled with
409 * the same values as @src, or %NULL, when @src is %NULL.
410 * Free it with e_webdav_privilege_free(), when no longer needed.
412 * Since: 3.26
414 EWebDAVPrivilege *
415 e_webdav_privilege_copy (const EWebDAVPrivilege *src)
417 if (!src)
418 return NULL;
420 return e_webdav_privilege_new (
421 src->ns_uri,
422 src->name,
423 src->description,
424 src->kind,
425 src->hint);
429 * e_webdav_privilege_free:
430 * @ptr: (nullable): an #EWebDAVPrivilege
432 * Frees an #EWebDAVPrivilege previously created with e_webdav_privilege_new()
433 * or e_webdav_privilege_copy(). The function does nothing, if @ptr is %NULL.
435 * Since: 3.26
437 void
438 e_webdav_privilege_free (gpointer ptr)
440 EWebDAVPrivilege *privilege = ptr;
442 if (privilege) {
443 g_free (privilege->ns_uri);
444 g_free (privilege->name);
445 g_free (privilege->description);
446 g_free (privilege);
451 * e_webdav_access_control_entry_new:
452 * @principal_kind: an #EWebDAVACEPrincipalKind
453 * @principal_href: (nullable): principal href; should be set only if @principal_kind is @E_WEBDAV_ACE_PRINCIPAL_HREF
454 * @flags: bit-or of #EWebDAVACEFlag values
455 * @inherited_href: (nullable): href of the resource from which inherits; should be set only if @flags contain E_WEBDAV_ACE_FLAG_INHERITED
457 * Describes one Access Control Entry (ACE).
459 * The @flags should always contain either %E_WEBDAV_ACE_FLAG_GRANT or
460 * %E_WEBDAV_ACE_FLAG_DENY value.
462 * Use e_webdav_access_control_entry_append_privilege() to add respective
463 * privileges to the entry.
465 * Returns: (transfer full): A newly created #EWebDAVAccessControlEntry, prefilled with
466 * given values. Free it with e_webdav_access_control_entry_free(), when no longer needed.
468 * Since: 3.26
470 EWebDAVAccessControlEntry *
471 e_webdav_access_control_entry_new (EWebDAVACEPrincipalKind principal_kind,
472 const gchar *principal_href,
473 guint32 flags,
474 const gchar *inherited_href)
476 EWebDAVAccessControlEntry *ace;
478 if (principal_kind == E_WEBDAV_ACE_PRINCIPAL_HREF)
479 g_return_val_if_fail (principal_href != NULL, NULL);
480 else
481 g_return_val_if_fail (principal_href == NULL, NULL);
483 if ((flags & E_WEBDAV_ACE_FLAG_INHERITED) != 0)
484 g_return_val_if_fail (inherited_href != NULL, NULL);
485 else
486 g_return_val_if_fail (inherited_href == NULL, NULL);
488 ace = g_new0 (EWebDAVAccessControlEntry, 1);
489 ace->principal_kind = principal_kind;
490 ace->principal_href = g_strdup (principal_href);
491 ace->flags = flags;
492 ace->inherited_href = g_strdup (inherited_href);
493 ace->privileges = NULL;
495 return ace;
499 * e_webdav_access_control_entry_copy:
500 * @src: (nullable): an #EWebDAVAccessControlEntry to make a copy of
502 * Returns: (transfer full): A new #EWebDAVAccessControlEntry prefilled with
503 * the same values as @src, or %NULL, when @src is %NULL.
504 * Free it with e_webdav_access_control_entry_free(), when no longer needed.
506 * Since: 3.26
508 EWebDAVAccessControlEntry *
509 e_webdav_access_control_entry_copy (const EWebDAVAccessControlEntry *src)
511 EWebDAVAccessControlEntry *ace;
512 GSList *link;
514 if (!src)
515 return NULL;
517 ace = e_webdav_access_control_entry_new (
518 src->principal_kind,
519 src->principal_href,
520 src->flags,
521 src->inherited_href);
522 if (!ace)
523 return NULL;
525 for (link = src->privileges; link; link = g_slist_next (link)) {
526 EWebDAVPrivilege *privilege = link->data;
528 if (privilege)
529 ace->privileges = g_slist_prepend (ace->privileges, e_webdav_privilege_copy (privilege));
532 ace->privileges = g_slist_reverse (ace->privileges);
534 return ace;
538 * e_webdav_access_control_entry_free:
539 * @ptr: (nullable): an #EWebDAVAccessControlEntry
541 * Frees an #EWebDAVAccessControlEntry previously created with e_webdav_access_control_entry_new()
542 * or e_webdav_access_control_entry_copy(). The function does nothing, if @ptr is %NULL.
544 * Since: 3.26
546 void
547 e_webdav_access_control_entry_free (gpointer ptr)
549 EWebDAVAccessControlEntry *ace = ptr;
551 if (ace) {
552 g_free (ace->principal_href);
553 g_free (ace->inherited_href);
554 g_slist_free_full (ace->privileges, e_webdav_privilege_free);
555 g_free (ace);
560 * e_webdav_access_control_entry_append_privilege:
561 * @ace: an #EWebDAVAccessControlEntry
562 * @privilege: (transfer full): an #EWebDAVPrivilege
564 * Appends a new @privilege to the list of privileges for the @ace.
565 * The function assumes ownership of the @privilege, which is freed
566 * together with the @ace.
568 * Since: 3.26
570 void
571 e_webdav_access_control_entry_append_privilege (EWebDAVAccessControlEntry *ace,
572 EWebDAVPrivilege *privilege)
574 g_return_if_fail (ace != NULL);
575 g_return_if_fail (privilege != NULL);
577 ace->privileges = g_slist_append (ace->privileges, privilege);
581 * e_webdav_access_control_entry_get_privileges:
582 * @ace: an #EWebDAVAccessControlEntry
584 * Returns: (element-type EWebDAVPrivilege) (transfer none): A #GSList of #EWebDAVPrivilege
585 * with the list of privileges for the @ace. The reurned #GSList, together with its data
586 * is owned by the @ace.
588 * Since: 3.26
590 GSList *
591 e_webdav_access_control_entry_get_privileges (EWebDAVAccessControlEntry *ace)
593 g_return_val_if_fail (ace != NULL, NULL);
595 return ace->privileges;
598 static void
599 e_webdav_session_class_init (EWebDAVSessionClass *klass)
601 g_type_class_add_private (klass, sizeof (EWebDAVSessionPrivate));
604 static void
605 e_webdav_session_init (EWebDAVSession *webdav)
607 webdav->priv = G_TYPE_INSTANCE_GET_PRIVATE (webdav, E_TYPE_WEBDAV_SESSION, EWebDAVSessionPrivate);
611 * e_webdav_session_new:
612 * @source: an #ESource
614 * Creates a new #EWebDAVSession associated with given @source.
615 * The #EWebDAVSession uses an #ESourceWebdav extension on certain
616 * places when it's defined for the @source.
618 * Returns: (transfer full): a new #EWebDAVSession; free it with g_object_unref(),
619 * when no longer needed.
621 * Since: 3.26
623 EWebDAVSession *
624 e_webdav_session_new (ESource *source)
626 g_return_val_if_fail (E_IS_SOURCE (source), NULL);
628 return g_object_new (E_TYPE_WEBDAV_SESSION,
629 "source", source,
630 NULL);
634 * e_webdav_session_new_request:
635 * @webdav: an #EWebDAVSession
636 * @method: an HTTP method
637 * @uri: (nullable): URI to create the request for, or %NULL to read from #ESource
638 * @error: return location for a #GError, or %NULL
640 * Returns: (transfer full): A new #SoupRequestHTTP for the given @uri, or, when %NULL,
641 * for the URI stored in the associated #ESource. Free the returned structure
642 * with g_object_unref(), when no longer needed.
644 * Since: 3.26
646 SoupRequestHTTP *
647 e_webdav_session_new_request (EWebDAVSession *webdav,
648 const gchar *method,
649 const gchar *uri,
650 GError **error)
652 ESoupSession *session;
653 SoupRequestHTTP *request;
654 SoupURI *soup_uri;
655 ESource *source;
656 ESourceWebdav *webdav_extension;
657 const gchar *path;
659 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), NULL);
661 session = E_SOUP_SESSION (webdav);
662 if (uri && *uri)
663 return e_soup_session_new_request (session, method, uri, error);
665 source = e_soup_session_get_source (session);
666 g_return_val_if_fail (E_IS_SOURCE (source), NULL);
668 if (!e_source_has_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND)) {
669 g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
670 _("Cannot determine destination URL without WebDAV extension"));
671 return NULL;
674 webdav_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
675 soup_uri = e_source_webdav_dup_soup_uri (webdav_extension);
677 g_return_val_if_fail (soup_uri != NULL, NULL);
679 /* The URI in the ESource should be to a collection, with an ending
680 forward slash, thus ensure it's there. */
681 path = soup_uri_get_path (soup_uri);
682 if (!path || !*path || !g_str_has_suffix (path, "/")) {
683 gchar *new_path;
685 new_path = g_strconcat (path ? path : "", "/", NULL);
686 soup_uri_set_path (soup_uri, new_path);
687 g_free (new_path);
690 request = e_soup_session_new_request_uri (session, method, soup_uri, error);
692 soup_uri_free (soup_uri);
694 return request;
697 static gboolean
698 e_webdav_session_extract_propstat_error_cb (EWebDAVSession *webdav,
699 xmlXPathContextPtr xpath_ctx,
700 const gchar *xpath_prop_prefix,
701 const SoupURI *request_uri,
702 const gchar *href,
703 guint status_code,
704 gpointer user_data)
706 GError **error = user_data;
708 g_return_val_if_fail (error != NULL, FALSE);
710 if (!xpath_prop_prefix)
711 return TRUE;
713 if (status_code != SOUP_STATUS_OK && (
714 status_code != SOUP_STATUS_FAILED_DEPENDENCY ||
715 !*error)) {
716 gchar *description;
718 description = e_xml_xpath_eval_as_string (xpath_ctx, "%s/../D:responsedescription", xpath_prop_prefix);
719 if (!description || !*description) {
720 g_free (description);
722 description = e_xml_xpath_eval_as_string (xpath_ctx, "%s/../../D:responsedescription", xpath_prop_prefix);
725 g_clear_error (error);
726 g_set_error_literal (error, SOUP_HTTP_ERROR, status_code,
727 e_soup_session_util_status_to_string (status_code, description));
729 g_free (description);
732 return TRUE;
735 static gboolean
736 e_webdav_session_extract_dav_error (EWebDAVSession *webdav,
737 xmlXPathContextPtr xpath_ctx,
738 const gchar *xpath_prefix,
739 gchar **out_detail_text)
741 xmlXPathObjectPtr xpath_obj;
742 gchar *detail_text;
744 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
745 g_return_val_if_fail (xpath_ctx != NULL, FALSE);
746 g_return_val_if_fail (xpath_prefix != NULL, FALSE);
747 g_return_val_if_fail (out_detail_text != NULL, FALSE);
749 if (!e_xml_xpath_eval_exists (xpath_ctx, "%s/D:error", xpath_prefix))
750 return FALSE;
752 detail_text = e_xml_xpath_eval_as_string (xpath_ctx, "%s/D:error", xpath_prefix);
754 xpath_obj = e_xml_xpath_eval (xpath_ctx, "%s/D:error", xpath_prefix);
755 if (xpath_obj) {
756 if (xpath_obj->type == XPATH_NODESET &&
757 xpath_obj->nodesetval &&
758 xpath_obj->nodesetval->nodeNr == 1 &&
759 xpath_obj->nodesetval->nodeTab &&
760 xpath_obj->nodesetval->nodeTab[0] &&
761 xpath_obj->nodesetval->nodeTab[0]->children) {
762 GString *text = g_string_new ("");
763 xmlNodePtr node;
765 for (node = xpath_obj->nodesetval->nodeTab[0]->children; node; node = node->next) {
766 if (node->type == XML_ELEMENT_NODE &&
767 node->name && *(node->name))
768 g_string_append_printf (text, "[%s]", (const gchar *) node->name);
771 if (text->len > 0) {
772 if (detail_text) {
773 g_strstrip (detail_text);
774 if (*detail_text)
775 g_string_prepend (text, detail_text);
776 g_free (detail_text);
779 detail_text = g_string_free (text, FALSE);
780 } else {
781 g_string_free (text, TRUE);
785 xmlXPathFreeObject (xpath_obj);
788 *out_detail_text = detail_text;
790 return detail_text != NULL;
794 * e_webdav_session_replace_with_detailed_error:
795 * @webdav: an #EWebDAVSession
796 * @request: a #SoupRequestHTTP
797 * @response_data: (nullable): received response data, or %NULL
798 * @ignore_multistatus: whether to ignore multistatus responses
799 * @prefix: (nullable): error message prefix, used when replacing, or %NULL
800 * @inout_error: (inout) (nullable) (transfer full): a #GError variable to replace content to, or %NULL
802 * Tries to read detailed error information from @response_data,
803 * if not provided, then from @request's response_body. If the detailed
804 * error cannot be found, then does nothing, otherwise frees the content
805 * of @inout_error, if any, and then populates it with an error message
806 * prefixed with @prefix.
808 * The @prefix might be of form "Failed to something", because the resulting
809 * error message will be:
810 * "Failed to something: HTTP error code XXX (reason_phrase): detailed_error".
811 * When @prefix is %NULL, the error message will be:
812 * "Failed with HTTP error code XXX (reason phrase): detailed_error".
814 * As the caller might not be interested in errors, also the @inout_error
815 * can be %NULL, in which case the function does nothing.
817 * Returns: Whether any detailed error had been recognized.
819 * Since: 3.26
821 gboolean
822 e_webdav_session_replace_with_detailed_error (EWebDAVSession *webdav,
823 SoupRequestHTTP *request,
824 const GByteArray *response_data,
825 gboolean ignore_multistatus,
826 const gchar *prefix,
827 GError **inout_error)
829 SoupMessage *message;
830 GByteArray byte_array = { 0 };
831 const gchar *content_type, *reason_phrase;
832 gchar *detail_text = NULL;
833 gchar *reason_phrase_copy = NULL;
834 gboolean error_set = FALSE;
835 guint status_code;
836 GError *local_error = NULL;
838 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
839 g_return_val_if_fail (SOUP_IS_REQUEST_HTTP (request), FALSE);
841 message = soup_request_http_get_message (request);
842 if (!message)
843 return FALSE;
845 status_code = message->status_code;
846 reason_phrase = message->reason_phrase;
847 byte_array.data = NULL;
848 byte_array.len = 0;
850 if (response_data && response_data->len) {
851 byte_array.data = (gpointer) response_data->data;
852 byte_array.len = response_data->len;
853 } else if (message->response_body && message->response_body->length) {
854 byte_array.data = (gpointer) message->response_body->data;
855 byte_array.len = message->response_body->length;
858 if (!byte_array.data || !byte_array.len)
859 goto out;
861 if (status_code == SOUP_STATUS_MULTI_STATUS &&
862 !ignore_multistatus &&
863 !e_webdav_session_traverse_multistatus_response (webdav, message, &byte_array,
864 e_webdav_session_extract_propstat_error_cb, &local_error, NULL)) {
865 g_clear_error (&local_error);
868 if (local_error) {
869 if (prefix)
870 g_prefix_error (&local_error, "%s: ", prefix);
871 g_propagate_error (inout_error, local_error);
873 g_object_unref (message);
875 return TRUE;
878 content_type = soup_message_headers_get_content_type (message->response_headers, NULL);
879 if (content_type && (
880 (g_ascii_strcasecmp (content_type, "application/xml") == 0 ||
881 g_ascii_strcasecmp (content_type, "text/xml") == 0))) {
882 xmlDocPtr doc;
884 if (status_code == SOUP_STATUS_MULTI_STATUS && ignore_multistatus)
885 doc = NULL;
886 else
887 doc = e_xml_parse_data (byte_array.data, byte_array.len);
889 if (doc) {
890 xmlXPathContextPtr xpath_ctx;
892 xpath_ctx = e_xml_new_xpath_context_with_namespaces (doc,
893 "D", E_WEBDAV_NS_DAV,
894 "C", E_WEBDAV_NS_CALDAV,
895 "A", E_WEBDAV_NS_CARDDAV,
896 NULL);
898 if (xpath_ctx &&
899 e_webdav_session_extract_dav_error (webdav, xpath_ctx, "", &detail_text)) {
900 /* do nothing, detail_text is set */
901 } else if (xpath_ctx) {
902 const gchar *path_prefix = NULL;
904 if (e_xml_xpath_eval_exists (xpath_ctx, "/D:multistatus/D:response/D:status"))
905 path_prefix = "/D:multistatus/D:response";
906 else if (e_xml_xpath_eval_exists (xpath_ctx, "/C:mkcalendar-response/D:status"))
907 path_prefix = "/C:mkcalendar-response";
908 else if (e_xml_xpath_eval_exists (xpath_ctx, "/D:mkcol-response/D:status"))
909 path_prefix = "/D:mkcol-response";
911 if (path_prefix) {
912 guint parsed_status = 0;
913 gchar *status;
915 status = e_xml_xpath_eval_as_string (xpath_ctx, "%s/D:status", path_prefix);
916 if (status && soup_headers_parse_status_line (status, NULL, &parsed_status, &reason_phrase_copy) &&
917 !SOUP_STATUS_IS_SUCCESSFUL (parsed_status)) {
918 status_code = parsed_status;
919 reason_phrase = reason_phrase_copy;
920 detail_text = e_xml_xpath_eval_as_string (xpath_ctx, "%s/D:responsedescription", path_prefix);
922 if (!detail_text)
923 e_webdav_session_extract_dav_error (webdav, xpath_ctx, path_prefix, &detail_text);
924 } else {
925 e_webdav_session_extract_dav_error (webdav, xpath_ctx, path_prefix, &detail_text);
928 g_free (status);
932 if (xpath_ctx)
933 xmlXPathFreeContext (xpath_ctx);
934 xmlFreeDoc (doc);
936 } else if (content_type &&
937 g_ascii_strcasecmp (content_type, "text/plain") == 0) {
938 detail_text = g_strndup ((const gchar *) byte_array.data, byte_array.len);
939 } else if (content_type &&
940 g_ascii_strcasecmp (content_type, "text/html") == 0) {
941 SoupURI *soup_uri;
942 gchar *uri = NULL;
944 soup_uri = soup_message_get_uri (message);
945 if (soup_uri) {
946 soup_uri = soup_uri_copy (soup_uri);
947 soup_uri_set_password (soup_uri, NULL);
949 uri = soup_uri_to_string (soup_uri, FALSE);
951 soup_uri_free (soup_uri);
954 if (uri && *uri)
955 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);
956 else
957 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."));
959 g_free (uri);
962 out:
963 if (detail_text)
964 g_strstrip (detail_text);
966 if (detail_text && *detail_text) {
967 error_set = TRUE;
969 g_clear_error (inout_error);
971 if (prefix) {
972 g_set_error (inout_error, SOUP_HTTP_ERROR, status_code,
973 /* Translators: The first '%s' is replaced with error prefix, as provided
974 by the caller, which can be in a form: "Failed with something".
975 The '%d' is replaced with actual HTTP status code.
976 The second '%s' is replaced with a reason phrase of the error (user readable text).
977 The last '%s' is replaced with detailed error text, as returned by the server. */
978 _("%s: HTTP error code %d (%s): %s"), prefix, status_code,
979 e_soup_session_util_status_to_string (status_code, reason_phrase),
980 detail_text);
981 } else {
982 g_set_error (inout_error, SOUP_HTTP_ERROR, status_code,
983 /* Translators: The '%d' is replaced with actual HTTP status code.
984 The '%s' is replaced with a reason phrase of the error (user readable text).
985 The last '%s' is replaced with detailed error text, as returned by the server. */
986 _("Failed with HTTP error code %d (%s): %s"), status_code,
987 e_soup_session_util_status_to_string (status_code, reason_phrase),
988 detail_text);
990 } else if (status_code && !SOUP_STATUS_IS_SUCCESSFUL (status_code)) {
991 error_set = TRUE;
993 g_clear_error (inout_error);
995 if (prefix) {
996 g_set_error (inout_error, SOUP_HTTP_ERROR, status_code,
997 /* Translators: The first '%s' is replaced with error prefix, as provided
998 by the caller, which can be in a form: "Failed with something".
999 The '%d' is replaced with actual HTTP status code.
1000 The second '%s' is replaced with a reason phrase of the error (user readable text). */
1001 _("%s: HTTP error code %d (%s)"), prefix, status_code,
1002 e_soup_session_util_status_to_string (status_code, reason_phrase));
1003 } else {
1004 g_set_error (inout_error, SOUP_HTTP_ERROR, status_code,
1005 /* Translators: The '%d' is replaced with actual HTTP status code.
1006 The '%s' is replaced with a reason phrase of the error (user readable text). */
1007 _("Failed with HTTP error code %d (%s)"), status_code,
1008 e_soup_session_util_status_to_string (status_code, reason_phrase));
1012 g_object_unref (message);
1013 g_free (reason_phrase_copy);
1014 g_free (detail_text);
1016 return error_set;
1020 * e_webdav_session_ensure_full_uri:
1021 * @webdav: an #EWebDAVSession
1022 * @request_uri: (nullable): a #SoupURI to which the @href belongs, or %NULL
1023 * @href: a possibly path-only href
1025 * Converts possibly path-only @href into a full URI under the @request_uri.
1026 * When the @request_uri is %NULL, the URI defined in associated #ESource is
1027 * used instead, taken from the #ESourceWebdav extension, if defined.
1029 * Free the returned pointer with g_free(), when no longer needed.
1031 * Returns: (transfer full): The @href as a full URI
1033 * Since: 3.24
1035 gchar *
1036 e_webdav_session_ensure_full_uri (EWebDAVSession *webdav,
1037 const SoupURI *request_uri,
1038 const gchar *href)
1040 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), NULL);
1041 g_return_val_if_fail (href != NULL, NULL);
1043 if (*href == '/' || !strstr (href, "://")) {
1044 SoupURI *soup_uri;
1045 gchar *full_uri;
1047 if (request_uri) {
1048 soup_uri = soup_uri_copy ((SoupURI *) request_uri);
1049 } else {
1050 ESource *source;
1051 ESourceWebdav *webdav_extension;
1053 source = e_soup_session_get_source (E_SOUP_SESSION (webdav));
1054 g_return_val_if_fail (E_IS_SOURCE (source), NULL);
1056 if (!e_source_has_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND))
1057 return g_strdup (href);
1059 webdav_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
1060 soup_uri = e_source_webdav_dup_soup_uri (webdav_extension);
1063 g_return_val_if_fail (soup_uri != NULL, NULL);
1065 soup_uri_set_path (soup_uri, href);
1066 soup_uri_set_user (soup_uri, NULL);
1067 soup_uri_set_password (soup_uri, NULL);
1069 full_uri = soup_uri_to_string (soup_uri, FALSE);
1071 soup_uri_free (soup_uri);
1073 return full_uri;
1076 return g_strdup (href);
1079 static GHashTable *
1080 e_webdav_session_comma_header_to_hashtable (SoupMessageHeaders *headers,
1081 const gchar *header_name)
1083 GHashTable *soup_params, *result;
1084 GHashTableIter iter;
1085 const gchar *value;
1086 gpointer key;
1088 g_return_val_if_fail (header_name != NULL, NULL);
1090 if (!headers)
1091 return NULL;
1093 value = soup_message_headers_get_list (headers, header_name);
1094 if (!value)
1095 return NULL;
1097 soup_params = soup_header_parse_param_list (value);
1098 if (!soup_params)
1099 return NULL;
1101 result = g_hash_table_new_full (camel_strcase_hash, camel_strcase_equal, g_free, NULL);
1103 g_hash_table_iter_init (&iter, soup_params);
1104 while (g_hash_table_iter_next (&iter, &key, NULL)) {
1105 value = key;
1107 if (value && *value)
1108 g_hash_table_insert (result, g_strdup (value), GINT_TO_POINTER (1));
1111 soup_header_free_param_list (soup_params);
1113 return result;
1117 * e_webdav_session_options_sync:
1118 * @webdav: an #EWebDAVSession
1119 * @uri: (nullable): URI to issue the request for, or %NULL to read from #ESource
1120 * @out_capabilities: (out) (transfer full): return location for DAV capabilities
1121 * @out_allows: (out) (transfer full): return location for allowed operations
1122 * @cancellable: optional #GCancellable object, or %NULL
1123 * @error: return location for a #GError, or %NULL
1125 * Issues OPTIONS request on the provided @uri, or, in case it's %NULL, on the URI
1126 * defined in associated #ESource.
1128 * The @out_capabilities contains a set of returned capabilities. Some known are
1129 * defined as E_WEBDAV_CAPABILITY_CLASS_1, and so on. The 'value' of the #GHashTable
1130 * doesn't have any particular meaning and the strings are compared case insensitively.
1131 * Free the hash table with g_hash_table_destroy(), when no longer needed. The returned
1132 * value can be %NULL on success, it's when the server doesn't provide the information.
1134 * The @out_allows contains a set of allowed methods returned by the server. Some known
1135 * are defined as %SOUP_METHOD_OPTIONS, and so on. The 'value' of the #GHashTable
1136 * doesn't have any particular meaning and the strings are compared case insensitively.
1137 * Free the hash table with g_hash_table_destroy(), when no longer needed. The returned
1138 * value can be %NULL on success, it's when the server doesn't provide the information.
1140 * Returns: Whether succeeded.
1142 * Since: 3.26
1144 gboolean
1145 e_webdav_session_options_sync (EWebDAVSession *webdav,
1146 const gchar *uri,
1147 GHashTable **out_capabilities,
1148 GHashTable **out_allows,
1149 GCancellable *cancellable,
1150 GError **error)
1152 SoupRequestHTTP *request;
1153 SoupMessage *message;
1154 GByteArray *bytes;
1156 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
1157 g_return_val_if_fail (out_capabilities != NULL, FALSE);
1158 g_return_val_if_fail (out_allows != NULL, FALSE);
1160 *out_capabilities = NULL;
1161 *out_allows = NULL;
1163 request = e_webdav_session_new_request (webdav, SOUP_METHOD_OPTIONS, uri, error);
1164 if (!request)
1165 return FALSE;
1167 bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, error);
1169 if (!bytes) {
1170 g_object_unref (request);
1171 return FALSE;
1174 message = soup_request_http_get_message (request);
1176 g_byte_array_free (bytes, TRUE);
1177 g_object_unref (request);
1179 g_return_val_if_fail (message != NULL, FALSE);
1181 *out_capabilities = e_webdav_session_comma_header_to_hashtable (message->response_headers, "DAV");
1182 *out_allows = e_webdav_session_comma_header_to_hashtable (message->response_headers, "Allow");
1184 g_object_unref (message);
1186 return TRUE;
1190 * e_webdav_session_post_sync:
1191 * @webdav: an #EWebDAVSession
1192 * @uri: (nullable): URI to issue the request for, or %NULL to read from #ESource
1193 * @data: data to post to the server
1194 * @data_length: length of @data, or -1, when @data is NUL-terminated
1195 * @out_content_type: (nullable) (transfer full): return location for response Content-Type, or %NULL
1196 * @out_content: (nullable) (transfer full): return location for response content, or %NULL
1197 * @cancellable: optional #GCancellable object, or %NULL
1198 * @error: return location for a #GError, or %NULL
1200 * Issues POST request on the provided @uri, or, in case it's %NULL, on the URI
1201 * defined in associated #ESource.
1203 * The optional @out_content_type can be used to get content type of the response.
1204 * Free it with g_free(), when no longer needed.
1206 * The optional @out_content can be used to get actual result content. Free it
1207 * with g_byte_array_free(), when no longer needed.
1209 * Returns: Whether succeeded.
1211 * Since: 3.26
1213 gboolean
1214 e_webdav_session_post_sync (EWebDAVSession *webdav,
1215 const gchar *uri,
1216 const gchar *data,
1217 gsize data_length,
1218 gchar **out_content_type,
1219 GByteArray **out_content,
1220 GCancellable *cancellable,
1221 GError **error)
1223 SoupRequestHTTP *request;
1224 SoupMessage *message;
1225 GByteArray *bytes;
1226 gboolean success;
1228 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
1229 g_return_val_if_fail (data != NULL, FALSE);
1231 if (out_content_type)
1232 *out_content_type = NULL;
1234 if (out_content)
1235 *out_content = NULL;
1237 if (data_length == (gsize) -1)
1238 data_length = strlen (data);
1240 request = e_webdav_session_new_request (webdav, SOUP_METHOD_POST, uri, error);
1241 if (!request)
1242 return FALSE;
1244 message = soup_request_http_get_message (request);
1245 if (!message) {
1246 g_warn_if_fail (message != NULL);
1247 g_object_unref (request);
1249 return FALSE;
1252 soup_message_set_request (message, E_WEBDAV_CONTENT_TYPE_XML,
1253 SOUP_MEMORY_COPY, data, data_length);
1255 bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, error);
1257 success = !e_webdav_session_replace_with_detailed_error (webdav, request, bytes, TRUE, _("Failed to post data"), error) &&
1258 bytes != NULL;
1260 if (success) {
1261 if (out_content_type) {
1262 *out_content_type = g_strdup (soup_message_headers_get_content_type (message->response_headers, NULL));
1265 if (out_content) {
1266 *out_content = bytes;
1267 bytes = NULL;
1271 if (bytes)
1272 g_byte_array_free (bytes, TRUE);
1273 g_object_unref (message);
1274 g_object_unref (request);
1276 return success;
1280 * e_webdav_session_propfind_sync:
1281 * @webdav: an #EWebDAVSession
1282 * @uri: (nullable): URI to issue the request for, or %NULL to read from #ESource
1283 * @depth: requested depth, can be one of %E_WEBDAV_DEPTH_THIS, %E_WEBDAV_DEPTH_THIS_AND_CHILDREN or %E_WEBDAV_DEPTH_INFINITY
1284 * @xml: (nullable): the request itself, as an #EXmlDocument, the root element should be DAV:propfind, or %NULL
1285 * @func: (scope call): an #EWebDAVPropstatTraverseFunc function to call for each DAV:propstat in the multistatus response
1286 * @func_user_data: (closure func): user data passed to @func
1287 * @cancellable: optional #GCancellable object, or %NULL
1288 * @error: return location for a #GError, or %NULL
1290 * Issues PROPFIND request on the provided @uri, or, in case it's %NULL, on the URI
1291 * defined in associated #ESource. On success, calls @func for each returned
1292 * DAV:propstat. The provided XPath context has registered %E_WEBDAV_NS_DAV namespace
1293 * with prefix "D". It doesn't have any other namespace registered.
1295 * The @func is called always at least once, with %NULL xpath_prop_prefix, which
1296 * is meant to let the caller setup the xpath_ctx, like to register its own namespaces
1297 * to it with e_xml_xpath_context_register_namespaces(). All other invocations of @func
1298 * will have xpath_prop_prefix non-%NULL.
1300 * The @xml can be %NULL, in which case the server should behave like DAV:allprop request.
1302 * Returns: Whether succeeded.
1304 * Since: 3.26
1306 gboolean
1307 e_webdav_session_propfind_sync (EWebDAVSession *webdav,
1308 const gchar *uri,
1309 const gchar *depth,
1310 const EXmlDocument *xml,
1311 EWebDAVPropstatTraverseFunc func,
1312 gpointer func_user_data,
1313 GCancellable *cancellable,
1314 GError **error)
1316 SoupRequestHTTP *request;
1317 SoupMessage *message;
1318 GByteArray *bytes;
1319 gboolean success;
1321 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
1322 g_return_val_if_fail (depth != NULL, FALSE);
1323 if (xml)
1324 g_return_val_if_fail (E_IS_XML_DOCUMENT (xml), FALSE);
1325 g_return_val_if_fail (func != NULL, FALSE);
1327 request = e_webdav_session_new_request (webdav, SOUP_METHOD_PROPFIND, uri, error);
1328 if (!request)
1329 return FALSE;
1331 message = soup_request_http_get_message (request);
1332 if (!message) {
1333 g_warn_if_fail (message != NULL);
1334 g_object_unref (request);
1336 return FALSE;
1339 soup_message_headers_replace (message->request_headers, "Depth", depth);
1341 if (xml) {
1342 gchar *content;
1343 gsize content_length;
1345 content = e_xml_document_get_content (xml, &content_length);
1346 if (!content) {
1347 g_object_unref (message);
1348 g_object_unref (request);
1350 g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Failed to get input XML content"));
1352 return FALSE;
1355 soup_message_set_request (message, E_WEBDAV_CONTENT_TYPE_XML,
1356 SOUP_MEMORY_TAKE, content, content_length);
1359 bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, error);
1361 success = !e_webdav_session_replace_with_detailed_error (webdav, request, bytes, TRUE, _("Failed to get properties"), error) &&
1362 bytes != NULL;
1364 if (success)
1365 success = e_webdav_session_traverse_multistatus_response (webdav, message, bytes, func, func_user_data, error);
1367 if (bytes)
1368 g_byte_array_free (bytes, TRUE);
1369 g_object_unref (message);
1370 g_object_unref (request);
1372 return success;
1376 * e_webdav_session_proppatch_sync:
1377 * @webdav: an #EWebDAVSession
1378 * @uri: (nullable): URI to issue the request for, or %NULL to read from #ESource
1379 * @xml: an #EXmlDocument with request changes, its root element should be DAV:propertyupdate
1380 * @cancellable: optional #GCancellable object, or %NULL
1381 * @error: return location for a #GError, or %NULL
1383 * Issues PROPPATCH request on the provided @uri, or, in case it's %NULL, on the URI
1384 * defined in associated #ESource, with the @changes. The order of requested changes
1385 * inside @xml is significant, unlike on other places.
1387 * Returns: Whether succeeded.
1389 * Since: 3.26
1391 gboolean
1392 e_webdav_session_proppatch_sync (EWebDAVSession *webdav,
1393 const gchar *uri,
1394 const EXmlDocument *xml,
1395 GCancellable *cancellable,
1396 GError **error)
1398 SoupRequestHTTP *request;
1399 SoupMessage *message;
1400 GByteArray *bytes;
1401 gchar *content;
1402 gsize content_length;
1403 gboolean success;
1405 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
1406 g_return_val_if_fail (E_IS_XML_DOCUMENT (xml), FALSE);
1408 request = e_webdav_session_new_request (webdav, SOUP_METHOD_PROPPATCH, uri, error);
1409 if (!request)
1410 return FALSE;
1412 message = soup_request_http_get_message (request);
1413 if (!message) {
1414 g_warn_if_fail (message != NULL);
1415 g_object_unref (request);
1417 return FALSE;
1420 content = e_xml_document_get_content (xml, &content_length);
1421 if (!content) {
1422 g_object_unref (message);
1423 g_object_unref (request);
1425 g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Failed to get input XML content"));
1427 return FALSE;
1430 soup_message_set_request (message, E_WEBDAV_CONTENT_TYPE_XML,
1431 SOUP_MEMORY_TAKE, content, content_length);
1433 bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, error);
1435 success = !e_webdav_session_replace_with_detailed_error (webdav, request, bytes, FALSE, _("Failed to update properties"), error) &&
1436 bytes != NULL;
1438 if (bytes)
1439 g_byte_array_free (bytes, TRUE);
1440 g_object_unref (message);
1441 g_object_unref (request);
1443 return success;
1447 * e_webdav_session_report_sync:
1448 * @webdav: an #EWebDAVSession
1449 * @uri: (nullable): URI to issue the request for, or %NULL to read from #ESource
1450 * @depth: (nullable): requested depth, can be %NULL, then no Depth header is sent
1451 * @xml: the request itself, as an #EXmlDocument
1452 * @func: (nullable) (scope call): an #EWebDAVPropstatTraverseFunc function to call for each DAV:propstat in the multistatus response, or %NULL
1453 * @func_user_data: (closure func): user data passed to @func
1454 * @out_content_type: (nullable) (transfer full): return location for response Content-Type, or %NULL
1455 * @out_content: (nullable) (transfer full): return location for response content, or %NULL
1456 * @cancellable: optional #GCancellable object, or %NULL
1457 * @error: return location for a #GError, or %NULL
1459 * Issues REPORT request on the provided @uri, or, in case it's %NULL, on the URI
1460 * defined in associated #ESource. On success, calls @func for each returned
1461 * DAV:propstat. The provided XPath context has registered %E_WEBDAV_NS_DAV namespace
1462 * with prefix "D". It doesn't have any other namespace registered.
1464 * The report can result in a multistatus response, but also to raw data. In case
1465 * the @func is provided and the result is a multistatus response, then it is traversed
1466 * using this @func. The @func is called always at least once, with %NULL xpath_prop_prefix,
1467 * which is meant to let the caller setup the xpath_ctx, like to register its own namespaces
1468 * to it with e_xml_xpath_context_register_namespaces(). All other invocations of @func
1469 * will have xpath_prop_prefix non-%NULL.
1471 * The optional @out_content_type can be used to get content type of the response.
1472 * Free it with g_free(), when no longer needed.
1474 * The optional @out_content can be used to get actual result content. Free it
1475 * with g_byte_array_free(), when no longer needed.
1477 * Returns: Whether succeeded.
1479 * Since: 3.26
1481 gboolean
1482 e_webdav_session_report_sync (EWebDAVSession *webdav,
1483 const gchar *uri,
1484 const gchar *depth,
1485 const EXmlDocument *xml,
1486 EWebDAVPropstatTraverseFunc func,
1487 gpointer func_user_data,
1488 gchar **out_content_type,
1489 GByteArray **out_content,
1490 GCancellable *cancellable,
1491 GError **error)
1493 SoupRequestHTTP *request;
1494 SoupMessage *message;
1495 GByteArray *bytes;
1496 gchar *content;
1497 gsize content_length;
1498 gboolean success;
1500 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
1501 g_return_val_if_fail (E_IS_XML_DOCUMENT (xml), FALSE);
1503 if (out_content_type)
1504 *out_content_type = NULL;
1506 if (out_content)
1507 *out_content = NULL;
1509 request = e_webdav_session_new_request (webdav, "REPORT", uri, error);
1510 if (!request)
1511 return FALSE;
1513 message = soup_request_http_get_message (request);
1514 if (!message) {
1515 g_warn_if_fail (message != NULL);
1516 g_object_unref (request);
1518 return FALSE;
1521 if (depth)
1522 soup_message_headers_replace (message->request_headers, "Depth", depth);
1524 content = e_xml_document_get_content (xml, &content_length);
1525 if (!content) {
1526 g_object_unref (message);
1527 g_object_unref (request);
1529 g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Failed to get input XML content"));
1531 return FALSE;
1534 soup_message_set_request (message, E_WEBDAV_CONTENT_TYPE_XML,
1535 SOUP_MEMORY_TAKE, content, content_length);
1537 bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, error);
1539 success = !e_webdav_session_replace_with_detailed_error (webdav, request, bytes, TRUE, _("Failed to issue REPORT"), error) &&
1540 bytes != NULL;
1542 if (success && func && message->status_code == SOUP_STATUS_MULTI_STATUS)
1543 success = e_webdav_session_traverse_multistatus_response (webdav, message, bytes, func, func_user_data, error);
1545 if (success) {
1546 if (out_content_type) {
1547 *out_content_type = g_strdup (soup_message_headers_get_content_type (message->response_headers, NULL));
1550 if (out_content) {
1551 *out_content = bytes;
1552 bytes = NULL;
1556 if (bytes)
1557 g_byte_array_free (bytes, TRUE);
1558 g_object_unref (message);
1559 g_object_unref (request);
1561 return success;
1565 * e_webdav_session_mkcol_sync:
1566 * @webdav: an #EWebDAVSession
1567 * @uri: URI of the collection to create
1568 * @cancellable: optional #GCancellable object, or %NULL
1569 * @error: return location for a #GError, or %NULL
1571 * Creates a new generic collection identified by @uri on the server.
1572 * To create specific collections use e_webdav_session_mkcalendar_sync()
1573 * or e_webdav_session_mkcol_addressbook_sync().
1575 * Returns: Whether succeeded.
1577 * Since: 3.26
1579 gboolean
1580 e_webdav_session_mkcol_sync (EWebDAVSession *webdav,
1581 const gchar *uri,
1582 GCancellable *cancellable,
1583 GError **error)
1585 SoupRequestHTTP *request;
1586 GByteArray *bytes;
1587 gboolean success;
1589 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
1590 g_return_val_if_fail (uri != NULL, FALSE);
1592 request = e_webdav_session_new_request (webdav, SOUP_METHOD_MKCOL, uri, error);
1593 if (!request)
1594 return FALSE;
1596 bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, error);
1598 success = !e_webdav_session_replace_with_detailed_error (webdav, request, bytes, FALSE, _("Failed to create collection"), error) &&
1599 bytes != NULL;
1601 if (bytes)
1602 g_byte_array_free (bytes, TRUE);
1603 g_object_unref (request);
1605 return success;
1609 * e_webdav_session_mkcol_addressbook_sync:
1610 * @webdav: an #EWebDAVSession
1611 * @uri: URI of the collection to create
1612 * @display_name: (nullable): a human-readable display name to set, or %NULL
1613 * @description: (nullable): a human-readable description of the address book, or %NULL
1614 * @cancellable: optional #GCancellable object, or %NULL
1615 * @error: return location for a #GError, or %NULL
1617 * Creates a new address book collection identified by @uri on the server.
1619 * Note that CardDAV RFC 6352 Section 5.2 forbids to create address book
1620 * resources under other address book resources (no nested address books
1621 * are allowed).
1623 * Returns: Whether succeeded.
1625 * Since: 3.26
1627 gboolean
1628 e_webdav_session_mkcol_addressbook_sync (EWebDAVSession *webdav,
1629 const gchar *uri,
1630 const gchar *display_name,
1631 const gchar *description,
1632 GCancellable *cancellable,
1633 GError **error)
1635 SoupRequestHTTP *request;
1636 SoupMessage *message;
1637 EXmlDocument *xml;
1638 gchar *content;
1639 gsize content_length;
1640 GByteArray *bytes;
1641 gboolean success;
1643 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
1644 g_return_val_if_fail (uri != NULL, FALSE);
1646 request = e_webdav_session_new_request (webdav, SOUP_METHOD_MKCOL, uri, error);
1647 if (!request)
1648 return FALSE;
1650 message = soup_request_http_get_message (request);
1651 if (!message) {
1652 g_warn_if_fail (message != NULL);
1653 g_object_unref (request);
1655 return FALSE;
1658 xml = e_xml_document_new (E_WEBDAV_NS_DAV, "mkcol");
1659 e_xml_document_add_namespaces (xml, "A", E_WEBDAV_NS_CARDDAV, NULL);
1661 e_xml_document_start_element (xml, E_WEBDAV_NS_DAV, "set");
1662 e_xml_document_start_element (xml, E_WEBDAV_NS_DAV, "prop");
1663 e_xml_document_start_element (xml, E_WEBDAV_NS_DAV, "resourcetype");
1664 e_xml_document_add_empty_element (xml, E_WEBDAV_NS_DAV, "collection");
1665 e_xml_document_add_empty_element (xml, E_WEBDAV_NS_CARDDAV, "addressbook");
1666 e_xml_document_end_element (xml); /* resourcetype */
1668 if (display_name && *display_name) {
1669 e_xml_document_start_text_element (xml, E_WEBDAV_NS_DAV, "displayname");
1670 e_xml_document_write_string (xml, display_name);
1671 e_xml_document_end_element (xml);
1674 if (description && *description) {
1675 e_xml_document_start_text_element (xml, E_WEBDAV_NS_CARDDAV, "addressbook-description");
1676 e_xml_document_write_string (xml, description);
1677 e_xml_document_end_element (xml);
1680 e_xml_document_end_element (xml); /* prop */
1681 e_xml_document_end_element (xml); /* set */
1683 content = e_xml_document_get_content (xml, &content_length);
1684 if (!content) {
1685 g_object_unref (message);
1686 g_object_unref (request);
1687 g_object_unref (xml);
1689 g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Failed to get XML request content"));
1691 return FALSE;
1694 soup_message_set_request (message, E_WEBDAV_CONTENT_TYPE_XML,
1695 SOUP_MEMORY_TAKE, content, content_length);
1697 g_object_unref (xml);
1699 bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, error);
1701 success = !e_webdav_session_replace_with_detailed_error (webdav, request, bytes, FALSE, _("Failed to create address book"), error) &&
1702 bytes != NULL;
1704 if (bytes)
1705 g_byte_array_free (bytes, TRUE);
1706 g_object_unref (message);
1707 g_object_unref (request);
1709 return success;
1713 * e_webdav_session_mkcalendar_sync:
1714 * @webdav: an #EWebDAVSession
1715 * @uri: URI of the collection to create
1716 * @display_name: (nullable): a human-readable display name to set, or %NULL
1717 * @description: (nullable): a human-readable description of the calendar, or %NULL
1718 * @color: (nullable): a color to set, in format "&num;RRGGBB", or %NULL
1719 * @supports: a bit-or of EWebDAVResourceSupports values
1720 * @cancellable: optional #GCancellable object, or %NULL
1721 * @error: return location for a #GError, or %NULL
1723 * Creates a new calendar collection identified by @uri on the server.
1724 * The @supports defines what component types can be stored into
1725 * the created calendar collection. Only %E_WEBDAV_RESOURCE_SUPPORTS_NONE
1726 * and values related to iCalendar content can be used here.
1727 * Using %E_WEBDAV_RESOURCE_SUPPORTS_NONE means that everything is supported.
1729 * Note that CalDAV RFC 4791 Section 4.2 forbids to create calendar
1730 * resources under other calendar resources (no nested calendars
1731 * are allowed).
1733 * Returns: Whether succeeded.
1735 * Since: 3.26
1737 gboolean
1738 e_webdav_session_mkcalendar_sync (EWebDAVSession *webdav,
1739 const gchar *uri,
1740 const gchar *display_name,
1741 const gchar *description,
1742 const gchar *color,
1743 guint32 supports,
1744 GCancellable *cancellable,
1745 GError **error)
1747 SoupRequestHTTP *request;
1748 SoupMessage *message;
1749 GByteArray *bytes;
1750 gboolean success;
1752 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
1753 g_return_val_if_fail (uri != NULL, FALSE);
1755 request = e_webdav_session_new_request (webdav, "MKCALENDAR", uri, error);
1756 if (!request)
1757 return FALSE;
1759 message = soup_request_http_get_message (request);
1760 if (!message) {
1761 g_warn_if_fail (message != NULL);
1762 g_object_unref (request);
1764 return FALSE;
1767 supports = supports & (
1768 E_WEBDAV_RESOURCE_SUPPORTS_EVENTS |
1769 E_WEBDAV_RESOURCE_SUPPORTS_MEMOS |
1770 E_WEBDAV_RESOURCE_SUPPORTS_TASKS |
1771 E_WEBDAV_RESOURCE_SUPPORTS_FREEBUSY |
1772 E_WEBDAV_RESOURCE_SUPPORTS_TIMEZONE);
1774 if ((display_name && *display_name) ||
1775 (description && *description) ||
1776 (color && *color) ||
1777 (supports != 0)) {
1778 EXmlDocument *xml;
1779 gchar *content;
1780 gsize content_length;
1782 xml = e_xml_document_new (E_WEBDAV_NS_CALDAV, "mkcalendar");
1783 e_xml_document_add_namespaces (xml, "D", E_WEBDAV_NS_DAV, NULL);
1785 e_xml_document_start_element (xml, E_WEBDAV_NS_DAV, "set");
1786 e_xml_document_start_element (xml, E_WEBDAV_NS_DAV, "prop");
1788 if (display_name && *display_name) {
1789 e_xml_document_start_text_element (xml, E_WEBDAV_NS_DAV, "displayname");
1790 e_xml_document_write_string (xml, display_name);
1791 e_xml_document_end_element (xml);
1794 if (description && *description) {
1795 e_xml_document_start_text_element (xml, E_WEBDAV_NS_CALDAV, "calendar-description");
1796 e_xml_document_write_string (xml, description);
1797 e_xml_document_end_element (xml);
1800 if (color && *color) {
1801 e_xml_document_add_namespaces (xml, "IC", E_WEBDAV_NS_ICAL, NULL);
1803 e_xml_document_start_text_element (xml, E_WEBDAV_NS_ICAL, "calendar-color");
1804 e_xml_document_write_string (xml, color);
1805 e_xml_document_end_element (xml);
1808 if (supports != 0) {
1809 struct SupportValues {
1810 guint32 mask;
1811 const gchar *value;
1812 } values[] = {
1813 { E_WEBDAV_RESOURCE_SUPPORTS_EVENTS, "VEVENT" },
1814 { E_WEBDAV_RESOURCE_SUPPORTS_MEMOS, "VJOURNAL" },
1815 { E_WEBDAV_RESOURCE_SUPPORTS_TASKS, "VTODO" },
1816 { E_WEBDAV_RESOURCE_SUPPORTS_FREEBUSY, "VFREEBUSY" },
1817 { E_WEBDAV_RESOURCE_SUPPORTS_TIMEZONE, "TIMEZONE" }
1819 gint ii;
1821 e_xml_document_start_text_element (xml, E_WEBDAV_NS_CALDAV, "supported-calendar-component-set");
1823 for (ii = 0; ii < G_N_ELEMENTS (values); ii++) {
1824 if ((supports & values[ii].mask) != 0) {
1825 e_xml_document_start_text_element (xml, E_WEBDAV_NS_CALDAV, "comp");
1826 e_xml_document_add_attribute (xml, NULL, "name", values[ii].value);
1827 e_xml_document_end_element (xml); /* comp */
1831 e_xml_document_end_element (xml); /* supported-calendar-component-set */
1834 e_xml_document_end_element (xml); /* prop */
1835 e_xml_document_end_element (xml); /* set */
1837 content = e_xml_document_get_content (xml, &content_length);
1838 if (!content) {
1839 g_object_unref (message);
1840 g_object_unref (request);
1841 g_object_unref (xml);
1843 g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Failed to get XML request content"));
1845 return FALSE;
1848 soup_message_set_request (message, E_WEBDAV_CONTENT_TYPE_XML,
1849 SOUP_MEMORY_TAKE, content, content_length);
1851 g_object_unref (xml);
1854 bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, error);
1856 success = !e_webdav_session_replace_with_detailed_error (webdav, request, bytes, FALSE, _("Failed to create calendar"), error) &&
1857 bytes != NULL;
1859 if (bytes)
1860 g_byte_array_free (bytes, TRUE);
1861 g_object_unref (message);
1862 g_object_unref (request);
1864 return success;
1867 static void
1868 e_webdav_session_extract_href_and_etag (SoupMessage *message,
1869 gchar **out_href,
1870 gchar **out_etag)
1872 g_return_if_fail (SOUP_IS_MESSAGE (message));
1874 if (out_href) {
1875 const gchar *header;
1877 *out_href = NULL;
1879 header = soup_message_headers_get_list (message->response_headers, "Location");
1880 if (header) {
1881 gchar *file = strrchr (header, '/');
1883 if (file) {
1884 gchar *decoded;
1886 decoded = soup_uri_decode (file + 1);
1887 *out_href = soup_uri_encode (decoded ? decoded : (file + 1), NULL);
1889 g_free (decoded);
1893 if (!*out_href)
1894 *out_href = soup_uri_to_string (soup_message_get_uri (message), FALSE);
1897 if (out_etag) {
1898 const gchar *header;
1900 *out_etag = NULL;
1902 header = soup_message_headers_get_list (message->response_headers, "ETag");
1903 if (header)
1904 *out_etag = e_webdav_session_util_maybe_dequote (g_strdup (header));
1909 * e_webdav_session_get_sync:
1910 * @webdav: an #EWebDAVSession
1911 * @uri: URI of the resource to read
1912 * @out_href: (out) (nullable) (transfer full): optional return location for href of the resource, or %NULL
1913 * @out_etag: (out) (nullable) (transfer full): optional return location for etag of the resource, or %NULL
1914 * @out_stream: (out caller-allocates): a #GOutputStream to write data to
1915 * @cancellable: optional #GCancellable object, or %NULL
1916 * @error: return location for a #GError, or %NULL
1918 * Reads a resource identified by @uri from the server and writes it
1919 * to the @stream. The URI cannot reference a collection.
1921 * Free returned pointer of @out_href and @out_etag, if not %NULL, with g_free(),
1922 * when no longer needed.
1924 * The e_webdav_session_get_data_sync() can be used to read the resource data
1925 * directly to memory.
1927 * Returns: Whether succeeded.
1929 * Since: 3.26
1931 gboolean
1932 e_webdav_session_get_sync (EWebDAVSession *webdav,
1933 const gchar *uri,
1934 gchar **out_href,
1935 gchar **out_etag,
1936 GOutputStream *out_stream,
1937 GCancellable *cancellable,
1938 GError **error)
1940 SoupRequestHTTP *request;
1941 SoupMessage *message;
1942 GInputStream *input_stream;
1943 gboolean success;
1945 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
1946 g_return_val_if_fail (uri != NULL, FALSE);
1947 g_return_val_if_fail (G_IS_OUTPUT_STREAM (out_stream), FALSE);
1949 request = e_webdav_session_new_request (webdav, SOUP_METHOD_GET, uri, error);
1950 if (!request)
1951 return FALSE;
1953 message = soup_request_http_get_message (request);
1954 if (!message) {
1955 g_warn_if_fail (message != NULL);
1956 g_object_unref (request);
1958 return FALSE;
1961 input_stream = e_soup_session_send_request_sync (E_SOUP_SESSION (webdav), request, cancellable, error);
1963 success = input_stream != NULL;
1965 if (success) {
1966 SoupLoggerLogLevel log_level = e_soup_session_get_log_level (E_SOUP_SESSION (webdav));
1967 gpointer buffer;
1968 gsize nread = 0, nwritten;
1969 gboolean first_chunk = TRUE;
1971 buffer = g_malloc (BUFFER_SIZE);
1973 while (success = g_input_stream_read_all (input_stream, buffer, BUFFER_SIZE, &nread, cancellable, error),
1974 success && nread > 0) {
1975 if (log_level == SOUP_LOGGER_LOG_BODY) {
1976 fwrite (buffer, 1, nread, stdout);
1977 fflush (stdout);
1980 if (first_chunk) {
1981 GByteArray tmp_bytes;
1983 first_chunk = FALSE;
1985 tmp_bytes.data = buffer;
1986 tmp_bytes.len = nread;
1988 success = !e_webdav_session_replace_with_detailed_error (webdav, request, &tmp_bytes, FALSE, _("Failed to read resource"), error);
1989 if (!success)
1990 break;
1993 success = g_output_stream_write_all (out_stream, buffer, nread, &nwritten, cancellable, error);
1994 if (!success)
1995 break;
1998 if (success && first_chunk) {
1999 success = !e_webdav_session_replace_with_detailed_error (webdav, request, NULL, FALSE, _("Failed to read resource"), error);
2000 } else if (success && !first_chunk && log_level == SOUP_LOGGER_LOG_BODY) {
2001 fprintf (stdout, "\n");
2002 fflush (stdout);
2005 g_free (buffer);
2008 if (success)
2009 e_webdav_session_extract_href_and_etag (message, out_href, out_etag);
2011 g_clear_object (&input_stream);
2012 g_object_unref (message);
2013 g_object_unref (request);
2015 return success;
2019 * e_webdav_session_get_data_sync:
2020 * @webdav: an #EWebDAVSession
2021 * @uri: URI of the resource to read
2022 * @out_href: (out) (nullable) (transfer full): optional return location for href of the resource, or %NULL
2023 * @out_etag: (out) (nullable) (transfer full): optional return location for etag of the resource, or %NULL
2024 * @out_bytes: (out) (transfer full): return location for bytes being read
2025 * @out_length: (out) (nullable): option return location for length of bytes being read, or %NULL
2026 * @cancellable: optional #GCancellable object, or %NULL
2027 * @error: return location for a #GError, or %NULL
2029 * Reads a resource identified by @uri from the server. The URI cannot
2030 * reference a collection.
2032 * The @out_bytes is filled by actual data being read. If not %NULL, @out_length
2033 * is populated with how many bytes had been read. The @out_bytes is always
2034 * NUL-terminated, while this termination byte is not part of @out_length.
2035 * Free the @out_bytes with g_free(), when no longer needed.
2037 * Free returned pointer of @out_href and @out_etag, if not %NULL, with g_free(),
2038 * when no longer needed.
2040 * To read large data use e_webdav_session_get_sync() instead.
2042 * Returns: Whether succeeded.
2044 * Since: 3.26
2046 gboolean
2047 e_webdav_session_get_data_sync (EWebDAVSession *webdav,
2048 const gchar *uri,
2049 gchar **out_href,
2050 gchar **out_etag,
2051 gchar **out_bytes,
2052 gsize *out_length,
2053 GCancellable *cancellable,
2054 GError **error)
2056 GOutputStream *output_stream;
2057 gsize bytes_written = 0;
2058 gboolean success;
2060 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
2061 g_return_val_if_fail (uri != NULL, FALSE);
2062 g_return_val_if_fail (out_bytes != NULL, FALSE);
2064 *out_bytes = NULL;
2065 if (out_length)
2066 *out_length = 0;
2068 output_stream = g_memory_output_stream_new_resizable ();
2070 success = e_webdav_session_get_sync (webdav, uri, out_href, out_etag, output_stream, cancellable, error) &&
2071 g_output_stream_write_all (output_stream, "", 1, &bytes_written, cancellable, error) &&
2072 g_output_stream_close (output_stream, cancellable, error);
2074 if (success) {
2075 if (out_length)
2076 *out_length = g_memory_output_stream_get_data_size (G_MEMORY_OUTPUT_STREAM (output_stream)) - 1;
2078 *out_bytes = g_memory_output_stream_steal_data (G_MEMORY_OUTPUT_STREAM (output_stream));
2081 g_object_unref (output_stream);
2083 return success;
2086 typedef struct _ChunkWriteData {
2087 SoupSession *session;
2088 SoupLoggerLogLevel log_level;
2089 GInputStream *stream;
2090 goffset read_from;
2091 gboolean wrote_any;
2092 gsize buffer_size;
2093 gpointer buffer;
2094 GCancellable *cancellable;
2095 GError *error;
2096 } ChunkWriteData;
2098 static void
2099 e_webdav_session_write_next_chunk (SoupMessage *message,
2100 gpointer user_data)
2102 ChunkWriteData *cwd = user_data;
2103 gsize nread;
2105 g_return_if_fail (SOUP_IS_MESSAGE (message));
2106 g_return_if_fail (cwd != NULL);
2108 if (!g_input_stream_read_all (cwd->stream, cwd->buffer, cwd->buffer_size, &nread, cwd->cancellable, &cwd->error)) {
2109 soup_session_cancel_message (cwd->session, message, SOUP_STATUS_CANCELLED);
2110 return;
2113 if (nread == 0) {
2114 soup_message_body_complete (message->request_body);
2115 } else {
2116 cwd->wrote_any = TRUE;
2117 soup_message_body_append (message->request_body, SOUP_MEMORY_TEMPORARY, cwd->buffer, nread);
2119 if (cwd->log_level == SOUP_LOGGER_LOG_BODY) {
2120 fwrite (cwd->buffer, 1, nread, stdout);
2121 fflush (stdout);
2126 static void
2127 e_webdav_session_write_restarted (SoupMessage *message,
2128 gpointer user_data)
2130 ChunkWriteData *cwd = user_data;
2132 g_return_if_fail (SOUP_IS_MESSAGE (message));
2133 g_return_if_fail (cwd != NULL);
2135 /* The 302 redirect will turn it into a GET request and
2136 * reset the body encoding back to "NONE". Fix that.
2138 soup_message_headers_set_encoding (message->request_headers, SOUP_ENCODING_CHUNKED);
2139 message->method = SOUP_METHOD_PUT;
2141 if (cwd->wrote_any) {
2142 cwd->wrote_any = FALSE;
2144 if (!G_IS_SEEKABLE (cwd->stream) || !g_seekable_can_seek (G_SEEKABLE (cwd->stream)) ||
2145 !g_seekable_seek (G_SEEKABLE (cwd->stream), cwd->read_from, G_SEEK_SET, cwd->cancellable, &cwd->error)) {
2146 if (!cwd->error)
2147 g_set_error_literal (&cwd->error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT,
2148 _("Cannot rewind input stream: Not supported"));
2150 soup_session_cancel_message (cwd->session, message, SOUP_STATUS_CANCELLED);
2151 } else {
2152 soup_message_body_truncate (message->request_body);
2157 static void
2158 e_webdav_session_set_if_match_header (SoupMessage *message,
2159 const gchar *etag)
2161 gint len;
2163 g_return_if_fail (SOUP_IS_MESSAGE (message));
2164 g_return_if_fail (etag != NULL);
2166 len = strlen (etag);
2168 if ((*etag == '\"' && len > 2 && etag[len - 1] == '\"') || strchr (etag, '\"')) {
2169 soup_message_headers_replace (message->request_headers, "If-Match", etag);
2170 } else {
2171 gchar *quoted;
2173 quoted = g_strconcat ("\"", etag, "\"", NULL);
2174 soup_message_headers_replace (message->request_headers, "If-Match", quoted);
2175 g_free (quoted);
2180 * e_webdav_session_put_sync:
2181 * @webdav: an #EWebDAVSession
2182 * @uri: URI of the resource to write
2183 * @etag: (nullable): an ETag of the resource, if it's an existing resource, or %NULL
2184 * @content_type: Content-Type of the @bytes to be written
2185 * @stream: a #GInputStream with data to be written
2186 * @out_href: (out) (nullable) (transfer full): optional return location for href of the resource, or %NULL
2187 * @out_etag: (out) (nullable) (transfer full): optional return location for etag of the resource, or %NULL
2188 * @cancellable: optional #GCancellable object, or %NULL
2189 * @error: return location for a #GError, or %NULL
2191 * Writes data from @stream to a resource identified by @uri to the server.
2192 * The URI cannot reference a collection.
2194 * The @etag argument is used to avoid clashes when overwriting existing
2195 * resources. It can contain three values:
2196 * - %NULL - to write completely new resource
2197 * - empty string - write new resource or overwrite any existing, regardless changes on the server
2198 * - valid ETag - overwrite existing resource only if it wasn't changed on the server.
2200 * Note that the actual behaviour is also influenced by #ESourceWebdav:avoid-ifmatch
2201 * property of the associated #ESource.
2203 * The @out_href, if provided, is filled with the resulting URI
2204 * of the written resource. It can be different from the @uri when the server
2205 * redirected to a different location.
2207 * The @out_etag contains ETag of the resource after it had been saved.
2209 * The @stream should support also #GSeekable interface, because the data
2210 * send can require restart of the send due to redirect or other reasons.
2212 * This method uses Transfer-Encoding:chunked, in contrast to the
2213 * e_webdav_session_put_data_sync(), which writes data stored in memory
2214 * like any other request.
2216 * Returns: Whether succeeded.
2218 * Since: 3.26
2220 gboolean
2221 e_webdav_session_put_sync (EWebDAVSession *webdav,
2222 const gchar *uri,
2223 const gchar *etag,
2224 const gchar *content_type,
2225 GInputStream *stream,
2226 gchar **out_href,
2227 gchar **out_etag,
2228 GCancellable *cancellable,
2229 GError **error)
2231 ChunkWriteData cwd;
2232 SoupRequestHTTP *request;
2233 SoupMessage *message;
2234 GByteArray *bytes;
2235 gulong restarted_id, wrote_headers_id, wrote_chunk_id;
2236 gboolean success;
2238 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
2239 g_return_val_if_fail (uri != NULL, FALSE);
2240 g_return_val_if_fail (content_type != NULL, FALSE);
2241 g_return_val_if_fail (G_IS_INPUT_STREAM (stream), FALSE);
2243 if (out_href)
2244 *out_href = NULL;
2245 if (out_etag)
2246 *out_etag = NULL;
2248 request = e_webdav_session_new_request (webdav, SOUP_METHOD_PUT, uri, error);
2249 if (!request)
2250 return FALSE;
2252 message = soup_request_http_get_message (request);
2253 if (!message) {
2254 g_warn_if_fail (message != NULL);
2255 g_object_unref (request);
2257 return FALSE;
2260 if (!etag || *etag) {
2261 ESource *source;
2262 gboolean avoid_ifmatch = FALSE;
2264 source = e_soup_session_get_source (E_SOUP_SESSION (webdav));
2265 if (source && e_source_has_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND)) {
2266 ESourceWebdav *webdav_extension;
2268 webdav_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
2269 avoid_ifmatch = e_source_webdav_get_avoid_ifmatch (webdav_extension);
2272 if (!avoid_ifmatch) {
2273 if (etag) {
2274 e_webdav_session_set_if_match_header (message, etag);
2275 } else {
2276 soup_message_headers_replace (message->request_headers, "If-None-Match", "*");
2281 cwd.session = SOUP_SESSION (webdav);
2282 cwd.log_level = e_soup_session_get_log_level (E_SOUP_SESSION (webdav));
2283 cwd.stream = stream;
2284 cwd.read_from = 0;
2285 cwd.wrote_any = FALSE;
2286 cwd.buffer_size = BUFFER_SIZE;
2287 cwd.buffer = g_malloc (cwd.buffer_size);
2288 cwd.cancellable = cancellable;
2289 cwd.error = NULL;
2291 if (G_IS_SEEKABLE (stream) && g_seekable_can_seek (G_SEEKABLE (stream)))
2292 cwd.read_from = g_seekable_tell (G_SEEKABLE (stream));
2294 if (content_type && *content_type)
2295 soup_message_headers_replace (message->request_headers, "Content-Type", content_type);
2297 soup_message_headers_set_encoding (message->request_headers, SOUP_ENCODING_CHUNKED);
2298 soup_message_body_set_accumulate (message->request_body, FALSE);
2299 soup_message_set_flags (message, SOUP_MESSAGE_CAN_REBUILD);
2301 restarted_id = g_signal_connect (message, "restarted", G_CALLBACK (e_webdav_session_write_restarted), &cwd);
2302 wrote_headers_id = g_signal_connect (message, "wrote-headers", G_CALLBACK (e_webdav_session_write_next_chunk), &cwd);
2303 wrote_chunk_id = g_signal_connect (message, "wrote-chunk", G_CALLBACK (e_webdav_session_write_next_chunk), &cwd);
2305 bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, error);
2307 g_signal_handler_disconnect (message, restarted_id);
2308 g_signal_handler_disconnect (message, wrote_headers_id);
2309 g_signal_handler_disconnect (message, wrote_chunk_id);
2311 success = !e_webdav_session_replace_with_detailed_error (webdav, request, bytes, FALSE, _("Failed to put data"), error) &&
2312 bytes != NULL;
2314 if (cwd.wrote_any && cwd.log_level == SOUP_LOGGER_LOG_BODY) {
2315 fprintf (stdout, "\n");
2316 fflush (stdout);
2319 if (cwd.error) {
2320 g_clear_error (error);
2321 g_propagate_error (error, cwd.error);
2322 success = FALSE;
2325 if (success) {
2326 if (success && !SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
2327 success = FALSE;
2329 g_set_error (error, SOUP_HTTP_ERROR, message->status_code,
2330 _("Failed to put data to server, error code %d (%s)"), message->status_code,
2331 e_soup_session_util_status_to_string (message->status_code, message->reason_phrase));
2335 if (success)
2336 e_webdav_session_extract_href_and_etag (message, out_href, out_etag);
2338 if (bytes)
2339 g_byte_array_free (bytes, TRUE);
2340 g_object_unref (message);
2341 g_object_unref (request);
2342 g_free (cwd.buffer);
2344 return success;
2348 * e_webdav_session_put_data_sync:
2349 * @webdav: an #EWebDAVSession
2350 * @uri: URI of the resource to write
2351 * @etag: (nullable): an ETag of the resource, if it's an existing resource, or %NULL
2352 * @content_type: Content-Type of the @bytes to be written
2353 * @bytes: actual bytes to be written
2354 * @length: how many bytes to write, or -1, when the @bytes is NUL-terminated
2355 * @out_href: (out) (nullable) (transfer full): optional return location for href of the resource, or %NULL
2356 * @out_etag: (out) (nullable) (transfer full): optional return location for etag of the resource, or %NULL
2357 * @cancellable: optional #GCancellable object, or %NULL
2358 * @error: return location for a #GError, or %NULL
2360 * Writes data to a resource identified by @uri to the server. The URI cannot
2361 * reference a collection.
2363 * The @etag argument is used to avoid clashes when overwriting existing
2364 * resources. It can contain three values:
2365 * - %NULL - to write completely new resource
2366 * - empty string - write new resource or overwrite any existing, regardless changes on the server
2367 * - valid ETag - overwrite existing resource only if it wasn't changed on the server.
2369 * Note that the actual usage of @etag is also influenced by #ESourceWebdav:avoid-ifmatch
2370 * property of the associated #ESource.
2372 * The @out_href, if provided, is filled with the resulting URI
2373 * of the written resource. It can be different from the @uri when the server
2374 * redirected to a different location.
2376 * The @out_etag contains ETag of the resource after it had been saved.
2378 * To write large data use e_webdav_session_put_sync() instead.
2380 * Returns: Whether succeeded.
2382 * Since: 3.26
2384 gboolean
2385 e_webdav_session_put_data_sync (EWebDAVSession *webdav,
2386 const gchar *uri,
2387 const gchar *etag,
2388 const gchar *content_type,
2389 const gchar *bytes,
2390 gsize length,
2391 gchar **out_href,
2392 gchar **out_etag,
2393 GCancellable *cancellable,
2394 GError **error)
2396 SoupRequestHTTP *request;
2397 SoupMessage *message;
2398 GByteArray *ret_bytes;
2399 gboolean success;
2401 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
2402 g_return_val_if_fail (uri != NULL, FALSE);
2403 g_return_val_if_fail (content_type != NULL, FALSE);
2404 g_return_val_if_fail (bytes != NULL, FALSE);
2406 if (length == (gsize) -1)
2407 length = strlen (bytes);
2408 if (out_href)
2409 *out_href = NULL;
2410 if (out_etag)
2411 *out_etag = NULL;
2413 request = e_webdav_session_new_request (webdav, SOUP_METHOD_PUT, uri, error);
2414 if (!request)
2415 return FALSE;
2417 message = soup_request_http_get_message (request);
2418 if (!message) {
2419 g_warn_if_fail (message != NULL);
2420 g_object_unref (request);
2422 return FALSE;
2425 if (!etag || *etag) {
2426 ESource *source;
2427 gboolean avoid_ifmatch = FALSE;
2429 source = e_soup_session_get_source (E_SOUP_SESSION (webdav));
2430 if (source && e_source_has_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND)) {
2431 ESourceWebdav *webdav_extension;
2433 webdav_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
2434 avoid_ifmatch = e_source_webdav_get_avoid_ifmatch (webdav_extension);
2437 if (!avoid_ifmatch) {
2438 if (etag) {
2439 e_webdav_session_set_if_match_header (message, etag);
2440 } else {
2441 soup_message_headers_replace (message->request_headers, "If-None-Match", "*");
2446 if (content_type && *content_type)
2447 soup_message_headers_replace (message->request_headers, "Content-Type", content_type);
2449 soup_message_headers_replace (message->request_headers, "Prefer", "return=minimal");
2451 soup_message_set_request (message, content_type, SOUP_MEMORY_TEMPORARY, bytes, length);
2453 ret_bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, error);
2455 success = !e_webdav_session_replace_with_detailed_error (webdav, request, ret_bytes, FALSE, _("Failed to put data"), error) &&
2456 ret_bytes != NULL;
2458 if (success) {
2459 if (success && !SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
2460 success = FALSE;
2462 g_set_error (error, SOUP_HTTP_ERROR, message->status_code,
2463 _("Failed to put data to server, error code %d (%s)"), message->status_code,
2464 e_soup_session_util_status_to_string (message->status_code, message->reason_phrase));
2468 if (success)
2469 e_webdav_session_extract_href_and_etag (message, out_href, out_etag);
2471 if (ret_bytes)
2472 g_byte_array_free (ret_bytes, TRUE);
2473 g_object_unref (message);
2474 g_object_unref (request);
2476 return success;
2480 * e_webdav_session_delete_sync:
2481 * @webdav: an #EWebDAVSession
2482 * @uri: URI of the resource to delete
2483 * @depth: (nullable): optional requested depth, can be one of %E_WEBDAV_DEPTH_THIS or %E_WEBDAV_DEPTH_INFINITY, or %NULL
2484 * @etag: (nullable): an optional ETag of the resource, or %NULL
2485 * @cancellable: optional #GCancellable object, or %NULL
2486 * @error: return location for a #GError, or %NULL
2488 * Deletes a resource identified by @uri on the server. The URI can
2489 * reference a collection, in which case @depth should be %E_WEBDAV_DEPTH_INFINITY.
2490 * Use @depth %E_WEBDAV_DEPTH_THIS when deleting a regular resource, or %NULL,
2491 * to let the server use default Depth.
2493 * The @etag argument is used to avoid clashes when overwriting existing resources.
2494 * Use %NULL @etag when deleting collection resources or to force the deletion,
2495 * otherwise provide a valid ETag of a non-collection resource to verify that
2496 * the version requested to delete is the same as on the server.
2498 * Note that the actual usage of @etag is also influenced by #ESourceWebdav:avoid-ifmatch
2499 * property of the associated #ESource.
2501 * Returns: Whether succeeded.
2503 * Since: 3.26
2505 gboolean
2506 e_webdav_session_delete_sync (EWebDAVSession *webdav,
2507 const gchar *uri,
2508 const gchar *depth,
2509 const gchar *etag,
2510 GCancellable *cancellable,
2511 GError **error)
2513 SoupRequestHTTP *request;
2514 SoupMessage *message;
2515 GByteArray *bytes;
2516 gboolean success;
2518 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
2519 g_return_val_if_fail (uri != NULL, FALSE);
2521 request = e_webdav_session_new_request (webdav, SOUP_METHOD_DELETE, uri, error);
2522 if (!request)
2523 return FALSE;
2525 message = soup_request_http_get_message (request);
2526 if (!message) {
2527 g_warn_if_fail (message != NULL);
2528 g_object_unref (request);
2530 return FALSE;
2533 if (etag) {
2534 ESource *source;
2535 gboolean avoid_ifmatch = FALSE;
2537 source = e_soup_session_get_source (E_SOUP_SESSION (webdav));
2538 if (source && e_source_has_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND)) {
2539 ESourceWebdav *webdav_extension;
2541 webdav_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
2542 avoid_ifmatch = e_source_webdav_get_avoid_ifmatch (webdav_extension);
2545 if (!avoid_ifmatch) {
2546 e_webdav_session_set_if_match_header (message, etag);
2550 if (depth)
2551 soup_message_headers_replace (message->request_headers, "Depth", depth);
2553 bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, error);
2555 success = !e_webdav_session_replace_with_detailed_error (webdav, request, bytes, FALSE, _("Failed to delete resource"), error) &&
2556 bytes != NULL;
2558 if (bytes)
2559 g_byte_array_free (bytes, TRUE);
2560 g_object_unref (message);
2561 g_object_unref (request);
2563 return success;
2567 * e_webdav_session_copy_sync:
2568 * @webdav: an #EWebDAVSession
2569 * @source_uri: URI of the resource or collection to copy
2570 * @destination_uri: URI of the destination
2571 * @depth: requested depth, can be one of %E_WEBDAV_DEPTH_THIS or %E_WEBDAV_DEPTH_INFINITY
2572 * @can_overwrite: whether can overwrite @destination_uri, when it exists
2573 * @cancellable: optional #GCancellable object, or %NULL
2574 * @error: return location for a #GError, or %NULL
2576 * Copies a resource identified by @source_uri to @destination_uri on the server.
2577 * The @source_uri can reference also collections, in which case the @depth influences
2578 * whether only the collection itself is copied (%E_WEBDAV_DEPTH_THIS) or whether
2579 * the collection with all its children is copied (%E_WEBDAV_DEPTH_INFINITY).
2581 * Returns: Whether succeeded.
2583 * Since: 3.26
2585 gboolean
2586 e_webdav_session_copy_sync (EWebDAVSession *webdav,
2587 const gchar *source_uri,
2588 const gchar *destination_uri,
2589 const gchar *depth,
2590 gboolean can_overwrite,
2591 GCancellable *cancellable,
2592 GError **error)
2594 SoupRequestHTTP *request;
2595 SoupMessage *message;
2596 GByteArray *bytes;
2597 gboolean success;
2599 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
2600 g_return_val_if_fail (source_uri != NULL, FALSE);
2601 g_return_val_if_fail (destination_uri != NULL, FALSE);
2602 g_return_val_if_fail (depth != NULL, FALSE);
2604 request = e_webdav_session_new_request (webdav, SOUP_METHOD_COPY, source_uri, error);
2605 if (!request)
2606 return FALSE;
2608 message = soup_request_http_get_message (request);
2609 if (!message) {
2610 g_warn_if_fail (message != NULL);
2611 g_object_unref (request);
2613 return FALSE;
2616 soup_message_headers_replace (message->request_headers, "Depth", depth);
2617 soup_message_headers_replace (message->request_headers, "Destination", destination_uri);
2618 soup_message_headers_replace (message->request_headers, "Overwrite", can_overwrite ? "T" : "F");
2620 bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, error);
2622 success = !e_webdav_session_replace_with_detailed_error (webdav, request, bytes, FALSE, _("Failed to copy resource"), error) &&
2623 bytes != NULL;
2625 if (bytes)
2626 g_byte_array_free (bytes, TRUE);
2627 g_object_unref (message);
2628 g_object_unref (request);
2630 return success;
2634 * e_webdav_session_move_sync:
2635 * @webdav: an #EWebDAVSession
2636 * @source_uri: URI of the resource or collection to copy
2637 * @destination_uri: URI of the destination
2638 * @can_overwrite: whether can overwrite @destination_uri, when it exists
2639 * @cancellable: optional #GCancellable object, or %NULL
2640 * @error: return location for a #GError, or %NULL
2642 * Moves a resource identified by @source_uri to @destination_uri on the server.
2643 * The @source_uri can reference also collections.
2645 * Returns: Whether succeeded.
2647 * Since: 3.26
2649 gboolean
2650 e_webdav_session_move_sync (EWebDAVSession *webdav,
2651 const gchar *source_uri,
2652 const gchar *destination_uri,
2653 gboolean can_overwrite,
2654 GCancellable *cancellable,
2655 GError **error)
2657 SoupRequestHTTP *request;
2658 SoupMessage *message;
2659 GByteArray *bytes;
2660 gboolean success;
2662 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
2663 g_return_val_if_fail (source_uri != NULL, FALSE);
2664 g_return_val_if_fail (destination_uri != NULL, FALSE);
2666 request = e_webdav_session_new_request (webdav, SOUP_METHOD_MOVE, source_uri, error);
2667 if (!request)
2668 return FALSE;
2670 message = soup_request_http_get_message (request);
2671 if (!message) {
2672 g_warn_if_fail (message != NULL);
2673 g_object_unref (request);
2675 return FALSE;
2678 soup_message_headers_replace (message->request_headers, "Depth", E_WEBDAV_DEPTH_INFINITY);
2679 soup_message_headers_replace (message->request_headers, "Destination", destination_uri);
2680 soup_message_headers_replace (message->request_headers, "Overwrite", can_overwrite ? "T" : "F");
2682 bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, error);
2684 success = !e_webdav_session_replace_with_detailed_error (webdav, request, bytes, FALSE, _("Failed to move resource"), error) &&
2685 bytes != NULL;
2687 if (bytes)
2688 g_byte_array_free (bytes, TRUE);
2689 g_object_unref (message);
2690 g_object_unref (request);
2692 return success;
2696 * e_webdav_session_lock_sync:
2697 * @webdav: an #EWebDAVSession
2698 * @uri: (nullable): URI to lock, or %NULL to read from #ESource
2699 * @depth: requested depth, can be one of %E_WEBDAV_DEPTH_THIS or %E_WEBDAV_DEPTH_INFINITY
2700 * @lock_timeout: timeout for the lock, in seconds, on 0 to infinity
2701 * @xml: an XML describing the lock request, with DAV:lockinfo root element
2702 * @out_lock_token: (out) (transfer full): return location of the obtained or refreshed lock token
2703 * @out_xml_response: (out) (nullable) (transfer full): optional return location for the server response as #xmlDocPtr
2704 * @cancellable: optional #GCancellable object, or %NULL
2705 * @error: return location for a #GError, or %NULL
2707 * Locks a resource identified by @uri, or, in case it's %NULL, on the URI
2708 * defined in associated #ESource.
2710 * The @out_lock_token can be refreshed with e_webdav_session_refresh_lock_sync().
2711 * Release the lock with e_webdav_session_unlock_sync().
2712 * Free the returned @out_lock_token with g_free(), when no longer needed.
2714 * If provided, free the returned @out_xml_response with xmlFreeDoc(),
2715 * when no longer needed.
2717 * Returns: Whether succeeded.
2719 * Since: 3.26
2721 gboolean
2722 e_webdav_session_lock_sync (EWebDAVSession *webdav,
2723 const gchar *uri,
2724 const gchar *depth,
2725 gint32 lock_timeout,
2726 const EXmlDocument *xml,
2727 gchar **out_lock_token,
2728 xmlDoc **out_xml_response,
2729 GCancellable *cancellable,
2730 GError **error)
2732 SoupRequestHTTP *request;
2733 SoupMessage *message;
2734 GByteArray *bytes;
2735 gboolean success;
2737 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
2738 g_return_val_if_fail (depth != NULL, FALSE);
2739 g_return_val_if_fail (E_IS_XML_DOCUMENT (xml), FALSE);
2740 g_return_val_if_fail (out_lock_token != NULL, FALSE);
2742 *out_lock_token = NULL;
2744 request = e_webdav_session_new_request (webdav, SOUP_METHOD_LOCK, uri, error);
2745 if (!request)
2746 return FALSE;
2748 message = soup_request_http_get_message (request);
2749 if (!message) {
2750 g_warn_if_fail (message != NULL);
2751 g_object_unref (request);
2753 return FALSE;
2756 if (depth)
2757 soup_message_headers_replace (message->request_headers, "Depth", depth);
2759 if (lock_timeout) {
2760 gchar *value;
2762 value = g_strdup_printf ("Second-%d", lock_timeout);
2763 soup_message_headers_replace (message->request_headers, "Timeout", value);
2764 g_free (value);
2765 } else {
2766 soup_message_headers_replace (message->request_headers, "Timeout", "Infinite");
2769 if (xml) {
2770 gchar *content;
2771 gsize content_length;
2773 content = e_xml_document_get_content (xml, &content_length);
2774 if (!content) {
2775 g_object_unref (message);
2776 g_object_unref (request);
2778 g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Failed to get input XML content"));
2780 return FALSE;
2783 soup_message_set_request (message, E_WEBDAV_CONTENT_TYPE_XML,
2784 SOUP_MEMORY_TAKE, content, content_length);
2787 bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, error);
2789 success = !e_webdav_session_replace_with_detailed_error (webdav, request, bytes, FALSE, _("Failed to lock resource"), error) &&
2790 bytes != NULL;
2792 if (success && out_xml_response) {
2793 const gchar *content_type;
2795 *out_xml_response = NULL;
2797 content_type = soup_message_headers_get_content_type (message->response_headers, NULL);
2798 if (!content_type ||
2799 (g_ascii_strcasecmp (content_type, "application/xml") != 0 &&
2800 g_ascii_strcasecmp (content_type, "text/xml") != 0)) {
2801 if (!content_type) {
2802 g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
2803 _("Expected application/xml response, but none returned"));
2804 } else {
2805 g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
2806 _("Expected application/xml response, but %s returned"), content_type);
2809 success = FALSE;
2812 if (success) {
2813 xmlDocPtr doc;
2815 doc = e_xml_parse_data (bytes->data, bytes->len);
2816 if (!doc) {
2817 g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
2818 _("Failed to parse XML data"));
2820 success = FALSE;
2821 } else {
2822 *out_xml_response = doc;
2827 if (success)
2828 *out_lock_token = g_strdup (soup_message_headers_get_list (message->response_headers, "Lock-Token"));
2830 if (bytes)
2831 g_byte_array_free (bytes, TRUE);
2832 g_object_unref (message);
2833 g_object_unref (request);
2835 return success;
2839 * e_webdav_session_refresh_lock_sync:
2840 * @webdav: an #EWebDAVSession
2841 * @uri: (nullable): URI to lock, or %NULL to read from #ESource
2842 * @lock_token: token of an existing lock
2843 * @lock_timeout: timeout for the lock, in seconds, on 0 to infinity
2844 * @cancellable: optional #GCancellable object, or %NULL
2845 * @error: return location for a #GError, or %NULL
2847 * Refreshes existing lock @lock_token for a resource identified by @uri,
2848 * or, in case it's %NULL, on the URI defined in associated #ESource.
2849 * The @lock_token is returned from e_webdav_session_lock_sync() and
2850 * the @uri should be the same as that used with e_webdav_session_lock_sync().
2852 * Returns: Whether succeeded.
2854 * Since: 3.26
2856 gboolean
2857 e_webdav_session_refresh_lock_sync (EWebDAVSession *webdav,
2858 const gchar *uri,
2859 const gchar *lock_token,
2860 gint32 lock_timeout,
2861 GCancellable *cancellable,
2862 GError **error)
2864 SoupRequestHTTP *request;
2865 SoupMessage *message;
2866 GByteArray *bytes;
2867 gchar *value;
2868 gboolean success;
2870 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
2871 g_return_val_if_fail (lock_token != NULL, FALSE);
2873 request = e_webdav_session_new_request (webdav, SOUP_METHOD_LOCK, uri, error);
2874 if (!request)
2875 return FALSE;
2877 message = soup_request_http_get_message (request);
2878 if (!message) {
2879 g_warn_if_fail (message != NULL);
2880 g_object_unref (request);
2882 return FALSE;
2885 if (lock_timeout) {
2886 value = g_strdup_printf ("Second-%d", lock_timeout);
2887 soup_message_headers_replace (message->request_headers, "Timeout", value);
2888 g_free (value);
2889 } else {
2890 soup_message_headers_replace (message->request_headers, "Timeout", "Infinite");
2893 soup_message_headers_replace (message->request_headers, "Lock-Token", lock_token);
2895 bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, error);
2897 success = !e_webdav_session_replace_with_detailed_error (webdav, request, bytes, FALSE, _("Failed to refresh lock"), error) &&
2898 bytes != NULL;
2900 if (bytes)
2901 g_byte_array_free (bytes, TRUE);
2902 g_object_unref (message);
2903 g_object_unref (request);
2905 return success;
2909 * e_webdav_session_unlock_sync:
2910 * @webdav: an #EWebDAVSession
2911 * @uri: (nullable): URI to lock, or %NULL to read from #ESource
2912 * @lock_token: token of an existing lock
2913 * @cancellable: optional #GCancellable object, or %NULL
2914 * @error: return location for a #GError, or %NULL
2916 * Releases (unlocks) existing lock @lock_token for a resource identified by @uri,
2917 * or, in case it's %NULL, on the URI defined in associated #ESource.
2918 * The @lock_token is returned from e_webdav_session_lock_sync() and
2919 * the @uri should be the same as that used with e_webdav_session_lock_sync().
2921 * Returns: Whether succeeded.
2923 * Since: 3.26
2925 gboolean
2926 e_webdav_session_unlock_sync (EWebDAVSession *webdav,
2927 const gchar *uri,
2928 const gchar *lock_token,
2929 GCancellable *cancellable,
2930 GError **error)
2932 SoupRequestHTTP *request;
2933 SoupMessage *message;
2934 GByteArray *bytes;
2935 gboolean success;
2937 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
2938 g_return_val_if_fail (lock_token != NULL, FALSE);
2940 request = e_webdav_session_new_request (webdav, SOUP_METHOD_UNLOCK, uri, error);
2941 if (!request)
2942 return FALSE;
2944 message = soup_request_http_get_message (request);
2945 if (!message) {
2946 g_warn_if_fail (message != NULL);
2947 g_object_unref (request);
2949 return FALSE;
2952 soup_message_headers_replace (message->request_headers, "Lock-Token", lock_token);
2954 bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, error);
2956 success = !e_webdav_session_replace_with_detailed_error (webdav, request, bytes, FALSE, _("Failed to unlock"), error) &&
2957 bytes != NULL;
2959 if (bytes)
2960 g_byte_array_free (bytes, TRUE);
2961 g_object_unref (message);
2962 g_object_unref (request);
2964 return success;
2967 static gboolean
2968 e_webdav_session_traverse_propstat_response (EWebDAVSession *webdav,
2969 const SoupMessage *message,
2970 const GByteArray *xml_data,
2971 gboolean require_multistatus,
2972 const gchar *additional_ns_prefix,
2973 const gchar *additional_ns,
2974 const gchar *propstat_path_prefix,
2975 EWebDAVPropstatTraverseFunc func,
2976 gpointer func_user_data,
2977 GError **error)
2979 SoupURI *request_uri = NULL;
2980 xmlDocPtr doc;
2981 xmlXPathContextPtr xpath_ctx;
2983 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
2984 g_return_val_if_fail (xml_data != NULL, FALSE);
2985 g_return_val_if_fail (propstat_path_prefix != NULL, FALSE);
2986 g_return_val_if_fail (func != NULL, FALSE);
2988 if (message) {
2989 const gchar *content_type;
2991 if (require_multistatus && message->status_code != SOUP_STATUS_MULTI_STATUS) {
2992 g_set_error (error, SOUP_HTTP_ERROR, message->status_code,
2993 _("Expected multistatus response, but %d returned (%s)"), message->status_code,
2994 e_soup_session_util_status_to_string (message->status_code, message->reason_phrase));
2996 return FALSE;
2999 content_type = soup_message_headers_get_content_type (message->response_headers, NULL);
3000 if (!content_type ||
3001 (g_ascii_strcasecmp (content_type, "application/xml") != 0 &&
3002 g_ascii_strcasecmp (content_type, "text/xml") != 0)) {
3003 if (!content_type) {
3004 g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
3005 _("Expected application/xml response, but none returned"));
3006 } else {
3007 g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
3008 _("Expected application/xml response, but %s returned"), content_type);
3011 return FALSE;
3014 request_uri = soup_message_get_uri ((SoupMessage *) message);
3017 doc = e_xml_parse_data (xml_data->data, xml_data->len);
3019 if (!doc) {
3020 g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
3021 _("Failed to parse XML data"));
3023 return FALSE;
3026 xpath_ctx = e_xml_new_xpath_context_with_namespaces (doc,
3027 "D", E_WEBDAV_NS_DAV,
3028 additional_ns_prefix, additional_ns,
3029 NULL);
3031 if (xpath_ctx &&
3032 func (webdav, xpath_ctx, NULL, request_uri, NULL, SOUP_STATUS_NONE, func_user_data)) {
3033 xmlXPathObjectPtr xpath_obj_response;
3035 xpath_obj_response = e_xml_xpath_eval (xpath_ctx, "%s", propstat_path_prefix);
3037 if (xpath_obj_response) {
3038 gboolean do_stop = FALSE;
3039 gint response_index, response_length;
3041 response_length = xmlXPathNodeSetGetLength (xpath_obj_response->nodesetval);
3043 for (response_index = 0; response_index < response_length && !do_stop; response_index++) {
3044 xmlXPathObjectPtr xpath_obj_propstat;
3046 xpath_obj_propstat = e_xml_xpath_eval (xpath_ctx,
3047 "%s[%d]/D:propstat",
3048 propstat_path_prefix, response_index + 1);
3050 if (xpath_obj_propstat) {
3051 gchar *href;
3052 gint propstat_index, propstat_length;
3054 href = e_xml_xpath_eval_as_string (xpath_ctx, "%s[%d]/D:href", propstat_path_prefix, response_index + 1);
3055 if (href) {
3056 gchar *full_uri;
3058 full_uri = e_webdav_session_ensure_full_uri (webdav, request_uri, href);
3059 if (full_uri) {
3060 g_free (href);
3061 href = full_uri;
3065 propstat_length = xmlXPathNodeSetGetLength (xpath_obj_propstat->nodesetval);
3067 for (propstat_index = 0; propstat_index < propstat_length && !do_stop; propstat_index++) {
3068 gchar *status, *propstat_prefix;
3069 guint status_code;
3071 propstat_prefix = g_strdup_printf ("%s[%d]/D:propstat[%d]/D:prop",
3072 propstat_path_prefix, response_index + 1, propstat_index + 1);
3074 status = e_xml_xpath_eval_as_string (xpath_ctx, "%s/../D:status", propstat_prefix);
3075 if (!status || !soup_headers_parse_status_line (status, NULL, &status_code, NULL))
3076 status_code = 0;
3077 g_free (status);
3079 do_stop = !func (webdav, xpath_ctx, propstat_prefix, request_uri, href, status_code, func_user_data);
3081 g_free (propstat_prefix);
3084 xmlXPathFreeObject (xpath_obj_propstat);
3085 g_free (href);
3089 xmlXPathFreeObject (xpath_obj_response);
3093 if (xpath_ctx)
3094 xmlXPathFreeContext (xpath_ctx);
3095 xmlFreeDoc (doc);
3097 return TRUE;
3101 * e_webdav_session_traverse_multistatus_response:
3102 * @webdav: an #EWebDAVSession
3103 * @message: (nullable): an optional #SoupMessage corresponding to the response, or %NULL
3104 * @xml_data: a #GByteArray containing DAV:multistatus response
3105 * @func: (scope call): an #EWebDAVPropstatTraverseFunc function to call for each DAV:propstat in the multistatus response
3106 * @func_user_data: (closure func): user data passed to @func
3107 * @error: return location for a #GError, or %NULL
3109 * Traverses a DAV:multistatus response and calls @func for each returned DAV:propstat.
3110 * The provided XPath context has registered %E_WEBDAV_NS_DAV namespace with prefix "D".
3111 * It doesn't have any other namespace registered.
3113 * The @message, if provided, is used to verify that the response is a multi-status
3114 * and that the Content-Type is properly set. It's used to get a request URI as well.
3116 * The @func is called always at least once, with %NULL xpath_prop_prefix, which
3117 * is meant to let the caller setup the xpath_ctx, like to register its own namespaces
3118 * to it with e_xml_xpath_context_register_namespaces(). All other invocations of @func
3119 * will have xpath_prop_prefix non-%NULL.
3121 * Returns: Whether succeeded.
3123 * Since: 3.26
3125 gboolean
3126 e_webdav_session_traverse_multistatus_response (EWebDAVSession *webdav,
3127 const SoupMessage *message,
3128 const GByteArray *xml_data,
3129 EWebDAVPropstatTraverseFunc func,
3130 gpointer func_user_data,
3131 GError **error)
3133 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
3134 g_return_val_if_fail (xml_data != NULL, FALSE);
3135 g_return_val_if_fail (func != NULL, FALSE);
3137 return e_webdav_session_traverse_propstat_response (webdav, message, xml_data, TRUE,
3138 NULL, NULL, "/D:multistatus/D:response",
3139 func, func_user_data, error);
3143 * e_webdav_session_traverse_mkcol_response:
3144 * @webdav: an #EWebDAVSession
3145 * @message: (nullable): an optional #SoupMessage corresponding to the response, or %NULL
3146 * @xml_data: a #GByteArray containing DAV:mkcol-response response
3147 * @func: (scope call): an #EWebDAVPropstatTraverseFunc function to call for each DAV:propstat in the response
3148 * @func_user_data: (closure func): user data passed to @func
3149 * @error: return location for a #GError, or %NULL
3151 * Traverses a DAV:mkcol-response response and calls @func for each returned DAV:propstat.
3152 * The provided XPath context has registered %E_WEBDAV_NS_DAV namespace with prefix "D".
3153 * It doesn't have any other namespace registered.
3155 * The @message, if provided, is used to verify that the response is an XML Content-Type.
3156 * It's used to get the request URI as well.
3158 * The @func is called always at least once, with %NULL xpath_prop_prefix, which
3159 * is meant to let the caller setup the xpath_ctx, like to register its own namespaces
3160 * to it with e_xml_xpath_context_register_namespaces(). All other invocations of @func
3161 * will have xpath_prop_prefix non-%NULL.
3163 * Returns: Whether succeeded.
3165 * Since: 3.26
3167 gboolean
3168 e_webdav_session_traverse_mkcol_response (EWebDAVSession *webdav,
3169 const SoupMessage *message,
3170 const GByteArray *xml_data,
3171 EWebDAVPropstatTraverseFunc func,
3172 gpointer func_user_data,
3173 GError **error)
3175 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
3176 g_return_val_if_fail (xml_data != NULL, FALSE);
3177 g_return_val_if_fail (func != NULL, FALSE);
3179 return e_webdav_session_traverse_propstat_response (webdav, message, xml_data, FALSE,
3180 NULL, NULL, "/D:mkcol-response",
3181 func, func_user_data, error);
3185 * e_webdav_session_traverse_mkcalendar_response:
3186 * @webdav: an #EWebDAVSession
3187 * @message: (nullable): an optional #SoupMessage corresponding to the response, or %NULL
3188 * @xml_data: a #GByteArray containing CALDAV:mkcalendar-response response
3189 * @func: (scope call): an #EWebDAVPropstatTraverseFunc function to call for each DAV:propstat in the response
3190 * @func_user_data: (closure func): user data passed to @func
3191 * @error: return location for a #GError, or %NULL
3193 * Traverses a CALDAV:mkcalendar-response response and calls @func for each returned DAV:propstat.
3194 * The provided XPath context has registered %E_WEBDAV_NS_DAV namespace with prefix "D" and
3195 * %E_WEBDAV_NS_CALDAV namespace with prefix "C". It doesn't have any other namespace registered.
3197 * The @message, if provided, is used to verify that the response is an XML Content-Type.
3198 * It's used to get the request URI as well.
3200 * The @func is called always at least once, with %NULL xpath_prop_prefix, which
3201 * is meant to let the caller setup the xpath_ctx, like to register its own namespaces
3202 * to it with e_xml_xpath_context_register_namespaces(). All other invocations of @func
3203 * will have xpath_prop_prefix non-%NULL.
3205 * Returns: Whether succeeded.
3207 * Since: 3.26
3209 gboolean
3210 e_webdav_session_traverse_mkcalendar_response (EWebDAVSession *webdav,
3211 const SoupMessage *message,
3212 const GByteArray *xml_data,
3213 EWebDAVPropstatTraverseFunc func,
3214 gpointer func_user_data,
3215 GError **error)
3217 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
3218 g_return_val_if_fail (xml_data != NULL, FALSE);
3219 g_return_val_if_fail (func != NULL, FALSE);
3221 return e_webdav_session_traverse_propstat_response (webdav, message, xml_data, FALSE,
3222 "C", E_WEBDAV_NS_CALDAV, "/C:mkcalendar-response",
3223 func, func_user_data, error);
3226 static gboolean
3227 e_webdav_session_getctag_cb (EWebDAVSession *webdav,
3228 xmlXPathContextPtr xpath_ctx,
3229 const gchar *xpath_prop_prefix,
3230 const SoupURI *request_uri,
3231 const gchar *href,
3232 guint status_code,
3233 gpointer user_data)
3235 if (!xpath_prop_prefix) {
3236 e_xml_xpath_context_register_namespaces (xpath_ctx,
3237 "CS", E_WEBDAV_NS_CALENDARSERVER,
3238 NULL);
3240 return TRUE;
3243 if (status_code == SOUP_STATUS_OK) {
3244 gchar **out_ctag = user_data;
3245 gchar *ctag;
3247 g_return_val_if_fail (out_ctag != NULL, FALSE);
3249 ctag = e_xml_xpath_eval_as_string (xpath_ctx, "%s/CS:getctag", xpath_prop_prefix);
3251 if (ctag && *ctag) {
3252 *out_ctag = e_webdav_session_util_maybe_dequote (ctag);
3253 } else {
3254 g_free (ctag);
3258 return FALSE;
3262 * e_webdav_session_getctag_sync:
3263 * @webdav: an #EWebDAVSession
3264 * @uri: (nullable): URI to issue the request for, or %NULL to read from #ESource
3265 * @out_ctag: (out) (transfer full): return location for the ctag
3266 * @cancellable: optional #GCancellable object, or %NULL
3267 * @error: return location for a #GError, or %NULL
3269 * Issues a getctag property request for a collection identified by @uri, or,
3270 * in case it's %NULL, on the URI defined in associated #ESource. The ctag is
3271 * a collection tag, which changes whenever the collection changes (similar
3272 * to etag). The getctag is an extension, thus the function can fail when
3273 * the server doesn't support it.
3275 * Free the returned @out_ctag with g_free(), when no longer needed.
3277 * Returns: Whether succeeded.
3279 * Since: 3.26
3281 gboolean
3282 e_webdav_session_getctag_sync (EWebDAVSession *webdav,
3283 const gchar *uri,
3284 gchar **out_ctag,
3285 GCancellable *cancellable,
3286 GError **error)
3288 EXmlDocument *xml;
3289 gboolean success;
3291 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
3292 g_return_val_if_fail (out_ctag != NULL, FALSE);
3294 *out_ctag = NULL;
3296 xml = e_xml_document_new (E_WEBDAV_NS_DAV, "propfind");
3297 g_return_val_if_fail (xml != NULL, FALSE);
3299 e_xml_document_add_namespaces (xml, "CS", E_WEBDAV_NS_CALENDARSERVER, NULL);
3301 e_xml_document_start_element (xml, NULL, "prop");
3302 e_xml_document_add_empty_element (xml, E_WEBDAV_NS_CALENDARSERVER, "getctag");
3303 e_xml_document_end_element (xml); /* prop */
3305 success = e_webdav_session_propfind_sync (webdav, uri, E_WEBDAV_DEPTH_THIS, xml,
3306 e_webdav_session_getctag_cb, out_ctag, cancellable, error);
3308 g_object_unref (xml);
3310 return success && *out_ctag != NULL;
3313 static EWebDAVResourceKind
3314 e_webdav_session_extract_kind (xmlXPathContextPtr xpath_ctx,
3315 const gchar *xpath_prop_prefix)
3317 g_return_val_if_fail (xpath_ctx != NULL, E_WEBDAV_RESOURCE_KIND_UNKNOWN);
3318 g_return_val_if_fail (xpath_prop_prefix != NULL, E_WEBDAV_RESOURCE_KIND_UNKNOWN);
3320 if (e_xml_xpath_eval_exists (xpath_ctx, "%s/D:resourcetype/A:addressbook", xpath_prop_prefix))
3321 return E_WEBDAV_RESOURCE_KIND_ADDRESSBOOK;
3323 if (e_xml_xpath_eval_exists (xpath_ctx, "%s/D:resourcetype/C:calendar", xpath_prop_prefix))
3324 return E_WEBDAV_RESOURCE_KIND_CALENDAR;
3326 if (e_xml_xpath_eval_exists (xpath_ctx, "%s/D:resourcetype/D:principal", xpath_prop_prefix))
3327 return E_WEBDAV_RESOURCE_KIND_PRINCIPAL;
3329 if (e_xml_xpath_eval_exists (xpath_ctx, "%s/D:resourcetype/D:collection", xpath_prop_prefix))
3330 return E_WEBDAV_RESOURCE_KIND_COLLECTION;
3332 return E_WEBDAV_RESOURCE_KIND_RESOURCE;
3335 static guint32
3336 e_webdav_session_extract_supports (xmlXPathContextPtr xpath_ctx,
3337 const gchar *xpath_prop_prefix)
3339 guint32 supports = E_WEBDAV_RESOURCE_SUPPORTS_NONE;
3341 g_return_val_if_fail (xpath_ctx != NULL, E_WEBDAV_RESOURCE_SUPPORTS_NONE);
3342 g_return_val_if_fail (xpath_prop_prefix != NULL, E_WEBDAV_RESOURCE_SUPPORTS_NONE);
3344 if (e_xml_xpath_eval_exists (xpath_ctx, "%s/D:resourcetype/A:addressbook", xpath_prop_prefix))
3345 supports = supports | E_WEBDAV_RESOURCE_SUPPORTS_CONTACTS;
3347 if (e_xml_xpath_eval_exists (xpath_ctx, "%s/C:supported-calendar-component-set", xpath_prop_prefix)) {
3348 xmlXPathObjectPtr xpath_obj;
3350 xpath_obj = e_xml_xpath_eval (xpath_ctx, "%s/C:supported-calendar-component-set/C:comp", xpath_prop_prefix);
3351 if (xpath_obj) {
3352 gint ii, length;
3354 length = xmlXPathNodeSetGetLength (xpath_obj->nodesetval);
3356 for (ii = 0; ii < length; ii++) {
3357 gchar *name;
3359 name = e_xml_xpath_eval_as_string (xpath_ctx, "%s/C:supported-calendar-component-set/C:comp[%d]/@name",
3360 xpath_prop_prefix, ii + 1);
3362 if (!name)
3363 continue;
3365 if (g_ascii_strcasecmp (name, "VEVENT") == 0)
3366 supports |= E_WEBDAV_RESOURCE_SUPPORTS_EVENTS;
3367 else if (g_ascii_strcasecmp (name, "VJOURNAL") == 0)
3368 supports |= E_WEBDAV_RESOURCE_SUPPORTS_MEMOS;
3369 else if (g_ascii_strcasecmp (name, "VTODO") == 0)
3370 supports |= E_WEBDAV_RESOURCE_SUPPORTS_TASKS;
3371 else if (g_ascii_strcasecmp (name, "VFREEBUSY") == 0)
3372 supports |= E_WEBDAV_RESOURCE_SUPPORTS_FREEBUSY;
3373 else if (g_ascii_strcasecmp (name, "VTIMEZONE") == 0)
3374 supports |= E_WEBDAV_RESOURCE_SUPPORTS_TIMEZONE;
3376 g_free (name);
3379 xmlXPathFreeObject (xpath_obj);
3380 } else {
3381 /* If the property is not present, assume all component
3382 * types are supported. (RFC 4791, Section 5.2.3) */
3383 supports = supports |
3384 E_WEBDAV_RESOURCE_SUPPORTS_EVENTS |
3385 E_WEBDAV_RESOURCE_SUPPORTS_MEMOS |
3386 E_WEBDAV_RESOURCE_SUPPORTS_TASKS |
3387 E_WEBDAV_RESOURCE_SUPPORTS_FREEBUSY |
3388 E_WEBDAV_RESOURCE_SUPPORTS_TIMEZONE;
3392 return supports;
3395 static gchar *
3396 e_webdav_session_extract_nonempty (xmlXPathContextPtr xpath_ctx,
3397 const gchar *xpath_prop_prefix,
3398 const gchar *prop,
3399 const gchar *alternative_prop)
3401 gchar *value;
3403 g_return_val_if_fail (xpath_ctx != NULL, NULL);
3404 g_return_val_if_fail (xpath_prop_prefix != NULL, NULL);
3405 g_return_val_if_fail (prop != NULL, NULL);
3407 value = e_xml_xpath_eval_as_string (xpath_ctx, "%s/%s", xpath_prop_prefix, prop);
3408 if (!value && alternative_prop)
3409 value = e_xml_xpath_eval_as_string (xpath_ctx, "%s/%s", xpath_prop_prefix, alternative_prop);
3410 if (!value)
3411 return NULL;
3413 if (!*value) {
3414 g_free (value);
3415 return NULL;
3418 return e_webdav_session_util_maybe_dequote (value);
3421 static gsize
3422 e_webdav_session_extract_content_length (xmlXPathContextPtr xpath_ctx,
3423 const gchar *xpath_prop_prefix)
3425 gchar *value;
3426 gsize length;
3428 g_return_val_if_fail (xpath_ctx != NULL, 0);
3429 g_return_val_if_fail (xpath_prop_prefix != NULL, 0);
3431 value = e_webdav_session_extract_nonempty (xpath_ctx, xpath_prop_prefix, "D:getcontentlength", NULL);
3432 if (!value)
3433 return 0;
3435 length = g_ascii_strtoll (value, NULL, 10);
3437 g_free (value);
3439 return length;
3442 static glong
3443 e_webdav_session_extract_datetime (xmlXPathContextPtr xpath_ctx,
3444 const gchar *xpath_prop_prefix,
3445 const gchar *prop,
3446 gboolean is_iso_property)
3448 gchar *value;
3449 GTimeVal tv;
3451 g_return_val_if_fail (xpath_ctx != NULL, -1);
3452 g_return_val_if_fail (xpath_prop_prefix != NULL, -1);
3454 value = e_webdav_session_extract_nonempty (xpath_ctx, xpath_prop_prefix, prop, NULL);
3455 if (!value)
3456 return -1;
3458 if (is_iso_property && !g_time_val_from_iso8601 (value, &tv)) {
3459 tv.tv_sec = -1;
3460 } else if (!is_iso_property) {
3461 tv.tv_sec = camel_header_decode_date (value, NULL);
3464 g_free (value);
3466 return tv.tv_sec;
3469 static gboolean
3470 e_webdav_session_list_cb (EWebDAVSession *webdav,
3471 xmlXPathContextPtr xpath_ctx,
3472 const gchar *xpath_prop_prefix,
3473 const SoupURI *request_uri,
3474 const gchar *href,
3475 guint status_code,
3476 gpointer user_data)
3478 GSList **out_resources = user_data;
3480 g_return_val_if_fail (out_resources != NULL, FALSE);
3481 g_return_val_if_fail (request_uri != NULL, FALSE);
3483 if (!xpath_prop_prefix) {
3484 e_xml_xpath_context_register_namespaces (xpath_ctx,
3485 "CS", E_WEBDAV_NS_CALENDARSERVER,
3486 "C", E_WEBDAV_NS_CALDAV,
3487 "A", E_WEBDAV_NS_CARDDAV,
3488 "IC", E_WEBDAV_NS_ICAL,
3489 NULL);
3491 return TRUE;
3494 if (status_code == SOUP_STATUS_OK) {
3495 EWebDAVResource *resource;
3496 EWebDAVResourceKind kind;
3497 guint32 supports;
3498 gchar *etag;
3499 gchar *display_name;
3500 gchar *content_type;
3501 gsize content_length;
3502 glong creation_date;
3503 glong last_modified;
3504 gchar *description;
3505 gchar *color;
3507 kind = e_webdav_session_extract_kind (xpath_ctx, xpath_prop_prefix);
3508 if (kind == E_WEBDAV_RESOURCE_KIND_UNKNOWN)
3509 return TRUE;
3511 supports = e_webdav_session_extract_supports (xpath_ctx, xpath_prop_prefix);
3512 etag = e_webdav_session_extract_nonempty (xpath_ctx, xpath_prop_prefix, "D:getetag", "CS:getctag");
3513 display_name = e_webdav_session_extract_nonempty (xpath_ctx, xpath_prop_prefix, "D:displayname", NULL);
3514 content_type = e_webdav_session_extract_nonempty (xpath_ctx, xpath_prop_prefix, "D:getcontenttype", NULL);
3515 content_length = e_webdav_session_extract_content_length (xpath_ctx, xpath_prop_prefix);
3516 creation_date = e_webdav_session_extract_datetime (xpath_ctx, xpath_prop_prefix, "D:creationdate", TRUE);
3517 last_modified = e_webdav_session_extract_datetime (xpath_ctx, xpath_prop_prefix, "D:getlastmodified", FALSE);
3518 description = e_webdav_session_extract_nonempty (xpath_ctx, xpath_prop_prefix, "C:calendar-description", "A:addressbook-description");
3519 color = e_webdav_session_extract_nonempty (xpath_ctx, xpath_prop_prefix, "IC:calendar-color", NULL);
3521 resource = e_webdav_resource_new (kind, supports,
3522 href,
3523 NULL, /* etag */
3524 NULL, /* display_name */
3525 NULL, /* content_type */
3526 content_length,
3527 creation_date,
3528 last_modified,
3529 NULL, /* description */
3530 NULL); /* color */
3531 resource->etag = etag;
3532 resource->display_name = display_name;
3533 resource->content_type = content_type;
3534 resource->description = description;
3535 resource->color = color;
3537 *out_resources = g_slist_prepend (*out_resources, resource);
3540 return TRUE;
3544 * e_webdav_session_list_sync:
3545 * @webdav: an #EWebDAVSession
3546 * @uri: (nullable): URI to issue the request for, or %NULL to read from #ESource
3547 * @depth: requested depth, can be one of %E_WEBDAV_DEPTH_THIS, %E_WEBDAV_DEPTH_THIS_AND_CHILDREN or %E_WEBDAV_DEPTH_INFINITY
3548 * @flags: a bit-or of #EWebDAVListFlags, claiming what properties to read
3549 * @out_resources: (out) (transfer full) (element-type EWebDAVResource): return location for the resources
3550 * @cancellable: optional #GCancellable object, or %NULL
3551 * @error: return location for a #GError, or %NULL
3553 * Lists content of the @uri, or, in case it's %NULL, of the URI defined
3554 * in associated #ESource, which should point to a collection. The @flags
3555 * influences which properties are read for the resources.
3557 * The @out_resources is in no particular order.
3559 * Free the returned @out_resources with
3560 * g_slist_free_full (resources, e_webdav_resource_free);
3561 * when no longer needed.
3563 * Returns: Whether succeeded.
3565 * Since: 3.26
3567 gboolean
3568 e_webdav_session_list_sync (EWebDAVSession *webdav,
3569 const gchar *uri,
3570 const gchar *depth,
3571 guint32 flags,
3572 GSList **out_resources,
3573 GCancellable *cancellable,
3574 GError **error)
3576 EXmlDocument *xml;
3577 gboolean success;
3579 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
3580 g_return_val_if_fail (out_resources != NULL, FALSE);
3582 *out_resources = NULL;
3584 xml = e_xml_document_new (E_WEBDAV_NS_DAV, "propfind");
3585 g_return_val_if_fail (xml != NULL, FALSE);
3587 e_xml_document_start_element (xml, NULL, "prop");
3589 e_xml_document_add_empty_element (xml, NULL, "resourcetype");
3591 if ((flags & E_WEBDAV_LIST_SUPPORTS) != 0 ||
3592 (flags & E_WEBDAV_LIST_DESCRIPTION) != 0 ||
3593 (flags & E_WEBDAV_LIST_COLOR) != 0) {
3594 e_xml_document_add_namespaces (xml, "C", E_WEBDAV_NS_CALDAV, NULL);
3597 if ((flags & E_WEBDAV_LIST_SUPPORTS) != 0) {
3598 e_xml_document_add_empty_element (xml, E_WEBDAV_NS_CALDAV, "supported-calendar-component-set");
3601 if ((flags & E_WEBDAV_LIST_DISPLAY_NAME) != 0) {
3602 e_xml_document_add_empty_element (xml, NULL, "displayname");
3605 if ((flags & E_WEBDAV_LIST_ETAG) != 0) {
3606 e_xml_document_add_empty_element (xml, NULL, "getetag");
3608 e_xml_document_add_namespaces (xml, "CS", E_WEBDAV_NS_CALENDARSERVER, NULL);
3610 e_xml_document_add_empty_element (xml, E_WEBDAV_NS_CALENDARSERVER, "getctag");
3613 if ((flags & E_WEBDAV_LIST_CONTENT_TYPE) != 0) {
3614 e_xml_document_add_empty_element (xml, NULL, "getcontenttype");
3617 if ((flags & E_WEBDAV_LIST_CONTENT_LENGTH) != 0) {
3618 e_xml_document_add_empty_element (xml, NULL, "getcontentlength");
3621 if ((flags & E_WEBDAV_LIST_CREATION_DATE) != 0) {
3622 e_xml_document_add_empty_element (xml, NULL, "creationdate");
3625 if ((flags & E_WEBDAV_LIST_LAST_MODIFIED) != 0) {
3626 e_xml_document_add_empty_element (xml, NULL, "getlastmodified");
3629 if ((flags & E_WEBDAV_LIST_DESCRIPTION) != 0) {
3630 e_xml_document_add_empty_element (xml, E_WEBDAV_NS_CALDAV, "calendar-description");
3632 e_xml_document_add_namespaces (xml, "A", E_WEBDAV_NS_CARDDAV, NULL);
3634 e_xml_document_add_empty_element (xml, E_WEBDAV_NS_CARDDAV, "addressbook-description");
3637 if ((flags & E_WEBDAV_LIST_COLOR) != 0) {
3638 e_xml_document_add_namespaces (xml, "IC", E_WEBDAV_NS_ICAL, NULL);
3640 e_xml_document_add_empty_element (xml, E_WEBDAV_NS_ICAL, "calendar-color");
3643 e_xml_document_end_element (xml); /* prop */
3645 success = e_webdav_session_propfind_sync (webdav, uri, depth, xml,
3646 e_webdav_session_list_cb, out_resources, cancellable, error);
3648 g_object_unref (xml);
3650 /* Ensure display name in case the resource doesn't have any */
3651 if (success && (flags & E_WEBDAV_LIST_DISPLAY_NAME) != 0) {
3652 GSList *link;
3654 for (link = *out_resources; link; link = g_slist_next (link)) {
3655 EWebDAVResource *resource = link->data;
3657 if (resource && !resource->display_name && resource->href) {
3658 gchar *href_decoded = soup_uri_decode (resource->href);
3660 if (href_decoded) {
3661 gchar *cp;
3663 /* Use the last non-empty path segment. */
3664 while ((cp = strrchr (href_decoded, '/')) != NULL) {
3665 if (*(cp + 1) == '\0')
3666 *cp = '\0';
3667 else {
3668 resource->display_name = g_strdup (cp + 1);
3669 break;
3674 g_free (href_decoded);
3679 if (success) {
3680 /* Honour order returned by the server, even it's not significant. */
3681 *out_resources = g_slist_reverse (*out_resources);
3684 return success;
3688 * e_webdav_session_update_properties_sync:
3689 * @webdav: an #EWebDAVSession
3690 * @uri: (nullable): URI to issue the request for, or %NULL to read from #ESource
3691 * @changes: (element-type EWebDAVResource): a #GSList with request changes
3692 * @cancellable: optional #GCancellable object, or %NULL
3693 * @error: return location for a #GError, or %NULL
3695 * Updates properties (set/remove) on the provided @uri, or, in case it's %NULL,
3696 * on the URI defined in associated #ESource, with the @changes. The order
3697 * of @changes is significant, unlike on other places.
3699 * This function supports only flat properties, those not under other element.
3700 * To support more complex property tries use e_webdav_session_proppatch_sync()
3701 * directly.
3703 * Returns: Whether succeeded.
3705 * Since: 3.26
3707 gboolean
3708 e_webdav_session_update_properties_sync (EWebDAVSession *webdav,
3709 const gchar *uri,
3710 const GSList *changes,
3711 GCancellable *cancellable,
3712 GError **error)
3714 EXmlDocument *xml;
3715 GSList *link;
3716 gboolean success;
3718 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
3719 g_return_val_if_fail (changes != NULL, FALSE);
3721 xml = e_xml_document_new (E_WEBDAV_NS_DAV, "propertyupdate");
3722 g_return_val_if_fail (xml != NULL, FALSE);
3724 for (link = (GSList *) changes; link; link = g_slist_next (link)) {
3725 EWebDAVPropertyChange *change = link->data;
3727 if (!change)
3728 continue;
3730 switch (change->kind) {
3731 case E_WEBDAV_PROPERTY_SET:
3732 e_xml_document_start_element (xml, NULL, "set");
3733 e_xml_document_start_element (xml, NULL, "prop");
3734 e_xml_document_start_text_element (xml, change->ns_uri, change->name);
3735 if (change->value) {
3736 e_xml_document_write_string (xml, change->value);
3738 e_xml_document_end_element (xml); /* change->name */
3739 e_xml_document_end_element (xml); /* prop */
3740 e_xml_document_end_element (xml); /* set */
3741 break;
3742 case E_WEBDAV_PROPERTY_REMOVE:
3743 e_xml_document_start_element (xml, NULL, "remove");
3744 e_xml_document_start_element (xml, NULL, "prop");
3745 e_xml_document_add_empty_element (xml, change->ns_uri, change->name);
3746 e_xml_document_end_element (xml); /* prop */
3747 e_xml_document_end_element (xml); /* remove */
3748 break;
3752 success = e_webdav_session_proppatch_sync (webdav, uri, xml, cancellable, error);
3754 g_object_unref (xml);
3756 return success;
3760 * e_webdav_session_lock_resource_sync:
3761 * @webdav: an #EWebDAVSession
3762 * @uri: (nullable): URI to lock, or %NULL to read from #ESource
3763 * @lock_scope: an #EWebDAVLockScope to define the scope of the lock
3764 * @lock_timeout: timeout for the lock, in seconds, on 0 to infinity
3765 * @owner: (nullable): optional identificator of the owner of the lock, or %NULL
3766 * @out_lock_token: (out) (transfer full): return location of the obtained or refreshed lock token
3767 * @cancellable: optional #GCancellable object, or %NULL
3768 * @error: return location for a #GError, or %NULL
3770 * Locks a resource identified by @uri, or, in case it's %NULL, by the URI defined
3771 * in associated #ESource. It obtains a write lock with the given @lock_scope.
3773 * The @owner is used to identify the lock owner. When it's an http:// or https://,
3774 * then it's referenced as DAV:href, otherwise the value is treated as plain text.
3775 * If it's %NULL, then the user name from the associated #ESource is used.
3777 * The @out_lock_token can be refreshed with e_webdav_session_refresh_lock_sync().
3778 * Release the lock with e_webdav_session_unlock_sync().
3779 * Free the returned @out_lock_token with g_free(), when no longer needed.
3781 * Returns: Whether succeeded.
3783 * Since: 3.26
3785 gboolean
3786 e_webdav_session_lock_resource_sync (EWebDAVSession *webdav,
3787 const gchar *uri,
3788 EWebDAVLockScope lock_scope,
3789 gint32 lock_timeout,
3790 const gchar *owner,
3791 gchar **out_lock_token,
3792 GCancellable *cancellable,
3793 GError **error)
3795 EXmlDocument *xml;
3796 gchar *owner_ref;
3797 gboolean success;
3799 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
3800 g_return_val_if_fail (out_lock_token != NULL, FALSE);
3802 xml = e_xml_document_new (E_WEBDAV_NS_DAV, "lockinfo");
3803 g_return_val_if_fail (xml != NULL, FALSE);
3805 e_xml_document_start_element (xml, NULL, "lockscope");
3806 switch (lock_scope) {
3807 case E_WEBDAV_LOCK_EXCLUSIVE:
3808 e_xml_document_add_empty_element (xml, NULL, "exclusive");
3809 break;
3810 case E_WEBDAV_LOCK_SHARED:
3811 e_xml_document_add_empty_element (xml, NULL, "shared");
3812 break;
3814 e_xml_document_end_element (xml); /* lockscope */
3816 e_xml_document_start_element (xml, NULL, "locktype");
3817 e_xml_document_add_empty_element (xml, NULL, "write");
3818 e_xml_document_end_element (xml); /* locktype */
3820 e_xml_document_start_text_element (xml, NULL, "owner");
3821 if (owner) {
3822 owner_ref = g_strdup (owner);
3823 } else {
3824 ESource *source = e_soup_session_get_source (E_SOUP_SESSION (webdav));
3826 owner_ref = NULL;
3828 if (e_source_has_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION)) {
3829 owner_ref = e_source_authentication_dup_user (
3830 e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION));
3832 if (owner_ref && !*owner_ref)
3833 g_clear_pointer (&owner_ref, g_free);
3836 if (!owner_ref && e_source_has_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND)) {
3837 owner_ref = e_source_webdav_dup_email_address (
3838 e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND));
3840 if (owner_ref && !*owner_ref)
3841 g_clear_pointer (&owner_ref, g_free);
3845 if (!owner_ref)
3846 owner_ref = g_strconcat (g_get_host_name (), " / ", g_get_user_name (), NULL);
3848 if (owner_ref) {
3849 if (g_str_has_prefix (owner_ref, "http://") ||
3850 g_str_has_prefix (owner_ref, "https://")) {
3851 e_xml_document_start_element (xml, NULL, "href");
3852 e_xml_document_write_string (xml, owner_ref);
3853 e_xml_document_end_element (xml); /* href */
3854 } else {
3855 e_xml_document_write_string (xml, owner_ref);
3859 g_free (owner_ref);
3860 e_xml_document_end_element (xml); /* owner */
3862 success = e_webdav_session_lock_sync (webdav, uri, E_WEBDAV_DEPTH_INFINITY, lock_timeout, xml,
3863 out_lock_token, NULL, cancellable, error);
3865 g_object_unref (xml);
3867 return success;
3870 static void
3871 e_webdav_session_traverse_privilege_level (xmlXPathContextPtr xpath_ctx,
3872 const gchar *xpath_prefix,
3873 GNode *parent)
3875 xmlXPathObjectPtr xpath_obj;
3877 g_return_if_fail (xpath_ctx != NULL);
3878 g_return_if_fail (xpath_prefix != NULL);
3879 g_return_if_fail (parent != NULL);
3881 xpath_obj = e_xml_xpath_eval (xpath_ctx, "%s/D:supported-privilege", xpath_prefix);
3883 if (xpath_obj) {
3884 gint ii, length;
3886 length = xmlXPathNodeSetGetLength (xpath_obj->nodesetval);
3888 for (ii = 0; ii < length; ii++) {
3889 xmlXPathObjectPtr xpath_obj_privilege;
3890 gchar *prefix;
3892 prefix = g_strdup_printf ("%s/D:supported-privilege[%d]", xpath_prefix, ii + 1);
3893 xpath_obj_privilege = e_xml_xpath_eval (xpath_ctx, "%s/D:privilege", prefix);
3895 if (xpath_obj_privilege &&
3896 xpath_obj_privilege->type == XPATH_NODESET &&
3897 xpath_obj_privilege->nodesetval &&
3898 xpath_obj_privilege->nodesetval->nodeNr == 1 &&
3899 xpath_obj_privilege->nodesetval->nodeTab &&
3900 xpath_obj_privilege->nodesetval->nodeTab[0] &&
3901 xpath_obj_privilege->nodesetval->nodeTab[0]->children) {
3902 xmlNodePtr node;
3904 for (node = xpath_obj_privilege->nodesetval->nodeTab[0]->children; node; node = node->next) {
3905 if (node->type == XML_ELEMENT_NODE &&
3906 node->name && *(node->name) &&
3907 node->ns && node->ns->href && *(node->ns->href)) {
3908 break;
3912 if (node) {
3913 GNode *child;
3914 gchar *description;
3915 EWebDAVPrivilegeKind kind = E_WEBDAV_PRIVILEGE_KIND_COMMON;
3916 EWebDAVPrivilegeHint hint = E_WEBDAV_PRIVILEGE_HINT_UNKNOWN;
3917 EWebDAVPrivilege *privilege;
3919 if (e_xml_xpath_eval_exists (xpath_ctx, "%s/D:abstract", prefix))
3920 kind = E_WEBDAV_PRIVILEGE_KIND_ABSTRACT;
3921 else if (e_xml_xpath_eval_exists (xpath_ctx, "%s/D:aggregate", prefix))
3922 kind = E_WEBDAV_PRIVILEGE_KIND_AGGREGATE;
3924 description = e_xml_xpath_eval_as_string (xpath_ctx, "%s/D:description", prefix);
3925 privilege = e_webdav_privilege_new ((const gchar *) node->ns->href, (const gchar *) node->name, description, kind, hint);
3926 child = g_node_new (privilege);
3927 g_node_append (parent, child);
3929 g_free (description);
3931 if (e_xml_xpath_eval_exists (xpath_ctx, "%s/D:supported-privilege", prefix))
3932 e_webdav_session_traverse_privilege_level (xpath_ctx, prefix, child);
3936 if (xpath_obj_privilege)
3937 xmlXPathFreeObject (xpath_obj_privilege);
3939 g_free (prefix);
3942 xmlXPathFreeObject (xpath_obj);
3946 static gboolean
3947 e_webdav_session_supported_privilege_set_cb (EWebDAVSession *webdav,
3948 xmlXPathContextPtr xpath_ctx,
3949 const gchar *xpath_prop_prefix,
3950 const SoupURI *request_uri,
3951 const gchar *href,
3952 guint status_code,
3953 gpointer user_data)
3955 GNode **out_privileges = user_data;
3957 g_return_val_if_fail (out_privileges != NULL, FALSE);
3959 if (!xpath_prop_prefix) {
3960 e_xml_xpath_context_register_namespaces (xpath_ctx,
3961 "C", E_WEBDAV_NS_CALDAV,
3962 NULL);
3963 } else if (status_code == SOUP_STATUS_OK &&
3964 e_xml_xpath_eval_exists (xpath_ctx, "%s/D:supported-privilege-set/D:supported-privilege", xpath_prop_prefix)) {
3965 GNode *root;
3966 gchar *prefix;
3968 prefix = g_strconcat (xpath_prop_prefix, "/D:supported-privilege-set", NULL);
3969 root = g_node_new (NULL);
3971 e_webdav_session_traverse_privilege_level (xpath_ctx, prefix, root);
3973 *out_privileges = root;
3975 g_free (prefix);
3978 return TRUE;
3982 * e_webdav_session_acl_sync:
3983 * @webdav: an #EWebDAVSession
3984 * @uri: (nullable): URI to issue the request for, or %NULL to read from #ESource
3985 * @xml:the request itself, as an #EXmlDocument, the root element should be DAV:acl
3986 * @cancellable: optional #GCancellable object, or %NULL
3987 * @error: return location for a #GError, or %NULL
3989 * Issues ACL request on the provided @uri, or, in case it's %NULL, on the URI
3990 * defined in associated #ESource.
3992 * Returns: Whether succeeded.
3994 * Since: 3.26
3996 gboolean
3997 e_webdav_session_acl_sync (EWebDAVSession *webdav,
3998 const gchar *uri,
3999 const EXmlDocument *xml,
4000 GCancellable *cancellable,
4001 GError **error)
4003 SoupRequestHTTP *request;
4004 SoupMessage *message;
4005 GByteArray *bytes;
4006 gchar *content;
4007 gsize content_length;
4008 gboolean success;
4010 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
4011 g_return_val_if_fail (E_IS_XML_DOCUMENT (xml), FALSE);
4013 request = e_webdav_session_new_request (webdav, "ACL", uri, error);
4014 if (!request)
4015 return FALSE;
4017 message = soup_request_http_get_message (request);
4018 if (!message) {
4019 g_warn_if_fail (message != NULL);
4020 g_object_unref (request);
4022 return FALSE;
4025 content = e_xml_document_get_content (xml, &content_length);
4026 if (!content) {
4027 g_object_unref (message);
4028 g_object_unref (request);
4030 g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Failed to get input XML content"));
4032 return FALSE;
4035 soup_message_set_request (message, E_WEBDAV_CONTENT_TYPE_XML,
4036 SOUP_MEMORY_TAKE, content, content_length);
4038 bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, error);
4040 success = !e_webdav_session_replace_with_detailed_error (webdav, request, bytes, TRUE, _("Failed to get access control list"), error) &&
4041 bytes != NULL;
4043 if (bytes)
4044 g_byte_array_free (bytes, TRUE);
4045 g_object_unref (message);
4046 g_object_unref (request);
4048 return success;
4052 * e_webdav_session_get_supported_privilege_set_sync:
4053 * @webdav: an #EWebDAVSession
4054 * @uri: (nullable): URI to issue the request for, or %NULL to read from #ESource
4055 * @out_privileges: (out) (transfer full) (element-type EWebDAVPrivilege): return location for the tree of supported privileges
4056 * @cancellable: optional #GCancellable object, or %NULL
4057 * @error: return location for a #GError, or %NULL
4059 * Gets supported privileges for the @uri, or, in case it's %NULL, for the URI
4060 * defined in associated #ESource.
4062 * The root node of @out_privileges has always %NULL data.
4064 * Free the returned @out_privileges with e_webdav_session_util_free_privileges()
4065 * when no longer needed.
4067 * Returns: Whether succeeded.
4069 * Since: 3.26
4071 gboolean
4072 e_webdav_session_get_supported_privilege_set_sync (EWebDAVSession *webdav,
4073 const gchar *uri,
4074 GNode **out_privileges,
4075 GCancellable *cancellable,
4076 GError **error)
4078 EXmlDocument *xml;
4079 gboolean success;
4081 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
4082 g_return_val_if_fail (out_privileges != NULL, FALSE);
4084 *out_privileges = NULL;
4086 xml = e_xml_document_new (E_WEBDAV_NS_DAV, "propfind");
4087 g_return_val_if_fail (xml != NULL, FALSE);
4089 e_xml_document_start_element (xml, NULL, "prop");
4090 e_xml_document_add_empty_element (xml, NULL, "supported-privilege-set");
4091 e_xml_document_end_element (xml); /* prop */
4093 success = e_webdav_session_propfind_sync (webdav, uri, E_WEBDAV_DEPTH_THIS, xml,
4094 e_webdav_session_supported_privilege_set_cb, out_privileges, cancellable, error);
4096 g_object_unref (xml);
4098 return success;
4101 static EWebDAVPrivilege *
4102 e_webdav_session_extract_privilege_simple (xmlXPathObjectPtr xpath_obj_privilege)
4104 EWebDAVPrivilege *privilege = NULL;
4106 if (xpath_obj_privilege &&
4107 xpath_obj_privilege->type == XPATH_NODESET &&
4108 xpath_obj_privilege->nodesetval &&
4109 xpath_obj_privilege->nodesetval->nodeNr == 1 &&
4110 xpath_obj_privilege->nodesetval->nodeTab &&
4111 xpath_obj_privilege->nodesetval->nodeTab[0] &&
4112 xpath_obj_privilege->nodesetval->nodeTab[0]->children) {
4113 xmlNodePtr node;
4115 for (node = xpath_obj_privilege->nodesetval->nodeTab[0]->children; node; node = node->next) {
4116 if (node->type == XML_ELEMENT_NODE &&
4117 node->name && *(node->name) &&
4118 node->ns && node->ns->href && *(node->ns->href)) {
4119 break;
4123 if (node) {
4124 privilege = e_webdav_privilege_new ((const gchar *) node->ns->href, (const gchar *) node->name,
4125 NULL, E_WEBDAV_PRIVILEGE_KIND_COMMON, E_WEBDAV_PRIVILEGE_HINT_UNKNOWN);
4129 return privilege;
4132 static gboolean
4133 e_webdav_session_current_user_privilege_set_cb (EWebDAVSession *webdav,
4134 xmlXPathContextPtr xpath_ctx,
4135 const gchar *xpath_prop_prefix,
4136 const SoupURI *request_uri,
4137 const gchar *href,
4138 guint status_code,
4139 gpointer user_data)
4141 GSList **out_privileges = user_data;
4143 g_return_val_if_fail (xpath_ctx != NULL, FALSE);
4144 g_return_val_if_fail (out_privileges != NULL, FALSE);
4146 if (!xpath_prop_prefix) {
4147 e_xml_xpath_context_register_namespaces (xpath_ctx,
4148 "C", E_WEBDAV_NS_CALDAV,
4149 NULL);
4150 } else if (status_code == SOUP_STATUS_OK &&
4151 e_xml_xpath_eval_exists (xpath_ctx, "%s/D:current-user-privilege-set/D:privilege", xpath_prop_prefix)) {
4152 xmlXPathObjectPtr xpath_obj;
4154 xpath_obj = e_xml_xpath_eval (xpath_ctx, "%s/D:current-user-privilege-set/D:privilege", xpath_prop_prefix);
4156 if (xpath_obj) {
4157 gint ii, length;
4159 length = xmlXPathNodeSetGetLength (xpath_obj->nodesetval);
4161 for (ii = 0; ii < length; ii++) {
4162 xmlXPathObjectPtr xpath_obj_privilege;
4164 xpath_obj_privilege = e_xml_xpath_eval (xpath_ctx, "%s/D:current-user-privilege-set/D:privilege[%d]", xpath_prop_prefix, ii + 1);
4166 if (xpath_obj_privilege) {
4167 EWebDAVPrivilege *privilege;
4169 privilege = e_webdav_session_extract_privilege_simple (xpath_obj_privilege);
4170 if (privilege)
4171 *out_privileges = g_slist_prepend (*out_privileges, privilege);
4173 xmlXPathFreeObject (xpath_obj_privilege);
4177 xmlXPathFreeObject (xpath_obj);
4181 return TRUE;
4185 * e_webdav_session_get_current_user_privilege_set_sync:
4186 * @webdav: an #EWebDAVSession
4187 * @uri: (nullable): URI to issue the request for, or %NULL to read from #ESource
4188 * @out_privileges: (out) (transfer full) (element-type EWebDAVPrivilege): return location for a %GSList of #EWebDAVPrivilege
4189 * @cancellable: optional #GCancellable object, or %NULL
4190 * @error: return location for a #GError, or %NULL
4192 * Gets current user privileges for the @uri, or, in case it's %NULL, for the URI
4193 * defined in associated #ESource.
4195 * Free the returned @out_privileges with
4196 * g_slist_free_full (privileges, e_webdav_privilege_free);
4197 * when no longer needed.
4199 * Returns: Whether succeeded.
4201 * Since: 3.26
4203 gboolean
4204 e_webdav_session_get_current_user_privilege_set_sync (EWebDAVSession *webdav,
4205 const gchar *uri,
4206 GSList **out_privileges,
4207 GCancellable *cancellable,
4208 GError **error)
4210 EXmlDocument *xml;
4211 gboolean success;
4213 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
4214 g_return_val_if_fail (out_privileges != NULL, FALSE);
4216 *out_privileges = NULL;
4218 xml = e_xml_document_new (E_WEBDAV_NS_DAV, "propfind");
4219 g_return_val_if_fail (xml != NULL, FALSE);
4221 e_xml_document_start_element (xml, NULL, "prop");
4222 e_xml_document_add_empty_element (xml, NULL, "current-user-privilege-set");
4223 e_xml_document_end_element (xml); /* prop */
4225 success = e_webdav_session_propfind_sync (webdav, uri, E_WEBDAV_DEPTH_THIS, xml,
4226 e_webdav_session_current_user_privilege_set_cb, out_privileges, cancellable, error);
4228 g_object_unref (xml);
4230 if (success)
4231 *out_privileges = g_slist_reverse (*out_privileges);
4233 return success;
4236 static EWebDAVACEPrincipalKind
4237 e_webdav_session_extract_acl_principal (xmlXPathContextPtr xpath_ctx,
4238 const gchar *principal_prefix,
4239 gchar **out_principal_href,
4240 GSList **out_principal_hrefs)
4242 g_return_val_if_fail (xpath_ctx != NULL, E_WEBDAV_ACE_PRINCIPAL_UNKNOWN);
4243 g_return_val_if_fail (principal_prefix != NULL, E_WEBDAV_ACE_PRINCIPAL_UNKNOWN);
4244 g_return_val_if_fail (out_principal_href != NULL || out_principal_hrefs != NULL, E_WEBDAV_ACE_PRINCIPAL_UNKNOWN);
4246 if (out_principal_href)
4247 *out_principal_href = NULL;
4249 if (out_principal_hrefs)
4250 *out_principal_hrefs = NULL;
4252 if (!e_xml_xpath_eval_exists (xpath_ctx, "%s", principal_prefix))
4253 return E_WEBDAV_ACE_PRINCIPAL_UNKNOWN;
4255 if (e_xml_xpath_eval_exists (xpath_ctx, "%s/D:href", principal_prefix)) {
4256 if (out_principal_href) {
4257 *out_principal_href = e_xml_xpath_eval_as_string (xpath_ctx, "%s/D:href", principal_prefix);
4258 } else {
4259 xmlXPathObjectPtr xpath_obj;
4261 *out_principal_hrefs = NULL;
4263 xpath_obj = e_xml_xpath_eval (xpath_ctx, "%s/D:href", principal_prefix);
4265 if (xpath_obj) {
4266 gint ii, length;
4268 length = xmlXPathNodeSetGetLength (xpath_obj->nodesetval);
4270 for (ii = 0; ii < length; ii++) {
4271 gchar *href;
4273 href = e_xml_xpath_eval_as_string (xpath_ctx, "%s/D:href[%d]", principal_prefix, ii + 1);
4274 if (href)
4275 *out_principal_hrefs = g_slist_prepend (*out_principal_hrefs, href);
4279 *out_principal_hrefs = g_slist_reverse (*out_principal_hrefs);
4282 return E_WEBDAV_ACE_PRINCIPAL_HREF;
4285 if (e_xml_xpath_eval_exists (xpath_ctx, "%s/D:all", principal_prefix))
4286 return E_WEBDAV_ACE_PRINCIPAL_ALL;
4288 if (e_xml_xpath_eval_exists (xpath_ctx, "%s/D:authenticated", principal_prefix))
4289 return E_WEBDAV_ACE_PRINCIPAL_AUTHENTICATED;
4291 if (e_xml_xpath_eval_exists (xpath_ctx, "%s/D:unauthenticated", principal_prefix))
4292 return E_WEBDAV_ACE_PRINCIPAL_UNAUTHENTICATED;
4294 if (e_xml_xpath_eval_exists (xpath_ctx, "%s/D:self", principal_prefix))
4295 return E_WEBDAV_ACE_PRINCIPAL_SELF;
4297 if (e_xml_xpath_eval_exists (xpath_ctx, "%s/D:property", principal_prefix)) {
4298 /* No details read about what properties */
4299 EWebDAVACEPrincipalKind kind = E_WEBDAV_ACE_PRINCIPAL_PROPERTY;
4301 /* Special-case owner */
4302 if (e_xml_xpath_eval_exists (xpath_ctx, "%s/D:property/D:owner", principal_prefix)) {
4303 xmlXPathObjectPtr xpath_obj_property;
4305 xpath_obj_property = e_xml_xpath_eval (xpath_ctx, "%s/D:property", principal_prefix);
4307 /* DAV:owner is the only child and there is only one DAV:property child of the DAV:principal */
4308 if (xpath_obj_property &&
4309 xpath_obj_property->type == XPATH_NODESET &&
4310 xmlXPathNodeSetGetLength (xpath_obj_property->nodesetval) == 1 &&
4311 xpath_obj_property->nodesetval &&
4312 xpath_obj_property->nodesetval->nodeNr == 1 &&
4313 xpath_obj_property->nodesetval->nodeTab &&
4314 xpath_obj_property->nodesetval->nodeTab[0] &&
4315 xpath_obj_property->nodesetval->nodeTab[0]->children) {
4316 xmlNodePtr node;
4317 gint subelements = 0;
4319 for (node = xpath_obj_property->nodesetval->nodeTab[0]->children; node && subelements <= 1; node = node->next) {
4320 if (node->type == XML_ELEMENT_NODE)
4321 subelements++;
4324 if (subelements == 1)
4325 kind = E_WEBDAV_ACE_PRINCIPAL_OWNER;
4328 if (xpath_obj_property)
4329 xmlXPathFreeObject (xpath_obj_property);
4332 return kind;
4335 return E_WEBDAV_ACE_PRINCIPAL_UNKNOWN;
4338 static gboolean
4339 e_webdav_session_acl_cb (EWebDAVSession *webdav,
4340 xmlXPathContextPtr xpath_ctx,
4341 const gchar *xpath_prop_prefix,
4342 const SoupURI *request_uri,
4343 const gchar *href,
4344 guint status_code,
4345 gpointer user_data)
4347 GSList **out_entries = user_data;
4349 g_return_val_if_fail (xpath_ctx != NULL, FALSE);
4350 g_return_val_if_fail (out_entries != NULL, FALSE);
4352 if (!xpath_prop_prefix) {
4353 } else if (status_code == SOUP_STATUS_OK &&
4354 e_xml_xpath_eval_exists (xpath_ctx, "%s/D:acl/D:ace", xpath_prop_prefix)) {
4355 xmlXPathObjectPtr xpath_obj_ace;
4357 xpath_obj_ace = e_xml_xpath_eval (xpath_ctx, "%s/D:acl/D:ace", xpath_prop_prefix);
4359 if (xpath_obj_ace) {
4360 gint ii, length;
4362 length = xmlXPathNodeSetGetLength (xpath_obj_ace->nodesetval);
4364 for (ii = 0; ii < length; ii++) {
4365 EWebDAVACEPrincipalKind principal_kind = E_WEBDAV_ACE_PRINCIPAL_UNKNOWN;
4366 xmlXPathObjectPtr xpath_obj = NULL;
4367 gchar *principal_href = NULL;
4368 guint32 flags = E_WEBDAV_ACE_FLAG_UNKNOWN;
4369 gchar *inherited_href = NULL;
4370 gchar *privilege_prefix = NULL;
4371 gchar *ace_prefix;
4373 ace_prefix = g_strdup_printf ("%s/D:acl/D:ace[%d]", xpath_prop_prefix, ii + 1);
4375 if (e_xml_xpath_eval_exists (xpath_ctx, "%s/D:invert", ace_prefix)) {
4376 gchar *prefix;
4378 flags |= E_WEBDAV_ACE_FLAG_INVERT;
4380 prefix = g_strdup_printf ("%s/D:invert/D:principal", ace_prefix);
4381 principal_kind = e_webdav_session_extract_acl_principal (xpath_ctx, prefix, &principal_href, NULL);
4382 g_free (prefix);
4383 } else {
4384 gchar *prefix;
4386 prefix = g_strdup_printf ("%s/D:principal", ace_prefix);
4387 principal_kind = e_webdav_session_extract_acl_principal (xpath_ctx, prefix, &principal_href, NULL);
4388 g_free (prefix);
4391 if (principal_kind == E_WEBDAV_ACE_PRINCIPAL_UNKNOWN) {
4392 g_free (ace_prefix);
4393 continue;
4396 if (e_xml_xpath_eval_exists (xpath_ctx, "%s/D:protected", ace_prefix))
4397 flags |= E_WEBDAV_ACE_FLAG_PROTECTED;
4399 if (e_xml_xpath_eval_exists (xpath_ctx, "%s/D:inherited/D:href", ace_prefix)) {
4400 flags |= E_WEBDAV_ACE_FLAG_INHERITED;
4401 inherited_href = e_xml_xpath_eval_as_string (xpath_ctx, "%s/D:inherited/D:href", ace_prefix);
4404 if (e_xml_xpath_eval_exists (xpath_ctx, "%s/D:grant", ace_prefix)) {
4405 privilege_prefix = g_strdup_printf ("%s/D:grant/D:privilege", ace_prefix);
4406 flags |= E_WEBDAV_ACE_FLAG_GRANT;
4407 } else if (e_xml_xpath_eval_exists (xpath_ctx, "%s/D:deny", ace_prefix)) {
4408 privilege_prefix = g_strdup_printf ("%s/D:deny/D:privilege", ace_prefix);
4409 flags |= E_WEBDAV_ACE_FLAG_DENY;
4412 if (privilege_prefix)
4413 xpath_obj = e_xml_xpath_eval (xpath_ctx, "%s", privilege_prefix);
4415 if (xpath_obj) {
4416 EWebDAVAccessControlEntry *ace;
4417 gint ii, length;
4419 ace = e_webdav_access_control_entry_new (principal_kind, principal_href, flags, inherited_href);
4420 if (ace) {
4421 length = xmlXPathNodeSetGetLength (xpath_obj->nodesetval);
4423 for (ii = 0; ii < length; ii++) {
4424 xmlXPathObjectPtr xpath_obj_privilege;
4426 xpath_obj_privilege = e_xml_xpath_eval (xpath_ctx, "%s[%d]", privilege_prefix, ii + 1);
4428 if (xpath_obj_privilege) {
4429 EWebDAVPrivilege *privilege;
4431 privilege = e_webdav_session_extract_privilege_simple (xpath_obj_privilege);
4432 if (privilege)
4433 ace->privileges = g_slist_prepend (ace->privileges, privilege);
4435 xmlXPathFreeObject (xpath_obj_privilege);
4439 ace->privileges = g_slist_reverse (ace->privileges);
4441 *out_entries = g_slist_prepend (*out_entries, ace);
4444 xmlXPathFreeObject (xpath_obj);
4447 g_free (principal_href);
4448 g_free (inherited_href);
4449 g_free (privilege_prefix);
4450 g_free (ace_prefix);
4453 xmlXPathFreeObject (xpath_obj_ace);
4457 return TRUE;
4461 * e_webdav_session_get_acl_sync:
4462 * @webdav: an #EWebDAVSession
4463 * @uri: (nullable): URI to issue the request for, or %NULL to read from #ESource
4464 * @out_entries: (out) (transfer full) (element-type EWebDAVAccessControlEntry): return location for a #GSList of #EWebDAVAccessControlEntry
4465 * @cancellable: optional #GCancellable object, or %NULL
4466 * @error: return location for a #GError, or %NULL
4468 * Gets Access Control List (ACL) for the @uri, or, in case it's %NULL, for the URI
4469 * defined in associated #ESource.
4471 * This function doesn't read general #E_WEBDAV_ACE_PRINCIPAL_PROPERTY.
4473 * Free the returned @out_entries with
4474 * g_slist_free_full (entries, e_webdav_access_control_entry_free);
4475 * when no longer needed.
4477 * Returns: Whether succeeded.
4479 * Since: 3.26
4481 gboolean
4482 e_webdav_session_get_acl_sync (EWebDAVSession *webdav,
4483 const gchar *uri,
4484 GSList **out_entries,
4485 GCancellable *cancellable,
4486 GError **error)
4488 EXmlDocument *xml;
4489 gboolean success;
4491 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
4492 g_return_val_if_fail (out_entries != NULL, FALSE);
4494 *out_entries = NULL;
4496 xml = e_xml_document_new (E_WEBDAV_NS_DAV, "propfind");
4497 g_return_val_if_fail (xml != NULL, FALSE);
4499 e_xml_document_start_element (xml, NULL, "prop");
4500 e_xml_document_add_empty_element (xml, NULL, "acl");
4501 e_xml_document_end_element (xml); /* prop */
4503 success = e_webdav_session_propfind_sync (webdav, uri, E_WEBDAV_DEPTH_THIS, xml,
4504 e_webdav_session_acl_cb, out_entries, cancellable, error);
4506 g_object_unref (xml);
4508 if (success)
4509 *out_entries = g_slist_reverse (*out_entries);
4511 return success;
4514 typedef struct _ACLRestrictionsData {
4515 guint32 *out_restrictions;
4516 EWebDAVACEPrincipalKind *out_principal_kind;
4517 GSList **out_principal_hrefs;
4518 } ACLRestrictionsData;
4520 static gboolean
4521 e_webdav_session_acl_restrictions_cb (EWebDAVSession *webdav,
4522 xmlXPathContextPtr xpath_ctx,
4523 const gchar *xpath_prop_prefix,
4524 const SoupURI *request_uri,
4525 const gchar *href,
4526 guint status_code,
4527 gpointer user_data)
4529 ACLRestrictionsData *ard = user_data;
4531 g_return_val_if_fail (xpath_ctx != NULL, FALSE);
4532 g_return_val_if_fail (ard != NULL, FALSE);
4534 if (!xpath_prop_prefix) {
4535 } else if (status_code == SOUP_STATUS_OK &&
4536 e_xml_xpath_eval_exists (xpath_ctx, "%s/D:acl-restrictions", xpath_prop_prefix)) {
4537 if (e_xml_xpath_eval_exists (xpath_ctx, "%s/D:acl-restrictions/D:grant-only", xpath_prop_prefix))
4538 *ard->out_restrictions |= E_WEBDAV_ACL_RESTRICTION_GRANT_ONLY;
4540 if (e_xml_xpath_eval_exists (xpath_ctx, "%s/D:acl-restrictions/D:no-invert", xpath_prop_prefix))
4541 *ard->out_restrictions |= E_WEBDAV_ACL_RESTRICTION_NO_INVERT;
4543 if (e_xml_xpath_eval_exists (xpath_ctx, "%s/D:acl-restrictions/D:deny-before-grant", xpath_prop_prefix))
4544 *ard->out_restrictions |= E_WEBDAV_ACL_RESTRICTION_DENY_BEFORE_GRANT;
4546 if (e_xml_xpath_eval_exists (xpath_ctx, "%s/D:acl-restrictions/D:required-principal", xpath_prop_prefix)) {
4547 gchar *prefix;
4549 *ard->out_restrictions |= E_WEBDAV_ACL_RESTRICTION_REQUIRED_PRINCIPAL;
4551 prefix = g_strdup_printf ("%s/D:acl-restrictions/D:required-principal", xpath_prop_prefix);
4552 *ard->out_principal_kind = e_webdav_session_extract_acl_principal (xpath_ctx, prefix, NULL, ard->out_principal_hrefs);
4553 g_free (prefix);
4557 return TRUE;
4561 * e_webdav_session_get_acl_restrictions_sync:
4562 * @webdav: an #EWebDAVSession
4563 * @uri: (nullable): URI to issue the request for, or %NULL to read from #ESource
4564 * @out_restrictions: (out): return location for bit-or of #EWebDAVACLRestrictions
4565 * @out_principal_kind: (out): return location for principal kind
4566 * @out_principal_hrefs: (out) (transfer full) (element-type utf8): return location for a #GSList of principal href-s
4567 * @cancellable: optional #GCancellable object, or %NULL
4568 * @error: return location for a #GError, or %NULL
4570 * Gets Access Control List (ACL) restrictions for the @uri, or, in case it's %NULL,
4571 * for the URI defined in associated #ESource. The @out_principal_kind is valid only
4572 * if the @out_restrictions contains #E_WEBDAV_ACL_RESTRICTION_REQUIRED_PRINCIPAL.
4573 * The @out_principal_hrefs is valid only if the @out_principal_kind is valid and when
4574 * it is #E_WEBDAV_ACE_PRINCIPAL_HREF.
4576 * Free the returned @out_principal_hrefs with
4577 * g_slist_free_full (entries, g_free);
4578 * when no longer needed.
4580 * Returns: Whether succeeded.
4582 * Since: 3.26
4584 gboolean
4585 e_webdav_session_get_acl_restrictions_sync (EWebDAVSession *webdav,
4586 const gchar *uri,
4587 guint32 *out_restrictions,
4588 EWebDAVACEPrincipalKind *out_principal_kind,
4589 GSList **out_principal_hrefs,
4590 GCancellable *cancellable,
4591 GError **error)
4593 ACLRestrictionsData ard;
4594 EXmlDocument *xml;
4595 gboolean success;
4597 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
4598 g_return_val_if_fail (out_restrictions != NULL, FALSE);
4599 g_return_val_if_fail (out_principal_kind != NULL, FALSE);
4600 g_return_val_if_fail (out_principal_hrefs != NULL, FALSE);
4602 *out_restrictions = E_WEBDAV_ACL_RESTRICTION_NONE;
4603 *out_principal_kind = E_WEBDAV_ACE_PRINCIPAL_UNKNOWN;
4604 *out_principal_hrefs = NULL;
4606 xml = e_xml_document_new (E_WEBDAV_NS_DAV, "propfind");
4607 g_return_val_if_fail (xml != NULL, FALSE);
4609 e_xml_document_start_element (xml, NULL, "prop");
4610 e_xml_document_add_empty_element (xml, NULL, "acl-restrictions");
4611 e_xml_document_end_element (xml); /* prop */
4613 ard.out_restrictions = out_restrictions;
4614 ard.out_principal_kind = out_principal_kind;
4615 ard.out_principal_hrefs = out_principal_hrefs;
4617 success = e_webdav_session_propfind_sync (webdav, uri, E_WEBDAV_DEPTH_THIS, xml,
4618 e_webdav_session_acl_restrictions_cb, &ard, cancellable, error);
4620 g_object_unref (xml);
4622 return success;
4625 static gboolean
4626 e_webdav_session_principal_collection_set_cb (EWebDAVSession *webdav,
4627 xmlXPathContextPtr xpath_ctx,
4628 const gchar *xpath_prop_prefix,
4629 const SoupURI *request_uri,
4630 const gchar *href,
4631 guint status_code,
4632 gpointer user_data)
4634 GSList **out_principal_hrefs = user_data;
4636 g_return_val_if_fail (xpath_ctx != NULL, FALSE);
4637 g_return_val_if_fail (out_principal_hrefs != NULL, FALSE);
4639 if (!xpath_prop_prefix) {
4640 } else if (status_code == SOUP_STATUS_OK &&
4641 e_xml_xpath_eval_exists (xpath_ctx, "%s/D:principal-collection-set", xpath_prop_prefix)) {
4642 xmlXPathObjectPtr xpath_obj;
4644 xpath_obj = e_xml_xpath_eval (xpath_ctx, "%s/D:principal-collection-set/D:href", xpath_prop_prefix);
4646 if (xpath_obj) {
4647 gint ii, length;
4649 length = xmlXPathNodeSetGetLength (xpath_obj->nodesetval);
4651 for (ii = 0; ii < length; ii++) {
4652 gchar *got_href;
4654 got_href = e_xml_xpath_eval_as_string (xpath_ctx, "%s/D:principal-collection-set/D:href[%d]", xpath_prop_prefix, ii + 1);
4655 if (got_href)
4656 *out_principal_hrefs = g_slist_prepend (*out_principal_hrefs, got_href);
4659 xmlXPathFreeObject (xpath_obj);
4663 return TRUE;
4667 * e_webdav_session_get_principal_collection_set_sync:
4668 * @webdav: an #EWebDAVSession
4669 * @uri: (nullable): URI to issue the request for, or %NULL to read from #ESource
4670 * @out_principal_hrefs: (out) (transfer full) (element-type utf8): return location for a #GSList of principal href-s
4671 * @cancellable: optional #GCancellable object, or %NULL
4672 * @error: return location for a #GError, or %NULL
4674 * Gets list of principal collection href for the @uri, or, in case it's %NULL,
4675 * for the URI defined in associated #ESource. The @out_principal_hrefs are root
4676 * collections that contain the principals that are available on the server that
4677 * implements this resource.
4679 * Free the returned @out_principal_hrefs with
4680 * g_slist_free_full (entries, g_free);
4681 * when no longer needed.
4683 * Returns: Whether succeeded.
4685 * Since: 3.26
4687 gboolean
4688 e_webdav_session_get_principal_collection_set_sync (EWebDAVSession *webdav,
4689 const gchar *uri,
4690 GSList **out_principal_hrefs, /* gchar * */
4691 GCancellable *cancellable,
4692 GError **error)
4694 EXmlDocument *xml;
4695 gboolean success;
4697 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
4698 g_return_val_if_fail (out_principal_hrefs != NULL, FALSE);
4700 *out_principal_hrefs = NULL;
4702 xml = e_xml_document_new (E_WEBDAV_NS_DAV, "propfind");
4703 g_return_val_if_fail (xml != NULL, FALSE);
4705 e_xml_document_start_element (xml, NULL, "prop");
4706 e_xml_document_add_empty_element (xml, NULL, "principal-collection-set");
4707 e_xml_document_end_element (xml); /* prop */
4709 success = e_webdav_session_propfind_sync (webdav, uri, E_WEBDAV_DEPTH_THIS, xml,
4710 e_webdav_session_principal_collection_set_cb, out_principal_hrefs, cancellable, error);
4712 g_object_unref (xml);
4714 if (success)
4715 *out_principal_hrefs = g_slist_reverse (*out_principal_hrefs);
4717 return success;
4721 * e_webdav_session_set_acl_sync:
4722 * @webdav: an #EWebDAVSession
4723 * @uri: (nullable): URI to issue the request for, or %NULL to read from #ESource
4724 * @entries: (element-type EWebDAVAccessControlEntry): entries to write
4725 * @cancellable: optional #GCancellable object, or %NULL
4726 * @error: return location for a #GError, or %NULL
4728 * Changes Access Control List (ACL) for the @uri, or, in case it's %NULL,
4729 * for the URI defined in associated #ESource.
4731 * Make sure that the @entries satisfy ACL restrictions, as returned
4732 * by e_webdav_session_get_acl_restrictions_sync(). The order in the @entries
4733 * is preserved. It cannot contain any %E_WEBDAV_ACE_FLAG_PROTECTED,
4734 * nor @E_WEBDAV_ACE_FLAG_INHERITED, items.
4736 * Use e_webdav_session_get_acl_sync() to read currently known ACL entries,
4737 * remove from the list those protected and inherited, and then modify
4738 * the rest with the required changed.
4740 * Note this function doesn't support general %E_WEBDAV_ACE_PRINCIPAL_PROPERTY and
4741 * returns %G_IO_ERROR_NOT_SUPPORTED error when any such is tried to be written.
4743 * In case the returned entries contain any %E_WEBDAV_ACE_PRINCIPAL_PROPERTY,
4744 * or there's a need to write such Access Control Entry, then do not use
4745 * e_webdav_session_get_acl_sync(), neither e_webdav_session_set_acl_sync(),
4746 * and write more generic implementation.
4748 * Returns: Whether succeeded.
4750 * Since: 3.26
4752 gboolean
4753 e_webdav_session_set_acl_sync (EWebDAVSession *webdav,
4754 const gchar *uri,
4755 const GSList *entries,
4756 GCancellable *cancellable,
4757 GError **error)
4759 EXmlDocument *xml;
4760 GSList *link, *plink;
4761 gboolean success;
4763 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
4764 g_return_val_if_fail (entries != NULL, FALSE);
4766 xml = e_xml_document_new (E_WEBDAV_NS_DAV, "acl");
4767 g_return_val_if_fail (xml != NULL, FALSE);
4769 for (link = (GSList *) entries; link; link = g_slist_next (link)) {
4770 EWebDAVAccessControlEntry *ace = link->data;
4772 if (!ace) {
4773 g_warn_if_fail (ace != NULL);
4774 g_object_unref (xml);
4775 return FALSE;
4778 if ((ace->flags & E_WEBDAV_ACE_FLAG_PROTECTED) != 0 ||
4779 (ace->flags & E_WEBDAV_ACE_FLAG_INHERITED) != 0) {
4780 g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
4781 _("Cannot store protected nor inherited Access Control Entry."));
4782 g_object_unref (xml);
4783 return FALSE;
4786 if (ace->principal_kind == E_WEBDAV_ACE_PRINCIPAL_UNKNOWN) {
4787 g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
4788 _("Provided invalid principal kind for Access Control Entry."));
4789 g_object_unref (xml);
4790 return FALSE;
4793 if (ace->principal_kind == E_WEBDAV_ACE_PRINCIPAL_PROPERTY) {
4794 g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
4795 _("Cannot store property-based Access Control Entry."));
4796 g_object_unref (xml);
4797 return FALSE;
4800 if ((ace->flags & (E_WEBDAV_ACE_FLAG_GRANT | E_WEBDAV_ACE_FLAG_DENY)) == 0) {
4801 g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
4802 _("Access Control Entry can be only to Grant or Deny, but not None."));
4803 g_object_unref (xml);
4804 return FALSE;
4807 if ((ace->flags & E_WEBDAV_ACE_FLAG_GRANT) != 0 &&
4808 (ace->flags & E_WEBDAV_ACE_FLAG_DENY) != 0) {
4809 g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
4810 _("Access Control Entry can be only to Grant or Deny, but not both."));
4811 g_object_unref (xml);
4812 return FALSE;
4815 e_xml_document_start_element (xml, NULL, "ace");
4817 if ((ace->flags & E_WEBDAV_ACE_FLAG_INVERT) != 0)
4818 e_xml_document_start_element (xml, NULL, "invert");
4820 e_xml_document_start_element (xml, NULL, "principal");
4821 switch (ace->principal_kind) {
4822 case E_WEBDAV_ACE_PRINCIPAL_UNKNOWN:
4823 g_warn_if_reached ();
4824 break;
4825 case E_WEBDAV_ACE_PRINCIPAL_HREF:
4826 e_xml_document_start_text_element (xml, NULL, "href");
4827 e_xml_document_write_string (xml, ace->principal_href);
4828 e_xml_document_end_element (xml);
4829 break;
4830 case E_WEBDAV_ACE_PRINCIPAL_ALL:
4831 e_xml_document_add_empty_element (xml, NULL, "all");
4832 break;
4833 case E_WEBDAV_ACE_PRINCIPAL_AUTHENTICATED:
4834 e_xml_document_add_empty_element (xml, NULL, "authenticated");
4835 break;
4836 case E_WEBDAV_ACE_PRINCIPAL_UNAUTHENTICATED:
4837 e_xml_document_add_empty_element (xml, NULL, "unauthenticated");
4838 break;
4839 case E_WEBDAV_ACE_PRINCIPAL_PROPERTY:
4840 g_warn_if_reached ();
4841 break;
4842 case E_WEBDAV_ACE_PRINCIPAL_SELF:
4843 e_xml_document_add_empty_element (xml, NULL, "self");
4844 break;
4845 case E_WEBDAV_ACE_PRINCIPAL_OWNER:
4846 e_xml_document_start_element (xml, NULL, "property");
4847 e_xml_document_add_empty_element (xml, NULL, "owner");
4848 e_xml_document_end_element (xml);
4849 break;
4852 e_xml_document_end_element (xml); /* principal */
4854 if ((ace->flags & E_WEBDAV_ACE_FLAG_INVERT) != 0)
4855 e_xml_document_end_element (xml); /* invert */
4857 if ((ace->flags & E_WEBDAV_ACE_FLAG_GRANT) != 0)
4858 e_xml_document_start_element (xml, NULL, "grant");
4859 else if ((ace->flags & E_WEBDAV_ACE_FLAG_DENY) != 0)
4860 e_xml_document_start_element (xml, NULL, "deny");
4861 else
4862 g_warn_if_reached ();
4864 for (plink = ace->privileges; plink; plink = g_slist_next (plink)) {
4865 EWebDAVPrivilege *privilege = plink->data;
4867 if (!privilege) {
4868 g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
4869 _("Access Control Entry privilege cannot be NULL."));
4870 g_object_unref (xml);
4871 return FALSE;
4874 e_xml_document_start_element (xml, NULL, "privilege");
4875 e_xml_document_add_empty_element (xml, privilege->ns_uri, privilege->name);
4876 e_xml_document_end_element (xml); /* privilege */
4879 e_xml_document_end_element (xml); /* grant or deny */
4881 e_xml_document_end_element (xml); /* ace */
4884 success = e_webdav_session_acl_sync (webdav, uri, xml, cancellable, error);
4886 g_object_unref (xml);
4888 return success;
4891 static gboolean
4892 e_webdav_session_principal_property_search_cb (EWebDAVSession *webdav,
4893 xmlXPathContextPtr xpath_ctx,
4894 const gchar *xpath_prop_prefix,
4895 const SoupURI *request_uri,
4896 const gchar *href,
4897 guint status_code,
4898 gpointer user_data)
4900 GSList **out_principals = user_data;
4902 g_return_val_if_fail (out_principals != NULL, FALSE);
4904 if (!xpath_prop_prefix) {
4905 } else if (status_code == SOUP_STATUS_OK) {
4906 EWebDAVResource *resource;
4907 gchar *display_name;
4909 display_name = e_webdav_session_extract_nonempty (xpath_ctx, xpath_prop_prefix, "D:displayname", NULL);
4911 resource = e_webdav_resource_new (
4912 E_WEBDAV_RESOURCE_KIND_PRINCIPAL,
4913 0, /* supports */
4914 href,
4915 NULL, /* etag */
4916 NULL, /* display_name */
4917 NULL, /* content_type */
4918 0, /* content_length */
4919 0, /* creation_date */
4920 0, /* last_modified */
4921 NULL, /* description */
4922 NULL); /* color */
4923 resource->display_name = display_name;
4925 *out_principals = g_slist_prepend (*out_principals, resource);
4928 return TRUE;
4932 * e_webdav_session_principal_property_search_sync:
4933 * @webdav: an #EWebDAVSession
4934 * @uri: (nullable): URI to issue the request for, or %NULL to read from #ESource
4935 * @apply_to_principal_collection_set: whether to apply to principal-collection-set
4936 * @match_ns_uri: (nullable): namespace URI of the property to search in, or %NULL for %E_WEBDAV_NS_DAV
4937 * @match_property: name of the property to search in
4938 * @match_value: a string value to search for
4939 * @out_principals: (out) (transfer full) (element-type EWebDAVResource): return location for matching principals
4940 * @cancellable: optional #GCancellable object, or %NULL
4941 * @error: return location for a #GError, or %NULL
4943 * Issues a DAV:principal-property-search for the @uri, or, in case it's %NULL,
4944 * for the URI defined in associated #ESource. The DAV:principal-property-search
4945 * performs a search for all principals whose properties contain character data
4946 * that matches the search criteria @match_value in @match_property property
4947 * of namespace @match_ns_uri.
4949 * By default, the function searches all members (at any depth) of the collection
4950 * identified by the @uri. If @apply_to_principal_collection_set is set to %TRUE,
4951 * the search is applied instead to each collection returned by
4952 * e_webdav_session_get_principal_collection_set_sync() for the @uri.
4954 * The @out_principals is a #GSList of #EWebDAVResource, where the kind
4955 * is set to %E_WEBDAV_RESOURCE_KIND_PRINCIPAL and only href with displayname
4956 * are filled. All other members of #EWebDAVResource are not set.
4958 * Free the returned @out_principals with
4959 * g_slist_free_full (principals, e_webdav_resource_free);
4960 * when no longer needed.
4962 * Returns: Whether succeeded. Note it can report success also when no matching
4963 * principal had been found.
4965 * Since: 3.26
4967 gboolean
4968 e_webdav_session_principal_property_search_sync (EWebDAVSession *webdav,
4969 const gchar *uri,
4970 gboolean apply_to_principal_collection_set,
4971 const gchar *match_ns_uri,
4972 const gchar *match_property,
4973 const gchar *match_value,
4974 GSList **out_principals,
4975 GCancellable *cancellable,
4976 GError **error)
4978 EXmlDocument *xml;
4979 gboolean success;
4981 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
4982 g_return_val_if_fail (match_property != NULL, FALSE);
4983 g_return_val_if_fail (match_value != NULL, FALSE);
4984 g_return_val_if_fail (out_principals != NULL, FALSE);
4986 *out_principals = NULL;
4988 xml = e_xml_document_new (E_WEBDAV_NS_DAV, "principal-property-search");
4989 g_return_val_if_fail (xml != NULL, FALSE);
4991 if (apply_to_principal_collection_set) {
4992 e_xml_document_add_empty_element (xml, NULL, "apply-to-principal-collection-set");
4995 e_xml_document_start_element (xml, NULL, "property-search");
4996 e_xml_document_start_element (xml, NULL, "prop");
4997 e_xml_document_add_empty_element (xml, match_ns_uri, match_property);
4998 e_xml_document_end_element (xml); /* prop */
4999 e_xml_document_start_text_element (xml, NULL, "match");
5000 e_xml_document_write_string (xml, match_value);
5001 e_xml_document_end_element (xml); /* match */
5002 e_xml_document_end_element (xml); /* property-search */
5004 e_xml_document_start_element (xml, NULL, "prop");
5005 e_xml_document_add_empty_element (xml, NULL, "displayname");
5006 e_xml_document_end_element (xml); /* prop */
5008 success = e_webdav_session_report_sync (webdav, uri, E_WEBDAV_DEPTH_THIS, xml,
5009 e_webdav_session_principal_property_search_cb, out_principals, NULL, NULL, cancellable, error);
5011 g_object_unref (xml);
5013 if (success)
5014 *out_principals = g_slist_reverse (*out_principals);
5016 return success;
5020 * e_webdav_session_util_maybe_dequote:
5021 * @text: (inout) (nullable): text to dequote
5023 * Dequotes @text, if it's enclosed in double-quotes. The function
5024 * changes @text, it doesn't allocate new string. The function does
5025 * nothing when the @text is not enclosed in double-quotes.
5027 * Returns: possibly dequoted @text
5029 * Since: 3.26
5031 gchar *
5032 e_webdav_session_util_maybe_dequote (gchar *text)
5034 gint len;
5036 if (!text || *text != '\"')
5037 return text;
5039 len = strlen (text);
5041 if (len < 2 || text[len - 1] != '\"')
5042 return text;
5044 memmove (text, text + 1, len - 2);
5045 text[len - 2] = '\0';
5047 return text;
5050 static gboolean
5051 e_webdav_session_free_in_traverse_cb (GNode *node,
5052 gpointer user_data)
5054 if (node) {
5055 e_webdav_privilege_free (node->data);
5056 node->data = NULL;
5059 return FALSE;
5063 * e_webdav_session_util_free_privileges:
5064 * @privileges: (nullable): a tree of #EWebDAVPrivilege structures
5066 * Frees @privileges returned by e_webdav_session_get_supported_privilege_set_sync().
5067 * The function does nothing, if @privileges is %NULL.
5069 * Since: 3.26
5071 void
5072 e_webdav_session_util_free_privileges (GNode *privileges)
5074 if (!privileges)
5075 return;
5077 g_node_traverse (privileges, G_PRE_ORDER, G_TRAVERSE_ALL, -1, e_webdav_session_free_in_traverse_cb, NULL);
5078 g_node_destroy (privileges);