[evolution-data-server/wip/offline-cache] Add more EWebDAVSession methods
- From: Milan Crha <mcrha src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [evolution-data-server/wip/offline-cache] Add more EWebDAVSession methods
- Date: Thu, 30 Mar 2017 09:07:07 +0000 (UTC)
commit 94ab8f892886f832a432cb7551a3688dcc42f8df
Author: Milan Crha <mcrha redhat com>
Date: Thu Mar 30 11:06:19 2017 +0200
Add more EWebDAVSession methods
po/POTFILES.in | 1 +
src/libedataserver/e-soup-session.c | 155 ++++-
src/libedataserver/e-soup-session.h | 7 +
src/libedataserver/e-webdav-session.c | 1196 ++++++++++++++++++++++++++++++---
src/libedataserver/e-webdav-session.h | 144 +++--
5 files changed, 1341 insertions(+), 162 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 518ba0f..38c86b7 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -188,6 +188,7 @@ src/libebackend/e-subprocess-factory.c
src/libebackend/e-user-prompter-server.c
src/libedataserver/e-categories.c
src/libedataserver/e-client.c
+src/libedataserver/e-soup-session.c
src/libedataserver/e-source.c
src/libedataserver/e-source-credentials-provider-impl.c
src/libedataserver/e-source-credentials-provider-impl-google.c
diff --git a/src/libedataserver/e-soup-session.c b/src/libedataserver/e-soup-session.c
index 56cc89f..0867481 100644
--- a/src/libedataserver/e-soup-session.c
+++ b/src/libedataserver/e-soup-session.c
@@ -27,6 +27,7 @@
#include "evolution-data-server-config.h"
#include <stdio.h>
+#include <glib/gi18n-lib.h>
#include "e-soup-ssl-trust.h"
#include "e-source-authentication.h"
@@ -34,6 +35,8 @@
#include "e-soup-session.h"
+#define BUFFER_SIZE 16384
+
struct _ESoupSessionPrivate {
GMutex property_lock;
ESource *source;
@@ -539,6 +542,93 @@ e_soup_session_new_request_uri (ESoupSession *session,
return request;
}
+static void
+e_webdav_session_extract_ssl_data (ESoupSession *session,
+ SoupMessage *message)
+{
+ GTlsCertificate *certificate = NULL;
+
+ g_return_if_fail (E_IS_SOUP_SESSION (session));
+ g_return_if_fail (SOUP_IS_MESSAGE (message));
+
+ g_mutex_lock (&session->priv->property_lock);
+
+ g_clear_pointer (&session->priv->ssl_certificate_pem, g_free);
+ session->priv->ssl_info_set = FALSE;
+
+ g_object_get (G_OBJECT (message),
+ "tls-certificate", &certificate,
+ "tls-errors", &session->priv->ssl_certificate_errors,
+ NULL);
+
+ if (certificate) {
+ g_object_get (certificate, "certificate-pem", &session->priv->ssl_certificate_pem, NULL);
+ session->priv->ssl_info_set = TRUE;
+
+ g_object_unref (certificate);
+ }
+
+ g_mutex_unlock (&session->priv->property_lock);
+}
+
+/**
+ * e_soup_session_check_result:
+ * @session: an #ESoupSession
+ * @request: a #SoupRequestHTTP
+ * @read_bytes: (nullable): optional bytes which had been read from the stream, or %NULL
+ * @bytes_length: how many bytes had been read; ignored when @read_bytes is %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Checks result of the @request and sets the @error if it failed.
+ * When it failed and the @read_bytes is provided, then these are
+ * set to @request's message response_body, thus it can be used
+ * later.
+ *
+ * Returns: Whether succeeded, aka %TRUE, when no error recognized
+ * and %FALSE otherwise.
+ *
+ * Since: 3.26
+ **/
+gboolean
+e_soup_session_check_result (ESoupSession *session,
+ SoupRequestHTTP *request,
+ gconstpointer read_bytes,
+ gsize bytes_length,
+ GError **error)
+{
+ SoupMessage *message;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_SOUP_SESSION (session), FALSE);
+ g_return_val_if_fail (SOUP_IS_REQUEST_HTTP (request), FALSE);
+
+ message = soup_request_http_get_message (request);
+ g_return_val_if_fail (SOUP_IS_MESSAGE (message), FALSE);
+
+ success = SOUP_STATUS_IS_SUCCESSFUL (message->status_code);
+ if (!success) {
+ if (message->status_code == SOUP_STATUS_CANCELLED) {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_CANCELLED, _("Operation was
cancelled"));
+ } else {
+ g_set_error (error, SOUP_HTTP_ERROR, message->status_code,
+ _("Failed with HTTP error %d: %s"), message->status_code,
+ e_soup_session_util_status_to_string (message->status_code,
message->reason_phrase));
+ }
+
+ if (message->status_code == SOUP_STATUS_SSL_FAILED)
+ e_webdav_session_extract_ssl_data (session, message);
+
+ if (read_bytes && bytes_length > 0) {
+ soup_message_body_truncate (message->response_body);
+ soup_message_body_append (message->response_body, SOUP_MEMORY_COPY, read_bytes,
bytes_length);
+ }
+ }
+
+ g_object_unref (message);
+
+ return success;
+}
+
/**
* e_soup_session_send_request_sync:
* @session: an #ESoupSession
@@ -561,6 +651,11 @@ e_soup_session_new_request_uri (ESoupSession *session,
* Note that SoupSession doesn't log content read from GInputStream,
* thus the caller may print the read content on its own when needed.
*
+ * Note the @request is fully filled only after there is anything
+ * read from the resulting #GInputStream, thus use
+ * e_soup_session_check_result() to verify that the receive had
+ * been finished properly.
+ *
* Returns: (transfer full): A newly allocated #GInputStream,
* that can be used to read from the URI pointed to by @request.
* Free it with g_object_unref(), when no longer needed.
@@ -601,26 +696,12 @@ e_soup_session_send_request_sync (ESoupSession *session,
return input_stream;
if (g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_SSL_FAILED)) {
- GTlsCertificate *certificate = NULL;
SoupMessage *message;
message = soup_request_http_get_message (request);
- g_mutex_lock (&session->priv->property_lock);
-
- g_object_get (G_OBJECT (message),
- "tls-certificate", &certificate,
- "tls-errors", &session->priv->ssl_certificate_errors,
- NULL);
-
- if (certificate) {
- g_object_get (certificate, "certificate-pem", &session->priv->ssl_certificate_pem,
NULL);
- session->priv->ssl_info_set = TRUE;
+ e_webdav_session_extract_ssl_data (session, message);
- g_object_unref (certificate);
- }
-
- g_mutex_unlock (&session->priv->property_lock);
g_clear_object (&message);
}
@@ -659,7 +740,7 @@ e_soup_session_send_request_simple_sync (ESoupSession *session,
GInputStream *input_stream;
GByteArray *bytes;
gint expected_length;
- void *buffer;
+ gpointer buffer;
gsize nread = 0;
gboolean success = FALSE;
@@ -676,9 +757,9 @@ e_soup_session_send_request_simple_sync (ESoupSession *session,
else
bytes = g_byte_array_new ();
- buffer = g_malloc (16384);
+ buffer = g_malloc (BUFFER_SIZE);
- while (success = g_input_stream_read_all (input_stream, buffer, 16384, &nread, cancellable, error),
+ while (success = g_input_stream_read_all (input_stream, buffer, BUFFER_SIZE, &nread, cancellable,
error),
success && nread > 0) {
g_byte_array_append (bytes, buffer, nread);
}
@@ -686,13 +767,45 @@ e_soup_session_send_request_simple_sync (ESoupSession *session,
g_free (buffer);
g_object_unref (input_stream);
+ if (bytes->len > 0 && e_soup_session_get_log_level (session) == SOUP_LOGGER_LOG_BODY) {
+ fwrite (bytes->data, 1, bytes->len, stdout);
+ fflush (stdout);
+ }
+
+ if (success)
+ success = e_soup_session_check_result (session, request, bytes->data, bytes->len, error);
+
if (!success) {
g_byte_array_free (bytes, TRUE);
bytes = NULL;
- } else if (e_soup_session_get_log_level (session) == SOUP_LOGGER_LOG_BODY) {
- fwrite (bytes->data, 1, bytes->len, stdout);
- fflush (stdout);
}
return bytes;
}
+
+/**
+ * e_soup_session_util_status_to_string:
+ * @status_code: an HTTP status code
+ * @reason_phrase: (nullable): preferred string to use for the message, or %NULL
+ *
+ * Returns the @reason_phrase, if it's non-%NULL and non-empty, a static string
+ * corresponding to @status_code. In case neither that can be found a localized
+ * "Unknown error" message is returned.
+ *
+ * Returns: (transfer none): Error text base don given arguments. The returned
+ * value is valid as long as @reason_phrase is not freed.
+ *
+ * Since: 3.26
+ **/
+const gchar *
+e_soup_session_util_status_to_string (guint status_code,
+ const gchar *reason_phrase)
+{
+ if (!reason_phrase || !*reason_phrase)
+ reason_phrase = soup_status_get_phrase (status_code);
+
+ if (reason_phrase && *reason_phrase)
+ return reason_phrase;
+
+ return _("Unknown error");
+}
diff --git a/src/libedataserver/e-soup-session.h b/src/libedataserver/e-soup-session.h
index d9e4763..c877829 100644
--- a/src/libedataserver/e-soup-session.h
+++ b/src/libedataserver/e-soup-session.h
@@ -99,6 +99,11 @@ SoupRequestHTTP *
const gchar *method,
SoupURI *uri,
GError **error);
+gboolean e_soup_session_check_result (ESoupSession *session,
+ SoupRequestHTTP *request,
+ gconstpointer read_bytes,
+ gsize bytes_length,
+ GError **error);
GInputStream * e_soup_session_send_request_sync (ESoupSession *session,
SoupRequestHTTP *request,
GCancellable *cancellable,
@@ -107,6 +112,8 @@ GByteArray * e_soup_session_send_request_simple_sync (ESoupSession *session,
SoupRequestHTTP *request,
GCancellable *cancellable,
GError **error);
+const gchar * e_soup_session_util_status_to_string (guint status_code,
+ const gchar *reason_phrase);
G_END_DECLS
diff --git a/src/libedataserver/e-webdav-session.c b/src/libedataserver/e-webdav-session.c
index 221f2b9..303ce36 100644
--- a/src/libedataserver/e-webdav-session.c
+++ b/src/libedataserver/e-webdav-session.c
@@ -27,6 +27,7 @@
#include "evolution-data-server-config.h"
+#include <stdio.h>
#include <glib/gi18n-lib.h>
#include "camel/camel.h"
@@ -36,6 +37,8 @@
#include "e-webdav-session.h"
+#define BUFFER_SIZE 16384
+
struct _EWebDAVSessionPrivate {
gboolean dummy;
};
@@ -43,6 +46,7 @@ struct _EWebDAVSessionPrivate {
G_DEFINE_TYPE (EWebDAVSession, e_webdav_session, E_TYPE_SOUP_SESSION)
G_DEFINE_BOXED_TYPE (EWebDAVResource, e_webdav_resource, e_webdav_resource_copy, e_webdav_resource_free)
+G_DEFINE_BOXED_TYPE (EWebDAVPropertyChange, e_webdav_property_change, e_webdav_property_change_copy,
e_webdav_property_change_free)
/**
* e_webdav_resource_new:
@@ -98,31 +102,31 @@ e_webdav_resource_new (EWebDAVResourceKind kind,
/**
* e_webdav_resource_copy:
- * @resource: (nullable): an #EWebDAVResource to make a copy of
+ * @src: (nullable): an #EWebDAVResource to make a copy of
*
* Returns: (transfer full): A new #EWebDAVResource prefilled with
- * the same values as @resource, or %NULL, when @resource is %NULL.
+ * the same values as @src, or %NULL, when @src is %NULL.
* Free it with e_webdav_resource_free(), when no longer needed.
*
* Since: 3.26
**/
EWebDAVResource *
-e_webdav_resource_copy (const EWebDAVResource *resource)
+e_webdav_resource_copy (const EWebDAVResource *src)
{
- if (!resource)
+ if (!src)
return NULL;
- return e_webdav_resource_new (resource->kind,
- resource->supports,
- resource->href,
- resource->etag,
- resource->display_name,
- resource->content_type,
- resource->content_length,
- resource->creation_date,
- resource->last_modified,
- resource->description,
- resource->color);
+ return e_webdav_resource_new (src->kind,
+ src->supports,
+ src->href,
+ src->etag,
+ src->display_name,
+ src->content_type,
+ src->content_length,
+ src->creation_date,
+ src->last_modified,
+ src->description,
+ src->color);
}
/**
@@ -130,7 +134,7 @@ e_webdav_resource_copy (const EWebDAVResource *resource)
* @ptr: (nullable): an #EWebDAVResource
*
* Frees an #EWebDAVResource previously created with e_webdav_resource_new()
- * or e_webdav_resource_copy(). The function does nothign if @ptr is %NULL.
+ * or e_webdav_resource_copy(). The function does nothing if @ptr is %NULL.
*
* Since: 3.26
**/
@@ -150,6 +154,121 @@ e_webdav_resource_free (gpointer ptr)
}
}
+static EWebDAVPropertyChange *
+e_webdav_property_change_new (EWebDAVPropertyChangeKind kind,
+ const gchar *ns_uri,
+ const gchar *name,
+ const gchar *value)
+{
+ EWebDAVPropertyChange *change;
+
+ change = g_new0 (EWebDAVPropertyChange, 1);
+ change->kind = kind;
+ change->ns_uri = g_strdup (ns_uri);
+ change->name = g_strdup (name);
+ change->value = g_strdup (value);
+
+ return change;
+}
+
+/**
+ * e_webdav_property_change_new_set:
+ * @ns_uri: namespace URI of the property
+ * @name: name of the property
+ * @value: (nullable): value of the property, or %NULL for empty value
+ *
+ * Creates a new #EWebDAVPropertyChange of kind %E_WEBDAV_PROPERTY_SET,
+ * which is used to modify or set the property value. The @value is a string
+ * representation of the value to store. It can be %NULL, but it means
+ * an empty value, not to remove it. To remove property use
+ * e_webdav_property_change_new_remove() instead.
+ *
+ * Returns: (transfer full): A new #EWebDAVPropertyChange. Free it with
+ * e_webdav_property_change_free(), when no longer needed.
+ *
+ * Since: 3.26
+ **/
+EWebDAVPropertyChange *
+e_webdav_property_change_new_set (const gchar *ns_uri,
+ const gchar *name,
+ const gchar *value)
+{
+ g_return_val_if_fail (ns_uri != NULL, NULL);
+ g_return_val_if_fail (name != NULL, NULL);
+
+ return e_webdav_property_change_new (E_WEBDAV_PROPERTY_SET, ns_uri, name, value);
+}
+
+/**
+ * e_webdav_property_change_new_remove:
+ * @ns_uri: namespace URI of the property
+ * @name: name of the property
+ *
+ * Creates a new #EWebDAVPropertyChange of kind %E_WEBDAV_PROPERTY_REMOVE,
+ * which is used to remove the given property. To change property value
+ * use e_webdav_property_change_new_set() instead.
+ *
+ * Returns: (transfer full): A new #EWebDAVPropertyChange. Free it with
+ * e_webdav_property_change_free(), when no longer needed.
+ *
+ * Since: 3.26
+ **/
+EWebDAVPropertyChange *
+e_webdav_property_change_new_remove (const gchar *ns_uri,
+ const gchar *name)
+{
+ g_return_val_if_fail (ns_uri != NULL, NULL);
+ g_return_val_if_fail (name != NULL, NULL);
+
+ return e_webdav_property_change_new (E_WEBDAV_PROPERTY_REMOVE, ns_uri, name, NULL);
+}
+
+/**
+ * e_webdav_property_change_copy:
+ * @src: (nullable): an #EWebDAVPropertyChange to make a copy of
+ *
+ * Returns: (transfer full): A new #EWebDAVPropertyChange prefilled with
+ * the same values as @src, or %NULL, when @src is %NULL.
+ * Free it with e_webdav_property_change_free(), when no longer needed.
+ *
+ * Since: 3.26
+ **/
+EWebDAVPropertyChange *
+e_webdav_property_change_copy (const EWebDAVPropertyChange *src)
+{
+ if (!src)
+ return NULL;
+
+ return e_webdav_property_change_new (
+ src->kind,
+ src->ns_uri,
+ src->name,
+ src->value);
+}
+
+/**
+ * e_webdav_property_change_free:
+ * @ptr: (nullable): an #EWebDAVPropertyChange
+ *
+ * Frees an #EWebDAVPropertyChange previously created with e_webdav_property_change_new_set(),
+ * e_webdav_property_change_new_remove() or or e_webdav_property_change_copy().
+ * The function does nothing if @ptr is %NULL.
+ *
+ * Since: 3.26
+ **/
+void
+e_webdav_property_change_free (gpointer ptr)
+{
+ EWebDAVPropertyChange *change = ptr;
+
+ if (change) {
+ g_free (change->ns_uri);
+ g_free (change->name);
+ g_free (change->value);
+ g_free (change);
+ }
+}
+
static void
e_webdav_session_class_init (EWebDAVSessionClass *klass)
{
@@ -333,9 +452,9 @@ e_webdav_session_options_sync (EWebDAVSession *webdav,
* e_webdav_session_propfind_sync:
* @webdav: an #EWebDAVSession
* @uri: (nullable): URI to issue the request for, or %NULL to read from #ESource
- * @depth: requested depth, can be one of %E_WEBDAV_DEPTH_0, %E_WEBDAV_DEPTH_1 or %E_WEBDAV_DEPTH_INFINITY
+ * @depth: requested depth, can be one of %E_WEBDAV_DEPTH_THIS, %E_WEBDAV_DEPTH_THIS_AND_CHILDREN or
%E_WEBDAV_DEPTH_INFINITY
* @xml: (nullable): the request itself, as an #EXmlDocument, the root element should be DAV:propfind, or
%NULL
- * @func: an #EWebDAVPropfindFunc function to call for each DAV:propstat in the multistatus response
+ * @func: an #EWebDAVMultistatusTraverseFunc function to call for each DAV:propstat in the multistatus
response
* @func_user_data: user data passed to @func
* @cancellable: optional #GCancellable object, or %NULL
* @error: return location for a #GError, or %NULL
@@ -361,7 +480,7 @@ e_webdav_session_propfind_sync (EWebDAVSession *webdav,
const gchar *uri,
const gchar *depth,
const EXmlDocument *xml,
- EWebDAVPropfindFunc func,
+ EWebDAVMultistatusTraverseFunc func,
gpointer func_user_data,
GCancellable *cancellable,
GError **error)
@@ -389,7 +508,7 @@ e_webdav_session_propfind_sync (EWebDAVSession *webdav,
return FALSE;
}
- soup_message_headers_append (message->request_headers, "Depth", depth);
+ soup_message_headers_replace (message->request_headers, "Depth", depth);
if (xml) {
gchar *content;
@@ -415,107 +534,128 @@ e_webdav_session_propfind_sync (EWebDAVSession *webdav,
success = bytes != NULL;
- if (success && message->status_code != SOUP_STATUS_MULTI_STATUS) {
- success = FALSE;
+ if (success)
+ success = e_webdav_session_traverse_multistatus_response (webdav, message, bytes, func,
func_user_data, error);
- g_set_error (error, SOUP_HTTP_ERROR, message->status_code,
- _("Expected multistatus response, but %d returned (%s)"), message->status_code,
- message->reason_phrase && *message->reason_phrase ? message->reason_phrase :
- (soup_status_get_phrase (message->status_code) ? soup_status_get_phrase
(message->status_code) : _("Unknown error")));
- }
+ if (bytes)
+ g_byte_array_free (bytes, TRUE);
+ g_object_unref (message);
- if (success) {
- const gchar *content_type;
+ return success;
+}
- content_type = soup_message_headers_get_content_type (message->response_headers, NULL);
- success = content_type &&
- (g_ascii_strcasecmp (content_type, "application/xml") == 0 ||
- g_ascii_strcasecmp (content_type, "text/xml") == 0);
+static gboolean
+e_webdav_session_extract_multistatus_error_cb (EWebDAVSession *webdav,
+ xmlXPathContextPtr xpath_ctx,
+ const gchar *xpath_prop_prefix,
+ const SoupURI *request_uri,
+ guint status_code,
+ gpointer user_data)
+{
+ GError **error = user_data;
- if (!success) {
- if (!content_type) {
- g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
- _("Expected application/xml response, but none returned"));
- } else {
- g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
- _("Expected application/xml response, but %s returned"),
content_type);
- }
- }
- }
+ g_return_val_if_fail (error != NULL, FALSE);
- if (success) {
- xmlDocPtr doc = e_xml_parse_data ((const gchar *) bytes->data, bytes->len);
+ if (!xpath_prop_prefix)
+ return TRUE;
- g_byte_array_free (bytes, TRUE);
- bytes = NULL;
+ if (status_code != SOUP_STATUS_OK && (
+ status_code != SOUP_STATUS_FAILED_DEPENDENCY ||
+ !*error)) {
+ gchar *description;
- if (doc) {
- xmlXPathContextPtr xpath_ctx;
- SoupURI *request_uri;
+ description = e_xml_xpath_eval_as_string (xpath_ctx, "%s/../D:responsedescription",
xpath_prop_prefix);
+ if (!description || !*description) {
+ g_free (description);
- request_uri = soup_message_get_uri (message);
+ description = e_xml_xpath_eval_as_string (xpath_ctx,
"%s/../../D:responsedescription", xpath_prop_prefix);
+ }
- xpath_ctx = e_xml_new_xpath_context_with_namespaces (doc,
- "D", E_WEBDAV_NS_DAV,
- NULL);
+ g_clear_error (error);
+ g_set_error (error, SOUP_HTTP_ERROR, status_code, _("Failed to update properties: %s"),
+ e_soup_session_util_status_to_string (status_code, description));
- if (xpath_ctx &&
- func (webdav, xpath_ctx, NULL, request_uri, SOUP_STATUS_NONE, func_user_data)) {
- xmlXPathObjectPtr xpath_obj_response;
+ g_free (description);
+ }
- xpath_obj_response = e_xml_xpath_eval (xpath_ctx,
"/D:multistatus/D:response");
+ return TRUE;
+}
- if (xpath_obj_response != NULL) {
- gboolean do_stop = FALSE;
- gint response_index, response_length;
+/**
+ * e_webdav_session_proppatch_sync:
+ * @webdav: an #EWebDAVSession
+ * @uri: (nullable): URI to issue the request for, or %NULL to read from #ESource
+ * @xml: an #EXmlDocument with request changes, its root element should be DAV:propertyupdate
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Issues PROPPATCH request on the provided @uri, or, in case it's %NULL, on the URI
+ * defined in associated #ESource, with the @changes. The order of requested changes
+ * inside @xml is significant, unlike on other places.
+ *
+ * Returns: Whether succeeded.
+ *
+ * Since: 3.26
+ **/
+gboolean
+e_webdav_session_proppatch_sync (EWebDAVSession *webdav,
+ const gchar *uri,
+ const EXmlDocument *xml,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupRequestHTTP *request;
+ SoupMessage *message;
+ GByteArray *bytes;
+ gchar *content;
+ gsize content_length;
+ gboolean success;
- response_length = xmlXPathNodeSetGetLength
(xpath_obj_response->nodesetval);
+ g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
+ g_return_val_if_fail (E_IS_XML_DOCUMENT (xml), FALSE);
- for (response_index = 0; response_index < response_length &&
!do_stop; response_index++) {
- xmlXPathObjectPtr xpath_obj_propstat;
+ request = e_webdav_session_new_request (webdav, SOUP_METHOD_PROPPATCH, uri, error);
+ if (!request)
+ return FALSE;
- xpath_obj_propstat = e_xml_xpath_eval (xpath_ctx,
- "/D:multistatus/D:response[%d]/D:propstat",
- response_index + 1);
+ message = soup_request_http_get_message (request);
+ if (!message) {
+ g_warn_if_fail (message != NULL);
+ g_object_unref (request);
- if (xpath_obj_propstat != NULL) {
- gint propstat_index, propstat_length;
+ return FALSE;
+ }
- propstat_length = xmlXPathNodeSetGetLength
(xpath_obj_propstat->nodesetval);
+ content = e_xml_document_get_content (xml, &content_length);
+ if (!content) {
+ g_object_unref (message);
+ g_object_unref (request);
- for (propstat_index = 0; propstat_index <
propstat_length && !do_stop; propstat_index++) {
- gchar *status, *propstat_prefix;
- guint status_code;
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Failed to get input
XML content"));
- propstat_prefix = g_strdup_printf
("/D:multistatus/D:response[%d]/D:propstat[%d]/D:prop",
- response_index + 1, propstat_index +
1);
+ return FALSE;
+ }
- status = e_xml_xpath_eval_as_string
(xpath_ctx, "%s/../D:status", propstat_prefix);
- if (!status ||
!soup_headers_parse_status_line (status, NULL, &status_code, NULL))
- status_code = 0;
- g_free (status);
+ soup_message_set_request (message, E_WEBDAV_CONTENT_TYPE_XML,
+ SOUP_MEMORY_TAKE, content, content_length);
- do_stop = !func (webdav, xpath_ctx,
propstat_prefix, request_uri, status_code, func_user_data);
+ bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable,
error);
- g_free (propstat_prefix);
- }
+ g_object_unref (request);
- xmlXPathFreeObject (xpath_obj_propstat);
- }
- }
+ success = bytes != NULL;
- xmlXPathFreeObject (xpath_obj_response);
- }
- }
+ if (success) {
+ GError *local_error = NULL;
- if (xpath_ctx)
- xmlXPathFreeContext (xpath_ctx);
- xmlFreeDoc (doc);
- } else {
- g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
- _("Failed to parse response as XML"));
+ success = e_webdav_session_traverse_multistatus_response (webdav, message, bytes,
+ e_webdav_session_extract_multistatus_error_cb, &local_error, error);
+ if (success && local_error) {
+ g_propagate_error (error, local_error);
success = FALSE;
+ } else if (local_error) {
+ g_clear_error (&local_error);
}
}
@@ -526,6 +666,47 @@ e_webdav_session_propfind_sync (EWebDAVSession *webdav,
return success;
}
+/**
+ * e_webdav_session_mkcol_sync:
+ * @webdav: an #EWebDAVSession
+ * @uri: URI of the collection to create
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Creates a new collection resource identified by @uri on the server.
+ *
+ * Returns: Whether succeeded.
+ *
+ * Since: 3.26
+ **/
+gboolean
+e_webdav_session_mkcol_sync (EWebDAVSession *webdav,
+ const gchar *uri,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupRequestHTTP *request;
+ GByteArray *bytes;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
+ g_return_val_if_fail (uri != NULL, FALSE);
+
+ request = e_webdav_session_new_request (webdav, SOUP_METHOD_MKCOL, uri, error);
+ if (!request)
+ return FALSE;
+
+ bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable,
error);
+
+ success = bytes != NULL;
+
+ if (bytes)
+ g_byte_array_free (bytes, TRUE);
+ g_object_unref (request);
+
+ return success;
+}
+
/* This assumes ownership of 'text' */
static gchar *
e_webdav_session_maybe_dequote (gchar *text)
@@ -547,6 +728,743 @@ e_webdav_session_maybe_dequote (gchar *text)
return dequoted;
}
+static void
+e_webdav_session_extract_href_and_etag (SoupMessage *message,
+ gchar **out_href,
+ gchar **out_etag)
+{
+ g_return_if_fail (SOUP_IS_MESSAGE (message));
+
+ if (out_href) {
+ const gchar *header;
+
+ *out_href = NULL;
+
+ header = soup_message_headers_get_list (message->response_headers, "Location");
+ if (header) {
+ gchar *file = strrchr (header, '/');
+
+ if (file) {
+ gchar *decoded;
+
+ decoded = soup_uri_decode (file + 1);
+ *out_href = soup_uri_encode (decoded ? decoded : (file + 1), NULL);
+
+ g_free (decoded);
+ }
+ }
+
+ if (!*out_href)
+ *out_href = soup_uri_to_string (soup_message_get_uri (message), FALSE);
+ }
+
+ if (out_etag) {
+ const gchar *header;
+
+ *out_etag = NULL;
+
+ header = soup_message_headers_get_list (message->response_headers, "ETag");
+ if (header)
+ *out_etag = e_webdav_session_maybe_dequote (g_strdup (header));
+ }
+}
+
+/**
+ * e_webdav_session_get_sync:
+ * @webdav: an #EWebDAVSession
+ * @uri: URI of the resource to read
+ * @out_href: (out) (nullable) (transfer full): optional return location for href of the resource, or %NULL
+ * @out_etag: (out) (nullable) (transfer full): optional return location for etag of the resource, or %NULL
+ * @out_stream: (out) (caller-allocates): a #GOutputStream to write data to
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Reads a resource identified by @uri from the server and writes it
+ * to the @stream. The URI cannot reference a collection.
+ *
+ * Free returned pointer of @out_href and @out_etag, if not %NULL, with g_free(),
+ * when no longer needed.
+ *
+ * The e_webdav_session_get_data_sync() can be used to read the resource data
+ * directly to memory.
+ *
+ * Returns: Whether succeeded.
+ *
+ * Since: 3.26
+ **/
+gboolean
+e_webdav_session_get_sync (EWebDAVSession *webdav,
+ const gchar *uri,
+ gchar **out_href,
+ gchar **out_etag,
+ GOutputStream *out_stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupRequestHTTP *request;
+ SoupMessage *message;
+ GInputStream *input_stream;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
+ g_return_val_if_fail (uri != NULL, FALSE);
+ g_return_val_if_fail (G_IS_OUTPUT_STREAM (out_stream), FALSE);
+
+ request = e_webdav_session_new_request (webdav, SOUP_METHOD_GET, uri, error);
+ if (!request)
+ return FALSE;
+
+ message = soup_request_http_get_message (request);
+ if (!message) {
+ g_warn_if_fail (message != NULL);
+ g_object_unref (request);
+
+ return FALSE;
+ }
+
+ input_stream = e_soup_session_send_request_sync (E_SOUP_SESSION (webdav), request, cancellable,
error);
+
+ success = input_stream != NULL;
+
+ if (success) {
+ SoupLoggerLogLevel log_level = e_soup_session_get_log_level (E_SOUP_SESSION (webdav));
+ gpointer buffer;
+ gsize nread = 0, nwritten;
+
+ buffer = g_malloc (BUFFER_SIZE);
+
+ while (success = g_input_stream_read_all (input_stream, buffer, BUFFER_SIZE, &nread,
cancellable, error),
+ success && nread > 0) {
+ if (log_level == SOUP_LOGGER_LOG_BODY) {
+ fwrite (buffer, 1, nread, stdout);
+ fflush (stdout);
+ }
+
+ success = g_output_stream_write_all (out_stream, buffer, nread, &nwritten,
cancellable, error);
+ if (!success)
+ break;
+ }
+
+ g_free (buffer);
+ }
+
+ if (success)
+ e_webdav_session_extract_href_and_etag (message, out_href, out_etag);
+
+ g_clear_object (&input_stream);
+ g_object_unref (message);
+ g_object_unref (request);
+
+ return success;
+}
+
+/**
+ * e_webdav_session_get_data_sync:
+ * @webdav: an #EWebDAVSession
+ * @uri: URI of the resource to read
+ * @out_href: (out) (nullable) (transfer full): optional return location for href of the resource, or %NULL
+ * @out_etag: (out) (nullable) (transfer full): optional return location for etag of the resource, or %NULL
+ * @out_bytes: (out) (transfer full): return location for bytes being read
+ * @out_length: (out) (nullable): option return location for length of bytes being read, or %NULL
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Reads a resource identified by @uri from the server. The URI cannot
+ * reference a collection.
+ *
+ * The @out_bytes is filled by actual data being read. If not %NULL, @out_length
+ * is populated with how many bytes had been read. Free the @out_bytes with g_free(),
+ * when no longer needed.
+ *
+ * Free returned pointer of @out_href and @out_etag, if not %NULL, with g_free(),
+ * when no longer needed.
+ *
+ * To read large data use e_webdav_session_get_sync() instead.
+ *
+ * Returns: Whether succeeded.
+ *
+ * Since: 3.26
+ **/
+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)
+{
+ GOutputStream *output_stream;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
+ g_return_val_if_fail (uri != NULL, FALSE);
+ g_return_val_if_fail (out_bytes != NULL, FALSE);
+
+ *out_bytes = NULL;
+ if (out_length)
+ *out_length = 0;
+
+ output_stream = g_memory_output_stream_new_resizable ();
+
+ success = e_webdav_session_get_sync (webdav, uri, out_href, out_etag, output_stream, cancellable,
error) &&
+ g_output_stream_close (output_stream, cancellable, error);
+
+ if (success) {
+ if (out_length)
+ *out_length = g_memory_output_stream_get_data_size (G_MEMORY_OUTPUT_STREAM
(output_stream));
+ *out_bytes = g_memory_output_stream_steal_data (G_MEMORY_OUTPUT_STREAM (output_stream));
+ }
+
+ g_object_unref (output_stream);
+
+ return success;
+}
+
+typedef struct _ChunkWriteData {
+ SoupSession *session;
+ SoupLoggerLogLevel log_level;
+ GInputStream *stream;
+ goffset read_from;
+ gboolean wrote_any;
+ gsize buffer_size;
+ gpointer buffer;
+ GCancellable *cancellable;
+ GError *error;
+} ChunkWriteData;
+
+static void
+e_webdav_session_write_next_chunk (SoupMessage *message,
+ gpointer user_data)
+{
+ ChunkWriteData *cwd = user_data;
+ gsize nread;
+
+ g_return_if_fail (SOUP_IS_MESSAGE (message));
+ g_return_if_fail (cwd != NULL);
+
+ if (!g_input_stream_read_all (cwd->stream, cwd->buffer, cwd->buffer_size, &nread, cwd->cancellable,
&cwd->error)) {
+ soup_session_cancel_message (cwd->session, message, SOUP_STATUS_CANCELLED);
+ return;
+ }
+
+ if (nread == 0) {
+ soup_message_body_complete (message->request_body);
+ } else {
+ cwd->wrote_any = TRUE;
+ soup_message_body_append (message->request_body, SOUP_MEMORY_TEMPORARY, cwd->buffer, nread);
+
+ if (cwd->log_level == SOUP_LOGGER_LOG_BODY) {
+ fwrite (cwd->buffer, 1, nread, stdout);
+ fflush (stdout);
+ }
+ }
+}
+
+static void
+e_webdav_session_write_restarted (SoupMessage *message,
+ gpointer user_data)
+{
+ ChunkWriteData *cwd = user_data;
+
+ g_return_if_fail (SOUP_IS_MESSAGE (message));
+ g_return_if_fail (cwd != NULL);
+
+ /* The 302 redirect will turn it into a GET request and
+ * reset the body encoding back to "NONE". Fix that.
+ */
+ soup_message_headers_set_encoding (message->request_headers, SOUP_ENCODING_CHUNKED);
+ message->method = SOUP_METHOD_PUT;
+
+ if (cwd->wrote_any) {
+ cwd->wrote_any = FALSE;
+
+ if (!G_IS_SEEKABLE (cwd->stream) || !g_seekable_can_seek (G_SEEKABLE (cwd->stream)) ||
+ !g_seekable_seek (G_SEEKABLE (cwd->stream), cwd->read_from, G_SEEK_SET, cwd->cancellable,
&cwd->error)) {
+ if (!cwd->error)
+ g_set_error_literal (&cwd->error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT,
+ _("Cannot rewind input stream: Not supported"));
+
+ soup_session_cancel_message (cwd->session, message, SOUP_STATUS_CANCELLED);
+ }
+ }
+}
+
+/**
+ * e_webdav_session_put_sync:
+ * @webdav: an #EWebDAVSession
+ * @uri: URI of the resource to write
+ * @etag: (nullable): an ETag of the resource, if it's an existing resource, or %NULL
+ * @content_type: Content-Type of the @bytes to be written
+ * @stream: a #GInputStream with data to be written
+ * @out_href: (out) (nullable) (transfer full): optional return location for href of the resource, or %NULL
+ * @out_etag: (out) (nullable) (transfer full): optional return location for etag of the resource, or %NULL
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Writes data from @stream to a resource identified by @uri to the server.
+ * The URI cannot reference a collection.
+ *
+ * The @etag argument is used to avoid clashes when overwriting existing
+ * resources. It can contain three values:
+ * - %NULL - to write completely new resource
+ * - empty string - write new resource or overwrite any existing, regardless changes on the server
+ * - valid ETag - overwrite existing resource only if it wasn't changed on the server.
+ *
+ * Note that the actual behaviour is also influenced by #ESourceWebdav:avoid-ifmatch
+ * property of the associated #ESource.
+ *
+ * The @out_href, if provided, is filled with the resulting URI
+ * of the written resource. It can be different from the @uri when the server
+ * redirected to a different location.
+ *
+ * The @out_etag contains ETag of the resource after it had been saved.
+ *
+ * The @stream should support also #GSeekable interface, because the data
+ * send can require restart of the send due to redirect or other reasons.
+ *
+ * The e_webdav_session_put_data_sync() can be used to write data stored in memory.
+ *
+ * Returns: Whether succeeded.
+ *
+ * Since: 3.26
+ **/
+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)
+{
+ ChunkWriteData cwd;
+ SoupRequestHTTP *request;
+ SoupMessage *message;
+ GByteArray *bytes;
+ gulong restarted_id, wrote_headers_id, wrote_chunk_id;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
+ g_return_val_if_fail (uri != NULL, FALSE);
+ g_return_val_if_fail (content_type != NULL, FALSE);
+ g_return_val_if_fail (G_IS_INPUT_STREAM (stream), FALSE);
+
+ if (out_href)
+ *out_href = NULL;
+ if (out_etag)
+ *out_etag = NULL;
+
+ request = e_webdav_session_new_request (webdav, SOUP_METHOD_PUT, uri, error);
+ if (!request)
+ return FALSE;
+
+ message = soup_request_http_get_message (request);
+ if (!message) {
+ g_warn_if_fail (message != NULL);
+ g_object_unref (request);
+
+ return FALSE;
+ }
+
+ if (!etag || *etag) {
+ ESource *source;
+ gboolean avoid_ifmatch = FALSE;
+
+ source = e_soup_session_get_source (E_SOUP_SESSION (webdav));
+ if (source && e_source_has_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND)) {
+ ESourceWebdav *webdav_extension;
+
+ webdav_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
+ avoid_ifmatch = e_source_webdav_get_avoid_ifmatch (webdav_extension);
+ }
+
+ if (!avoid_ifmatch) {
+ if (etag) {
+ gint len = strlen (etag);
+
+ if (*etag == '\"' && len > 2 && etag[len - 1] == '\"') {
+ soup_message_headers_replace (message->request_headers, "If-Match",
etag);
+ } else {
+ gchar *quoted;
+
+ quoted = g_strconcat ("\"", etag, "\"", NULL);
+ soup_message_headers_replace (message->request_headers, "If-Match",
quoted);
+ g_free (quoted);
+ }
+ } else {
+ soup_message_headers_replace (message->request_headers, "If-None-Match", "*");
+ }
+ }
+ }
+
+ cwd.session = SOUP_SESSION (webdav);
+ cwd.log_level = e_soup_session_get_log_level (E_SOUP_SESSION (webdav));
+ cwd.stream = stream;
+ cwd.read_from = 0;
+ cwd.wrote_any = FALSE;
+ cwd.buffer_size = BUFFER_SIZE;
+ cwd.buffer = g_malloc (cwd.buffer_size);
+ cwd.cancellable = cancellable;
+ cwd.error = NULL;
+
+ if (G_IS_SEEKABLE (stream) && g_seekable_can_seek (G_SEEKABLE (stream)))
+ cwd.read_from = g_seekable_tell (G_SEEKABLE (stream));
+
+ if (content_type && *content_type)
+ soup_message_headers_replace (message->request_headers, "Content-Type", content_type);
+
+ soup_message_headers_set_encoding (message->request_headers, SOUP_ENCODING_CHUNKED);
+ soup_message_body_set_accumulate (message->request_body, FALSE);
+ soup_message_set_flags (message, SOUP_MESSAGE_CAN_REBUILD);
+
+ restarted_id = g_signal_connect (message, "restarted", G_CALLBACK (e_webdav_session_write_restarted),
&cwd);
+ wrote_headers_id = g_signal_connect (message, "wrote-headers", G_CALLBACK
(e_webdav_session_write_next_chunk), &cwd);
+ wrote_chunk_id = g_signal_connect (message, "wrote-chunk", G_CALLBACK
(e_webdav_session_write_next_chunk), &cwd);
+
+ bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable,
error);
+
+ g_signal_handler_disconnect (message, restarted_id);
+ g_signal_handler_disconnect (message, wrote_headers_id);
+ g_signal_handler_disconnect (message, wrote_chunk_id);
+
+ success = bytes != NULL;
+
+ if (cwd.error) {
+ g_clear_error (error);
+ g_propagate_error (error, cwd.error);
+ success = FALSE;
+ }
+
+ if (success) {
+ if (success && !SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
+ success = FALSE;
+
+ g_set_error (error, SOUP_HTTP_ERROR, message->status_code,
+ _("Failed to put data to server, error code %d (%s)"), message->status_code,
+ e_soup_session_util_status_to_string (message->status_code,
message->reason_phrase));
+ }
+ }
+
+ if (success)
+ e_webdav_session_extract_href_and_etag (message, out_href, out_etag);
+
+ if (bytes)
+ g_byte_array_free (bytes, TRUE);
+ g_object_unref (message);
+ g_object_unref (request);
+ g_free (cwd.buffer);
+
+ return success;
+}
+
+/**
+ * e_webdav_session_put_data_sync:
+ * @webdav: an #EWebDAVSession
+ * @uri: URI of the resource to write
+ * @etag: (nullable): an ETag of the resource, if it's an existing resource, or %NULL
+ * @content_type: Content-Type of the @bytes to be written
+ * @bytes: actual bytes to be written
+ * @length: how many bytes to write, or -1, when the @bytes is NUL-terminated
+ * @out_href: (out) (nullable) (transfer full): optional return location for href of the resource, or %NULL
+ * @out_etag: (out) (nullable) (transfer full): optional return location for etag of the resource, or %NULL
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Writes data to a resource identified by @uri to the server. The URI cannot
+ * reference a collection.
+ *
+ * The @etag argument is used to avoid clashes when overwriting existing
+ * resources. It can contain three values:
+ * - %NULL - to write completely new resource
+ * - empty string - write new resource or overwrite any existing, regardless changes on the server
+ * - valid ETag - overwrite existing resource only if it wasn't changed on the server.
+ *
+ * Note that the actual usage of @etag is also influenced by #ESourceWebdav:avoid-ifmatch
+ * property of the associated #ESource.
+ *
+ * The @out_href, if provided, is filled with the resulting URI
+ * of the written resource. It can be different from the @uri when the server
+ * redirected to a different location.
+ *
+ * The @out_etag contains ETag of the resource after it had been saved.
+ *
+ * To read large data use e_webdav_session_put_sync() instead.
+ *
+ * Returns: Whether succeeded.
+ *
+ * Since: 3.26
+ **/
+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)
+{
+ GInputStream *input_stream;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
+ g_return_val_if_fail (uri != NULL, FALSE);
+ g_return_val_if_fail (content_type != NULL, FALSE);
+ g_return_val_if_fail (bytes != NULL, FALSE);
+
+ if (length == (gsize) -1)
+ length = strlen (bytes);
+
+ input_stream = g_memory_input_stream_new_from_data (bytes, length, NULL);
+
+ success = e_webdav_session_put_sync (webdav, uri, etag, content_type,
+ input_stream, out_href, out_etag, cancellable, error);
+
+ g_object_unref (input_stream);
+
+ return success;
+}
+
+/**
+ * e_webdav_session_delete_sync:
+ * @webdav: an #EWebDAVSession
+ * @uri: URI of the resource to delete
+ * @depth: requested depth, can be one of %E_WEBDAV_DEPTH_THIS or %E_WEBDAV_DEPTH_INFINITY
+ * @etag: (nullable): an optional ETag of the resource, or %NULL
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Deletes a resource identified by @uri on the server. The URI can
+ * reference a collection, in which case @depth should be %E_WEBDAV_DEPTH_INFINITY.
+ * Use @depth %E_WEBDAV_DEPTH_THIS when deleting a regular resource.
+ *
+ * The @etag argument is used to avoid clashes when overwriting existing resources.
+ * Use %NULL @etag when deleting collection resources or to force the deletion,
+ * otherwise provide a valid ETag of a non-collection resource to verify that
+ * the version requested to delete is the same as on the server.
+ *
+ * Note that the actual usage of @etag is also influenced by #ESourceWebdav:avoid-ifmatch
+ * property of the associated #ESource.
+ *
+ * Returns: Whether succeeded.
+ *
+ * Since: 3.26
+ **/
+gboolean
+e_webdav_session_delete_sync (EWebDAVSession *webdav,
+ const gchar *uri,
+ const gchar *depth,
+ const gchar *etag,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupRequestHTTP *request;
+ SoupMessage *message;
+ GByteArray *bytes;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
+ g_return_val_if_fail (uri != NULL, FALSE);
+ g_return_val_if_fail (depth != NULL, FALSE);
+
+ request = e_webdav_session_new_request (webdav, SOUP_METHOD_DELETE, uri, error);
+ if (!request)
+ return FALSE;
+
+ message = soup_request_http_get_message (request);
+ if (!message) {
+ g_warn_if_fail (message != NULL);
+ g_object_unref (request);
+
+ return FALSE;
+ }
+
+ if (etag) {
+ ESource *source;
+ gboolean avoid_ifmatch = FALSE;
+
+ source = e_soup_session_get_source (E_SOUP_SESSION (webdav));
+ if (source && e_source_has_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND)) {
+ ESourceWebdav *webdav_extension;
+
+ webdav_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
+ avoid_ifmatch = e_source_webdav_get_avoid_ifmatch (webdav_extension);
+ }
+
+ if (!avoid_ifmatch) {
+ gint len = strlen (etag);
+
+ if (*etag == '\"' && len > 2 && etag[len - 1] == '\"') {
+ soup_message_headers_replace (message->request_headers, "If-Match", etag);
+ } else {
+ gchar *quoted;
+
+ quoted = g_strconcat ("\"", etag, "\"", NULL);
+ soup_message_headers_replace (message->request_headers, "If-Match", quoted);
+ g_free (quoted);
+ }
+ }
+ }
+
+ soup_message_headers_replace (message->request_headers, "Depth", depth);
+
+ bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable,
error);
+
+ success = bytes != NULL;
+
+ if (bytes)
+ g_byte_array_free (bytes, TRUE);
+ g_object_unref (message);
+ g_object_unref (request);
+
+ return success;
+}
+
+/**
+ * e_webdav_session_traverse_multistatus_response:
+ * @webdav: an #EWebDAVSession
+ * @message: (nullable): an optional #SoupMessage corresponding to the response, or %NULL
+ * @xml_data: a #GByteArray containing DAV:multistatus response
+ * @func: an #EWebDAVMultistatusTraverseFunc function to call for each DAV:propstat in the multistatus
response
+ * @func_user_data: user data passed to @func
+ * @error: return location for a #GError, or %NULL
+ *
+ * Traverses a DAV:multistatus response and calls @func for each returned DAV:propstat.
+ * The provided XPath context has registered %E_WEBDAV_NS_DAV namespace with prefix "D".
+ * It doesn't have any other namespace registered.
+ *
+ * The @message, if provided, is used to verify that the response is a multi-status
+ * and that the Content-Type is properly set. It's used to get a request URI as well.
+ *
+ * The @func is called always at least once, with %NULL xpath_prop_prefix, which
+ * is meant to let the caller setup the xpath_ctx, like to register its own namespaces
+ * to it with e_xml_xpath_context_register_namespaces(). All other invocations of @func
+ * will have xpath_prop_prefix non-%NULL.
+ *
+ * Returns: Whether succeeded.
+ *
+ * Since: 3.26
+ **/
+gboolean
+e_webdav_session_traverse_multistatus_response (EWebDAVSession *webdav,
+ const SoupMessage *message,
+ const GByteArray *xml_data,
+ EWebDAVMultistatusTraverseFunc func,
+ gpointer func_user_data,
+ GError **error)
+{
+ SoupURI *request_uri = NULL;
+ xmlDocPtr doc;
+ xmlXPathContextPtr xpath_ctx;
+
+ g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
+ g_return_val_if_fail (xml_data != NULL, FALSE);
+ g_return_val_if_fail (func != NULL, FALSE);
+
+ if (message) {
+ const gchar *content_type;
+
+ if (message->status_code != SOUP_STATUS_MULTI_STATUS) {
+ g_set_error (error, SOUP_HTTP_ERROR, message->status_code,
+ _("Expected multistatus response, but %d returned (%s)"),
message->status_code,
+ e_soup_session_util_status_to_string (message->status_code,
message->reason_phrase));
+
+ return FALSE;
+ }
+
+ content_type = soup_message_headers_get_content_type (message->response_headers, NULL);
+ if (!content_type ||
+ (g_ascii_strcasecmp (content_type, "application/xml") != 0 &&
+ g_ascii_strcasecmp (content_type, "text/xml") != 0)) {
+ if (!content_type) {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
+ _("Expected application/xml response, but none returned"));
+ } else {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
+ _("Expected application/xml response, but %s returned"),
content_type);
+ }
+
+ return FALSE;
+ }
+
+ request_uri = soup_message_get_uri ((SoupMessage *) message);
+ }
+
+ doc = e_xml_parse_data ((const gchar *) xml_data->data, xml_data->len);
+
+ if (!doc) {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
+ _("Failed to parse XML data"));
+
+ return FALSE;
+ }
+
+ xpath_ctx = e_xml_new_xpath_context_with_namespaces (doc,
+ "D", E_WEBDAV_NS_DAV,
+ NULL);
+
+ if (xpath_ctx &&
+ func (webdav, xpath_ctx, NULL, request_uri, SOUP_STATUS_NONE, func_user_data)) {
+ xmlXPathObjectPtr xpath_obj_response;
+
+ xpath_obj_response = e_xml_xpath_eval (xpath_ctx, "/D:multistatus/D:response");
+
+ if (xpath_obj_response != NULL) {
+ gboolean do_stop = FALSE;
+ gint response_index, response_length;
+
+ response_length = xmlXPathNodeSetGetLength (xpath_obj_response->nodesetval);
+
+ for (response_index = 0; response_index < response_length && !do_stop;
response_index++) {
+ xmlXPathObjectPtr xpath_obj_propstat;
+
+ xpath_obj_propstat = e_xml_xpath_eval (xpath_ctx,
+ "/D:multistatus/D:response[%d]/D:propstat",
+ response_index + 1);
+
+ if (xpath_obj_propstat != NULL) {
+ gint propstat_index, propstat_length;
+
+ propstat_length = xmlXPathNodeSetGetLength
(xpath_obj_propstat->nodesetval);
+
+ for (propstat_index = 0; propstat_index < propstat_length &&
!do_stop; propstat_index++) {
+ gchar *status, *propstat_prefix;
+ guint status_code;
+
+ propstat_prefix = g_strdup_printf
("/D:multistatus/D:response[%d]/D:propstat[%d]/D:prop",
+ response_index + 1, propstat_index + 1);
+
+ status = e_xml_xpath_eval_as_string (xpath_ctx,
"%s/../D:status", propstat_prefix);
+ if (!status || !soup_headers_parse_status_line (status, NULL,
&status_code, NULL))
+ status_code = 0;
+ g_free (status);
+
+ do_stop = !func (webdav, xpath_ctx, propstat_prefix,
request_uri, status_code, func_user_data);
+
+ g_free (propstat_prefix);
+ }
+
+ xmlXPathFreeObject (xpath_obj_propstat);
+ }
+ }
+
+ xmlXPathFreeObject (xpath_obj_response);
+ }
+ }
+
+ if (xpath_ctx)
+ xmlXPathFreeContext (xpath_ctx);
+ xmlFreeDoc (doc);
+
+ return TRUE;
+}
+
static gboolean
e_webdav_session_getctag_cb (EWebDAVSession *webdav,
xmlXPathContextPtr xpath_ctx,
@@ -625,7 +1543,7 @@ e_webdav_session_getctag_sync (EWebDAVSession *webdav,
e_xml_document_end_element (xml); /* getctag */
e_xml_document_end_element (xml); /* prop */
- success = e_webdav_session_propfind_sync (webdav, uri, E_WEBDAV_DEPTH_0, xml,
+ success = e_webdav_session_propfind_sync (webdav, uri, E_WEBDAV_DEPTH_THIS, xml,
e_webdav_session_getctag_cb, out_ctag, cancellable, error);
g_object_unref (xml);
@@ -691,6 +1609,8 @@ e_webdav_session_extract_supports (xmlXPathContextPtr xpath_ctx,
supports |= E_WEBDAV_RESOURCE_SUPPORTS_MEMOS;
else if (g_ascii_strcasecmp (name, "VTODO") == 0)
supports |= E_WEBDAV_RESOURCE_SUPPORTS_TASKS;
+ else if (g_ascii_strcasecmp (name, "VFREEBUSY") == 0)
+ supports |= E_WEBDAV_RESOURCE_SUPPORTS_FREEBUSY;
g_free (name);
}
@@ -702,7 +1622,8 @@ e_webdav_session_extract_supports (xmlXPathContextPtr xpath_ctx,
supports = supports |
E_WEBDAV_RESOURCE_SUPPORTS_EVENTS |
E_WEBDAV_RESOURCE_SUPPORTS_MEMOS |
- E_WEBDAV_RESOURCE_SUPPORTS_TASKS;
+ E_WEBDAV_RESOURCE_SUPPORTS_TASKS |
+ E_WEBDAV_RESOURCE_SUPPORTS_FREEBUSY;
}
}
@@ -742,12 +1663,12 @@ e_webdav_session_extract_content_length (xmlXPathContextPtr xpath_ctx,
gchar *value;
gsize length;
- g_return_val_if_fail (xpath_ctx != NULL, -1);
- g_return_val_if_fail (xpath_prop_prefix != NULL, -1);
+ g_return_val_if_fail (xpath_ctx != NULL, 0);
+ g_return_val_if_fail (xpath_prop_prefix != NULL, 0);
value = e_webdav_session_extract_nonempty (xpath_ctx, xpath_prop_prefix, "D:getcontentlength", NULL);
if (!value)
- return -1;
+ return 0;
length = g_ascii_strtoll (value, NULL, 10);
@@ -883,6 +1804,7 @@ e_webdav_session_list_cb (EWebDAVSession *webdav,
* e_webdav_session_list_sync:
* @webdav: an #EWebDAVSession
* @uri: (nullable): URI to issue the request for, or %NULL to read from #ESource
+ * @depth: requested depth, can be one of %E_WEBDAV_DEPTH_THIS, %E_WEBDAV_DEPTH_THIS_AND_CHILDREN or
%E_WEBDAV_DEPTH_INFINITY
* @flags: a bit-or of #EWebDAVListFlags, claiming what properties to read
* @out_resources: (out) (transfer full) (element-type EWebDAVResource): return location for the resources
* @cancellable: optional #GCancellable object, or %NULL
@@ -905,6 +1827,7 @@ e_webdav_session_list_cb (EWebDAVSession *webdav,
gboolean
e_webdav_session_list_sync (EWebDAVSession *webdav,
const gchar *uri,
+ const gchar *depth,
guint32 flags,
GSList **out_resources,
GCancellable *cancellable,
@@ -991,7 +1914,7 @@ e_webdav_session_list_sync (EWebDAVSession *webdav,
e_xml_document_end_element (xml); /* prop */
- success = e_webdav_session_propfind_sync (webdav, uri, E_WEBDAV_DEPTH_1, xml,
+ success = e_webdav_session_propfind_sync (webdav, uri, depth, xml,
e_webdav_session_list_cb, out_resources, cancellable, error);
g_object_unref (xml);
@@ -1032,3 +1955,76 @@ e_webdav_session_list_sync (EWebDAVSession *webdav,
return success;
}
+
+/**
+ * e_webdav_session_update_properties_sync:
+ * @webdav: an #EWebDAVSession
+ * @uri: (nullable): URI to issue the request for, or %NULL to read from #ESource
+ * @changes: (element-type EWebDAVResource): a #GSList with request changes
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Updates proeprties (set/remove) on the provided @uri, or, in case it's %NULL,
+ * on the URI defined in associated #ESource, with the @changes. The order
+ * of @changes is significant, unlike on other places.
+ *
+ * This function supports only flat properties, those not under other element.
+ * To support more complex property tries use e_webdav_session_proppatch_sync()
+ * directly.
+ *
+ * Returns: Whether succeeded.
+ *
+ * Since: 3.26
+ **/
+gboolean
+e_webdav_session_update_properties_sync (EWebDAVSession *webdav,
+ const gchar *uri,
+ const GSList *changes,
+ GCancellable *cancellable,
+ GError **error)
+{
+ EXmlDocument *xml;
+ GSList *link;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
+ g_return_val_if_fail (changes != NULL, FALSE);
+
+ xml = e_xml_document_new (E_WEBDAV_NS_DAV, "propertyupdate");
+ g_return_val_if_fail (xml != NULL, FALSE);
+
+ for (link = (GSList *) changes; link; link = g_slist_next (link)) {
+ EWebDAVPropertyChange *change = link->data;
+
+ if (!change)
+ continue;
+
+ switch (change->kind) {
+ case E_WEBDAV_PROPERTY_SET:
+ e_xml_document_start_element (xml, NULL, "set");
+ e_xml_document_start_element (xml, NULL, "prop");
+ e_xml_document_start_text_element (xml, change->ns_uri, change->name);
+ if (change->value) {
+ e_xml_document_write_string (xml, change->value);
+ }
+ e_xml_document_end_element (xml); /* change->name */
+ e_xml_document_end_element (xml); /* prop */
+ e_xml_document_end_element (xml); /* set */
+ break;
+ case E_WEBDAV_PROPERTY_REMOVE:
+ e_xml_document_start_element (xml, NULL, "remove");
+ e_xml_document_start_element (xml, NULL, "prop");
+ e_xml_document_start_element (xml, change->ns_uri, change->name);
+ e_xml_document_end_element (xml); /* change->name */
+ e_xml_document_end_element (xml); /* prop */
+ e_xml_document_end_element (xml); /* set */
+ break;
+ }
+ }
+
+ success = e_webdav_session_proppatch_sync (webdav, uri, xml, cancellable, error);
+
+ g_object_unref (xml);
+
+ return success;
+}
diff --git a/src/libedataserver/e-webdav-session.h b/src/libedataserver/e-webdav-session.h
index 219f93b..987aa6a 100644
--- a/src/libedataserver/e-webdav-session.h
+++ b/src/libedataserver/e-webdav-session.h
@@ -63,33 +63,37 @@ G_BEGIN_DECLS
#define E_WEBDAV_CAPABILITY_CALENDAR_AUTO_SCHEDULE "calendar-auto-schedule"
#define E_WEBDAV_CAPABILITY_CALENDAR_PROXY "calendar-proxy"
-#define E_WEBDAV_ALLOW_OPTIONS "OPTIONS"
-#define E_WEBDAV_ALLOW_PROPFIND "PROPFIND"
-#define E_WEBDAV_ALLOW_REPORT "REPORT"
-#define E_WEBDAV_ALLOW_DELETE "DELETE"
-#define E_WEBDAV_ALLOW_GET "GET"
-#define E_WEBDAV_ALLOW_PUT "PUT"
-#define E_WEBDAV_ALLOW_HEAD "HEAD"
-#define E_WEBDAV_ALLOW_ACL "ACL"
-#define E_WEBDAV_ALLOW_LOCK "LOCK"
-#define E_WEBDAV_ALLOW_UNLOCK "UNLOCK"
-#define E_WEBDAV_ALLOW_MOVE "MOVE"
-#define E_WEBDAV_ALLOW_MKTICKET "MKTICKET"
-#define E_WEBDAV_ALLOW_DELTICKET "DELTICKET"
+#define E_WEBDAV_ALLOW_OPTIONS "OPTIONS"
+#define E_WEBDAV_ALLOW_PROPFIND "PROPFIND"
+#define E_WEBDAV_ALLOW_REPORT "REPORT"
+#define E_WEBDAV_ALLOW_DELETE "DELETE"
+#define E_WEBDAV_ALLOW_GET "GET"
+#define E_WEBDAV_ALLOW_PUT "PUT"
+#define E_WEBDAV_ALLOW_HEAD "HEAD"
+#define E_WEBDAV_ALLOW_ACL "ACL"
+#define E_WEBDAV_ALLOW_LOCK "LOCK"
+#define E_WEBDAV_ALLOW_UNLOCK "UNLOCK"
+#define E_WEBDAV_ALLOW_MOVE "MOVE"
+#define E_WEBDAV_ALLOW_MKTICKET "MKTICKET"
+#define E_WEBDAV_ALLOW_DELTICKET "DELTICKET"
-#define E_WEBDAV_DEPTH_0 "0"
-#define E_WEBDAV_DEPTH_1 "1"
-#define E_WEBDAV_DEPTH_INFINITY "infinity"
+#define E_WEBDAV_DEPTH_THIS "0"
+#define E_WEBDAV_DEPTH_THIS_AND_CHILDREN "1"
+#define E_WEBDAV_DEPTH_INFINITY "infinity"
-#define E_WEBDAV_CONTENT_TYPE_XML "application/xml; charset=\"utf-8\""
-#define E_WEBDAV_CONTENT_TYPE_CALENDAR "text/calendar; charset=\"utf-8\""
-#define E_WEBDAV_CONTENT_TYPE_VCARD "text/vcard; charset=\"utf-8\""
+#define E_WEBDAV_CONTENT_TYPE_XML "application/xml; charset=\"utf-8\""
+#define E_WEBDAV_CONTENT_TYPE_CALENDAR "text/calendar; charset=\"utf-8\""
+#define E_WEBDAV_CONTENT_TYPE_VCARD "text/vcard; charset=\"utf-8\""
-#define E_WEBDAV_NS_DAV "DAV:"
-#define E_WEBDAV_NS_CALDAV "urn:ietf:params:xml:ns:caldav"
-#define E_WEBDAV_NS_CARDDAV "urn:ietf:params:xml:ns:carddav"
-#define E_WEBDAV_NS_CALENDARSERVER "http://calendarserver.org/ns/"
-#define E_WEBDAV_NS_ICAL "http://apple.com/ns/ical/"
+#define E_WEBDAV_NS_DAV "DAV:"
+#define E_WEBDAV_NS_CALDAV "urn:ietf:params:xml:ns:caldav"
+#define E_WEBDAV_NS_CARDDAV "urn:ietf:params:xml:ns:carddav"
+#define E_WEBDAV_NS_CALENDARSERVER "http://calendarserver.org/ns/"
+#define E_WEBDAV_NS_ICAL "http://apple.com/ns/ical/"
+
+typedef struct _EWebDAVSession EWebDAVSession;
+typedef struct _EWebDAVSessionClass EWebDAVSessionClass;
+typedef struct _EWebDAVSessionPrivate EWebDAVSessionPrivate;
typedef enum {
E_WEBDAV_RESOURCE_KIND_UNKNOWN,
@@ -105,7 +109,8 @@ typedef enum {
E_WEBDAV_RESOURCE_SUPPORTS_CONTACTS = 1 << 0,
E_WEBDAV_RESOURCE_SUPPORTS_EVENTS = 1 << 1,
E_WEBDAV_RESOURCE_SUPPORTS_MEMOS = 1 << 2,
- E_WEBDAV_RESOURCE_SUPPORTS_TASKS = 1 << 3
+ E_WEBDAV_RESOURCE_SUPPORTS_TASKS = 1 << 3,
+ E_WEBDAV_RESOURCE_SUPPORTS_FREEBUSY = 1 << 4
} EWebDAVResourceSupports;
typedef struct _EWebDAVResource {
@@ -136,7 +141,7 @@ EWebDAVResource *
const gchar *description,
const gchar *color);
EWebDAVResource *
- e_webdav_resource_copy (const EWebDAVResource *resource);
+ e_webdav_resource_copy (const EWebDAVResource *src);
void e_webdav_resource_free (gpointer ptr /* EWebDAVResource * */);
typedef enum {
@@ -153,12 +158,8 @@ typedef enum {
E_WEBDAV_LIST_COLOR = 1 << 8
} EWebDAVListFlags;
-typedef struct _EWebDAVSession EWebDAVSession;
-typedef struct _EWebDAVSessionClass EWebDAVSessionClass;
-typedef struct _EWebDAVSessionPrivate EWebDAVSessionPrivate;
-
/**
- * EWebDAVPropfindFunc:
+ * EWebDAVMultistatusTraverseFunc:
* @webdav: an #EWebDAVSession
* @xpath_ctx: an #xmlXPathContextPtr
* @xpath_prop_prefix: (nullable): an XPath prefix for the current prop element, without trailing forward
slash
@@ -178,12 +179,36 @@ typedef struct _EWebDAVSessionPrivate EWebDAVSessionPrivate;
*
* Since: 3.26
**/
-typedef gboolean (* EWebDAVPropfindFunc)(EWebDAVSession *webdav,
- xmlXPathContextPtr xpath_ctx,
- const gchar *xpath_prop_prefix,
- const SoupURI *request_uri,
- guint status_code,
- gpointer user_data);
+typedef gboolean (* EWebDAVMultistatusTraverseFunc) (EWebDAVSession *webdav,
+ xmlXPathContextPtr xpath_ctx,
+ const gchar *xpath_prop_prefix,
+ const SoupURI *request_uri,
+ guint status_code,
+ gpointer user_data);
+
+typedef enum {
+ E_WEBDAV_PROPERTY_SET,
+ E_WEBDAV_PROPERTY_REMOVE
+} EWebDAVPropertyChangeKind;
+
+typedef struct _EWebDAVPropertyChange {
+ EWebDAVPropertyChangeKind kind;
+ gchar *ns_uri;
+ gchar *name;
+ gchar *value;
+} EWebDAVPropertyChange;
+
+GType e_webdav_property_change_get_type (void) G_GNUC_CONST;
+EWebDAVPropertyChange *
+ e_webdav_property_change_new_set (const gchar *ns_uri,
+ const gchar *name,
+ const gchar *value);
+EWebDAVPropertyChange *
+ e_webdav_property_change_new_remove (const gchar *ns_uri,
+ const gchar *name);
+EWebDAVPropertyChange *
+ e_webdav_property_change_copy (const EWebDAVPropertyChange *src);
+void e_webdav_property_change_free (gpointer ptr); /* EWebDAVPropertyChange * */
/**
* EWebDAVSession:
@@ -219,11 +244,11 @@ gboolean e_webdav_session_propfind_sync (EWebDAVSession *webdav,
const gchar *uri,
const gchar *depth,
const EXmlDocument *xml,
- EWebDAVPropfindFunc func,
+ EWebDAVMultistatusTraverseFunc func,
gpointer func_user_data,
GCancellable *cancellable,
GError **error);
-/*gboolean e_webdav_session_proppatch_sync (EWebDAVSession *webdav,
+gboolean e_webdav_session_proppatch_sync (EWebDAVSession *webdav,
const gchar *uri,
const EXmlDocument *xml,
GCancellable *cancellable,
@@ -234,21 +259,45 @@ gboolean e_webdav_session_mkcol_sync (EWebDAVSession *webdav,
GError **error);
gboolean e_webdav_session_get_sync (EWebDAVSession *webdav,
const gchar *uri,
+ gchar **out_href,
+ gchar **out_etag,
+ GOutputStream *out_stream,
+ GCancellable *cancellable,
+ GError **error);
+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);
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);
+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_redirected_uri,
+ gchar **out_href,
+ gchar **out_etag,
GCancellable *cancellable,
GError **error);
gboolean e_webdav_session_delete_sync (EWebDAVSession *webdav,
const gchar *uri,
+ const gchar *depth,
+ const gchar *etag,
GCancellable *cancellable,
GError **error);
-gboolean e_webdav_session_copy_sync (EWebDAVSession *webdav,
+/*gboolean e_webdav_session_copy_sync (EWebDAVSession *webdav,
const gchar *source_uri,
const gchar *destination_uri,
const gchar *depth,
@@ -277,6 +326,13 @@ gboolean e_webdav_session_unlock_sync (EWebDAVSession *webdav,
GCancellable *cancellable,
GError **error);*/
+gboolean e_webdav_session_traverse_multistatus_response
+ (EWebDAVSession *webdav,
+ const SoupMessage *message,
+ const GByteArray *xml_data,
+ EWebDAVMultistatusTraverseFunc func,
+ gpointer func_user_data,
+ GError **error);
gboolean e_webdav_session_getctag_sync (EWebDAVSession *webdav,
const gchar *uri,
gchar **out_ctag,
@@ -284,10 +340,16 @@ gboolean e_webdav_session_getctag_sync (EWebDAVSession *webdav,
GError **error);
gboolean e_webdav_session_list_sync (EWebDAVSession *webdav,
const gchar *uri,
+ const gchar *depth,
guint32 flags, /* bit-or of EWebDAVListFlags */
GSList **out_resources, /* EWebDAVResource * */
GCancellable *cancellable,
GError **error);
+gboolean e_webdav_session_update_properties_sync (EWebDAVSession *webdav,
+ const gchar *uri,
+ const GSList *changes, /* EWebDAVPropertyChange * */
+ GCancellable *cancellable,
+ GError **error);
G_END_DECLS
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]