[evolution-data-server/wip/offline-cache] Fill EWebDAVSession with some functions and add more helper bits
- From: Milan Crha <mcrha src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [evolution-data-server/wip/offline-cache] Fill EWebDAVSession with some functions and add more helper bits
- Date: Tue, 28 Mar 2017 16:42:22 +0000 (UTC)
commit c0018d42cb9c080631672f18682a9348d18d2c5b
Author: Milan Crha <mcrha redhat com>
Date: Tue Mar 28 18:41:45 2017 +0200
Fill EWebDAVSession with some functions and add more helper bits
po/POTFILES.in | 1 +
src/calendar/backends/http/e-cal-backend-http.c | 8 +-
src/libedataserver/CMakeLists.txt | 6 +-
src/libedataserver/e-soup-session.c | 158 ++++-
src/libedataserver/e-soup-session.h | 16 +-
src/libedataserver/e-webdav-session.c | 960 +++++++++++++++++++++++
src/libedataserver/e-webdav-session.h | 211 +++++
src/libedataserver/e-xml-document.c | 601 ++++++++++++++
src/libedataserver/e-xml-document.h | 115 +++
src/libedataserver/e-xml-utils.c | 265 +++++++
src/libedataserver/e-xml-utils.h | 24 +-
src/libedataserver/libedataserver.h | 1 +
12 files changed, 2338 insertions(+), 28 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 5826587..518ba0f 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -198,6 +198,7 @@ src/libedataserver/e-source-registry.c
src/libedataserver/e-source-webdav.c
src/libedataserver/e-time-utils.c
src/libedataserver/e-webdav-discover.c
+src/libedataserver/e-webdav-session.c
src/libedataserverui/e-credentials-prompter.c
src/libedataserverui/e-credentials-prompter-impl-google.c
src/libedataserverui/e-credentials-prompter-impl-password.c
diff --git a/src/calendar/backends/http/e-cal-backend-http.c b/src/calendar/backends/http/e-cal-backend-http.c
index 3e85504..137edfe 100644
--- a/src/calendar/backends/http/e-cal-backend-http.c
+++ b/src/calendar/backends/http/e-cal-backend-http.c
@@ -134,19 +134,15 @@ ecb_http_connect_sync (ECalMetaBackend *meta_backend,
e_soup_session_set_credentials (cbhttp->priv->session, credentials);
- request = soup_session_request_http (SOUP_SESSION (cbhttp->priv->session), SOUP_METHOD_GET, uri,
&local_error);
+ request = e_soup_session_new_request (cbhttp->priv->session, SOUP_METHOD_GET, uri, &local_error);
success = request != NULL;
if (success) {
SoupMessage *message;
message = soup_request_http_get_message (request);
- if (message) {
- soup_message_headers_append (message->request_headers, "User-Agent", "Evolution/"
VERSION);
- soup_message_headers_append (message->request_headers, "Connection", "close");
- }
- input_stream = e_soup_session_send_request_sync (cbhttp->priv->session, SOUP_REQUEST
(request), cancellable, &local_error);
+ input_stream = e_soup_session_send_request_sync (cbhttp->priv->session, request, cancellable,
&local_error);
success = input_stream != NULL;
diff --git a/src/libedataserver/CMakeLists.txt b/src/libedataserver/CMakeLists.txt
index 33a0eb4..fc523ea 100644
--- a/src/libedataserver/CMakeLists.txt
+++ b/src/libedataserver/CMakeLists.txt
@@ -120,8 +120,9 @@ set(SOURCES
e-webdav-discover.c
e-webdav-session.c
e-data-server-util.c
- e-xml-utils.c
+ e-xml-document.c
e-xml-hash-utils.c
+ e-xml-utils.c
libedataserver-private.h
eds-version.c
${CMAKE_CURRENT_BINARY_DIR}/e-source-enumtypes.c
@@ -201,8 +202,9 @@ set(HEADERS
e-webdav-discover.h
e-webdav-session.h
e-data-server-util.h
- e-xml-utils.h
+ e-xml-document.h
e-xml-hash-utils.h
+ e-xml-utils.h
${CMAKE_CURRENT_BINARY_DIR}/e-source-enumtypes.h
${CMAKE_CURRENT_BINARY_DIR}/eds-version.h
)
diff --git a/src/libedataserver/e-soup-session.c b/src/libedataserver/e-soup-session.c
index 69edeeb..56cc89f 100644
--- a/src/libedataserver/e-soup-session.c
+++ b/src/libedataserver/e-soup-session.c
@@ -26,6 +26,8 @@
#include "evolution-data-server-config.h"
+#include <stdio.h>
+
#include "e-soup-ssl-trust.h"
#include "e-source-authentication.h"
#include "e-source-webdav.h"
@@ -40,6 +42,8 @@ struct _ESoupSessionPrivate {
gboolean ssl_info_set;
gchar *ssl_certificate_pem;
GTlsCertificateFlags ssl_certificate_errors;
+
+ SoupLoggerLogLevel log_level;
};
enum {
@@ -221,6 +225,7 @@ e_soup_session_init (ESoupSession *session)
{
session->priv = G_TYPE_INSTANCE_GET_PRIVATE (session, E_TYPE_SOUP_SESSION, ESoupSessionPrivate);
session->priv->ssl_info_set = FALSE;
+ session->priv->log_level = SOUP_LOGGER_LOG_NONE;
g_mutex_init (&session->priv->property_lock);
@@ -273,6 +278,8 @@ e_soup_session_new (ESource *source)
* "1" - the same as "all".
* Any other value, including %NULL, disables logging.
*
+ * Use e_soup_session_get_log_level() to get current log level.
+ *
* Since: 3.26
**/
void
@@ -280,11 +287,11 @@ e_soup_session_setup_logging (ESoupSession *session,
const gchar *logging_level)
{
SoupLogger *logger;
- SoupLoggerLogLevel level;
g_return_if_fail (E_IS_SOUP_SESSION (session));
soup_session_remove_feature_by_type (SOUP_SESSION (session), SOUP_TYPE_LOGGER);
+ session->priv->log_level = SOUP_LOGGER_LOG_NONE;
if (!logging_level)
return;
@@ -292,20 +299,36 @@ e_soup_session_setup_logging (ESoupSession *session,
if (g_ascii_strcasecmp (logging_level, "all") == 0 ||
g_ascii_strcasecmp (logging_level, "body") == 0 ||
g_ascii_strcasecmp (logging_level, "1") == 0)
- level = SOUP_LOGGER_LOG_BODY;
+ session->priv->log_level = SOUP_LOGGER_LOG_BODY;
else if (g_ascii_strcasecmp (logging_level, "headers") == 0)
- level = SOUP_LOGGER_LOG_HEADERS;
+ session->priv->log_level = SOUP_LOGGER_LOG_HEADERS;
else if (g_ascii_strcasecmp (logging_level, "min") == 0)
- level = SOUP_LOGGER_LOG_MINIMAL;
+ session->priv->log_level = SOUP_LOGGER_LOG_MINIMAL;
else
return;
- logger = soup_logger_new (level, 10 * 1024 * 1024);
+ logger = soup_logger_new (session->priv->log_level, -1);
soup_session_add_feature (SOUP_SESSION (session), SOUP_SESSION_FEATURE (logger));
g_object_unref (logger);
}
/**
+ * e_soup_session_get_log_level:
+ * @session: an #ESoupSession
+ *
+ * Returns: Current log level, as #SoupLoggerLogLevel
+ *
+ * Since: 3.26
+ **/
+SoupLoggerLogLevel
+e_soup_session_get_log_level (ESoupSession *session)
+{
+ g_return_val_if_fail (E_IS_SOUP_SESSION (session), SOUP_LOGGER_LOG_NONE);
+
+ return session->priv->log_level;
+}
+
+/**
* e_soup_session_get_source:
* @session: an #ESoupSession
*
@@ -423,10 +446,103 @@ e_soup_session_get_ssl_error_details (ESoupSession *session,
return TRUE;
}
+static void
+e_soup_session_preset_request (SoupRequestHTTP *request)
+{
+ SoupMessage *message;
+
+ if (!request)
+ return;
+
+ message = soup_request_http_get_message (request);
+ if (message) {
+ soup_message_headers_append (message->request_headers, "User-Agent", "Evolution/" VERSION);
+ soup_message_headers_append (message->request_headers, "Connection", "close");
+
+ /* Disable caching for proxies (RFC 4918, section 10.4.5) */
+ soup_message_headers_append (message->request_headers, "Cache-Control", "no-cache");
+ soup_message_headers_append (message->request_headers, "Pragma", "no-cache");
+
+ g_clear_object (&message);
+ }
+}
+
+/**
+ * e_soup_session_new_request:
+ * @session: an #ESoupSession
+ * @method: an HTTP method
+ * @uri_string: a URI string to use for the request
+ * @error: return location for a #GError, or %NULL
+ *
+ * Creates a new #SoupRequestHTTP, similar to soup_session_request_http(),
+ * but also presets request headers with "User-Agent" to be "Evolution/version"
+ * and with "Connection" to be "close".
+ *
+ * See also e_soup_session_new_request_uri().
+ *
+ * Returns: (transfer full): a new #SoupRequestHTTP, or %NULL on error
+ *
+ * Since: 3.26
+ **/
+SoupRequestHTTP *
+e_soup_session_new_request (ESoupSession *session,
+ const gchar *method,
+ const gchar *uri_string,
+ GError **error)
+{
+ SoupRequestHTTP *request;
+
+ g_return_val_if_fail (E_IS_SOUP_SESSION (session), NULL);
+
+ request = soup_session_request_http (SOUP_SESSION (session), method, uri_string, error);
+ if (!request)
+ return NULL;
+
+ e_soup_session_preset_request (request);
+
+ return request;
+}
+
+/**
+ * e_soup_session_new_request:
+ * @session: an #ESoupSession
+ * @method: an HTTP method
+ * @uri: a #SoupURI to use for the request
+ * @error: return location for a #GError, or %NULL
+ *
+ * Creates a new #SoupRequestHTTP, similar to soup_session_request_http_uri(),
+ * but also presets request headers with "User-Agent" to be "Evolution/version"
+ * and with "Connection" to be "close".
+ *
+ * See also e_soup_session_new_request().
+ *
+ * Returns: (transfer full): a new #SoupRequestHTTP, or %NULL on error
+ *
+ * Since: 3.26
+ **/
+SoupRequestHTTP *
+e_soup_session_new_request_uri (ESoupSession *session,
+ const gchar *method,
+ SoupURI *uri,
+ GError **error)
+{
+ SoupRequestHTTP *request;
+
+ g_return_val_if_fail (E_IS_SOUP_SESSION (session), NULL);
+
+ request = soup_session_request_http_uri (SOUP_SESSION (session), method, uri, error);
+ if (!request)
+ return NULL;
+
+ e_soup_session_preset_request (request);
+
+ return request;
+}
+
/**
* e_soup_session_send_request_sync:
* @session: an #ESoupSession
- * @request: a #SoupRequest to send
+ * @request: a #SoupRequestHTTP to send
* @cancellable: optional #GCancellable object, or %NULL
* @error: return location for a #GError, or %NULL
*
@@ -442,6 +558,9 @@ e_soup_session_get_ssl_error_details (ESoupSession *session,
* Use e_soup_session_send_request_simple_sync() to read whole
* content into a #GByteArray.
*
+ * Note that SoupSession doesn't log content read from GInputStream,
+ * thus the caller may print the read content on its own when needed.
+ *
* 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.
@@ -450,7 +569,7 @@ e_soup_session_get_ssl_error_details (ESoupSession *session,
**/
GInputStream *
e_soup_session_send_request_sync (ESoupSession *session,
- SoupRequest *request,
+ SoupRequestHTTP *request,
GCancellable *cancellable,
GError **error)
{
@@ -458,7 +577,7 @@ e_soup_session_send_request_sync (ESoupSession *session,
GError *local_error = NULL;
g_return_val_if_fail (E_IS_SOUP_SESSION (session), NULL);
- g_return_val_if_fail (SOUP_IS_REQUEST (request), NULL);
+ g_return_val_if_fail (SOUP_IS_REQUEST_HTTP (request), NULL);
g_mutex_lock (&session->priv->property_lock);
g_clear_pointer (&session->priv->ssl_certificate_pem, g_free);
@@ -467,18 +586,17 @@ e_soup_session_send_request_sync (ESoupSession *session,
g_mutex_unlock (&session->priv->property_lock);
if (session->priv->source &&
- e_source_has_extension (session->priv->source, E_SOURCE_EXTENSION_WEBDAV_BACKEND) &&
- SOUP_IS_REQUEST_HTTP (request)) {
+ e_source_has_extension (session->priv->source, E_SOURCE_EXTENSION_WEBDAV_BACKEND)) {
SoupMessage *message;
- message = soup_request_http_get_message (SOUP_REQUEST_HTTP (request));
+ message = soup_request_http_get_message (request);
e_soup_ssl_trust_connect (message, session->priv->source);
g_clear_object (&message);
}
- input_stream = soup_request_send (request, cancellable, &local_error);
+ input_stream = soup_request_send (SOUP_REQUEST (request), cancellable, &local_error);
if (input_stream)
return input_stream;
@@ -486,7 +604,7 @@ e_soup_session_send_request_sync (ESoupSession *session,
GTlsCertificate *certificate = NULL;
SoupMessage *message;
- message = soup_request_http_get_message (SOUP_REQUEST_HTTP (request));
+ message = soup_request_http_get_message (request);
g_mutex_lock (&session->priv->property_lock);
@@ -515,7 +633,7 @@ e_soup_session_send_request_sync (ESoupSession *session,
/**
* e_soup_session_send_request_simple_sync:
* @session: an #ESoupSession
- * @request: a #SoupRequest to send
+ * @request: a #SoupRequestHTTP to send
* @cancellable: optional #GCancellable object, or %NULL
* @error: return location for a #GError, or %NULL
*
@@ -524,6 +642,9 @@ e_soup_session_send_request_sync (ESoupSession *session,
* Use e_soup_session_send_request_sync() when you want to have
* more control on the content read.
*
+ * The function prints read content to stdout when
+ * e_soup_session_get_log_level() returns #SOUP_LOGGER_LOG_BODY.
+ *
* Returns: (transfer full): A newly allocated #GByteArray,
* which contains whole content from the URI pointed to by @request.
*
@@ -531,7 +652,7 @@ e_soup_session_send_request_sync (ESoupSession *session,
**/
GByteArray *
e_soup_session_send_request_simple_sync (ESoupSession *session,
- SoupRequest *request,
+ SoupRequestHTTP *request,
GCancellable *cancellable,
GError **error)
{
@@ -543,13 +664,13 @@ e_soup_session_send_request_simple_sync (ESoupSession *session,
gboolean success = FALSE;
g_return_val_if_fail (E_IS_SOUP_SESSION (session), NULL);
- g_return_val_if_fail (SOUP_IS_REQUEST (request), NULL);
+ g_return_val_if_fail (SOUP_IS_REQUEST_HTTP (request), NULL);
input_stream = e_soup_session_send_request_sync (session, request, cancellable, error);
if (!input_stream)
return NULL;
- expected_length = soup_request_get_content_length (request);
+ expected_length = soup_request_get_content_length (SOUP_REQUEST (request));
if (expected_length > 0)
bytes = g_byte_array_sized_new (expected_length);
else
@@ -568,6 +689,9 @@ e_soup_session_send_request_simple_sync (ESoupSession *session,
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;
diff --git a/src/libedataserver/e-soup-session.h b/src/libedataserver/e-soup-session.h
index 09784e8..d9e4763 100644
--- a/src/libedataserver/e-soup-session.h
+++ b/src/libedataserver/e-soup-session.h
@@ -79,6 +79,8 @@ GType e_soup_session_get_type (void) G_GNUC_CONST;
ESoupSession * e_soup_session_new (ESource *source);
void e_soup_session_setup_logging (ESoupSession *session,
const gchar *logging_level);
+SoupLoggerLogLevel
+ e_soup_session_get_log_level (ESoupSession *session);
ESource * e_soup_session_get_source (ESoupSession *session);
void e_soup_session_set_credentials (ESoupSession *session,
const ENamedParameters *credentials);
@@ -87,12 +89,22 @@ ENamedParameters *
gboolean e_soup_session_get_ssl_error_details (ESoupSession *session,
gchar **out_certificate_pem,
GTlsCertificateFlags *out_certificate_errors);
+SoupRequestHTTP *
+ e_soup_session_new_request (ESoupSession *session,
+ const gchar *method,
+ const gchar *uri_string,
+ GError **error);
+SoupRequestHTTP *
+ e_soup_session_new_request_uri (ESoupSession *session,
+ const gchar *method,
+ SoupURI *uri,
+ GError **error);
GInputStream * e_soup_session_send_request_sync (ESoupSession *session,
- SoupRequest *request,
+ SoupRequestHTTP *request,
GCancellable *cancellable,
GError **error);
GByteArray * e_soup_session_send_request_simple_sync (ESoupSession *session,
- SoupRequest *request,
+ SoupRequestHTTP *request,
GCancellable *cancellable,
GError **error);
diff --git a/src/libedataserver/e-webdav-session.c b/src/libedataserver/e-webdav-session.c
index 246c36a..221f2b9 100644
--- a/src/libedataserver/e-webdav-session.c
+++ b/src/libedataserver/e-webdav-session.c
@@ -27,7 +27,12 @@
#include "evolution-data-server-config.h"
+#include <glib/gi18n-lib.h>
+
+#include "camel/camel.h"
+
#include "e-source-webdav.h"
+#include "e-xml-utils.h"
#include "e-webdav-session.h"
@@ -37,6 +42,114 @@ 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)
+
+/**
+ * e_webdav_resource_new:
+ * @kind: an #EWebDAVResourceKind of the resource
+ * @supports: bit-or of #EWebDAVResourceSupports values
+ * @href: href of the resource
+ * @etag: (nullable): optional ETag of the resource, or %NULL
+ * @display_name: (nullable): optional display name of the resource, or %NULL
+ * @description: (nullable): optional description of the resource, or %NULL
+ * @color: (nullable): optional color of the resource, or %NULL
+ *
+ * Some values of the resource are not always valid, depending on the @kind,
+ * but also whether server stores such values and whether it had been asked
+ * for them to be fetched.
+ *
+ * The @etag for %E_WEBDAV_RESOURCE_KIND_COLLECTION can be a change tag instead.
+ *
+ * Returns: (transfer full): A newly created #EWebDAVResource, prefilled with
+ * given values. Free it with e_webdav_resource_free(), when no longer needed.
+ *
+ * Since: 3.26
+ **/
+EWebDAVResource *
+e_webdav_resource_new (EWebDAVResourceKind kind,
+ guint32 supports,
+ const gchar *href,
+ const gchar *etag,
+ const gchar *display_name,
+ const gchar *content_type,
+ gsize content_length,
+ glong creation_date,
+ glong last_modified,
+ const gchar *description,
+ const gchar *color)
+{
+ EWebDAVResource *resource;
+
+ resource = g_new0 (EWebDAVResource, 1);
+ resource->kind = kind;
+ resource->supports = supports;
+ resource->href = g_strdup (href);
+ resource->etag = g_strdup (etag);
+ resource->display_name = g_strdup (display_name);
+ resource->content_type = g_strdup (content_type);
+ resource->content_length = content_length;
+ resource->creation_date = creation_date;
+ resource->last_modified = last_modified;
+ resource->description = g_strdup (description);
+ resource->color = g_strdup (color);
+
+ return resource;
+}
+
+/**
+ * e_webdav_resource_copy:
+ * @resource: (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.
+ * Free it with e_webdav_resource_free(), when no longer needed.
+ *
+ * Since: 3.26
+ **/
+EWebDAVResource *
+e_webdav_resource_copy (const EWebDAVResource *resource)
+{
+ if (!resource)
+ 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);
+}
+
+/**
+ * e_webdav_resource_free:
+ * @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.
+ *
+ * Since: 3.26
+ **/
+void
+e_webdav_resource_free (gpointer ptr)
+{
+ EWebDAVResource *resource = ptr;
+
+ if (resource) {
+ g_free (resource->href);
+ g_free (resource->etag);
+ g_free (resource->display_name);
+ g_free (resource->content_type);
+ g_free (resource->description);
+ g_free (resource->color);
+ g_free (resource);
+ }
+}
+
static void
e_webdav_session_class_init (EWebDAVSessionClass *klass)
{
@@ -72,3 +185,850 @@ e_webdav_session_new (ESource *source)
"source", source,
NULL);
}
+
+static SoupRequestHTTP *
+e_webdav_session_new_request (EWebDAVSession *webdav,
+ const gchar *method,
+ const gchar *uri,
+ GError **error)
+{
+ ESoupSession *session;
+ SoupRequestHTTP *request;
+ SoupURI *soup_uri;
+ ESource *source;
+ ESourceWebdav *webdav_extension;
+
+ g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), NULL);
+
+ session = E_SOUP_SESSION (webdav);
+ if (uri && *uri)
+ return e_soup_session_new_request (session, method, uri, error);
+
+ source = e_soup_session_get_source (session);
+ g_return_val_if_fail (E_IS_SOURCE (source), NULL);
+
+ webdav_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
+ soup_uri = e_source_webdav_dup_soup_uri (webdav_extension);
+
+ g_return_val_if_fail (soup_uri != NULL, NULL);
+
+ request = e_soup_session_new_request_uri (session, method, soup_uri, error);
+
+ soup_uri_free (soup_uri);
+
+ return request;
+}
+
+static GHashTable *
+e_webdav_session_comma_header_to_hashtable (SoupMessageHeaders *headers,
+ const gchar *header_name)
+{
+ GHashTable *soup_params, *result;
+ GHashTableIter iter;
+ const gchar *value;
+ gpointer key;
+
+ g_return_val_if_fail (header_name != NULL, NULL);
+
+ if (!headers)
+ return NULL;
+
+ value = soup_message_headers_get_list (headers, header_name);
+ if (!value)
+ return NULL;
+
+ soup_params = soup_header_parse_param_list (value);
+ if (!soup_params)
+ return NULL;
+
+ result = g_hash_table_new_full (camel_strcase_hash, camel_strcase_equal, g_free, NULL);
+
+ g_hash_table_iter_init (&iter, soup_params);
+ while (g_hash_table_iter_next (&iter, &key, NULL)) {
+ value = key;
+
+ if (value && *value)
+ g_hash_table_insert (result, g_strdup (value), GINT_TO_POINTER (1));
+ }
+
+ soup_header_free_param_list (soup_params);
+
+ return result;
+}
+
+/**
+ * e_webdav_session_options_sync:
+ * @webdav: an #EWebDAVSession
+ * @uri: (nullable): URI to issue the request for, or %NULL to read from #ESource
+ * @out_capabilities: (out) (transfer full): return location for DAV capabilities
+ * @out_allows: (out) (transfer full): return location for allowed operations
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Issues OPTIONS request on the provided @uri, or, in case it's %NULL, on the URI
+ * defined in associated #ESource.
+ *
+ * The @out_capabilities contains a set of returned capabilities. Some known are
+ * defined as E_WEBDAV_CAPABILITY_CLASS_1, and so on. The 'value' of the #GHashTable
+ * doesn't have any particular meaning and the strings are compared case insensitively.
+ * Free the hash table with g_hash_table_destroy(), when no longer needed. The returned
+ * value can be %NULL on success, it's when the server doesn't provide the information.
+ *
+ * The @out_allows contains a set of allowed methods returned by the server. Some known
+ * are defined as E_WEBDAV_ALLOW_OPTIONS, and so on. The 'value' of the #GHashTable
+ * doesn't have any particular meaning and the strings are compared case insensitively.
+ * Free the hash table with g_hash_table_destroy(), when no longer needed. The returned
+ * value can be %NULL on success, it's when the server doesn't provide the information.
+ *
+ * Returns: Whether succeeded.
+ *
+ * Since: 3.26
+ **/
+gboolean
+e_webdav_session_options_sync (EWebDAVSession *webdav,
+ const gchar *uri,
+ GHashTable **out_capabilities,
+ GHashTable **out_allows,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupRequestHTTP *request;
+ SoupMessage *message;
+ GByteArray *bytes;
+
+ g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
+ g_return_val_if_fail (out_capabilities != NULL, FALSE);
+ g_return_val_if_fail (out_allows != NULL, FALSE);
+
+ *out_capabilities = NULL;
+ *out_allows = NULL;
+
+ request = e_webdav_session_new_request (webdav, SOUP_METHOD_OPTIONS, uri, error);
+ if (!request)
+ return FALSE;
+
+ bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable,
error);
+
+ if (!bytes) {
+ g_object_unref (request);
+ return FALSE;
+ }
+
+ message = soup_request_http_get_message (request);
+
+ g_byte_array_free (bytes, TRUE);
+ g_object_unref (request);
+
+ g_return_val_if_fail (message != NULL, FALSE);
+
+ *out_capabilities = e_webdav_session_comma_header_to_hashtable (message->response_headers, "DAV");
+ *out_allows = e_webdav_session_comma_header_to_hashtable (message->response_headers, "Allow");
+
+ g_object_unref (message);
+
+ return TRUE;
+}
+
+/**
+ * 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
+ * @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_user_data: user data passed to @func
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Issues PROPFIND request on the provided @uri, or, in case it's %NULL, on the URI
+ * defined in associated #ESource. On success, 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 @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.
+ *
+ * The @xml can be %NULL, in which case the server should behave like DAV:allprop request.
+ *
+ * Returns: Whether succeeded.
+ *
+ * Since: 3.26
+ **/
+gboolean
+e_webdav_session_propfind_sync (EWebDAVSession *webdav,
+ const gchar *uri,
+ const gchar *depth,
+ const EXmlDocument *xml,
+ EWebDAVPropfindFunc func,
+ gpointer func_user_data,
+ 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 (depth != NULL, FALSE);
+ if (xml)
+ g_return_val_if_fail (E_IS_XML_DOCUMENT (xml), FALSE);
+ g_return_val_if_fail (func != NULL, FALSE);
+
+ request = e_webdav_session_new_request (webdav, SOUP_METHOD_PROPFIND, 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;
+ }
+
+ soup_message_headers_append (message->request_headers, "Depth", depth);
+
+ if (xml) {
+ gchar *content;
+ gsize content_length;
+
+ content = e_xml_document_get_content (xml, &content_length);
+ if (!content) {
+ g_object_unref (message);
+ g_object_unref (request);
+
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Failed to get
input XML content"));
+
+ return FALSE;
+ }
+
+ soup_message_set_request (message, E_WEBDAV_CONTENT_TYPE_XML,
+ SOUP_MEMORY_TAKE, content, content_length);
+ }
+
+ bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable,
error);
+
+ g_object_unref (request);
+
+ success = bytes != NULL;
+
+ if (success && message->status_code != SOUP_STATUS_MULTI_STATUS) {
+ success = FALSE;
+
+ 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 (success) {
+ const gchar *content_type;
+
+ 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);
+
+ 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);
+ }
+ }
+ }
+
+ if (success) {
+ xmlDocPtr doc = e_xml_parse_data ((const gchar *) bytes->data, bytes->len);
+
+ g_byte_array_free (bytes, TRUE);
+ bytes = NULL;
+
+ if (doc) {
+ xmlXPathContextPtr xpath_ctx;
+ SoupURI *request_uri;
+
+ request_uri = soup_message_get_uri (message);
+
+ 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);
+ } else {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
+ _("Failed to parse response as XML"));
+
+ success = FALSE;
+ }
+ }
+
+ if (bytes)
+ g_byte_array_free (bytes, TRUE);
+ g_object_unref (message);
+
+ return success;
+}
+
+/* This assumes ownership of 'text' */
+static gchar *
+e_webdav_session_maybe_dequote (gchar *text)
+{
+ gchar *dequoted;
+ gint len;
+
+ if (!text || *text != '\"')
+ return text;
+
+ len = strlen (text);
+
+ if (len < 2 || text[len - 1] != '\"')
+ return text;
+
+ dequoted = g_strndup (text + 1, len - 2);
+ g_free (text);
+
+ return dequoted;
+}
+
+static gboolean
+e_webdav_session_getctag_cb (EWebDAVSession *webdav,
+ xmlXPathContextPtr xpath_ctx,
+ const gchar *xpath_prop_prefix,
+ const SoupURI *request_uri,
+ guint status_code,
+ gpointer user_data)
+{
+ if (!xpath_prop_prefix) {
+ e_xml_xpath_context_register_namespaces (xpath_ctx,
+ "CS", E_WEBDAV_NS_CALENDARSERVER,
+ NULL);
+
+ return TRUE;
+ }
+
+ if (status_code == SOUP_STATUS_OK) {
+ gchar **out_ctag = user_data;
+ gchar *ctag;
+
+ g_return_val_if_fail (out_ctag != NULL, FALSE);
+
+ ctag = e_xml_xpath_eval_as_string (xpath_ctx, "%s/CS:getctag", xpath_prop_prefix);
+
+ if (ctag && *ctag) {
+ *out_ctag = e_webdav_session_maybe_dequote (ctag);
+ } else {
+ g_free (ctag);
+ }
+ }
+
+ return FALSE;
+}
+
+/**
+ * e_webdav_session_getctag_sync:
+ * @webdav: an #EWebDAVSession
+ * @uri: (nullable): URI to issue the request for, or %NULL to read from #ESource
+ * @out_ctag: (out) (transfer full): return location for the ctag
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Issues a getctag property request for a collection identified by @uri, or
+ * by the #ESource resource reference. The ctag is a collection tag, which
+ * changes whenever the collection changes (similar to etag). The getctag is
+ * an extension, thus the function can fail when the server doesn't support it.
+ *
+ * Free the returned @out_ctag with g_free(), when no longer needed.
+ *
+ * Returns: Whether succeeded.
+ *
+ * Since: 3.26
+ **/
+gboolean
+e_webdav_session_getctag_sync (EWebDAVSession *webdav,
+ const gchar *uri,
+ gchar **out_ctag,
+ GCancellable *cancellable,
+ GError **error)
+{
+ EXmlDocument *xml;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
+ g_return_val_if_fail (out_ctag != NULL, FALSE);
+
+ *out_ctag = NULL;
+
+ xml = e_xml_document_new (E_WEBDAV_NS_DAV, "propfind");
+ g_return_val_if_fail (xml != NULL, FALSE);
+
+ e_xml_document_add_namespaces (xml, "CS", E_WEBDAV_NS_CALENDARSERVER, NULL);
+
+ e_xml_document_start_element (xml, NULL, "prop");
+ e_xml_document_start_element (xml, E_WEBDAV_NS_CALENDARSERVER, "getctag");
+ 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,
+ e_webdav_session_getctag_cb, out_ctag, cancellable, error);
+
+ g_object_unref (xml);
+
+ return success && *out_ctag != NULL;
+}
+
+static EWebDAVResourceKind
+e_webdav_session_extract_kind (xmlXPathContextPtr xpath_ctx,
+ const gchar *xpath_prop_prefix)
+{
+ g_return_val_if_fail (xpath_ctx != NULL, E_WEBDAV_RESOURCE_KIND_UNKNOWN);
+ g_return_val_if_fail (xpath_prop_prefix != NULL, E_WEBDAV_RESOURCE_KIND_UNKNOWN);
+
+ if (e_xml_xpath_eval_exists (xpath_ctx, "%s/D:resourcetype/A:addressbook", xpath_prop_prefix))
+ return E_WEBDAV_RESOURCE_KIND_ADDRESSBOOK;
+
+ if (e_xml_xpath_eval_exists (xpath_ctx, "%s/D:resourcetype/C:calendar", xpath_prop_prefix))
+ return E_WEBDAV_RESOURCE_KIND_CALENDAR;
+
+ if (e_xml_xpath_eval_exists (xpath_ctx, "%s/D:resourcetype/D:principal", xpath_prop_prefix))
+ return E_WEBDAV_RESOURCE_KIND_PRINCIPAL;
+
+ if (e_xml_xpath_eval_exists (xpath_ctx, "%s/D:resourcetype/D:collection", xpath_prop_prefix))
+ return E_WEBDAV_RESOURCE_KIND_COLLECTION;
+
+ return E_WEBDAV_RESOURCE_KIND_RESOURCE;
+}
+
+static guint32
+e_webdav_session_extract_supports (xmlXPathContextPtr xpath_ctx,
+ const gchar *xpath_prop_prefix)
+{
+ guint32 supports = E_WEBDAV_RESOURCE_SUPPORTS_NONE;
+
+ g_return_val_if_fail (xpath_ctx != NULL, E_WEBDAV_RESOURCE_SUPPORTS_NONE);
+ g_return_val_if_fail (xpath_prop_prefix != NULL, E_WEBDAV_RESOURCE_SUPPORTS_NONE);
+
+ if (e_xml_xpath_eval_exists (xpath_ctx, "%s/D:resourcetype/A:addressbook", xpath_prop_prefix))
+ supports = supports | E_WEBDAV_RESOURCE_SUPPORTS_CONTACTS;
+
+ if (e_xml_xpath_eval_exists (xpath_ctx, "%s/C:supported-calendar-component-set", xpath_prop_prefix)) {
+ xmlXPathObjectPtr xpath_obj;
+
+ xpath_obj = e_xml_xpath_eval (xpath_ctx, "%s/C:supported-calendar-component-set/C:comp",
xpath_prop_prefix);
+ if (xpath_obj) {
+ gint ii, length;
+
+ length = xmlXPathNodeSetGetLength (xpath_obj->nodesetval);
+
+ for (ii = 0; ii < length; ii++) {
+ gchar *name;
+
+ name = e_xml_xpath_eval_as_string (xpath_ctx,
"%s/C:supported-calendar-component-set/C:comp[%d]/@name",
+ xpath_prop_prefix, ii + 1);
+
+ if (!name)
+ continue;
+
+ if (g_ascii_strcasecmp (name, "VEVENT") == 0)
+ supports |= E_WEBDAV_RESOURCE_SUPPORTS_EVENTS;
+ else if (g_ascii_strcasecmp (name, "VJOURNAL") == 0)
+ supports |= E_WEBDAV_RESOURCE_SUPPORTS_MEMOS;
+ else if (g_ascii_strcasecmp (name, "VTODO") == 0)
+ supports |= E_WEBDAV_RESOURCE_SUPPORTS_TASKS;
+
+ g_free (name);
+ }
+
+ xmlXPathFreeObject (xpath_obj);
+ } else {
+ /* If the property is not present, assume all component
+ * types are supported. (RFC 4791, Section 5.2.3) */
+ supports = supports |
+ E_WEBDAV_RESOURCE_SUPPORTS_EVENTS |
+ E_WEBDAV_RESOURCE_SUPPORTS_MEMOS |
+ E_WEBDAV_RESOURCE_SUPPORTS_TASKS;
+ }
+ }
+
+ return supports;
+}
+
+static gchar *
+e_webdav_session_extract_nonempty (xmlXPathContextPtr xpath_ctx,
+ const gchar *xpath_prop_prefix,
+ const gchar *prop,
+ const gchar *alternative_prop)
+{
+ gchar *value;
+
+ g_return_val_if_fail (xpath_ctx != NULL, NULL);
+ g_return_val_if_fail (xpath_prop_prefix != NULL, NULL);
+ g_return_val_if_fail (prop != NULL, NULL);
+
+ value = e_xml_xpath_eval_as_string (xpath_ctx, "%s/%s", xpath_prop_prefix, prop);
+ if (!value && alternative_prop)
+ value = e_xml_xpath_eval_as_string (xpath_ctx, "%s/%s", xpath_prop_prefix, alternative_prop);
+ if (!value)
+ return NULL;
+
+ if (!*value) {
+ g_free (value);
+ return NULL;
+ }
+
+ return e_webdav_session_maybe_dequote (value);
+}
+
+static gsize
+e_webdav_session_extract_content_length (xmlXPathContextPtr xpath_ctx,
+ const gchar *xpath_prop_prefix)
+{
+ gchar *value;
+ gsize length;
+
+ g_return_val_if_fail (xpath_ctx != NULL, -1);
+ g_return_val_if_fail (xpath_prop_prefix != NULL, -1);
+
+ value = e_webdav_session_extract_nonempty (xpath_ctx, xpath_prop_prefix, "D:getcontentlength", NULL);
+ if (!value)
+ return -1;
+
+ length = g_ascii_strtoll (value, NULL, 10);
+
+ g_free (value);
+
+ return length;
+}
+
+static glong
+e_webdav_session_extract_datetime (xmlXPathContextPtr xpath_ctx,
+ const gchar *xpath_prop_prefix,
+ const gchar *prop,
+ gboolean is_iso_property)
+{
+ gchar *value;
+ GTimeVal tv;
+
+ g_return_val_if_fail (xpath_ctx != NULL, -1);
+ g_return_val_if_fail (xpath_prop_prefix != NULL, -1);
+
+ value = e_webdav_session_extract_nonempty (xpath_ctx, xpath_prop_prefix, prop, NULL);
+ if (!value)
+ return -1;
+
+ if (is_iso_property && !g_time_val_from_iso8601 (value, &tv)) {
+ tv.tv_sec = -1;
+ } else if (!is_iso_property) {
+ tv.tv_sec = camel_header_decode_date (value, NULL);
+ }
+
+ g_free (value);
+
+ return tv.tv_sec;
+}
+
+static gboolean
+e_webdav_session_list_cb (EWebDAVSession *webdav,
+ xmlXPathContextPtr xpath_ctx,
+ const gchar *xpath_prop_prefix,
+ const SoupURI *request_uri,
+ guint status_code,
+ gpointer user_data)
+{
+ GSList **out_resources = user_data;
+
+ g_return_val_if_fail (out_resources != NULL, FALSE);
+ g_return_val_if_fail (request_uri != NULL, FALSE);
+
+ if (!xpath_prop_prefix) {
+ e_xml_xpath_context_register_namespaces (xpath_ctx,
+ "CS", E_WEBDAV_NS_CALENDARSERVER,
+ "C", E_WEBDAV_NS_CALDAV,
+ "A", E_WEBDAV_NS_CARDDAV,
+ "IC", E_WEBDAV_NS_ICAL,
+ NULL);
+
+ return TRUE;
+ }
+
+ if (status_code == SOUP_STATUS_OK) {
+ EWebDAVResource *resource;
+ EWebDAVResourceKind kind;
+ guint32 supports;
+ gchar *href;
+ gchar *etag;
+ gchar *display_name;
+ gchar *content_type;
+ gsize content_length;
+ glong creation_date;
+ glong last_modified;
+ gchar *description;
+ gchar *color;
+
+ kind = e_webdav_session_extract_kind (xpath_ctx, xpath_prop_prefix);
+ if (kind == E_WEBDAV_RESOURCE_KIND_UNKNOWN)
+ return TRUE;
+
+ href = e_xml_xpath_eval_as_string (xpath_ctx, "%s/../../D:href", xpath_prop_prefix);
+ if (!href)
+ return TRUE;
+
+ if (!strstr (href, "://")) {
+ SoupURI *soup_uri;
+ gchar *full_uri;
+
+ soup_uri = soup_uri_copy ((SoupURI *) request_uri);
+ soup_uri_set_path (soup_uri, href);
+ soup_uri_set_user (soup_uri, NULL);
+ soup_uri_set_password (soup_uri, NULL);
+
+ full_uri = soup_uri_to_string (soup_uri, FALSE);
+
+ soup_uri_free (soup_uri);
+ g_free (href);
+
+ href = full_uri;
+ }
+
+ supports = e_webdav_session_extract_supports (xpath_ctx, xpath_prop_prefix);
+ etag = e_webdav_session_extract_nonempty (xpath_ctx, xpath_prop_prefix, "D:getetag",
"CS:getctag");
+ display_name = e_webdav_session_extract_nonempty (xpath_ctx, xpath_prop_prefix,
"D:displayname", NULL);
+ content_type = e_webdav_session_extract_nonempty (xpath_ctx, xpath_prop_prefix,
"D:getcontenttype", NULL);
+ content_length = e_webdav_session_extract_content_length (xpath_ctx, xpath_prop_prefix);
+ creation_date = e_webdav_session_extract_datetime (xpath_ctx, xpath_prop_prefix,
"D:creationdate", TRUE);
+ last_modified = e_webdav_session_extract_datetime (xpath_ctx, xpath_prop_prefix,
"D:getlastmodified", FALSE);
+ description = e_webdav_session_extract_nonempty (xpath_ctx, xpath_prop_prefix,
"C:calendar-description", "A:addressbook-description");
+ color = e_webdav_session_extract_nonempty (xpath_ctx, xpath_prop_prefix, "IC:calendar-color",
NULL);
+
+ resource = e_webdav_resource_new (kind, supports,
+ NULL, /* href */
+ NULL, /* etag */
+ NULL, /* display_name */
+ NULL, /* content_type */
+ content_length,
+ creation_date,
+ last_modified,
+ NULL, /* description */
+ NULL); /* color */
+ resource->href = href;
+ resource->etag = etag;
+ resource->display_name = display_name;
+ resource->content_type = content_type;
+ resource->description = description;
+ resource->color = color;
+
+ *out_resources = g_slist_prepend (*out_resources, resource);
+ }
+
+ return TRUE;
+}
+
+/**
+ * e_webdav_session_list_sync:
+ * @webdav: an #EWebDAVSession
+ * @uri: (nullable): URI to issue the request for, or %NULL to read from #ESource
+ * @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
+ * @error: return location for a #GError, or %NULL
+ *
+ * Lists content of the @uri, or the one stored with the associated #ESource,
+ * which should point to a collection. The @flags influences which properties
+ * are read for the resources.
+ *
+ * The @out_resources is in no particular order.
+ *
+ * Free the returned @out_resources with
+ * g_slist_free_full (resources, e_webdav_resource_free);
+ * when no longer needed.
+ *
+ * Returns: Whether succeeded.
+ *
+ * Since: 3.26
+ **/
+gboolean
+e_webdav_session_list_sync (EWebDAVSession *webdav,
+ const gchar *uri,
+ guint32 flags,
+ GSList **out_resources,
+ GCancellable *cancellable,
+ GError **error)
+{
+ EXmlDocument *xml;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
+ g_return_val_if_fail (out_resources != NULL, FALSE);
+
+ *out_resources = NULL;
+
+ xml = e_xml_document_new (E_WEBDAV_NS_DAV, "propfind");
+ g_return_val_if_fail (xml != NULL, FALSE);
+
+ e_xml_document_start_element (xml, NULL, "prop");
+
+ e_xml_document_start_element (xml, NULL, "resourcetype");
+ e_xml_document_end_element (xml);
+
+ if ((flags & E_WEBDAV_LIST_SUPPORTS) != 0 ||
+ (flags & E_WEBDAV_LIST_DESCRIPTION) != 0 ||
+ (flags & E_WEBDAV_LIST_COLOR) != 0) {
+ e_xml_document_add_namespaces (xml, "C", E_WEBDAV_NS_CALDAV, NULL);
+ }
+
+ if ((flags & E_WEBDAV_LIST_SUPPORTS) != 0) {
+ e_xml_document_start_element (xml, E_WEBDAV_NS_CALDAV, "supported-calendar-component-set");
+ e_xml_document_end_element (xml);
+ }
+
+ if ((flags & E_WEBDAV_LIST_DISPLAY_NAME) != 0) {
+ e_xml_document_start_element (xml, NULL, "displayname");
+ e_xml_document_end_element (xml);
+ }
+
+ if ((flags & E_WEBDAV_LIST_ETAG) != 0) {
+ e_xml_document_start_element (xml, NULL, "getetag");
+ e_xml_document_end_element (xml);
+
+ e_xml_document_add_namespaces (xml, "CS", E_WEBDAV_NS_CALENDARSERVER, NULL);
+
+ e_xml_document_start_element (xml, E_WEBDAV_NS_CALENDARSERVER, "getctag");
+ e_xml_document_end_element (xml);
+ }
+
+ if ((flags & E_WEBDAV_LIST_CONTENT_TYPE) != 0) {
+ e_xml_document_start_element (xml, NULL, "getcontenttype");
+ e_xml_document_end_element (xml);
+ }
+
+ if ((flags & E_WEBDAV_LIST_CONTENT_LENGTH) != 0) {
+ e_xml_document_start_element (xml, NULL, "getcontentlength");
+ e_xml_document_end_element (xml);
+ }
+
+ if ((flags & E_WEBDAV_LIST_CREATION_DATE) != 0) {
+ e_xml_document_start_element (xml, NULL, "creationdate");
+ e_xml_document_end_element (xml);
+ }
+
+ if ((flags & E_WEBDAV_LIST_LAST_MODIFIED) != 0) {
+ e_xml_document_start_element (xml, NULL, "getlastmodified");
+ e_xml_document_end_element (xml);
+ }
+
+ if ((flags & E_WEBDAV_LIST_DESCRIPTION) != 0) {
+ e_xml_document_start_element (xml, E_WEBDAV_NS_CALDAV, "calendar-description");
+ e_xml_document_end_element (xml);
+
+ e_xml_document_add_namespaces (xml, "A", E_WEBDAV_NS_CARDDAV, NULL);
+
+ e_xml_document_start_element (xml, E_WEBDAV_NS_CARDDAV, "addressbook-description");
+ e_xml_document_end_element (xml);
+ }
+
+ if ((flags & E_WEBDAV_LIST_COLOR) != 0) {
+ e_xml_document_add_namespaces (xml, "IC", E_WEBDAV_NS_ICAL, NULL);
+
+ e_xml_document_start_element (xml, E_WEBDAV_NS_ICAL, "calendar-color");
+ e_xml_document_end_element (xml);
+ }
+
+ e_xml_document_end_element (xml); /* prop */
+
+ success = e_webdav_session_propfind_sync (webdav, uri, E_WEBDAV_DEPTH_1, xml,
+ e_webdav_session_list_cb, out_resources, cancellable, error);
+
+ g_object_unref (xml);
+
+ /* Ensure display name in case the resource doesn't have any */
+ if (success && (flags & E_WEBDAV_LIST_DISPLAY_NAME) != 0) {
+ GSList *link;
+
+ for (link = *out_resources; link; link = g_slist_next (link)) {
+ EWebDAVResource *resource = link->data;
+
+ if (resource && !resource->display_name && resource->href) {
+ gchar *href_decoded = soup_uri_decode (resource->href);
+
+ if (href_decoded) {
+ gchar *cp;
+
+ /* Use the last non-empty path segment. */
+ while ((cp = strrchr (href_decoded, '/')) != NULL) {
+ if (*(cp + 1) == '\0')
+ *cp = '\0';
+ else {
+ resource->display_name = g_strdup (cp + 1);
+ break;
+ }
+ }
+ }
+
+ g_free (href_decoded);
+ }
+ }
+ }
+
+ if (success) {
+ /* Honour order returned by the server, even it's not significant. */
+ *out_resources = g_slist_reverse (*out_resources);
+ }
+
+ return success;
+}
diff --git a/src/libedataserver/e-webdav-session.h b/src/libedataserver/e-webdav-session.h
index 280ab72..219f93b 100644
--- a/src/libedataserver/e-webdav-session.h
+++ b/src/libedataserver/e-webdav-session.h
@@ -23,10 +23,12 @@
#define E_WEBDAV_SESSION_H
#include <glib.h>
+#include <libxml/xpath.h>
#include <libedataserver/e-data-server-util.h>
#include <libedataserver/e-soup-session.h>
#include <libedataserver/e-source.h>
+#include <libedataserver/e-xml-document.h>
/* Standard GObject macros */
#define E_TYPE_WEBDAV_SESSION \
@@ -49,11 +51,141 @@
G_BEGIN_DECLS
+#define E_WEBDAV_CAPABILITY_CLASS_1 "1"
+#define E_WEBDAV_CAPABILITY_CLASS_2 "2"
+#define E_WEBDAV_CAPABILITY_CLASS_3 "3"
+#define E_WEBDAV_CAPABILITY_ACCESS_CONTROL "access-control"
+#define E_WEBDAV_CAPABILITY_BIND "bind"
+#define E_WEBDAV_CAPABILITY_EXTENDED_MKCOL "extended-mkcol"
+#define E_WEBDAV_CAPABILITY_ADDRESSBOOK "addressbook"
+#define E_WEBDAV_CAPABILITY_CALENDAR_ACCESS "calendar-access"
+#define E_WEBDAV_CAPABILITY_CALENDAR_SCHEDULE "calendar-schedule"
+#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_DEPTH_0 "0"
+#define E_WEBDAV_DEPTH_1 "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_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 enum {
+ E_WEBDAV_RESOURCE_KIND_UNKNOWN,
+ E_WEBDAV_RESOURCE_KIND_ADDRESSBOOK,
+ E_WEBDAV_RESOURCE_KIND_CALENDAR,
+ E_WEBDAV_RESOURCE_KIND_PRINCIPAL,
+ E_WEBDAV_RESOURCE_KIND_COLLECTION,
+ E_WEBDAV_RESOURCE_KIND_RESOURCE
+} EWebDAVResourceKind;
+
+typedef enum {
+ E_WEBDAV_RESOURCE_SUPPORTS_NONE = 0,
+ 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
+} EWebDAVResourceSupports;
+
+typedef struct _EWebDAVResource {
+ EWebDAVResourceKind kind;
+ guint32 supports;
+ gchar *href;
+ gchar *etag;
+ gchar *display_name;
+ gchar *content_type;
+ gsize content_length;
+ glong creation_date;
+ glong last_modified;
+ gchar *description;
+ gchar *color;
+} EWebDAVResource;
+
+GType e_webdav_resource_get_type (void) G_GNUC_CONST;
+EWebDAVResource *
+ e_webdav_resource_new (EWebDAVResourceKind kind,
+ guint32 supports,
+ const gchar *href,
+ const gchar *etag,
+ const gchar *display_name,
+ const gchar *content_type,
+ gsize content_length,
+ glong creation_date,
+ glong last_modified,
+ const gchar *description,
+ const gchar *color);
+EWebDAVResource *
+ e_webdav_resource_copy (const EWebDAVResource *resource);
+void e_webdav_resource_free (gpointer ptr /* EWebDAVResource * */);
+
+typedef enum {
+ E_WEBDAV_LIST_ALL = 0xFFFFFFFF,
+ E_WEBDAV_LIST_NONE = 0,
+ E_WEBDAV_LIST_SUPPORTS = 1 << 0,
+ E_WEBDAV_LIST_ETAG = 1 << 1,
+ E_WEBDAV_LIST_DISPLAY_NAME = 1 << 2,
+ E_WEBDAV_LIST_CONTENT_TYPE = 1 << 3,
+ E_WEBDAV_LIST_CONTENT_LENGTH = 1 << 4,
+ E_WEBDAV_LIST_CREATION_DATE = 1 << 5,
+ E_WEBDAV_LIST_LAST_MODIFIED = 1 << 6,
+ E_WEBDAV_LIST_DESCRIPTION = 1 << 7,
+ E_WEBDAV_LIST_COLOR = 1 << 8
+} EWebDAVListFlags;
+
typedef struct _EWebDAVSession EWebDAVSession;
typedef struct _EWebDAVSessionClass EWebDAVSessionClass;
typedef struct _EWebDAVSessionPrivate EWebDAVSessionPrivate;
/**
+ * EWebDAVPropfindFunc:
+ * @webdav: an #EWebDAVSession
+ * @xpath_ctx: an #xmlXPathContextPtr
+ * @xpath_prop_prefix: (nullable): an XPath prefix for the current prop element, without trailing forward
slash
+ * @request_uri: a #SoupURI, containing the request URI, maybe redirected by the server
+ * @status_code: an HTTP status code for this property
+ * @user_data: user data, as passed to e_webdav_session_propfind_sync()
+ *
+ * A callback function for e_webdav_session_propfind_sync().
+ *
+ * The @xpath_prop_prefix can be %NULL only once, for the first time,
+ * 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 the function will have @xpath_prop_prefix non-%NULL.
+ *
+ * Returns: %TRUE to continue traversal of the returned multistatus response,
+ * %FALSE otherwise.
+ *
+ * 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);
+
+/**
* EWebDAVSession:
*
* Contains only private data that should be read and manipulated using the
@@ -77,6 +209,85 @@ struct _EWebDAVSessionClass {
GType e_webdav_session_get_type (void) G_GNUC_CONST;
EWebDAVSession *e_webdav_session_new (ESource *source);
+gboolean e_webdav_session_options_sync (EWebDAVSession *webdav,
+ const gchar *uri,
+ GHashTable **out_capabilities,
+ GHashTable **out_allows,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_webdav_session_propfind_sync (EWebDAVSession *webdav,
+ const gchar *uri,
+ const gchar *depth,
+ const EXmlDocument *xml,
+ EWebDAVPropfindFunc func,
+ gpointer func_user_data,
+ GCancellable *cancellable,
+ GError **error);
+/*gboolean e_webdav_session_proppatch_sync (EWebDAVSession *webdav,
+ const gchar *uri,
+ const EXmlDocument *xml,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_webdav_session_mkcol_sync (EWebDAVSession *webdav,
+ const gchar *uri,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_webdav_session_get_sync (EWebDAVSession *webdav,
+ const gchar *uri,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_webdav_session_put_sync (EWebDAVSession *webdav,
+ const gchar *uri,
+ const gchar *content_type,
+ const gchar *bytes,
+ gsize length,
+ gchar **out_redirected_uri,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_webdav_session_delete_sync (EWebDAVSession *webdav,
+ const gchar *uri,
+ GCancellable *cancellable,
+ GError **error);
+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);
+gboolean e_webdav_session_move_sync (EWebDAVSession *webdav,
+ const gchar *source_uri,
+ const gchar *destination_uri,
+ gboolean can_overwrite,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_webdav_session_lock_sync (EWebDAVSession *webdav,
+ const gchar *uri,
+ const gchar *depth,
+ const gchar *lock_token,
+ guint32 lock_timeout,
+ const EXmlDocument *xml,
+ gchar **out_lock_token,
+ EXmlDocument **out_xml_response,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_webdav_session_unlock_sync (EWebDAVSession *webdav,
+ const gchar *uri,
+ const gchar *lock_token,
+ GCancellable *cancellable,
+ GError **error);*/
+
+gboolean e_webdav_session_getctag_sync (EWebDAVSession *webdav,
+ const gchar *uri,
+ gchar **out_ctag,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_webdav_session_list_sync (EWebDAVSession *webdav,
+ const gchar *uri,
+ guint32 flags, /* bit-or of EWebDAVListFlags */
+ GSList **out_resources, /* EWebDAVResource * */
+ GCancellable *cancellable,
+ GError **error);
G_END_DECLS
diff --git a/src/libedataserver/e-xml-document.c b/src/libedataserver/e-xml-document.c
new file mode 100644
index 0000000..9212df4
--- /dev/null
+++ b/src/libedataserver/e-xml-document.c
@@ -0,0 +1,601 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2017 Red Hat, Inc. (www.redhat.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION: e-xml-document
+ * @include: libedataserver/libedataserver.h
+ * @short_description: An XML document wrapper
+ *
+ * The #EXmlDocument class wraps creation of XML documents.
+ **/
+
+#include "evolution-data-server-config.h"
+
+#include <string.h>
+
+#include "e-xml-document.h"
+
+struct _EXmlDocumentPrivate {
+ xmlDocPtr doc;
+ xmlNodePtr root;
+ xmlNodePtr current_element;
+
+ GHashTable *namespaces_by_href; /* gchar *ns_href ~> xmlNsPtr */
+};
+
+G_DEFINE_TYPE (EXmlDocument, e_xml_document, G_TYPE_OBJECT)
+
+static void
+e_xml_document_finalize (GObject *object)
+{
+ EXmlDocument *xml = E_XML_DOCUMENT (object);
+
+ if (xml->priv->doc) {
+ xmlFreeDoc (xml->priv->doc);
+ xml->priv->doc = NULL;
+ }
+
+ xml->priv->root = NULL;
+ xml->priv->current_element = NULL;
+
+ if (xml->priv->namespaces_by_href) {
+ g_hash_table_destroy (xml->priv->namespaces_by_href);
+ xml->priv->namespaces_by_href = NULL;
+ }
+
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (e_xml_document_parent_class)->finalize (object);
+}
+
+static void
+e_xml_document_class_init (EXmlDocumentClass *klass)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (klass, sizeof (EXmlDocumentPrivate));
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->finalize = e_xml_document_finalize;
+}
+
+static void
+e_xml_document_init (EXmlDocument *xml)
+{
+ xml->priv = G_TYPE_INSTANCE_GET_PRIVATE (xml, E_TYPE_XML_DOCUMENT, EXmlDocumentPrivate);
+
+ xml->priv->doc = xmlNewDoc ((const xmlChar *) "1.0");
+ g_return_if_fail (xml->priv->doc != NULL);
+
+ xml->priv->doc->encoding = xmlCharStrdup ("UTF-8");
+
+ xml->priv->namespaces_by_href = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+}
+
+/**
+ * e_xml_document_new:
+ * @ns_href: (nullable): default namespace href to use, or %NULL
+ * @root_element: root element name
+ *
+ * Creates a new #EXmlDocument with root element @root_element and optionally
+ * also with set default namespace @ns_href.
+ *
+ * Returns: (transfer full): a new #EXmlDocument; free it with g_object_unref(),
+ * when no longer needed.
+ *
+ * Since: 3.26
+ **/
+EXmlDocument *
+e_xml_document_new (const gchar *ns_href,
+ const gchar *root_element)
+{
+ EXmlDocument *xml;
+
+ g_return_val_if_fail (root_element != NULL, NULL);
+ g_return_val_if_fail (*root_element, NULL);
+
+ xml = g_object_new (E_TYPE_XML_DOCUMENT, NULL);
+
+ xml->priv->root = xmlNewDocNode (xml->priv->doc, NULL, (const xmlChar *) root_element, NULL);
+ if (ns_href) {
+ xmlNsPtr ns;
+
+ ns = xmlNewNs (xml->priv->root, (const xmlChar *) ns_href, NULL);
+ g_warn_if_fail (ns != NULL);
+
+ xmlSetNs (xml->priv->root, ns);
+
+ if (ns)
+ g_hash_table_insert (xml->priv->namespaces_by_href, g_strdup (ns_href), ns);
+ }
+
+ xmlDocSetRootElement (xml->priv->doc, xml->priv->root);
+
+ xml->priv->current_element = xml->priv->root;
+
+ return xml;
+}
+
+/**
+ * e_xml_document_get_xmldoc:
+ * @xml: an #EXmlDocument
+ *
+ * Returns: (transfer none): Underlying #xmlDocPtr.
+ *
+ * Since: 3.26
+ **/
+xmlDocPtr
+e_xml_document_get_xmldoc (EXmlDocument *xml)
+{
+ g_return_val_if_fail (E_IS_XML_DOCUMENT (xml), NULL);
+
+ return xml->priv->doc;
+}
+
+/**
+ * e_xml_document_get_content:
+ * @xml: an #EXmlDocument
+ * @out_length: (out) (nullable): optional return location for length of the content, or %NULL
+ *
+ * Gets content of the @xml as string. The string is nul-terminated, but
+ * if @out_length is also provided, then it doesn't contain this additional
+ * nul character.
+ *
+ * Returns: (transfer full): Content of the @xml as newly allocated string.
+ * Free it with g_free(), when no longer needed.
+ *
+ * Since: 3.26
+ **/
+gchar *
+e_xml_document_get_content (const EXmlDocument *xml,
+ gsize *out_length)
+{
+ xmlOutputBufferPtr xmlbuffer;
+ gsize length;
+ gchar *text;
+
+ g_return_val_if_fail (E_IS_XML_DOCUMENT (xml), NULL);
+
+ xmlbuffer = xmlAllocOutputBuffer (NULL);
+ xmlNodeDumpOutput (xmlbuffer, xml->priv->doc, xml->priv->root, 0, 1, NULL);
+ xmlOutputBufferFlush (xmlbuffer);
+
+#ifdef LIBXML2_NEW_BUFFER
+ length = xmlOutputBufferGetSize (xmlbuffer);
+ text = g_strndup ((const gchar *) xmlOutputBufferGetContent (xmlbuffer), length);
+#else
+ length = xmlbuffer->buffer->use;
+ text = g_strndup ((const gchar *) xmlbuffer->buffer->content, length);
+#endif
+
+ xmlOutputBufferClose (xmlbuffer);
+
+ if (out_length)
+ *out_length = length;
+
+ return text;
+}
+
+/**
+ * e_xml_document_add_namespaces:
+ * @xml: an #EXmlDocument
+ * @ns_prefix: namespace prefix to use for this namespace
+ * @ns_href: namespace href
+ * @...: %NULL-terminated pairs of (ns_prefix, ns_href)
+ *
+ * Adds one or more namespaces to @xml, which can be referenced
+ * later by @ns_href. The caller should take care that neither
+ * used @ns_prefix, nor @ns_href, is already used by @xml.
+ *
+ * Since: 3.26
+ **/
+void
+e_xml_document_add_namespaces (EXmlDocument *xml,
+ const gchar *ns_prefix,
+ const gchar *ns_href,
+ ...)
+{
+ xmlNsPtr ns;
+ va_list va;
+
+ g_return_if_fail (E_IS_XML_DOCUMENT (xml));
+ g_return_if_fail (ns_prefix != NULL);
+ g_return_if_fail (xml->priv->root != NULL);
+
+ if (!ns_href)
+ ns_href = "";
+
+ if (!g_hash_table_contains (xml->priv->namespaces_by_href, ns_href)) {
+ ns = xmlNewNs (xml->priv->root, (const xmlChar *) ns_href, (const xmlChar *) ns_prefix);
+ g_return_if_fail (ns != NULL);
+
+ g_hash_table_insert (xml->priv->namespaces_by_href, g_strdup (ns_href), ns);
+ }
+
+ va_start (va, ns_href);
+
+ while (ns_prefix = va_arg (va, const gchar *), ns_prefix) {
+ ns_href = va_arg (va, const gchar *);
+ if (!ns_href)
+ ns_href = "";
+
+ if (!g_hash_table_contains (xml->priv->namespaces_by_href, ns_href)) {
+ ns = xmlNewNs (xml->priv->root, (const xmlChar *) ns_href, (const xmlChar *)
ns_prefix);
+ g_return_if_fail (ns != NULL);
+
+ g_hash_table_insert (xml->priv->namespaces_by_href, g_strdup (ns_href), ns);
+ }
+ }
+
+ va_end (va);
+}
+
+static gchar *
+e_xml_document_number_to_alpha (gint number)
+{
+ GString *alpha;
+
+ g_return_val_if_fail (number >= 0, NULL);
+
+ alpha = g_string_new ("");
+ g_string_append_c (alpha, 'A' + (number % 26));
+
+ while (number = number / 26, number > 0) {
+ g_string_prepend_c (alpha, 'A' + (number % 26));
+ }
+
+ return g_string_free (alpha, FALSE);
+}
+
+static gchar *
+e_xml_document_gen_ns_prefix (EXmlDocument *xml,
+ const gchar *ns_href)
+{
+ GHashTable *prefixes;
+ GHashTableIter iter;
+ gpointer value;
+ gchar *new_prefix = NULL;
+ const gchar *ptr;
+ gint counter = 0, n_prefixes;
+
+ g_return_val_if_fail (E_IS_XML_DOCUMENT (xml), NULL);
+ g_return_val_if_fail (ns_href && *ns_href, NULL);
+
+ if (!ns_href)
+ return NULL;
+
+ prefixes = g_hash_table_new (g_str_hash, g_str_equal);
+
+ g_hash_table_iter_init (&iter, xml->priv->namespaces_by_href);
+ while (g_hash_table_iter_next (&iter, NULL, &value)) {
+ xmlNsPtr ns = value;
+
+ if (ns && ns->prefix)
+ g_hash_table_insert (prefixes, (gpointer) ns->prefix, NULL);
+ }
+
+ ptr = strrchr (ns_href, ':');
+
+ /* the ns_href ends with ':' */
+ if (ptr && !ptr[1] && g_ascii_isalpha (ns_href[0])) {
+ new_prefix = g_strndup (ns_href, 1);
+ } else if (ptr && strchr (ns_href, ':') < ptr && g_ascii_isalpha (ptr[1])) {
+ new_prefix = g_strndup (ptr + 1, 1);
+ } else if (g_str_has_prefix (ns_href, "http://") &&
+ g_ascii_isalpha (ns_href[7])) {
+ new_prefix = g_strndup (ns_href + 7, 1);
+ }
+
+ n_prefixes = g_hash_table_size (prefixes);
+
+ while (!new_prefix || g_hash_table_contains (prefixes, new_prefix)) {
+ g_free (new_prefix);
+
+ if (counter > n_prefixes + 2) {
+ new_prefix = NULL;
+ break;
+ }
+
+ new_prefix = e_xml_document_number_to_alpha (counter);
+ counter++;
+ }
+
+ g_hash_table_destroy (prefixes);
+
+ return new_prefix;
+}
+
+static xmlNsPtr
+e_xml_document_ensure_namespace (EXmlDocument *xml,
+ const gchar *ns_href)
+{
+ xmlNsPtr ns;
+ gchar *ns_prefix;
+
+ g_return_val_if_fail (E_IS_XML_DOCUMENT (xml), NULL);
+
+ if (!ns_href)
+ return NULL;
+
+ ns = g_hash_table_lookup (xml->priv->namespaces_by_href, ns_href);
+ if (ns || !*ns_href)
+ return ns;
+
+ ns_prefix = e_xml_document_gen_ns_prefix (xml, ns_href);
+
+ e_xml_document_add_namespaces (xml, ns_prefix, ns_href, NULL);
+
+ g_free (ns_prefix);
+
+ return g_hash_table_lookup (xml->priv->namespaces_by_href, ns_href);
+}
+
+/**
+ * e_xml_document_start_element:
+ * @xml: an #EXmlDocument
+ * @ns_href: (nullable): optional namespace href for the new element, or %NULL
+ * @name: name of the new element
+ *
+ * Starts a new non-text element as a child of the current element.
+ * Each such call should be ended with corresponding e_xml_document_end_element().
+ * Use %NULL @ns_href, to use the default namespace, otherwise either previously
+ * added namespace with the same href from e_xml_document_add_namespaces() is picked,
+ * or a new namespace with generated prefix is added.
+ *
+ * To start a text node use e_xml_document_start_text_element().
+ *
+ * Since: 3.26
+ **/
+void
+e_xml_document_start_element (EXmlDocument *xml,
+ const gchar *ns_href,
+ const gchar *name)
+{
+ g_return_if_fail (E_IS_XML_DOCUMENT (xml));
+ g_return_if_fail (name != NULL);
+ g_return_if_fail (*name);
+ g_return_if_fail (xml->priv->current_element != NULL);
+
+ xml->priv->current_element = xmlNewChild (xml->priv->current_element,
+ e_xml_document_ensure_namespace (xml, ns_href), (const xmlChar *) name, NULL);
+}
+
+/**
+ * e_xml_document_start_text_element:
+ * @xml: an #EXmlDocument
+ * @ns_href: (nullable): optional namespace href for the new element, or %NULL
+ * @name: name of the new element
+ *
+ * Starts a new text element as a child of the current element.
+ * Each such call should be ended with corresponding e_xml_document_end_element().
+ * Use %NULL @ns_href, to use the default namespace, otherwise either previously
+ * added namespace with the same href from e_xml_document_add_namespaces() is picked,
+ * or a new namespace with generated prefix is added.
+ *
+ * To start a non-text node use e_xml_document_start_element().
+ *
+ * Since: 3.26
+ **/
+void
+e_xml_document_start_text_element (EXmlDocument *xml,
+ const gchar *ns_href,
+ const gchar *name)
+{
+ g_return_if_fail (E_IS_XML_DOCUMENT (xml));
+ g_return_if_fail (name != NULL);
+ g_return_if_fail (*name);
+ g_return_if_fail (xml->priv->current_element != NULL);
+
+ xml->priv->current_element = xmlNewTextChild (xml->priv->current_element,
+ e_xml_document_ensure_namespace (xml, ns_href), (const xmlChar *) name, NULL);
+}
+
+/**
+ * e_xml_document_end_element:
+ * @xml: an #EXmlDocument
+ *
+ * This is a pair function for e_xml_document_start_element() and
+ * e_xml_document_start_text_element(), which changes current
+ * element to the parent of that element.
+ *
+ * Since: 3.26
+ **/
+void
+e_xml_document_end_element (EXmlDocument *xml)
+{
+ g_return_if_fail (E_IS_XML_DOCUMENT (xml));
+ g_return_if_fail (xml->priv->current_element != NULL);
+ g_return_if_fail (xml->priv->current_element != xml->priv->root);
+
+ xml->priv->current_element = xml->priv->current_element->parent;
+}
+
+/**
+ * e_xml_document_add_attribute:
+ * @xml: an #EXmlDocument
+ * @ns_href: (nullable): optional namespace href for the new attribute, or %NULL
+ * @name: name of the attribute
+ * @value: value of the attribute
+ *
+ * Adds a new attribute to the current element.
+ * Use %NULL @ns_href, to use the default namespace, otherwise either previously
+ * added namespace with the same href from e_xml_document_add_namespaces() is picked,
+ * or a new namespace with generated prefix is added.
+ *
+ * Since: 3.26
+ **/
+void
+e_xml_document_add_attribute (EXmlDocument *xml,
+ const gchar *ns_href,
+ const gchar *name,
+ const gchar *value)
+{
+ g_return_if_fail (E_IS_XML_DOCUMENT (xml));
+ g_return_if_fail (xml->priv->current_element != NULL);
+ g_return_if_fail (name != NULL);
+ g_return_if_fail (value != NULL);
+
+ xmlNewNsProp (
+ xml->priv->current_element,
+ e_xml_document_ensure_namespace (xml, ns_href),
+ (const xmlChar *) name,
+ (const xmlChar *) value);
+}
+
+/**
+ * e_xml_document_write_int:
+ * @xml: an #EXmlDocument
+ * @value: value to write as the content
+ *
+ * Writes @value as content of the current element.
+ *
+ * Since: 3.26
+ **/
+void
+e_xml_document_write_int (EXmlDocument *xml,
+ gint64 value)
+{
+ gchar *strvalue;
+
+ g_return_if_fail (E_IS_XML_DOCUMENT (xml));
+ g_return_if_fail (xml->priv->current_element != NULL);
+
+ strvalue = g_strdup_printf ("%" G_GINT64_FORMAT, value);
+ e_xml_document_write_string (xml, strvalue);
+ g_free (strvalue);
+}
+
+/**
+ * e_xml_document_write_double:
+ * @xml: an #EXmlDocument
+ * @value: value to write as the content
+ *
+ * Writes @value as content of the current element.
+ *
+ * Since: 3.26
+ **/
+void
+e_xml_document_write_double (EXmlDocument *xml,
+ gdouble value)
+{
+ gchar *strvalue;
+
+ g_return_if_fail (E_IS_XML_DOCUMENT (xml));
+ g_return_if_fail (xml->priv->current_element != NULL);
+
+ strvalue = g_strdup_printf ("%f", value);
+ e_xml_document_write_string (xml, strvalue);
+ g_free (strvalue);
+}
+
+/**
+ * e_xml_document_write_base64:
+ * @xml: an #EXmlDocument
+ * @value: value to write as the content
+ * @len: length of @value
+ *
+ * Writes @value of length @len, encoded to base64, as content of the current element.
+ *
+ * Since: 3.26
+ **/
+void
+e_xml_document_write_base64 (EXmlDocument *xml,
+ const gchar *value,
+ gint len)
+{
+ gchar *strvalue;
+
+ g_return_if_fail (E_IS_XML_DOCUMENT (xml));
+ g_return_if_fail (xml->priv->current_element != NULL);
+ g_return_if_fail (value != NULL);
+
+ strvalue = g_base64_encode ((const guchar *) value, len);
+ e_xml_document_write_string (xml, strvalue);
+ g_free (strvalue);
+}
+
+/**
+ * e_xml_document_write_time:
+ * @xml: an #EXmlDocument
+ * @value: value to write as the content
+ *
+ * Writes @value in ISO 8601 format as content of the current element.
+ *
+ * Since: 3.26
+ **/
+void
+e_xml_document_write_time (EXmlDocument *xml,
+ time_t value)
+{
+ GTimeVal tv;
+ gchar *strvalue;
+
+ g_return_if_fail (E_IS_XML_DOCUMENT (xml));
+ g_return_if_fail (xml->priv->current_element != NULL);
+
+ tv.tv_usec = 0;
+ tv.tv_sec = value;
+
+ strvalue = g_time_val_to_iso8601 (&tv);
+ e_xml_document_write_string (xml, strvalue);
+ g_free (strvalue);
+}
+
+/**
+ * e_xml_document_write_string:
+ * @xml: an #EXmlDocument
+ * @value: value to write as the content
+ *
+ * Writes @value as content of the current element.
+ *
+ * Since: 3.26
+ **/
+void
+e_xml_document_write_string (EXmlDocument *xml,
+ const gchar *value)
+{
+ g_return_if_fail (E_IS_XML_DOCUMENT (xml));
+ g_return_if_fail (xml->priv->current_element != NULL);
+ g_return_if_fail (value != NULL);
+
+ xmlNodeAddContent (
+ xml->priv->current_element,
+ (const xmlChar *) value);
+}
+
+/**
+ * e_xml_document_write_buffer:
+ * @xml: an #EXmlDocument
+ * @value: value to write as the content
+ * @len: length of @value
+ *
+ * Writes @value of length @len as content of the current element.
+ *
+ * Since: 3.26
+ **/
+void
+e_xml_document_write_buffer (EXmlDocument *xml,
+ const gchar *value,
+ gint len)
+{
+ g_return_if_fail (E_IS_XML_DOCUMENT (xml));
+ g_return_if_fail (xml->priv->current_element != NULL);
+ g_return_if_fail (value != NULL);
+
+ xmlNodeAddContentLen (
+ xml->priv->current_element,
+ (const xmlChar *) value, len);
+}
diff --git a/src/libedataserver/e-xml-document.h b/src/libedataserver/e-xml-document.h
new file mode 100644
index 0000000..afadf2e
--- /dev/null
+++ b/src/libedataserver/e-xml-document.h
@@ -0,0 +1,115 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2017 Red Hat, Inc. (www.redhat.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined (__LIBEDATASERVER_H_INSIDE__) && !defined (LIBEDATASERVER_COMPILATION)
+#error "Only <libedataserver/libedataserver.h> should be included directly."
+#endif
+
+#ifndef E_XML_DOCUMENT_H
+#define E_XML_DOCUMENT_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <libxml/parser.h>
+
+/* Standard GObject macros */
+#define E_TYPE_XML_DOCUMENT \
+ (e_xml_document_get_type ())
+#define E_XML_DOCUMENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_XML_DOCUMENT, EXmlDocument))
+#define E_XML_DOCUMENT_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_XML_DOCUMENT, EXmlDocumentClass))
+#define E_IS_XML_DOCUMENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_XML_DOCUMENT))
+#define E_IS_XML_DOCUMENT_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_XML_DOCUMENT))
+#define E_XML_DOCUMENT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_XML_DOCUMENT, EXmlDocumentClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EXmlDocument EXmlDocument;
+typedef struct _EXmlDocumentClass EXmlDocumentClass;
+typedef struct _EXmlDocumentPrivate EXmlDocumentPrivate;
+
+/**
+ * EXmlDocument:
+ *
+ * Contains only private data that should be read and manipulated using the
+ * functions below.
+ *
+ * Since: 3.26
+ **/
+struct _EXmlDocument {
+ /*< private >*/
+ GObject parent;
+ EXmlDocumentPrivate *priv;
+};
+
+struct _EXmlDocumentClass {
+ GObjectClass parent_class;
+
+ /* Padding for future expansion */
+ gpointer reserved[10];
+};
+
+GType e_xml_document_get_type (void) G_GNUC_CONST;
+
+EXmlDocument * e_xml_document_new (const gchar *ns_href,
+ const gchar *root_element);
+xmlDocPtr e_xml_document_get_xmldoc (EXmlDocument *xml);
+gchar * e_xml_document_get_content (const EXmlDocument *xml,
+ gsize *out_length);
+void e_xml_document_add_namespaces (EXmlDocument *xml,
+ const gchar *ns_prefix,
+ const gchar *ns_href,
+ ...) G_GNUC_NULL_TERMINATED;
+void e_xml_document_start_element (EXmlDocument *xml,
+ const gchar *ns_href,
+ const gchar *name);
+void e_xml_document_start_text_element
+ (EXmlDocument *xml,
+ const gchar *ns_href,
+ const gchar *name);
+void e_xml_document_end_element (EXmlDocument *xml);
+void e_xml_document_add_attribute (EXmlDocument *xml,
+ const gchar *ns_href,
+ const gchar *name,
+ const gchar *value);
+void e_xml_document_write_int (EXmlDocument *xml,
+ gint64 value);
+void e_xml_document_write_double (EXmlDocument *xml,
+ gdouble value);
+void e_xml_document_write_base64 (EXmlDocument *xml,
+ const gchar *value,
+ gint len);
+void e_xml_document_write_time (EXmlDocument *xml,
+ time_t value);
+void e_xml_document_write_string (EXmlDocument *xml,
+ const gchar *value);
+void e_xml_document_write_buffer (EXmlDocument *xml,
+ const gchar *value,
+ gint len);
+
+G_END_DECLS
+
+#endif /* E_XML_DOCUMENT_H */
diff --git a/src/libedataserver/e-xml-utils.c b/src/libedataserver/e-xml-utils.c
index 56f0f66..a02c327 100644
--- a/src/libedataserver/e-xml-utils.c
+++ b/src/libedataserver/e-xml-utils.c
@@ -28,6 +28,7 @@
#include <libxml/parser.h>
#include <libxml/tree.h>
+#include <libxml/xpathInternals.h>
#include <glib/gstdio.h>
@@ -177,3 +178,267 @@ e_xml_get_child_by_name (const xmlNode *parent,
return NULL;
}
+/**
+ * e_xml_parse_data:
+ * @data: an XML data
+ * @length: (length-of data): length of data, should be greated than zero
+ *
+ * Parses XML data into an #xmlDocPtr. Free returned pointer
+ * with xmlFreeDoc(), when no longer needed.
+ *
+ * Returns: (nullable) (transfer full): a new #xmlDocPtr with parsed @data,
+ * or %NULL on error.
+ *
+ * Since: 3.26
+ **/
+xmlDocPtr
+e_xml_parse_data (const gchar *data,
+ gsize length)
+{
+ g_return_val_if_fail (data != NULL, NULL);
+ g_return_val_if_fail (length > 0, NULL);
+
+ return xmlReadMemory (data, length, "data.xml", NULL, 0);
+}
+
+/**
+ * e_xml_new_xpath_context_with_namespaces:
+ * @doc: an #xmlDocPtr
+ * @...: %NULL-terminated list of pairs (prefix, href) with namespaces
+ *
+ * Creates a new #xmlXPathContextPtr on @doc with preregistered
+ * namespaces. The namepsaces are pair of (prefix, href), terminated
+ * by %NULL.
+ *
+ * Returns: (transfer full): a new #xmlXPathContextPtr. Free the returned
+ * pointer with xmlXPathFreeContext() when no longer needed.
+ *
+ * Since: 3.26
+ **/
+xmlXPathContextPtr
+e_xml_new_xpath_context_with_namespaces (xmlDocPtr doc,
+ ...)
+{
+ xmlXPathContextPtr xpath_ctx;
+ va_list va;
+ const gchar *prefix;
+
+ g_return_val_if_fail (doc != NULL, NULL);
+
+ xpath_ctx = xmlXPathNewContext (doc);
+ g_return_val_if_fail (xpath_ctx != NULL, NULL);
+
+ va_start (va, doc);
+
+ while (prefix = va_arg (va, const gchar *), prefix) {
+ const gchar *href = va_arg (va, const gchar *);
+
+ if (!href) {
+ g_warn_if_fail (href != NULL);
+ break;
+ }
+
+ xmlXPathRegisterNs (xpath_ctx, (const xmlChar *) prefix, (const xmlChar *) href);
+ }
+
+ va_end (va);
+
+ return xpath_ctx;
+}
+
+/**
+ * e_xml_xpath_context_register_namespaces:
+ * @xpath_ctx: an #xmlXPathContextPtr
+ * @prefix: namespace prefix
+ * @href: namespace href
+ * @...: %NULL-terminated list of pairs (prefix, href) with additional namespaces
+ *
+ * Registers one or more additional namespaces. It's a caller's error
+ * to try to register a namespace with the same prefix again, unless
+ * the prefix uses the same namespace href.
+ *
+ * Since: 3.26
+ **/
+void
+e_xml_xpath_context_register_namespaces (xmlXPathContextPtr xpath_ctx,
+ const gchar *prefix,
+ const gchar *href,
+ ...)
+{
+ va_list va;
+ const gchar *used_href;
+
+ g_return_if_fail (xpath_ctx != NULL);
+ g_return_if_fail (prefix != NULL);
+ g_return_if_fail (href != NULL);
+
+ used_href = (const gchar *) xmlXPathNsLookup (xpath_ctx, (const xmlChar *) prefix);
+ if (used_href && g_strcmp0 (used_href, href) != 0) {
+ g_warning ("%s: Trying to register prefix '%s' with href '%s', but it already points to '%s'",
+ G_STRFUNC, prefix, href, used_href);
+ } else if (!used_href) {
+ xmlXPathRegisterNs (xpath_ctx, (const xmlChar *) prefix, (const xmlChar *) href);
+ }
+
+ va_start (va, href);
+
+ while (prefix = va_arg (va, const gchar *), prefix) {
+ href = va_arg (va, const gchar *);
+
+ if (!href) {
+ g_warn_if_fail (href != NULL);
+ break;
+ }
+
+ used_href = (const gchar *) xmlXPathNsLookup (xpath_ctx, (const xmlChar *) prefix);
+ if (used_href && g_strcmp0 (used_href, href) != 0) {
+ g_warning ("%s: Trying to register prefix '%s' with href '%s', but it already points
to '%s'",
+ G_STRFUNC, prefix, href, used_href);
+ } else if (!used_href) {
+ xmlXPathRegisterNs (xpath_ctx, (const xmlChar *) prefix, (const xmlChar *) href);
+ }
+ }
+
+ va_end (va);
+}
+
+/**
+ * e_xml_xpath_eval:
+ * @xpath_ctx: an #xmlXPathContextPtr
+ * @format: printf-like format specifier of path to evaluate
+ * @...: arguments for the @format
+ *
+ * Evaluates path specified by @format and returns its #xmlXPathObjectPtr,
+ * in case the path evaluates to a non-empty node set. See also
+ * e_xml_xpath_eval_as_string() which evaluates the path to string.
+ *
+ * Returns: (nullable) (transfer full): a new #xmlXPathObjectPtr which
+ * references given path, or %NULL if path cannot be found or when
+ * it evaluates to an empty nodeset. Free returned pointer with
+ * xmlXPathFreeObject(), when no longer needed.
+ *
+ * Since: 3.26
+ **/
+xmlXPathObjectPtr
+e_xml_xpath_eval (xmlXPathContextPtr xpath_ctx,
+ const gchar *format,
+ ...)
+{
+ xmlXPathObjectPtr object;
+ va_list va;
+ gchar *expr;
+
+ g_return_val_if_fail (xpath_ctx != NULL, NULL);
+ g_return_val_if_fail (format != NULL, NULL);
+
+ va_start (va, format);
+ expr = g_strdup_vprintf (format, va);
+ va_end (va);
+
+ object = xmlXPathEvalExpression ((const xmlChar *) expr, xpath_ctx);
+ g_free (expr);
+
+ if (!object)
+ return NULL;
+
+ if (object->type == XPATH_NODESET &&
+ xmlXPathNodeSetIsEmpty (object->nodesetval)) {
+ xmlXPathFreeObject (object);
+ return NULL;
+ }
+
+ return object;
+}
+
+/**
+ * e_xml_xpath_eval_as_string:
+ * @xpath_ctx: an #xmlXPathContextPtr
+ * @format: printf-like format specifier of path to evaluate
+ * @...: arguments for the @format
+ *
+ * Evaluates path specified by @format and returns its result as string,
+ * in case the path evaluates to a non-empty node set. See also
+ * e_xml_xpath_eval() which evaluates the path to an #xmlXPathObjectPtr.
+ *
+ * Returns: (nullable) (transfer full): a new string which contains value
+ * of the given path, or %NULL if path cannot be found or when
+ * it evaluates to an empty nodeset. Free returned pointer with
+ * g_free(), when no longer needed.
+ *
+ * Since: 3.26
+ **/
+gchar *
+e_xml_xpath_eval_as_string (xmlXPathContextPtr xpath_ctx,
+ const gchar *format,
+ ...)
+{
+ xmlXPathObjectPtr object;
+ va_list va;
+ gchar *expr, *value;
+
+ g_return_val_if_fail (xpath_ctx != NULL, NULL);
+ g_return_val_if_fail (format != NULL, NULL);
+
+ va_start (va, format);
+ expr = g_strdup_vprintf (format, va);
+ va_end (va);
+
+ if (!g_str_has_prefix (format, "string(")) {
+ gchar *tmp = expr;
+
+ expr = g_strconcat ("string(", expr, ")", NULL);
+
+ g_free (tmp);
+ }
+
+ object = e_xml_xpath_eval (xpath_ctx, "%s", expr);
+ if (!object)
+ return NULL;
+
+ if (object->type == XPATH_STRING &&
+ *object->stringval)
+ value = g_strdup ((const gchar *) object->stringval);
+ else
+ value = NULL;
+
+ xmlXPathFreeObject (object);
+
+ return value;
+}
+
+/**
+ * e_xml_xpath_eval_exists:
+ * @xpath_ctx: an #xmlXPathContextPtr
+ * @format: printf-like format specifier of path to evaluate
+ * @...: arguments for the @format
+ *
+ * Evaluates path specified by @format and returns whether it exists.
+ *
+ * Returns: %TRUE, when the given XPath exists, %FALSE otherwise.
+ *
+ * Since: 3.26
+ **/
+gboolean
+e_xml_xpath_eval_exists (xmlXPathContextPtr xpath_ctx,
+ const gchar *format,
+ ...)
+{
+ xmlXPathObjectPtr object;
+ va_list va;
+ gchar *expr;
+
+ g_return_val_if_fail (xpath_ctx != NULL, FALSE);
+ g_return_val_if_fail (format != NULL, FALSE);
+
+ va_start (va, format);
+ expr = g_strdup_vprintf (format, va);
+ va_end (va);
+
+ object = e_xml_xpath_eval (xpath_ctx, "%s", expr);
+ if (!object)
+ return FALSE;
+
+ xmlXPathFreeObject (object);
+
+ return TRUE;
+}
diff --git a/src/libedataserver/e-xml-utils.h b/src/libedataserver/e-xml-utils.h
index de3cebf..2c98ecb 100644
--- a/src/libedataserver/e-xml-utils.h
+++ b/src/libedataserver/e-xml-utils.h
@@ -25,6 +25,7 @@
#include <glib.h>
#include <libxml/parser.h>
+#include <libxml/xpath.h>
G_BEGIN_DECLS
@@ -34,7 +35,28 @@ gint e_xml_save_file (const gchar *filename,
xmlNode * e_xml_get_child_by_name (const xmlNode *parent,
const xmlChar *child_name);
+xmlDocPtr e_xml_parse_data (const gchar *data,
+ gsize length);
+xmlXPathContextPtr
+ e_xml_new_xpath_context_with_namespaces
+ (xmlDocPtr doc,
+ ...) G_GNUC_NULL_TERMINATED;
+void e_xml_xpath_context_register_namespaces
+ (xmlXPathContextPtr xpath_ctx,
+ const gchar *prefix,
+ const gchar *href,
+ ...) G_GNUC_NULL_TERMINATED;
+xmlXPathObjectPtr
+ e_xml_xpath_eval (xmlXPathContextPtr xpath_ctx,
+ const gchar *format,
+ ...) G_GNUC_PRINTF (2, 3);
+gchar * e_xml_xpath_eval_as_string (xmlXPathContextPtr xpath_ctx,
+ const gchar *format,
+ ...) G_GNUC_PRINTF (2, 3);
+gboolean e_xml_xpath_eval_exists (xmlXPathContextPtr xpath_ctx,
+ const gchar *format,
+ ...) G_GNUC_PRINTF (2, 3);
+
G_END_DECLS
#endif /* E_XML_UTILS_H */
-
diff --git a/src/libedataserver/libedataserver.h b/src/libedataserver/libedataserver.h
index 437cb02..b11f8f8 100644
--- a/src/libedataserver/libedataserver.h
+++ b/src/libedataserver/libedataserver.h
@@ -93,6 +93,7 @@
#include <libedataserver/e-url.h>
#include <libedataserver/e-webdav-discover.h>
#include <libedataserver/e-webdav-session.h>
+#include <libedataserver/e-xml-document.h>
#include <libedataserver/e-xml-hash-utils.h>
#include <libedataserver/e-xml-utils.h>
#include <libedataserver/eds-version.h>
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]