1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
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
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/>.
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).
29 #include "evolution-data-server-config.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
{
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
)
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.
81 e_webdav_resource_new (EWebDAVResourceKind kind
,
85 const gchar
*display_name
,
86 const gchar
*content_type
,
90 const gchar
*description
,
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
);
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.
122 e_webdav_resource_copy (const EWebDAVResource
*src
)
127 return e_webdav_resource_new (src
->kind
,
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.
150 e_webdav_resource_free (gpointer ptr
)
152 EWebDAVResource
*resource
= ptr
;
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
);
165 static EWebDAVPropertyChange
*
166 e_webdav_property_change_new (EWebDAVPropertyChangeKind kind
,
171 EWebDAVPropertyChange
*change
;
173 change
= g_new0 (EWebDAVPropertyChange
, 1);
175 change
->ns_uri
= g_strdup (ns_uri
);
176 change
->name
= g_strdup (name
);
177 change
->value
= g_strdup (value
);
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.
199 EWebDAVPropertyChange
*
200 e_webdav_property_change_new_set (const gchar
*ns_uri
,
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.
224 EWebDAVPropertyChange
*
225 e_webdav_property_change_new_remove (const gchar
*ns_uri
,
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.
244 EWebDAVPropertyChange
*
245 e_webdav_property_change_copy (const EWebDAVPropertyChange
*src
)
250 return e_webdav_property_change_new (
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.
268 e_webdav_property_change_free (gpointer ptr
)
270 EWebDAVPropertyChange
*change
= ptr
;
273 g_free (change
->ns_uri
);
274 g_free (change
->name
);
275 g_free (change
->value
);
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.
302 e_webdav_privilege_new (const gchar
*ns_uri
,
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
;
314 case E_WEBDAV_PRIVILEGE_HINT_UNKNOWN
:
316 case E_WEBDAV_PRIVILEGE_HINT_READ
:
319 case E_WEBDAV_PRIVILEGE_HINT_WRITE
:
322 case E_WEBDAV_PRIVILEGE_HINT_WRITE_PROPERTIES
:
323 use_name
= "write-properties";
325 case E_WEBDAV_PRIVILEGE_HINT_WRITE_CONTENT
:
326 use_name
= "write-content";
328 case E_WEBDAV_PRIVILEGE_HINT_UNLOCK
:
331 case E_WEBDAV_PRIVILEGE_HINT_READ_ACL
:
332 use_name
= "read-acl";
334 case E_WEBDAV_PRIVILEGE_HINT_WRITE_ACL
:
335 use_name
= "write-acl";
337 case E_WEBDAV_PRIVILEGE_HINT_READ_CURRENT_USER_PRIVILEGE_SET
:
338 use_name
= "read-current-user-privilege-set";
340 case E_WEBDAV_PRIVILEGE_HINT_BIND
:
343 case E_WEBDAV_PRIVILEGE_HINT_UNBIND
:
346 case E_WEBDAV_PRIVILEGE_HINT_ALL
:
349 case E_WEBDAV_PRIVILEGE_HINT_CALDAV_READ_FREE_BUSY
:
350 use_ns_uri
= E_WEBDAV_NS_CALDAV
;
351 use_name
= "read-free-busy";
356 ns_uri
= use_ns_uri
? use_ns_uri
: E_WEBDAV_NS_DAV
;
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
;
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.
415 e_webdav_privilege_copy (const EWebDAVPrivilege
*src
)
420 return e_webdav_privilege_new (
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.
438 e_webdav_privilege_free (gpointer ptr
)
440 EWebDAVPrivilege
*privilege
= ptr
;
443 g_free (privilege
->ns_uri
);
444 g_free (privilege
->name
);
445 g_free (privilege
->description
);
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.
470 EWebDAVAccessControlEntry
*
471 e_webdav_access_control_entry_new (EWebDAVACEPrincipalKind principal_kind
,
472 const gchar
*principal_href
,
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
);
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
);
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
);
492 ace
->inherited_href
= g_strdup (inherited_href
);
493 ace
->privileges
= NULL
;
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.
508 EWebDAVAccessControlEntry
*
509 e_webdav_access_control_entry_copy (const EWebDAVAccessControlEntry
*src
)
511 EWebDAVAccessControlEntry
*ace
;
517 ace
= e_webdav_access_control_entry_new (
521 src
->inherited_href
);
525 for (link
= src
->privileges
; link
; link
= g_slist_next (link
)) {
526 EWebDAVPrivilege
*privilege
= link
->data
;
529 ace
->privileges
= g_slist_prepend (ace
->privileges
, e_webdav_privilege_copy (privilege
));
532 ace
->privileges
= g_slist_reverse (ace
->privileges
);
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.
547 e_webdav_access_control_entry_free (gpointer ptr
)
549 EWebDAVAccessControlEntry
*ace
= ptr
;
552 g_free (ace
->principal_href
);
553 g_free (ace
->inherited_href
);
554 g_slist_free_full (ace
->privileges
, e_webdav_privilege_free
);
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.
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.
591 e_webdav_access_control_entry_get_privileges (EWebDAVAccessControlEntry
*ace
)
593 g_return_val_if_fail (ace
!= NULL
, NULL
);
595 return ace
->privileges
;
599 e_webdav_session_class_init (EWebDAVSessionClass
*klass
)
601 g_type_class_add_private (klass
, sizeof (EWebDAVSessionPrivate
));
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.
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
,
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.
647 e_webdav_session_new_request (EWebDAVSession
*webdav
,
652 ESoupSession
*session
;
653 SoupRequestHTTP
*request
;
656 ESourceWebdav
*webdav_extension
;
659 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav
), NULL
);
661 session
= E_SOUP_SESSION (webdav
);
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"));
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
, "/")) {
685 new_path
= g_strconcat (path
? path
: "", "/", NULL
);
686 soup_uri_set_path (soup_uri
, new_path
);
690 request
= e_soup_session_new_request_uri (session
, method
, soup_uri
, error
);
692 soup_uri_free (soup_uri
);
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
,
706 GError
**error
= user_data
;
708 g_return_val_if_fail (error
!= NULL
, FALSE
);
710 if (!xpath_prop_prefix
)
713 if (status_code
!= SOUP_STATUS_OK
&& (
714 status_code
!= SOUP_STATUS_FAILED_DEPENDENCY
||
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
);
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
;
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
))
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
);
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 ("");
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
);
773 g_strstrip (detail_text
);
775 g_string_prepend (text
, detail_text
);
776 g_free (detail_text
);
779 detail_text
= g_string_free (text
, FALSE
);
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.
822 e_webdav_session_replace_with_detailed_error (EWebDAVSession
*webdav
,
823 SoupRequestHTTP
*request
,
824 const GByteArray
*response_data
,
825 gboolean ignore_multistatus
,
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
;
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
);
845 status_code
= message
->status_code
;
846 reason_phrase
= message
->reason_phrase
;
847 byte_array
.data
= NULL
;
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
)
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
);
870 g_prefix_error (&local_error
, "%s: ", prefix
);
871 g_propagate_error (inout_error
, local_error
);
873 g_object_unref (message
);
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))) {
884 if (status_code
== SOUP_STATUS_MULTI_STATUS
&& ignore_multistatus
)
887 doc
= e_xml_parse_data (byte_array
.data
, byte_array
.len
);
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
,
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";
912 guint parsed_status
= 0;
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
);
923 e_webdav_session_extract_dav_error (webdav
, xpath_ctx
, path_prefix
, &detail_text
);
925 e_webdav_session_extract_dav_error (webdav
, xpath_ctx
, path_prefix
, &detail_text
);
933 xmlXPathFreeContext (xpath_ctx
);
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) {
944 soup_uri
= soup_message_get_uri (message
);
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
);
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
);
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."));
964 g_strstrip (detail_text
);
966 if (detail_text
&& *detail_text
) {
969 g_clear_error (inout_error
);
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
),
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
),
990 } else if (status_code
&& !SOUP_STATUS_IS_SUCCESSFUL (status_code
)) {
993 g_clear_error (inout_error
);
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
));
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
);
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
1036 e_webdav_session_ensure_full_uri (EWebDAVSession
*webdav
,
1037 const SoupURI
*request_uri
,
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
, "://")) {
1048 soup_uri
= soup_uri_copy ((SoupURI
*) request_uri
);
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
);
1076 return g_strdup (href
);
1080 e_webdav_session_comma_header_to_hashtable (SoupMessageHeaders
*headers
,
1081 const gchar
*header_name
)
1083 GHashTable
*soup_params
, *result
;
1084 GHashTableIter iter
;
1088 g_return_val_if_fail (header_name
!= NULL
, NULL
);
1093 value
= soup_message_headers_get_list (headers
, header_name
);
1097 soup_params
= soup_header_parse_param_list (value
);
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
)) {
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
);
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.
1145 e_webdav_session_options_sync (EWebDAVSession
*webdav
,
1147 GHashTable
**out_capabilities
,
1148 GHashTable
**out_allows
,
1149 GCancellable
*cancellable
,
1152 SoupRequestHTTP
*request
;
1153 SoupMessage
*message
;
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
;
1163 request
= e_webdav_session_new_request (webdav
, SOUP_METHOD_OPTIONS
, uri
, error
);
1167 bytes
= e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav
), request
, cancellable
, error
);
1170 g_object_unref (request
);
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
);
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.
1214 e_webdav_session_post_sync (EWebDAVSession
*webdav
,
1218 gchar
**out_content_type
,
1219 GByteArray
**out_content
,
1220 GCancellable
*cancellable
,
1223 SoupRequestHTTP
*request
;
1224 SoupMessage
*message
;
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
;
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
);
1244 message
= soup_request_http_get_message (request
);
1246 g_warn_if_fail (message
!= NULL
);
1247 g_object_unref (request
);
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
) &&
1261 if (out_content_type
) {
1262 *out_content_type
= g_strdup (soup_message_headers_get_content_type (message
->response_headers
, NULL
));
1266 *out_content
= bytes
;
1272 g_byte_array_free (bytes
, TRUE
);
1273 g_object_unref (message
);
1274 g_object_unref (request
);
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.
1307 e_webdav_session_propfind_sync (EWebDAVSession
*webdav
,
1310 const EXmlDocument
*xml
,
1311 EWebDAVPropstatTraverseFunc func
,
1312 gpointer func_user_data
,
1313 GCancellable
*cancellable
,
1316 SoupRequestHTTP
*request
;
1317 SoupMessage
*message
;
1321 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav
), FALSE
);
1322 g_return_val_if_fail (depth
!= NULL
, FALSE
);
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
);
1331 message
= soup_request_http_get_message (request
);
1333 g_warn_if_fail (message
!= NULL
);
1334 g_object_unref (request
);
1339 soup_message_headers_replace (message
->request_headers
, "Depth", depth
);
1343 gsize content_length
;
1345 content
= e_xml_document_get_content (xml
, &content_length
);
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"));
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
) &&
1365 success
= e_webdav_session_traverse_multistatus_response (webdav
, message
, bytes
, func
, func_user_data
, error
);
1368 g_byte_array_free (bytes
, TRUE
);
1369 g_object_unref (message
);
1370 g_object_unref (request
);
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.
1392 e_webdav_session_proppatch_sync (EWebDAVSession
*webdav
,
1394 const EXmlDocument
*xml
,
1395 GCancellable
*cancellable
,
1398 SoupRequestHTTP
*request
;
1399 SoupMessage
*message
;
1402 gsize content_length
;
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
);
1412 message
= soup_request_http_get_message (request
);
1414 g_warn_if_fail (message
!= NULL
);
1415 g_object_unref (request
);
1420 content
= e_xml_document_get_content (xml
, &content_length
);
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"));
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
) &&
1439 g_byte_array_free (bytes
, TRUE
);
1440 g_object_unref (message
);
1441 g_object_unref (request
);
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.
1482 e_webdav_session_report_sync (EWebDAVSession
*webdav
,
1485 const EXmlDocument
*xml
,
1486 EWebDAVPropstatTraverseFunc func
,
1487 gpointer func_user_data
,
1488 gchar
**out_content_type
,
1489 GByteArray
**out_content
,
1490 GCancellable
*cancellable
,
1493 SoupRequestHTTP
*request
;
1494 SoupMessage
*message
;
1497 gsize content_length
;
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
;
1507 *out_content
= NULL
;
1509 request
= e_webdav_session_new_request (webdav
, "REPORT", uri
, error
);
1513 message
= soup_request_http_get_message (request
);
1515 g_warn_if_fail (message
!= NULL
);
1516 g_object_unref (request
);
1522 soup_message_headers_replace (message
->request_headers
, "Depth", depth
);
1524 content
= e_xml_document_get_content (xml
, &content_length
);
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"));
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
) &&
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
);
1546 if (out_content_type
) {
1547 *out_content_type
= g_strdup (soup_message_headers_get_content_type (message
->response_headers
, NULL
));
1551 *out_content
= bytes
;
1557 g_byte_array_free (bytes
, TRUE
);
1558 g_object_unref (message
);
1559 g_object_unref (request
);
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.
1580 e_webdav_session_mkcol_sync (EWebDAVSession
*webdav
,
1582 GCancellable
*cancellable
,
1585 SoupRequestHTTP
*request
;
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
);
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
) &&
1602 g_byte_array_free (bytes
, TRUE
);
1603 g_object_unref (request
);
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
1623 * Returns: Whether succeeded.
1628 e_webdav_session_mkcol_addressbook_sync (EWebDAVSession
*webdav
,
1630 const gchar
*display_name
,
1631 const gchar
*description
,
1632 GCancellable
*cancellable
,
1635 SoupRequestHTTP
*request
;
1636 SoupMessage
*message
;
1639 gsize content_length
;
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
);
1650 message
= soup_request_http_get_message (request
);
1652 g_warn_if_fail (message
!= NULL
);
1653 g_object_unref (request
);
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
);
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"));
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
) &&
1705 g_byte_array_free (bytes
, TRUE
);
1706 g_object_unref (message
);
1707 g_object_unref (request
);
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 "#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
1733 * Returns: Whether succeeded.
1738 e_webdav_session_mkcalendar_sync (EWebDAVSession
*webdav
,
1740 const gchar
*display_name
,
1741 const gchar
*description
,
1744 GCancellable
*cancellable
,
1747 SoupRequestHTTP
*request
;
1748 SoupMessage
*message
;
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
);
1759 message
= soup_request_http_get_message (request
);
1761 g_warn_if_fail (message
!= NULL
);
1762 g_object_unref (request
);
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
) ||
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
{
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" }
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
);
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"));
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
) &&
1860 g_byte_array_free (bytes
, TRUE
);
1861 g_object_unref (message
);
1862 g_object_unref (request
);
1868 e_webdav_session_extract_href_and_etag (SoupMessage
*message
,
1872 g_return_if_fail (SOUP_IS_MESSAGE (message
));
1875 const gchar
*header
;
1879 header
= soup_message_headers_get_list (message
->response_headers
, "Location");
1881 gchar
*file
= strrchr (header
, '/');
1886 decoded
= soup_uri_decode (file
+ 1);
1887 *out_href
= soup_uri_encode (decoded
? decoded
: (file
+ 1), NULL
);
1894 *out_href
= soup_uri_to_string (soup_message_get_uri (message
), FALSE
);
1898 const gchar
*header
;
1902 header
= soup_message_headers_get_list (message
->response_headers
, "ETag");
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.
1932 e_webdav_session_get_sync (EWebDAVSession
*webdav
,
1936 GOutputStream
*out_stream
,
1937 GCancellable
*cancellable
,
1940 SoupRequestHTTP
*request
;
1941 SoupMessage
*message
;
1942 GInputStream
*input_stream
;
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
);
1953 message
= soup_request_http_get_message (request
);
1955 g_warn_if_fail (message
!= NULL
);
1956 g_object_unref (request
);
1961 input_stream
= e_soup_session_send_request_sync (E_SOUP_SESSION (webdav
), request
, cancellable
, error
);
1963 success
= input_stream
!= NULL
;
1966 SoupLoggerLogLevel log_level
= e_soup_session_get_log_level (E_SOUP_SESSION (webdav
));
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
);
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
);
1993 success
= g_output_stream_write_all (out_stream
, buffer
, nread
, &nwritten
, cancellable
, error
);
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");
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
);
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.
2047 e_webdav_session_get_data_sync (EWebDAVSession
*webdav
,
2053 GCancellable
*cancellable
,
2056 GOutputStream
*output_stream
;
2057 gsize bytes_written
= 0;
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
);
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
);
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
);
2086 typedef struct _ChunkWriteData
{
2087 SoupSession
*session
;
2088 SoupLoggerLogLevel log_level
;
2089 GInputStream
*stream
;
2094 GCancellable
*cancellable
;
2099 e_webdav_session_write_next_chunk (SoupMessage
*message
,
2102 ChunkWriteData
*cwd
= user_data
;
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
);
2114 soup_message_body_complete (message
->request_body
);
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
);
2127 e_webdav_session_write_restarted (SoupMessage
*message
,
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
)) {
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
);
2152 soup_message_body_truncate (message
->request_body
);
2158 e_webdav_session_set_if_match_header (SoupMessage
*message
,
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
);
2173 quoted
= g_strconcat ("\"", etag
, "\"", NULL
);
2174 soup_message_headers_replace (message
->request_headers
, "If-Match", 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.
2221 e_webdav_session_put_sync (EWebDAVSession
*webdav
,
2224 const gchar
*content_type
,
2225 GInputStream
*stream
,
2228 GCancellable
*cancellable
,
2232 SoupRequestHTTP
*request
;
2233 SoupMessage
*message
;
2235 gulong restarted_id
, wrote_headers_id
, wrote_chunk_id
;
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
);
2248 request
= e_webdav_session_new_request (webdav
, SOUP_METHOD_PUT
, uri
, error
);
2252 message
= soup_request_http_get_message (request
);
2254 g_warn_if_fail (message
!= NULL
);
2255 g_object_unref (request
);
2260 if (!etag
|| *etag
) {
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
) {
2274 e_webdav_session_set_if_match_header (message
, etag
);
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
;
2285 cwd
.wrote_any
= FALSE
;
2286 cwd
.buffer_size
= BUFFER_SIZE
;
2287 cwd
.buffer
= g_malloc (cwd
.buffer_size
);
2288 cwd
.cancellable
= cancellable
;
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
) &&
2314 if (cwd
.wrote_any
&& cwd
.log_level
== SOUP_LOGGER_LOG_BODY
) {
2315 fprintf (stdout
, "\n");
2320 g_clear_error (error
);
2321 g_propagate_error (error
, cwd
.error
);
2326 if (success
&& !SOUP_STATUS_IS_SUCCESSFUL (message
->status_code
)) {
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
));
2336 e_webdav_session_extract_href_and_etag (message
, out_href
, out_etag
);
2339 g_byte_array_free (bytes
, TRUE
);
2340 g_object_unref (message
);
2341 g_object_unref (request
);
2342 g_free (cwd
.buffer
);
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.
2385 e_webdav_session_put_data_sync (EWebDAVSession
*webdav
,
2388 const gchar
*content_type
,
2393 GCancellable
*cancellable
,
2396 SoupRequestHTTP
*request
;
2397 SoupMessage
*message
;
2398 GByteArray
*ret_bytes
;
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
);
2413 request
= e_webdav_session_new_request (webdav
, SOUP_METHOD_PUT
, uri
, error
);
2417 message
= soup_request_http_get_message (request
);
2419 g_warn_if_fail (message
!= NULL
);
2420 g_object_unref (request
);
2425 if (!etag
|| *etag
) {
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
) {
2439 e_webdav_session_set_if_match_header (message
, etag
);
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
) &&
2459 if (success
&& !SOUP_STATUS_IS_SUCCESSFUL (message
->status_code
)) {
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
));
2469 e_webdav_session_extract_href_and_etag (message
, out_href
, out_etag
);
2472 g_byte_array_free (ret_bytes
, TRUE
);
2473 g_object_unref (message
);
2474 g_object_unref (request
);
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.
2506 e_webdav_session_delete_sync (EWebDAVSession
*webdav
,
2510 GCancellable
*cancellable
,
2513 SoupRequestHTTP
*request
;
2514 SoupMessage
*message
;
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
);
2525 message
= soup_request_http_get_message (request
);
2527 g_warn_if_fail (message
!= NULL
);
2528 g_object_unref (request
);
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
);
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
) &&
2559 g_byte_array_free (bytes
, TRUE
);
2560 g_object_unref (message
);
2561 g_object_unref (request
);
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.
2586 e_webdav_session_copy_sync (EWebDAVSession
*webdav
,
2587 const gchar
*source_uri
,
2588 const gchar
*destination_uri
,
2590 gboolean can_overwrite
,
2591 GCancellable
*cancellable
,
2594 SoupRequestHTTP
*request
;
2595 SoupMessage
*message
;
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
);
2608 message
= soup_request_http_get_message (request
);
2610 g_warn_if_fail (message
!= NULL
);
2611 g_object_unref (request
);
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
) &&
2626 g_byte_array_free (bytes
, TRUE
);
2627 g_object_unref (message
);
2628 g_object_unref (request
);
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.
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
,
2657 SoupRequestHTTP
*request
;
2658 SoupMessage
*message
;
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
);
2670 message
= soup_request_http_get_message (request
);
2672 g_warn_if_fail (message
!= NULL
);
2673 g_object_unref (request
);
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
) &&
2688 g_byte_array_free (bytes
, TRUE
);
2689 g_object_unref (message
);
2690 g_object_unref (request
);
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.
2722 e_webdav_session_lock_sync (EWebDAVSession
*webdav
,
2725 gint32 lock_timeout
,
2726 const EXmlDocument
*xml
,
2727 gchar
**out_lock_token
,
2728 xmlDoc
**out_xml_response
,
2729 GCancellable
*cancellable
,
2732 SoupRequestHTTP
*request
;
2733 SoupMessage
*message
;
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
);
2748 message
= soup_request_http_get_message (request
);
2750 g_warn_if_fail (message
!= NULL
);
2751 g_object_unref (request
);
2757 soup_message_headers_replace (message
->request_headers
, "Depth", depth
);
2762 value
= g_strdup_printf ("Second-%d", lock_timeout
);
2763 soup_message_headers_replace (message
->request_headers
, "Timeout", value
);
2766 soup_message_headers_replace (message
->request_headers
, "Timeout", "Infinite");
2771 gsize content_length
;
2773 content
= e_xml_document_get_content (xml
, &content_length
);
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"));
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
) &&
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"));
2805 g_set_error (error
, G_IO_ERROR
, G_IO_ERROR_INVALID_DATA
,
2806 _("Expected application/xml response, but %s returned"), content_type
);
2815 doc
= e_xml_parse_data (bytes
->data
, bytes
->len
);
2817 g_set_error_literal (error
, G_IO_ERROR
, G_IO_ERROR_INVALID_DATA
,
2818 _("Failed to parse XML data"));
2822 *out_xml_response
= doc
;
2828 *out_lock_token
= g_strdup (soup_message_headers_get_list (message
->response_headers
, "Lock-Token"));
2831 g_byte_array_free (bytes
, TRUE
);
2832 g_object_unref (message
);
2833 g_object_unref (request
);
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.
2857 e_webdav_session_refresh_lock_sync (EWebDAVSession
*webdav
,
2859 const gchar
*lock_token
,
2860 gint32 lock_timeout
,
2861 GCancellable
*cancellable
,
2864 SoupRequestHTTP
*request
;
2865 SoupMessage
*message
;
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
);
2877 message
= soup_request_http_get_message (request
);
2879 g_warn_if_fail (message
!= NULL
);
2880 g_object_unref (request
);
2886 value
= g_strdup_printf ("Second-%d", lock_timeout
);
2887 soup_message_headers_replace (message
->request_headers
, "Timeout", value
);
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
) &&
2901 g_byte_array_free (bytes
, TRUE
);
2902 g_object_unref (message
);
2903 g_object_unref (request
);
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.
2926 e_webdav_session_unlock_sync (EWebDAVSession
*webdav
,
2928 const gchar
*lock_token
,
2929 GCancellable
*cancellable
,
2932 SoupRequestHTTP
*request
;
2933 SoupMessage
*message
;
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
);
2944 message
= soup_request_http_get_message (request
);
2946 g_warn_if_fail (message
!= NULL
);
2947 g_object_unref (request
);
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
) &&
2960 g_byte_array_free (bytes
, TRUE
);
2961 g_object_unref (message
);
2962 g_object_unref (request
);
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
,
2979 SoupURI
*request_uri
= NULL
;
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
);
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
));
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"));
3007 g_set_error (error
, G_IO_ERROR
, G_IO_ERROR_INVALID_DATA
,
3008 _("Expected application/xml response, but %s returned"), content_type
);
3014 request_uri
= soup_message_get_uri ((SoupMessage
*) message
);
3017 doc
= e_xml_parse_data (xml_data
->data
, xml_data
->len
);
3020 g_set_error_literal (error
, G_IO_ERROR
, G_IO_ERROR_INVALID_DATA
,
3021 _("Failed to parse XML data"));
3026 xpath_ctx
= e_xml_new_xpath_context_with_namespaces (doc
,
3027 "D", E_WEBDAV_NS_DAV
,
3028 additional_ns_prefix
, additional_ns
,
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
) {
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);
3058 full_uri
= e_webdav_session_ensure_full_uri (webdav
, request_uri
, href
);
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
;
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
))
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
);
3089 xmlXPathFreeObject (xpath_obj_response
);
3094 xmlXPathFreeContext (xpath_ctx
);
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.
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
,
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.
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
,
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.
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
,
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
);
3227 e_webdav_session_getctag_cb (EWebDAVSession
*webdav
,
3228 xmlXPathContextPtr xpath_ctx
,
3229 const gchar
*xpath_prop_prefix
,
3230 const SoupURI
*request_uri
,
3235 if (!xpath_prop_prefix
) {
3236 e_xml_xpath_context_register_namespaces (xpath_ctx
,
3237 "CS", E_WEBDAV_NS_CALENDARSERVER
,
3243 if (status_code
== SOUP_STATUS_OK
) {
3244 gchar
**out_ctag
= user_data
;
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
);
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.
3282 e_webdav_session_getctag_sync (EWebDAVSession
*webdav
,
3285 GCancellable
*cancellable
,
3291 g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav
), FALSE
);
3292 g_return_val_if_fail (out_ctag
!= NULL
, FALSE
);
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
;
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
);
3354 length
= xmlXPathNodeSetGetLength (xpath_obj
->nodesetval
);
3356 for (ii
= 0; ii
< length
; ii
++) {
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);
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
;
3379 xmlXPathFreeObject (xpath_obj
);
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
;
3396 e_webdav_session_extract_nonempty (xmlXPathContextPtr xpath_ctx
,
3397 const gchar
*xpath_prop_prefix
,
3399 const gchar
*alternative_prop
)
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
);
3418 return e_webdav_session_util_maybe_dequote (value
);
3422 e_webdav_session_extract_content_length (xmlXPathContextPtr xpath_ctx
,
3423 const gchar
*xpath_prop_prefix
)
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
);
3435 length
= g_ascii_strtoll (value
, NULL
, 10);
3443 e_webdav_session_extract_datetime (xmlXPathContextPtr xpath_ctx
,
3444 const gchar
*xpath_prop_prefix
,
3446 gboolean is_iso_property
)
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
);
3458 if (is_iso_property
&& !g_time_val_from_iso8601 (value
, &tv
)) {
3460 } else if (!is_iso_property
) {
3461 tv
.tv_sec
= camel_header_decode_date (value
, NULL
);
3470 e_webdav_session_list_cb (EWebDAVSession
*webdav
,
3471 xmlXPathContextPtr xpath_ctx
,
3472 const gchar
*xpath_prop_prefix
,
3473 const SoupURI
*request_uri
,
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
,
3494 if (status_code
== SOUP_STATUS_OK
) {
3495 EWebDAVResource
*resource
;
3496 EWebDAVResourceKind kind
;
3499 gchar
*display_name
;
3500 gchar
*content_type
;
3501 gsize content_length
;
3502 glong creation_date
;
3503 glong last_modified
;
3507 kind
= e_webdav_session_extract_kind (xpath_ctx
, xpath_prop_prefix
);
3508 if (kind
== E_WEBDAV_RESOURCE_KIND_UNKNOWN
)
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
,
3524 NULL
, /* display_name */
3525 NULL
, /* content_type */
3529 NULL
, /* description */
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
);
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.
3568 e_webdav_session_list_sync (EWebDAVSession
*webdav
,
3572 GSList
**out_resources
,
3573 GCancellable
*cancellable
,
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) {
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
);
3663 /* Use the last non-empty path segment. */
3664 while ((cp
= strrchr (href_decoded
, '/')) != NULL
) {
3665 if (*(cp
+ 1) == '\0')
3668 resource
->display_name
= g_strdup (cp
+ 1);
3674 g_free (href_decoded
);
3680 /* Honour order returned by the server, even it's not significant. */
3681 *out_resources
= g_slist_reverse (*out_resources
);
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()
3703 * Returns: Whether succeeded.
3708 e_webdav_session_update_properties_sync (EWebDAVSession
*webdav
,
3710 const GSList
*changes
,
3711 GCancellable
*cancellable
,
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
;
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 */
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 */
3752 success
= e_webdav_session_proppatch_sync (webdav
, uri
, xml
, cancellable
, error
);
3754 g_object_unref (xml
);
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.
3786 e_webdav_session_lock_resource_sync (EWebDAVSession
*webdav
,
3788 EWebDAVLockScope lock_scope
,
3789 gint32 lock_timeout
,
3791 gchar
**out_lock_token
,
3792 GCancellable
*cancellable
,
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");
3810 case E_WEBDAV_LOCK_SHARED
:
3811 e_xml_document_add_empty_element (xml
, NULL
, "shared");
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");
3822 owner_ref
= g_strdup (owner
);
3824 ESource
*source
= e_soup_session_get_source (E_SOUP_SESSION (webdav
));
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
);
3846 owner_ref
= g_strconcat (g_get_host_name (), " / ", g_get_user_name (), NULL
);
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 */
3855 e_xml_document_write_string (xml
, 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
);
3871 e_webdav_session_traverse_privilege_level (xmlXPathContextPtr xpath_ctx
,
3872 const gchar
*xpath_prefix
,
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
);
3886 length
= xmlXPathNodeSetGetLength (xpath_obj
->nodesetval
);
3888 for (ii
= 0; ii
< length
; ii
++) {
3889 xmlXPathObjectPtr xpath_obj_privilege
;
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
) {
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
)) {
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
);
3942 xmlXPathFreeObject (xpath_obj
);
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
,
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
,
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
)) {
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
;
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.
3997 e_webdav_session_acl_sync (EWebDAVSession
*webdav
,
3999 const EXmlDocument
*xml
,
4000 GCancellable
*cancellable
,
4003 SoupRequestHTTP
*request
;
4004 SoupMessage
*message
;
4007 gsize content_length
;
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
);
4017 message
= soup_request_http_get_message (request
);
4019 g_warn_if_fail (message
!= NULL
);
4020 g_object_unref (request
);
4025 content
= e_xml_document_get_content (xml
, &content_length
);
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"));
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
) &&
4044 g_byte_array_free (bytes
, TRUE
);
4045 g_object_unref (message
);
4046 g_object_unref (request
);
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.
4072 e_webdav_session_get_supported_privilege_set_sync (EWebDAVSession
*webdav
,
4074 GNode
**out_privileges
,
4075 GCancellable
*cancellable
,
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
);
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
) {
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
)) {
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
);
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
,
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
,
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
);
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
);
4171 *out_privileges
= g_slist_prepend (*out_privileges
, privilege
);
4173 xmlXPathFreeObject (xpath_obj_privilege
);
4177 xmlXPathFreeObject (xpath_obj
);
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.
4204 e_webdav_session_get_current_user_privilege_set_sync (EWebDAVSession
*webdav
,
4206 GSList
**out_privileges
,
4207 GCancellable
*cancellable
,
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
);
4231 *out_privileges
= g_slist_reverse (*out_privileges
);
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
);
4259 xmlXPathObjectPtr xpath_obj
;
4261 *out_principal_hrefs
= NULL
;
4263 xpath_obj
= e_xml_xpath_eval (xpath_ctx
, "%s/D:href", principal_prefix
);
4268 length
= xmlXPathNodeSetGetLength (xpath_obj
->nodesetval
);
4270 for (ii
= 0; ii
< length
; ii
++) {
4273 href
= e_xml_xpath_eval_as_string (xpath_ctx
, "%s/D:href[%d]", principal_prefix
, ii
+ 1);
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
) {
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
)
4324 if (subelements
== 1)
4325 kind
= E_WEBDAV_ACE_PRINCIPAL_OWNER
;
4328 if (xpath_obj_property
)
4329 xmlXPathFreeObject (xpath_obj_property
);
4335 return E_WEBDAV_ACE_PRINCIPAL_UNKNOWN
;
4339 e_webdav_session_acl_cb (EWebDAVSession
*webdav
,
4340 xmlXPathContextPtr xpath_ctx
,
4341 const gchar
*xpath_prop_prefix
,
4342 const SoupURI
*request_uri
,
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
) {
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
;
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
)) {
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
);
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
);
4391 if (principal_kind
== E_WEBDAV_ACE_PRINCIPAL_UNKNOWN
) {
4392 g_free (ace_prefix
);
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
);
4416 EWebDAVAccessControlEntry
*ace
;
4419 ace
= e_webdav_access_control_entry_new (principal_kind
, principal_href
, flags
, inherited_href
);
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
);
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
);
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.
4482 e_webdav_session_get_acl_sync (EWebDAVSession
*webdav
,
4484 GSList
**out_entries
,
4485 GCancellable
*cancellable
,
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
);
4509 *out_entries
= g_slist_reverse (*out_entries
);
4514 typedef struct _ACLRestrictionsData
{
4515 guint32
*out_restrictions
;
4516 EWebDAVACEPrincipalKind
*out_principal_kind
;
4517 GSList
**out_principal_hrefs
;
4518 } ACLRestrictionsData
;
4521 e_webdav_session_acl_restrictions_cb (EWebDAVSession
*webdav
,
4522 xmlXPathContextPtr xpath_ctx
,
4523 const gchar
*xpath_prop_prefix
,
4524 const SoupURI
*request_uri
,
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
)) {
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
);
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.
4585 e_webdav_session_get_acl_restrictions_sync (EWebDAVSession
*webdav
,
4587 guint32
*out_restrictions
,
4588 EWebDAVACEPrincipalKind
*out_principal_kind
,
4589 GSList
**out_principal_hrefs
,
4590 GCancellable
*cancellable
,
4593 ACLRestrictionsData ard
;
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
);
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
,
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
);
4649 length
= xmlXPathNodeSetGetLength (xpath_obj
->nodesetval
);
4651 for (ii
= 0; ii
< length
; ii
++) {
4654 got_href
= e_xml_xpath_eval_as_string (xpath_ctx
, "%s/D:principal-collection-set/D:href[%d]", xpath_prop_prefix
, ii
+ 1);
4656 *out_principal_hrefs
= g_slist_prepend (*out_principal_hrefs
, got_href
);
4659 xmlXPathFreeObject (xpath_obj
);
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.
4688 e_webdav_session_get_principal_collection_set_sync (EWebDAVSession
*webdav
,
4690 GSList
**out_principal_hrefs
, /* gchar * */
4691 GCancellable
*cancellable
,
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
);
4715 *out_principal_hrefs
= g_slist_reverse (*out_principal_hrefs
);
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.
4753 e_webdav_session_set_acl_sync (EWebDAVSession
*webdav
,
4755 const GSList
*entries
,
4756 GCancellable
*cancellable
,
4760 GSList
*link
, *plink
;
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
;
4773 g_warn_if_fail (ace
!= NULL
);
4774 g_object_unref (xml
);
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
);
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
);
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
);
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
);
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
);
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 ();
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
);
4830 case E_WEBDAV_ACE_PRINCIPAL_ALL
:
4831 e_xml_document_add_empty_element (xml
, NULL
, "all");
4833 case E_WEBDAV_ACE_PRINCIPAL_AUTHENTICATED
:
4834 e_xml_document_add_empty_element (xml
, NULL
, "authenticated");
4836 case E_WEBDAV_ACE_PRINCIPAL_UNAUTHENTICATED
:
4837 e_xml_document_add_empty_element (xml
, NULL
, "unauthenticated");
4839 case E_WEBDAV_ACE_PRINCIPAL_PROPERTY
:
4840 g_warn_if_reached ();
4842 case E_WEBDAV_ACE_PRINCIPAL_SELF
:
4843 e_xml_document_add_empty_element (xml
, NULL
, "self");
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
);
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");
4862 g_warn_if_reached ();
4864 for (plink
= ace
->privileges
; plink
; plink
= g_slist_next (plink
)) {
4865 EWebDAVPrivilege
*privilege
= plink
->data
;
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
);
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
);
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
,
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
,
4916 NULL
, /* display_name */
4917 NULL
, /* content_type */
4918 0, /* content_length */
4919 0, /* creation_date */
4920 0, /* last_modified */
4921 NULL
, /* description */
4923 resource
->display_name
= display_name
;
4925 *out_principals
= g_slist_prepend (*out_principals
, resource
);
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.
4968 e_webdav_session_principal_property_search_sync (EWebDAVSession
*webdav
,
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
,
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
);
5014 *out_principals
= g_slist_reverse (*out_principals
);
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
5032 e_webdav_session_util_maybe_dequote (gchar
*text
)
5036 if (!text
|| *text
!= '\"')
5039 len
= strlen (text
);
5041 if (len
< 2 || text
[len
- 1] != '\"')
5044 memmove (text
, text
+ 1, len
- 2);
5045 text
[len
- 2] = '\0';
5051 e_webdav_session_free_in_traverse_cb (GNode
*node
,
5055 e_webdav_privilege_free (node
->data
);
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.
5072 e_webdav_session_util_free_privileges (GNode
*privileges
)
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
);