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