/*
* Copyright (C) 2015 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 .
*
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include
#include
#include "e-soup-auth-bearer.h"
#include "e-soup-ssl-trust.h"
#include "e-source-authentication.h"
#include "e-source-webdav.h"
#include "e-webdav-discover.h"
#define XC(string) ((xmlChar *) string)
/* Standard Namespaces */
#define NS_WEBDAV "DAV:"
#define NS_CALDAV "urn:ietf:params:xml:ns:caldav"
#define NS_CARDDAV "urn:ietf:params:xml:ns:carddav"
/* Application-Specific Namespaces */
#define NS_ICAL "http://apple.com/ns/ical/"
/* Mainly for readability. */
enum {
DEPTH_0 = 0,
DEPTH_1 = 1
};
typedef struct _EWebDAVDiscoverContext {
ESource *source;
gchar *url_use_path;
guint32 only_supports;
ENamedParameters *credentials;
gchar *out_certificate_pem;
GTlsCertificateFlags out_certificate_errors;
GSList *out_discovered_sources;
GSList *out_calendar_user_addresses;
} EWebDAVDiscoverContext;
static EWebDAVDiscoverContext *
e_webdav_discover_context_new (ESource *source,
const gchar *url_use_path,
guint32 only_supports,
const ENamedParameters *credentials)
{
EWebDAVDiscoverContext *context;
context = g_new0 (EWebDAVDiscoverContext, 1);
context->source = g_object_ref (source);
context->url_use_path = g_strdup (url_use_path);
context->only_supports = only_supports;
context->credentials = e_named_parameters_new_clone (credentials);
context->out_certificate_pem = NULL;
context->out_certificate_errors = 0;
context->out_discovered_sources = NULL;
context->out_calendar_user_addresses = NULL;
return context;
}
static void
e_webdav_discover_context_free (gpointer ptr)
{
EWebDAVDiscoverContext *context = ptr;
if (!context)
return;
g_clear_object (&context->source);
g_free (context->url_use_path);
e_named_parameters_free (context->credentials);
g_free (context->out_certificate_pem);
e_webdav_discover_free_discovered_sources (context->out_discovered_sources);
g_slist_free_full (context->out_calendar_user_addresses, g_free);
g_free (context);
}
static gchar *
e_webdav_discover_make_href_full_uri (SoupURI *base_uri,
const gchar *href)
{
SoupURI *soup_uri;
gchar *full_uri;
if (!base_uri || !href)
return g_strdup (href);
if (strstr (href, "://"))
return g_strdup (href);
soup_uri = soup_uri_copy (base_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);
return full_uri;
}
static void
e_webdav_discover_redirect (SoupMessage *message,
SoupSession *session)
{
SoupURI *soup_uri;
const gchar *location;
if (!SOUP_STATUS_IS_REDIRECTION (message->status_code))
return;
location = soup_message_headers_get_list (message->response_headers, "Location");
if (location == NULL)
return;
soup_uri = soup_uri_new_with_base (soup_message_get_uri (message), location);
if (soup_uri == NULL) {
soup_message_set_status_full (
message, SOUP_STATUS_MALFORMED,
_("Invalid Redirect URL"));
return;
}
soup_message_set_uri (message, soup_uri);
soup_session_requeue_message (session, message);
soup_uri_free (soup_uri);
}
static gconstpointer
compat_libxml_output_buffer_get_content (xmlOutputBufferPtr buf,
gsize *out_len)
{
#ifdef LIBXML2_NEW_BUFFER
*out_len = xmlOutputBufferGetSize (buf);
return xmlOutputBufferGetContent (buf);
#else
*out_len = buf->buffer->use;
return buf->buffer->content;
#endif
}
static G_GNUC_NULL_TERMINATED SoupMessage *
e_webdav_discover_new_propfind (SoupSession *session,
SoupURI *soup_uri,
gint depth,
...)
{
GHashTable *namespaces;
SoupMessage *message;
xmlDocPtr doc;
xmlNodePtr root;
xmlNodePtr node;
xmlNsPtr ns;
xmlOutputBufferPtr output;
gconstpointer content;
gsize length;
gpointer key;
va_list va;
/* Construct the XML content. */
doc = xmlNewDoc (XC ("1.0"));
node = xmlNewDocNode (doc, NULL, XC ("propfind"), NULL);
/* Build a hash table of namespace URIs to xmlNs structs. */
namespaces = g_hash_table_new (NULL, NULL);
ns = xmlNewNs (node, XC (NS_CALDAV), XC ("C"));
g_hash_table_insert (namespaces, (gpointer) NS_CALDAV, ns);
ns = xmlNewNs (node, XC (NS_CARDDAV), XC ("A"));
g_hash_table_insert (namespaces, (gpointer) NS_CARDDAV, ns);
ns = xmlNewNs (node, XC (NS_ICAL), XC ("IC"));
g_hash_table_insert (namespaces, (gpointer) NS_ICAL, ns);
/* Add WebDAV last since we use it below. */
ns = xmlNewNs (node, XC (NS_WEBDAV), XC ("D"));
g_hash_table_insert (namespaces, (gpointer) NS_WEBDAV, ns);
xmlSetNs (node, ns);
xmlDocSetRootElement (doc, node);
node = xmlNewTextChild (node, ns, XC ("prop"), NULL);
va_start (va, depth);
while ((key = va_arg (va, gpointer)) != NULL) {
xmlChar *name;
ns = g_hash_table_lookup (namespaces, key);
name = va_arg (va, xmlChar *);
if (ns != NULL && name != NULL)
xmlNewTextChild (node, ns, name, NULL);
else
g_warn_if_reached ();
}
va_end (va);
g_hash_table_destroy (namespaces);
/* Construct the SoupMessage. */
message = soup_message_new_from_uri (SOUP_METHOD_PROPFIND, soup_uri);
soup_message_set_flags (message, SOUP_MESSAGE_NO_REDIRECT);
soup_message_headers_append (
message->request_headers,
"User-Agent", "Evolution/" VERSION);
soup_message_headers_append (
message->request_headers,
"Connection", "close");
soup_message_headers_append (
message->request_headers,
"Depth", (depth == 0) ? "0" : "1");
output = xmlAllocOutputBuffer (NULL);
root = xmlDocGetRootElement (doc);
xmlNodeDumpOutput (output, doc, root, 0, 1, NULL);
xmlOutputBufferFlush (output);
content = compat_libxml_output_buffer_get_content (output, &length);
soup_message_set_request (
message, "application/xml", SOUP_MEMORY_COPY,
content, length);
xmlOutputBufferClose (output);
xmlFreeDoc (doc);
soup_message_add_header_handler (
message, "got-body", "Location",
G_CALLBACK (e_webdav_discover_redirect), session);
return message;
}
static xmlXPathObjectPtr
e_webdav_discover_get_xpath (xmlXPathContextPtr xp_ctx,
const gchar *path_format,
...)
{
xmlXPathObjectPtr xp_obj;
va_list va;
gchar *path;
va_start (va, path_format);
path = g_strdup_vprintf (path_format, va);
va_end (va);
xp_obj = xmlXPathEvalExpression (XC (path), xp_ctx);
g_free (path);
if (xp_obj == NULL)
return NULL;
if (xp_obj->type != XPATH_NODESET) {
xmlXPathFreeObject (xp_obj);
return NULL;
}
if (xmlXPathNodeSetGetLength (xp_obj->nodesetval) == 0) {
xmlXPathFreeObject (xp_obj);
return NULL;
}
return xp_obj;
}
static gchar *
e_webdav_discover_get_xpath_string (xmlXPathContextPtr xp_ctx,
const gchar *path_format,
...)
{
xmlXPathObjectPtr xp_obj;
va_list va;
gchar *path;
gchar *expression;
gchar *string = NULL;
va_start (va, path_format);
path = g_strdup_vprintf (path_format, va);
va_end (va);
expression = g_strdup_printf ("string(%s)", path);
xp_obj = xmlXPathEvalExpression (XC (expression), xp_ctx);
g_free (expression);
g_free (path);
if (xp_obj == NULL)
return NULL;
if (xp_obj->type == XPATH_STRING)
string = g_strdup ((gchar *) xp_obj->stringval);
/* If the string is empty, return NULL. */
if (string != NULL && *string == '\0') {
g_free (string);
string = NULL;
}
xmlXPathFreeObject (xp_obj);
return string;
}
typedef struct _AuthenticateData {
ESource *source;
const ENamedParameters *credentials;
} AuthenticateData;
static void
e_webdav_discover_authenticate_cb (SoupSession *session,
SoupMessage *msg,
SoupAuth *auth,
gboolean retrying,
gpointer user_data)
{
AuthenticateData *auth_data = user_data;
g_return_if_fail (auth_data != NULL);
if (retrying)
return;
if (E_IS_SOUP_AUTH_BEARER (auth)) {
gchar *access_token = NULL;
gint expires_in_seconds = -1;
GError *local_error = NULL;
e_source_get_oauth2_access_token_sync (
auth_data->source, NULL, &access_token,
&expires_in_seconds, &local_error);
e_soup_auth_bearer_set_access_token (
E_SOUP_AUTH_BEARER (auth),
access_token, expires_in_seconds);
if (local_error != NULL) {
soup_message_set_status_full (msg, SOUP_STATUS_FORBIDDEN, local_error->message);
g_error_free (local_error);
}
g_free (access_token);
} else {
gchar *auth_user = NULL;
if (e_named_parameters_get (auth_data->credentials, E_SOURCE_CREDENTIAL_USERNAME))
auth_user = g_strdup (e_named_parameters_get (auth_data->credentials, E_SOURCE_CREDENTIAL_USERNAME));
if (auth_user && !*auth_user) {
g_free (auth_user);
auth_user = NULL;
}
if (!auth_user) {
ESourceAuthentication *auth_extension;
auth_extension = e_source_get_extension (auth_data->source, E_SOURCE_EXTENSION_AUTHENTICATION);
auth_user = e_source_authentication_dup_user (auth_extension);
}
if (!auth_user || !*auth_user || !auth_data->credentials || !e_named_parameters_get (auth_data->credentials, E_SOURCE_CREDENTIAL_PASSWORD))
soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
else
soup_auth_authenticate (auth, auth_user, e_named_parameters_get (auth_data->credentials, E_SOURCE_CREDENTIAL_PASSWORD));
g_free (auth_user);
}
}
static gboolean
e_webdav_discover_check_successful (SoupMessage *message,
gchar **out_certificate_pem,
GTlsCertificateFlags *out_certificate_errors,
GError **error)
{
GIOErrorEnum error_code;
g_return_val_if_fail (message != NULL, FALSE);
/* Loosely copied from the GVFS DAV backend. */
if (SOUP_STATUS_IS_SUCCESSFUL (message->status_code))
return TRUE;
switch (message->status_code) {
case SOUP_STATUS_CANCELLED:
error_code = G_IO_ERROR_CANCELLED;
break;
case SOUP_STATUS_NOT_FOUND:
error_code = G_IO_ERROR_NOT_FOUND;
break;
case SOUP_STATUS_UNAUTHORIZED:
case SOUP_STATUS_PAYMENT_REQUIRED:
case SOUP_STATUS_FORBIDDEN:
error_code = G_IO_ERROR_PERMISSION_DENIED;
break;
case SOUP_STATUS_REQUEST_TIMEOUT:
error_code = G_IO_ERROR_TIMED_OUT;
break;
case SOUP_STATUS_CANT_RESOLVE:
error_code = G_IO_ERROR_HOST_NOT_FOUND;
break;
case SOUP_STATUS_NOT_IMPLEMENTED:
error_code = G_IO_ERROR_NOT_SUPPORTED;
break;
case SOUP_STATUS_INSUFFICIENT_STORAGE:
error_code = G_IO_ERROR_NO_SPACE;
break;
case SOUP_STATUS_SSL_FAILED:
if (out_certificate_pem) {
GTlsCertificate *certificate = NULL;
g_free (*out_certificate_pem);
*out_certificate_pem = NULL;
g_object_get (G_OBJECT (message), "tls-certificate", &certificate, NULL);
if (certificate) {
g_object_get (certificate, "certificate-pem", out_certificate_pem, NULL);
g_object_unref (certificate);
}
}
if (out_certificate_errors) {
*out_certificate_errors = 0;
g_object_get (G_OBJECT (message), "tls-errors", out_certificate_errors, NULL);
}
g_set_error (
error, SOUP_HTTP_ERROR, message->status_code,
_("HTTP Error: %s"), message->reason_phrase);
return FALSE;
default:
error_code = G_IO_ERROR_FAILED;
break;
}
g_set_error (
error, G_IO_ERROR, error_code,
_("HTTP Error: %s"), message->reason_phrase);
return FALSE;
}
static xmlDocPtr
e_webdav_discover_parse_xml (SoupMessage *message,
const gchar *expected_name,
gchar **out_certificate_pem,
GTlsCertificateFlags *out_certificate_errors,
GError **error)
{
xmlDocPtr doc;
xmlNodePtr root;
if (!e_webdav_discover_check_successful (message, out_certificate_pem, out_certificate_errors, error))
return NULL;
doc = xmlReadMemory (
message->response_body->data,
message->response_body->length,
"response.xml", NULL,
XML_PARSE_NONET |
XML_PARSE_NOWARNING |
XML_PARSE_NOCDATA |
XML_PARSE_COMPACT);
if (doc == NULL) {
g_set_error_literal (
error, G_IO_ERROR, G_IO_ERROR_FAILED,
_("Could not parse response"));
return NULL;
}
root = xmlDocGetRootElement (doc);
if (root == NULL || root->children == NULL) {
g_set_error_literal (
error, G_IO_ERROR, G_IO_ERROR_FAILED,
_("Empty response"));
xmlFreeDoc (doc);
return NULL;
}
if (g_strcmp0 ((gchar *) root->name, expected_name) != 0) {
g_set_error_literal (
error, G_IO_ERROR, G_IO_ERROR_FAILED,
_("Unexpected reply from server"));
xmlFreeDoc (doc);
return NULL;
}
return doc;
}
static void
e_webdav_discover_process_user_address_set (xmlXPathContextPtr xp_ctx,
GSList **out_calendar_user_addresses)
{
xmlXPathObjectPtr xp_obj;
gint ii, length;
if (!out_calendar_user_addresses)
return;
xp_obj = e_webdav_discover_get_xpath (
xp_ctx,
"/D:multistatus"
"/D:response"
"/D:propstat"
"/D:prop"
"/C:calendar-user-address-set");
if (xp_obj == NULL)
return;
length = xmlXPathNodeSetGetLength (xp_obj->nodesetval);
for (ii = 0; ii < length; ii++) {
GSList *duplicate;
const gchar *address;
gchar *href;
href = e_webdav_discover_get_xpath_string (
xp_ctx,
"/D:multistatus"
"/D:response"
"/D:propstat"
"/D:prop"
"/C:calendar-user-address-set"
"/D:href[%d]", ii + 1);
if (href == NULL)
continue;
if (!g_str_has_prefix (href, "mailto:")) {
g_free (href);
continue;
}
/* strlen("mailto:") == 7 */
address = href + 7;
/* Avoid duplicates. */
duplicate = g_slist_find_custom (
*out_calendar_user_addresses,
address, (GCompareFunc) g_ascii_strcasecmp);
if (duplicate != NULL) {
g_free (href);
continue;
}
*out_calendar_user_addresses = g_slist_prepend (
*out_calendar_user_addresses, g_strdup (address));
g_free (href);
}
xmlXPathFreeObject (xp_obj);
}
static guint32
e_webdav_discover_get_supported_component_set (xmlXPathContextPtr xp_ctx,
gint index)
{
xmlXPathObjectPtr xp_obj;
guint32 set = 0;
gint ii, length;
xp_obj = e_webdav_discover_get_xpath (
xp_ctx,
"/D:multistatus"
"/D:response[%d]"
"/D:propstat"
"/D:prop"
"/C:supported-calendar-component-set"
"/C:comp", index);
/* If the property is not present, assume all component
* types are supported. (RFC 4791, Section 5.2.3) */
if (xp_obj == NULL)
return E_WEBDAV_DISCOVER_SUPPORTS_EVENTS |
E_WEBDAV_DISCOVER_SUPPORTS_MEMOS |
E_WEBDAV_DISCOVER_SUPPORTS_TASKS;
length = xmlXPathNodeSetGetLength (xp_obj->nodesetval);
for (ii = 0; ii < length; ii++) {
gchar *name;
name = e_webdav_discover_get_xpath_string (
xp_ctx,
"/D:multistatus"
"/D:response[%d]"
"/D:propstat"
"/D:prop"
"/C:supported-calendar-component-set"
"/C:comp[%d]"
"/@name", index, ii + 1);
if (name == NULL)
continue;
if (g_ascii_strcasecmp (name, "VEVENT") == 0)
set |= E_WEBDAV_DISCOVER_SUPPORTS_EVENTS;
else if (g_ascii_strcasecmp (name, "VJOURNAL") == 0)
set |= E_WEBDAV_DISCOVER_SUPPORTS_MEMOS;
else if (g_ascii_strcasecmp (name, "VTODO") == 0)
set |= E_WEBDAV_DISCOVER_SUPPORTS_TASKS;
g_free (name);
}
xmlXPathFreeObject (xp_obj);
return set;
}
static void
e_webdav_discover_process_calendar_response (SoupMessage *message,
xmlXPathContextPtr xp_ctx,
gint index,
GSList **out_discovered_sources)
{
xmlXPathObjectPtr xp_obj;
guint32 comp_set;
gchar *color_spec;
gchar *display_name;
gchar *description;
gchar *href_encoded;
gchar *status_line;
guint status;
gboolean success;
EWebDAVDiscoveredSource *discovered_source;
if (!out_discovered_sources)
return;
status_line = e_webdav_discover_get_xpath_string (
xp_ctx,
"/D:multistatus"
"/D:response[%d]"
"/D:propstat"
"/D:status",
index);
if (status_line == NULL)
return;
success = soup_headers_parse_status_line (
status_line, NULL, &status, NULL);
g_free (status_line);
if (!success || status != SOUP_STATUS_OK)
return;
comp_set = e_webdav_discover_get_supported_component_set (xp_ctx, index);
if (comp_set == E_WEBDAV_DISCOVER_SUPPORTS_NONE)
return;
href_encoded = e_webdav_discover_get_xpath_string (
xp_ctx,
"/D:multistatus"
"/D:response[%d]"
"/D:href",
index);
if (href_encoded == NULL)
return;
/* Make sure the resource is a calendar. */
xp_obj = e_webdav_discover_get_xpath (
xp_ctx,
"/D:multistatus"
"/D:response[%d]"
"/D:propstat"
"/D:prop"
"/D:resourcetype"
"/C:calendar",
index);
if (xp_obj == NULL) {
g_free (href_encoded);
return;
}
xmlXPathFreeObject (xp_obj);
/* Get the display name or fall back to the href. */
display_name = e_webdav_discover_get_xpath_string (
xp_ctx,
"/D:multistatus"
"/D:response[%d]"
"/D:propstat"
"/D:prop"
"/D:displayname",
index);
if (display_name == NULL) {
gchar *href_decoded = soup_uri_decode (href_encoded);
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 {
display_name = g_strdup (cp + 1);
break;
}
}
}
g_free (href_decoded);
}
description = e_webdav_discover_get_xpath_string (
xp_ctx,
"/D:multistatus"
"/D:response[%d]"
"/D:propstat"
"/D:prop"
"/C:calendar-description",
index);
/* Get the color specification string. */
color_spec = e_webdav_discover_get_xpath_string (
xp_ctx,
"/D:multistatus"
"/D:response[%d]"
"/D:propstat"
"/D:prop"
"/IC:calendar-color",
index);
discovered_source = g_new0 (EWebDAVDiscoveredSource, 1);
discovered_source->href = e_webdav_discover_make_href_full_uri (soup_message_get_uri (message), href_encoded);
discovered_source->supports = comp_set;
discovered_source->display_name = g_strdup (display_name);
discovered_source->description = g_strdup (description);
discovered_source->color = g_strdup (color_spec);
*out_discovered_sources = g_slist_prepend (*out_discovered_sources, discovered_source);
g_free (href_encoded);
g_free (display_name);
g_free (description);
g_free (color_spec);
}
static gboolean
e_webdav_discover_get_calendar_collection_details (SoupSession *session,
SoupMessage *message,
const gchar *path_or_uri,
ESource *source,
gchar **out_certificate_pem,
GTlsCertificateFlags *out_certificate_errors,
GSList **out_discovered_sources,
GCancellable *cancellable,
GError **error)
{
xmlDocPtr doc;
xmlXPathContextPtr xp_ctx;
xmlXPathObjectPtr xp_obj;
SoupURI *soup_uri;
if (g_cancellable_is_cancelled (cancellable))
return FALSE;
soup_uri = soup_uri_new (path_or_uri);
if (!soup_uri ||
!soup_uri_get_scheme (soup_uri) ||
!soup_uri_get_host (soup_uri) ||
!soup_uri_get_path (soup_uri) ||
!*soup_uri_get_scheme (soup_uri) ||
!*soup_uri_get_host (soup_uri) ||
!*soup_uri_get_path (soup_uri)) {
/* it's a path only, not full uri */
if (soup_uri)
soup_uri_free (soup_uri);
soup_uri = soup_uri_copy (soup_message_get_uri (message));
soup_uri_set_path (soup_uri, path_or_uri);
}
message = e_webdav_discover_new_propfind (
session, soup_uri, DEPTH_1,
NS_WEBDAV, XC ("displayname"),
NS_WEBDAV, XC ("resourcetype"),
NS_CALDAV, XC ("calendar-description"),
NS_CALDAV, XC ("supported-calendar-component-set"),
NS_CALDAV, XC ("calendar-user-address-set"),
NS_ICAL, XC ("calendar-color"),
NULL);
e_soup_ssl_trust_connect (message, source);
/* This takes ownership of the message. */
soup_session_send_message (session, message);
soup_uri_free (soup_uri);
doc = e_webdav_discover_parse_xml (message, "multistatus", out_certificate_pem, out_certificate_errors, error);
if (!doc) {
g_clear_object (&message);
return FALSE;
}
xp_ctx = xmlXPathNewContext (doc);
xmlXPathRegisterNs (xp_ctx, XC ("D"), XC (NS_WEBDAV));
xmlXPathRegisterNs (xp_ctx, XC ("C"), XC (NS_CALDAV));
xmlXPathRegisterNs (xp_ctx, XC ("A"), XC (NS_CARDDAV));
xmlXPathRegisterNs (xp_ctx, XC ("IC"), XC (NS_ICAL));
xp_obj = e_webdav_discover_get_xpath (
xp_ctx,
"/D:multistatus"
"/D:response");
if (xp_obj != NULL) {
gint length, ii;
length = xmlXPathNodeSetGetLength (xp_obj->nodesetval);
for (ii = 0; ii < length; ii++)
e_webdav_discover_process_calendar_response (
message, xp_ctx, ii + 1, out_discovered_sources);
xmlXPathFreeObject (xp_obj);
}
xmlXPathFreeContext (xp_ctx);
xmlFreeDoc (doc);
g_clear_object (&message);
return TRUE;
}
static gboolean
e_webdav_discover_process_calendar_home_set (SoupSession *session,
SoupMessage *message,
ESource *source,
gchar **out_certificate_pem,
GTlsCertificateFlags *out_certificate_errors,
GSList **out_discovered_sources,
GSList **out_calendar_user_addresses,
GCancellable *cancellable,
GError **error)
{
SoupURI *soup_uri;
xmlDocPtr doc;
xmlXPathContextPtr xp_ctx;
xmlXPathObjectPtr xp_obj;
gchar *calendar_home_set;
gboolean success;
g_return_val_if_fail (SOUP_IS_SESSION (session), FALSE);
g_return_val_if_fail (SOUP_IS_MESSAGE (message), FALSE);
g_return_val_if_fail (out_discovered_sources != NULL, FALSE);
g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
if (g_cancellable_is_cancelled (cancellable))
return FALSE;
doc = e_webdav_discover_parse_xml (message, "multistatus", out_certificate_pem, out_certificate_errors, error);
if (!doc)
return FALSE;
xp_ctx = xmlXPathNewContext (doc);
xmlXPathRegisterNs (xp_ctx, XC ("D"), XC (NS_WEBDAV));
xmlXPathRegisterNs (xp_ctx, XC ("C"), XC (NS_CALDAV));
xmlXPathRegisterNs (xp_ctx, XC ("A"), XC (NS_CARDDAV));
/* Record any "C:calendar-user-address-set" properties. */
e_webdav_discover_process_user_address_set (xp_ctx, out_calendar_user_addresses);
/* Try to find the calendar home URL using the
* following properties in order of preference:
*
* "C:calendar-home-set"
* "D:current-user-principal"
* "D:principal-URL"
*
* If the second or third URL preference is used, rerun
* the PROPFIND method on that URL at Depth=1 in hopes
* of getting a proper "C:calendar-home-set" property.
*/
/* FIXME There can be multiple "D:href" elements for a
* "C:calendar-home-set". We're only processing
* the first one. Need to iterate over them. */
calendar_home_set = e_webdav_discover_get_xpath_string (
xp_ctx,
"/D:multistatus"
"/D:response"
"/D:propstat"
"/D:prop"
"/C:calendar-home-set"
"/D:href");
if (calendar_home_set != NULL)
goto get_collection_details;
g_free (calendar_home_set);
calendar_home_set = e_webdav_discover_get_xpath_string (
xp_ctx,
"/D:multistatus"
"/D:response"
"/D:propstat"
"/D:prop"
"/D:current-user-principal"
"/D:href");
if (calendar_home_set != NULL)
goto retry_propfind;
g_free (calendar_home_set);
calendar_home_set = e_webdav_discover_get_xpath_string (
xp_ctx,
"/D:multistatus"
"/D:response"
"/D:propstat"
"/D:prop"
"/D:principal-URL"
"/D:href");
if (calendar_home_set != NULL)
goto retry_propfind;
g_free (calendar_home_set);
calendar_home_set = NULL;
/* None of the aforementioned properties are present. If the
* user-supplied CalDAV URL is a calendar resource, use that. */
xp_obj = e_webdav_discover_get_xpath (
xp_ctx,
"/D:multistatus"
"/D:response"
"/D:propstat"
"/D:prop"
"/D:resourcetype"
"/C:calendar");
if (xp_obj != NULL) {
soup_uri = soup_message_get_uri (message);
if (soup_uri->path != NULL && *soup_uri->path != '\0') {
gchar *slash;
soup_uri = soup_uri_copy (soup_uri);
slash = strrchr (soup_uri->path, '/');
while (slash != NULL && slash != soup_uri->path) {
if (slash[1] != '\0') {
slash[1] = '\0';
calendar_home_set =
g_strdup (soup_uri->path);
break;
}
slash[0] = '\0';
slash = strrchr (soup_uri->path, '/');
}
soup_uri_free (soup_uri);
}
xmlXPathFreeObject (xp_obj);
}
if (calendar_home_set == NULL || *calendar_home_set == '\0') {
g_free (calendar_home_set);
xmlXPathFreeContext (xp_ctx);
xmlFreeDoc (doc);
return TRUE;
}
get_collection_details:
xmlXPathFreeContext (xp_ctx);
xmlFreeDoc (doc);
if (!e_webdav_discover_get_calendar_collection_details (
session, message, calendar_home_set, source,
out_certificate_pem, out_certificate_errors, out_discovered_sources,
cancellable, error)) {
g_free (calendar_home_set);
return FALSE;
}
g_free (calendar_home_set);
return TRUE;
retry_propfind:
xmlXPathFreeContext (xp_ctx);
xmlFreeDoc (doc);
soup_uri = soup_uri_copy (soup_message_get_uri (message));
soup_uri_set_path (soup_uri, calendar_home_set);
/* Note that we omit "D:resourcetype", "D:current-user-principal"
* and "D:principal-URL" in order to short-circuit the recursion. */
message = e_webdav_discover_new_propfind (
session, soup_uri, DEPTH_1,
NS_CALDAV, XC ("calendar-home-set"),
NS_CALDAV, XC ("calendar-user-address-set"),
NULL);
e_soup_ssl_trust_connect (message, source);
/* This takes ownership of the message. */
soup_session_send_message (session, message);
soup_uri_free (soup_uri);
g_free (calendar_home_set);
success = e_webdav_discover_process_calendar_home_set (session, message, source,
out_certificate_pem, out_certificate_errors, out_discovered_sources, out_calendar_user_addresses,
cancellable, error);
g_object_unref (message);
return success;
}
static void
e_webdav_discover_process_addressbook_response (SoupMessage *message,
xmlXPathContextPtr xp_ctx,
gint index,
GSList **out_discovered_sources)
{
xmlXPathObjectPtr xp_obj;
gchar *display_name;
gchar *description;
gchar *href_encoded;
gchar *status_line;
guint status;
gboolean success;
EWebDAVDiscoveredSource *discovered_source;
if (!out_discovered_sources)
return;
status_line = e_webdav_discover_get_xpath_string (
xp_ctx,
"/D:multistatus"
"/D:response[%d]"
"/D:propstat"
"/D:status",
index);
if (status_line == NULL)
return;
success = soup_headers_parse_status_line (
status_line, NULL, &status, NULL);
g_free (status_line);
if (!success || status != SOUP_STATUS_OK)
return;
href_encoded = e_webdav_discover_get_xpath_string (
xp_ctx,
"/D:multistatus"
"/D:response[%d]"
"/D:href",
index);
if (href_encoded == NULL)
return;
/* Make sure the resource is an addressbook. */
xp_obj = e_webdav_discover_get_xpath (
xp_ctx,
"/D:multistatus"
"/D:response[%d]"
"/D:propstat"
"/D:prop"
"/D:resourcetype"
"/A:addressbook",
index);
if (xp_obj == NULL) {
g_free (href_encoded);
return;
}
xmlXPathFreeObject (xp_obj);
/* Get the display name or fall back to the href. */
display_name = e_webdav_discover_get_xpath_string (
xp_ctx,
"/D:multistatus"
"/D:response[%d]"
"/D:propstat"
"/D:prop"
"/D:displayname",
index);
if (display_name == NULL) {
gchar *href_decoded = soup_uri_decode (href_encoded);
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 {
display_name = g_strdup (cp + 1);
break;
}
}
}
g_free (href_decoded);
}
description = e_webdav_discover_get_xpath_string (
xp_ctx,
"/D:multistatus"
"/D:response[%d]"
"/D:propstat"
"/D:prop"
"/A:addressbook-description",
index);
discovered_source = g_new0 (EWebDAVDiscoveredSource, 1);
discovered_source->href = e_webdav_discover_make_href_full_uri (soup_message_get_uri (message), href_encoded);
discovered_source->supports = E_WEBDAV_DISCOVER_SUPPORTS_CONTACTS;
discovered_source->display_name = g_strdup (display_name);
discovered_source->description = g_strdup (description);
discovered_source->color = NULL;
*out_discovered_sources = g_slist_prepend (*out_discovered_sources, discovered_source);
g_free (href_encoded);
g_free (display_name);
g_free (description);
}
static gboolean
e_webdav_discover_get_addressbook_collection_details (SoupSession *session,
SoupMessage *message,
const gchar *path_or_uri,
ESource *source,
gchar **out_certificate_pem,
GTlsCertificateFlags *out_certificate_errors,
GSList **out_discovered_sources,
GCancellable *cancellable,
GError **error)
{
xmlDocPtr doc;
xmlXPathContextPtr xp_ctx;
xmlXPathObjectPtr xp_obj;
SoupURI *soup_uri;
if (g_cancellable_is_cancelled (cancellable))
return FALSE;
soup_uri = soup_uri_new (path_or_uri);
if (!soup_uri ||
!soup_uri_get_scheme (soup_uri) ||
!soup_uri_get_host (soup_uri) ||
!soup_uri_get_path (soup_uri) ||
!*soup_uri_get_scheme (soup_uri) ||
!*soup_uri_get_host (soup_uri) ||
!*soup_uri_get_path (soup_uri)) {
/* it's a path only, not full uri */
if (soup_uri)
soup_uri_free (soup_uri);
soup_uri = soup_uri_copy (soup_message_get_uri (message));
soup_uri_set_path (soup_uri, path_or_uri);
}
message = e_webdav_discover_new_propfind (
session, soup_uri, DEPTH_1,
NS_WEBDAV, XC ("displayname"),
NS_WEBDAV, XC ("resourcetype"),
NS_CARDDAV, XC ("addressbook-description"),
NULL);
e_soup_ssl_trust_connect (message, source);
/* This takes ownership of the message. */
soup_session_send_message (session, message);
soup_uri_free (soup_uri);
doc = e_webdav_discover_parse_xml (message, "multistatus", out_certificate_pem, out_certificate_errors, error);
if (!doc) {
g_clear_object (&message);
return FALSE;
}
xp_ctx = xmlXPathNewContext (doc);
xmlXPathRegisterNs (xp_ctx, XC ("D"), XC (NS_WEBDAV));
xmlXPathRegisterNs (xp_ctx, XC ("C"), XC (NS_CALDAV));
xmlXPathRegisterNs (xp_ctx, XC ("A"), XC (NS_CARDDAV));
xmlXPathRegisterNs (xp_ctx, XC ("IC"), XC (NS_ICAL));
xp_obj = e_webdav_discover_get_xpath (
xp_ctx,
"/D:multistatus"
"/D:response");
if (xp_obj != NULL) {
gint length, ii;
length = xmlXPathNodeSetGetLength (xp_obj->nodesetval);
for (ii = 0; ii < length; ii++)
e_webdav_discover_process_addressbook_response (
message, xp_ctx, ii + 1, out_discovered_sources);
xmlXPathFreeObject (xp_obj);
}
xmlXPathFreeContext (xp_ctx);
xmlFreeDoc (doc);
g_clear_object (&message);
return TRUE;
}
static gboolean
e_webdav_discover_process_addressbook_home_set (SoupSession *session,
SoupMessage *message,
ESource *source,
gchar **out_certificate_pem,
GTlsCertificateFlags *out_certificate_errors,
GSList **out_discovered_sources,
GCancellable *cancellable,
GError **error)
{
SoupURI *soup_uri;
xmlDocPtr doc;
xmlXPathContextPtr xp_ctx;
xmlXPathObjectPtr xp_obj;
gchar *addressbook_home_set;
gboolean success;
g_return_val_if_fail (SOUP_IS_SESSION (session), FALSE);
g_return_val_if_fail (SOUP_IS_MESSAGE (message), FALSE);
g_return_val_if_fail (out_discovered_sources != NULL, FALSE);
g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
if (g_cancellable_is_cancelled (cancellable))
return FALSE;
doc = e_webdav_discover_parse_xml (message, "multistatus", out_certificate_pem, out_certificate_errors, error);
if (!doc)
return FALSE;
xp_ctx = xmlXPathNewContext (doc);
xmlXPathRegisterNs (xp_ctx, XC ("D"), XC (NS_WEBDAV));
xmlXPathRegisterNs (xp_ctx, XC ("C"), XC (NS_CALDAV));
xmlXPathRegisterNs (xp_ctx, XC ("A"), XC (NS_CARDDAV));
/* Try to find the addressbook home URL using the
* following properties in order of preference:
*
* "A:addressbook-home-set"
* "D:current-user-principal"
* "D:principal-URL"
*
* If the second or third URL preference is used, rerun
* the PROPFIND method on that URL at Depth=1 in hopes
* of getting a proper "A:addressbook-home-set" property.
*/
/* FIXME There can be multiple "D:href" elements for a
* "A:addressbook-home-set". We're only processing
* the first one. Need to iterate over them. */
addressbook_home_set = e_webdav_discover_get_xpath_string (
xp_ctx,
"/D:multistatus"
"/D:response"
"/D:propstat"
"/D:prop"
"/A:addressbook-home-set"
"/D:href");
if (addressbook_home_set != NULL)
goto get_collection_details;
g_free (addressbook_home_set);
addressbook_home_set = e_webdav_discover_get_xpath_string (
xp_ctx,
"/D:multistatus"
"/D:response"
"/D:propstat"
"/D:prop"
"/D:current-user-principal"
"/D:href");
if (addressbook_home_set != NULL)
goto retry_propfind;
g_free (addressbook_home_set);
addressbook_home_set = e_webdav_discover_get_xpath_string (
xp_ctx,
"/D:multistatus"
"/D:response"
"/D:propstat"
"/D:prop"
"/D:principal-URL"
"/D:href");
if (addressbook_home_set != NULL)
goto retry_propfind;
g_free (addressbook_home_set);
addressbook_home_set = NULL;
/* None of the aforementioned properties are present. If the
* user-supplied CardDAV URL is an addressbook resource, use that. */
xp_obj = e_webdav_discover_get_xpath (
xp_ctx,
"/D:multistatus"
"/D:response"
"/D:propstat"
"/D:prop"
"/D:resourcetype"
"/A:addressbook");
if (xp_obj != NULL) {
soup_uri = soup_message_get_uri (message);
if (soup_uri->path != NULL && *soup_uri->path != '\0') {
gchar *slash;
soup_uri = soup_uri_copy (soup_uri);
slash = strrchr (soup_uri->path, '/');
while (slash != NULL && slash != soup_uri->path) {
if (slash[1] != '\0') {
slash[1] = '\0';
addressbook_home_set =
g_strdup (soup_uri->path);
break;
}
slash[0] = '\0';
slash = strrchr (soup_uri->path, '/');
}
soup_uri_free (soup_uri);
}
xmlXPathFreeObject (xp_obj);
}
if (addressbook_home_set == NULL || *addressbook_home_set == '\0') {
g_free (addressbook_home_set);
xmlXPathFreeContext (xp_ctx);
xmlFreeDoc (doc);
return TRUE;
}
get_collection_details:
xmlXPathFreeContext (xp_ctx);
xmlFreeDoc (doc);
if (!e_webdav_discover_get_addressbook_collection_details (
session, message, addressbook_home_set, source,
out_certificate_pem, out_certificate_errors, out_discovered_sources,
cancellable, error)) {
g_free (addressbook_home_set);
return FALSE;
}
g_free (addressbook_home_set);
return TRUE;
retry_propfind:
xmlXPathFreeContext (xp_ctx);
xmlFreeDoc (doc);
soup_uri = soup_uri_copy (soup_message_get_uri (message));
soup_uri_set_path (soup_uri, addressbook_home_set);
/* Note that we omit "D:resourcetype", "D:current-user-principal"
* and "D:principal-URL" in order to short-circuit the recursion. */
message = e_webdav_discover_new_propfind (
session, soup_uri, DEPTH_1,
NS_CARDDAV, XC ("addressbook-home-set"),
NULL);
e_soup_ssl_trust_connect (message, source);
/* This takes ownership of the message. */
soup_session_send_message (session, message);
soup_uri_free (soup_uri);
g_free (addressbook_home_set);
success = e_webdav_discover_process_addressbook_home_set (session, message, source,
out_certificate_pem, out_certificate_errors, out_discovered_sources,
cancellable, error);
g_object_unref (message);
return success;
}
static void
e_webdav_discover_source_free (gpointer ptr)
{
EWebDAVDiscoveredSource *discovered_source = ptr;
if (discovered_source) {
g_free (discovered_source->href);
g_free (discovered_source->display_name);
g_free (discovered_source->description);
g_free (discovered_source->color);
g_free (discovered_source);
}
}
/**
* e_webdav_discover_free_discovered_sources:
* @discovered_sources: A #GSList of discovered sources
*
* Frees a @GSList of discovered sources returned from
* e_webdav_discover_sources_finish() or e_webdav_discover_sources_sync().
*
* Since: 3.18
**/
void
e_webdav_discover_free_discovered_sources (GSList *discovered_sources)
{
g_slist_free_full (discovered_sources, e_webdav_discover_source_free);
}
static void
e_webdav_discover_sources_thread (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
EWebDAVDiscoverContext *context = task_data;
gboolean success;
GError *local_error = NULL;
g_return_if_fail (context != NULL);
g_return_if_fail (E_IS_SOURCE (source_object));
success = e_webdav_discover_sources_sync (E_SOURCE (source_object),
context->url_use_path, context->only_supports, context->credentials,
&context->out_certificate_pem, &context->out_certificate_errors,
&context->out_discovered_sources, &context->out_calendar_user_addresses,
cancellable, &local_error);
if (local_error != NULL) {
g_task_return_error (task, local_error);
} else {
g_task_return_boolean (task, success);
}
}
/**
* e_webdav_discover_sources:
* @source: an #ESource from which to take connection details
* @url_use_path: (allow-none): optional URL override, or %NULL
* @only_supports: bit-or of EWebDAVDiscoverSupports, to limit what type of sources to search
* @credentials: (allow-none): credentials to use for authentication to the server
* @cancellable: (allow-none): optional #GCancellable object, or %NULL
* @callback: (scope async): a #GAsyncReadyCallback to call when the request
* is satisfied
* @user_data: (closure): data to pass to the callback function
*
* Asynchronously runs discovery of the WebDAV sources (CalDAV and CardDAV), eventually
* limited by the @only_supports filter, which can be %E_WEBDAV_DISCOVER_SUPPORTS_NONE
* to search all types. Note that the list of returned calendars can be more general,
* thus check for its actual support type for further filtering of the results.
* The @url_use_path can be used to override actual server path, or even complete URL,
* for the given @source.
*
* When the operation is finished, @callback will be called. You can then
* call e_webdav_discover_sources_finish() to get the result of the operation.
*
* Since: 3.18
**/
void
e_webdav_discover_sources (ESource *source,
const gchar *url_use_path,
guint32 only_supports,
const ENamedParameters *credentials,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
EWebDAVDiscoverContext *context;
GTask *task;
g_return_if_fail (E_IS_SOURCE (source));
context = e_webdav_discover_context_new (source, url_use_path, only_supports, credentials);
task = g_task_new (source, cancellable, callback, user_data);
g_task_set_source_tag (task, e_webdav_discover_sources);
g_task_set_task_data (task, context, e_webdav_discover_context_free);
g_task_run_in_thread (task, e_webdav_discover_sources_thread);
g_object_unref (task);
}
/**
* e_webdav_discover_sources_finish:
* @source: an #ESource on which the operation was started
* @result: a #GAsyncResult
* @out_certificate_pem: (out): (allow-none): optional return location
* for a server SSL certificate in PEM format, when the operation failed
* with an SSL error
* @out_certificate_errors: (out): (allow-none): optional #GTlsCertificateFlags,
* with certificate error flags when the operation failed with SSL error
* @out_discovered_sources: (out): (element-type EWebDAVDiscoveredSource): a #GSList
* of all discovered sources
* @out_calendar_user_addresses: (out): (allow-none): (element-type gchar *): a #GSList of
* all discovered mail addresses for calendar sources
* @error: (allow-none): return location for a #GError, or %NULL
*
* Finishes the operation started with e_webdav_discover_sources(). If an
* error occurred, the function will set @error and return %FALSE. The function
* can return success and no discovered sources, the same as it can return failure,
* but still set some output arguments, like the certificate related output
* arguments with SOUP_STATUS_SSL_FAILED error.
*
* The return value of @out_certificate_pem should be freed with g_free()
* when no longer needed.
*
* The return value of @out_discovered_sources should be freed
* with e_webdav_discover_free_discovered_sources() when no longer needed.
*
* The return value of @out_calendar_user_addresses should be freed
* with g_slist_free_full (calendar_user_addresses, g_free); when
* no longer needed.
*
* Returns: %TRUE on success, %FALSE on failure
*
* Since: 3.18
**/
gboolean
e_webdav_discover_sources_finish (ESource *source,
GAsyncResult *result,
gchar **out_certificate_pem,
GTlsCertificateFlags *out_certificate_errors,
GSList **out_discovered_sources,
GSList **out_calendar_user_addresses,
GError **error)
{
EWebDAVDiscoverContext *context;
g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
g_return_val_if_fail (g_task_is_valid (result, source), FALSE);
g_return_val_if_fail (
g_async_result_is_tagged (
result, e_webdav_discover_sources), FALSE);
context = g_task_get_task_data (G_TASK (result));
g_return_val_if_fail (context != NULL, FALSE);
if (out_certificate_pem) {
*out_certificate_pem = context->out_certificate_pem;
context->out_certificate_pem = NULL;
}
if (out_certificate_errors)
*out_certificate_errors = context->out_certificate_errors;
if (out_discovered_sources) {
*out_discovered_sources = context->out_discovered_sources;
context->out_discovered_sources = NULL;
}
if (out_calendar_user_addresses) {
*out_calendar_user_addresses = context->out_calendar_user_addresses;
context->out_calendar_user_addresses = NULL;
}
return g_task_propagate_boolean (G_TASK (result), error);
}
static void
e_webdav_discover_cancelled_cb (GCancellable *cancellable,
SoupSession *session)
{
soup_session_abort (session);
}
/**
* e_webdav_discover_sources_sync:
* @source: an #ESource from which to take connection details
* @url_use_path: (allow-none): optional URL override, or %NULL
* @only_supports: bit-or of EWebDAVDiscoverSupports, to limit what type of sources to search
* @credentials: (allow-none): credentials to use for authentication to the server
* @out_certificate_pem: (out): (allow-none): optional return location
* for a server SSL certificate in PEM format, when the operation failed
* with an SSL error
* @out_certificate_errors: (out): (allow-none): optional #GTlsCertificateFlags,
* with certificate error flags when the operation failed with SSL error
* @out_discovered_sources: (out): (element-type EWebDAVDiscoveredSource): a #GSList
* of all discovered sources
* @out_calendar_user_addresses: (out): (allow-none): (element-type gchar *): a #GSList of
* all discovered mail addresses for calendar sources
* @cancellable: (allow-none): optional #GCancellable object, or %NULL
* @error: (allow-none): return location for a #GError, or %NULL
*
* Synchronously runs discovery of the WebDAV sources (CalDAV and CardDAV), eventually
* limited by the @only_supports filter, which can be %E_WEBDAV_DISCOVER_SUPPORTS_NONE
* to search all types. Note that the list of returned calendars can be more general,
* thus check for its actual support type for further filtering of the results.
* The @url_use_path can be used to override actual server path, or even complete URL,
* for the given @source.
*
* If an error occurred, the function will set @error and return %FALSE. The function
* can return success and no discovered sources, the same as it can return failure,
* but still set some output arguments, like the certificate related output
* arguments with SOUP_STATUS_SSL_FAILED error.
*
* The return value of @out_certificate_pem should be freed with g_free()
* when no longer needed.
*
* The return value of @out_discovered_sources should be freed
* with e_webdav_discover_free_discovered_sources() when no longer needed.
*
* The return value of @out_calendar_user_addresses should be freed
* with g_slist_free_full (calendar_user_addresses, g_free); when
* no longer needed.
*
* Returns: %TRUE on success, %FALSE on failure
*
* Since: 3.18
**/
gboolean
e_webdav_discover_sources_sync (ESource *source,
const gchar *url_use_path,
guint32 only_supports,
const ENamedParameters *credentials,
gchar **out_certificate_pem,
GTlsCertificateFlags *out_certificate_errors,
GSList **out_discovered_sources,
GSList **out_calendar_user_addresses,
GCancellable *cancellable,
GError **error)
{
ESourceWebdav *webdav_extension;
AuthenticateData auth_data;
SoupSession *session;
SoupMessage *message;
SoupURI *soup_uri;
gulong cancelled_handler_id = 0, authenticate_handler_id;
gboolean success;
g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
if (url_use_path && (g_ascii_strncasecmp (url_use_path, "http://", 7) == 0 ||
g_ascii_strncasecmp (url_use_path, "https://", 8) == 0)) {
soup_uri = soup_uri_new (url_use_path);
url_use_path = NULL;
} else {
g_return_val_if_fail (e_source_has_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND), FALSE);
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, FALSE);
if (url_use_path) {
GString *new_path;
/* Absolute path overrides whole path, while relative path is only appended. */
if (*url_use_path == '/') {
new_path = g_string_new (url_use_path);
} else {
const gchar *current_path;
current_path = soup_uri_get_path (soup_uri);
new_path = g_string_new (current_path ? current_path : "");
if (!new_path->len || new_path->str[new_path->len - 1] != '/')
g_string_append_c (new_path, '/');
g_string_append (new_path, url_use_path);
}
if (!new_path->len || new_path->str[new_path->len - 1] != '/')
g_string_append_c (new_path, '/');
soup_uri_set_path (soup_uri, new_path->str);
g_string_free (new_path, TRUE);
}
session = soup_session_new ();
message = e_webdav_discover_new_propfind (
session, soup_uri, DEPTH_0,
NS_WEBDAV, XC ("resourcetype"),
NS_WEBDAV, XC ("current-user-principal"),
NS_WEBDAV, XC ("principal-URL"),
NS_CALDAV, XC ("calendar-home-set"),
NS_CALDAV, XC ("calendar-user-address-set"),
NS_CARDDAV, XC ("addressbook-home-set"),
NS_CARDDAV, XC ("principal-address"),
NULL);
if (!message) {
soup_uri_free (soup_uri);
g_object_unref (session);
return FALSE;
}
if (g_getenv ("WEBDAV_DEBUG") != NULL) {
SoupLogger *logger;
logger = soup_logger_new (SOUP_LOGGER_LOG_BODY, 100 * 1024 * 1024);
soup_session_add_feature (session, SOUP_SESSION_FEATURE (logger));
g_object_unref (logger);
}
if (e_source_has_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION)) {
SoupSessionFeature *feature;
ESourceAuthentication *auth_extension;
gchar *auth_method;
feature = soup_session_get_feature (session, SOUP_TYPE_AUTH_MANAGER);
soup_session_feature_add_feature (feature, E_TYPE_SOUP_AUTH_BEARER);
success = TRUE;
auth_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION);
auth_method = e_source_authentication_dup_method (auth_extension);
if (g_strcmp0 (auth_method, "OAuth2") == 0) {
SoupAuth *soup_auth;
gchar *access_token = NULL;
gint expires_in_seconds = -1;
soup_auth = g_object_new (E_TYPE_SOUP_AUTH_BEARER, SOUP_AUTH_HOST, soup_uri->host, NULL);
success = e_source_get_oauth2_access_token_sync (
source, cancellable, &access_token,
&expires_in_seconds, error);
if (success) {
e_soup_auth_bearer_set_access_token (
E_SOUP_AUTH_BEARER (soup_auth),
access_token, expires_in_seconds);
soup_auth_manager_use_auth (
SOUP_AUTH_MANAGER (feature),
soup_uri, soup_auth);
}
g_free (access_token);
g_object_unref (soup_auth);
}
g_free (auth_method);
if (!success) {
soup_uri_free (soup_uri);
g_object_unref (message);
g_object_unref (session);
return FALSE;
}
}
auth_data.source = source;
auth_data.credentials = credentials;
authenticate_handler_id = g_signal_connect (session, "authenticate",
G_CALLBACK (e_webdav_discover_authenticate_cb), &auth_data);
if (cancellable)
cancelled_handler_id = g_cancellable_connect (cancellable, G_CALLBACK (e_webdav_discover_cancelled_cb), session, NULL);
if (!g_cancellable_set_error_if_cancelled (cancellable, error)) {
GSList *calendars = NULL, *addressbooks = NULL;
GError *local_error = NULL;
e_soup_ssl_trust_connect (message, source);
soup_session_send_message (session, message);
success = TRUE;
if (only_supports == E_WEBDAV_DISCOVER_SUPPORTS_NONE ||
(only_supports & (E_WEBDAV_DISCOVER_SUPPORTS_EVENTS | E_WEBDAV_DISCOVER_SUPPORTS_MEMOS | E_WEBDAV_DISCOVER_SUPPORTS_TASKS)) != 0) {
success = e_webdav_discover_process_calendar_home_set (session, message, source, out_certificate_pem,
out_certificate_errors, &calendars, out_calendar_user_addresses, cancellable, &local_error);
if (!calendars && !g_cancellable_is_cancelled (cancellable) && (!soup_uri_get_path (soup_uri) ||
!strstr (soup_uri_get_path (soup_uri), "/.well-known/"))) {
g_clear_object (&message);
soup_uri_set_path (soup_uri, "/.well-known/caldav");
message = e_webdav_discover_new_propfind (
session, soup_uri, DEPTH_0,
NS_WEBDAV, XC ("resourcetype"),
NS_WEBDAV, XC ("current-user-principal"),
NS_WEBDAV, XC ("principal-URL"),
NS_CALDAV, XC ("calendar-home-set"),
NS_CALDAV, XC ("calendar-user-address-set"),
NULL);
if (message) {
soup_session_send_message (session, message);
/* Ignore errors here */
e_webdav_discover_process_calendar_home_set (session, message, source, out_certificate_pem,
out_certificate_errors, &calendars, out_calendar_user_addresses, cancellable, NULL);
}
}
}
if (success && (only_supports == E_WEBDAV_DISCOVER_SUPPORTS_NONE ||
(only_supports & (E_WEBDAV_DISCOVER_SUPPORTS_CONTACTS)) != 0)) {
success = e_webdav_discover_process_addressbook_home_set (session, message, source, out_certificate_pem,
out_certificate_errors, &addressbooks, cancellable, &local_error);
if (!addressbooks && !g_cancellable_is_cancelled (cancellable)) {
g_clear_object (&message);
soup_uri_set_path (soup_uri, "/.well-known/carddav");
message = e_webdav_discover_new_propfind (
session, soup_uri, DEPTH_0,
NS_WEBDAV, XC ("resourcetype"),
NS_WEBDAV, XC ("current-user-principal"),
NS_WEBDAV, XC ("principal-URL"),
NS_CARDDAV, XC ("addressbook-home-set"),
NS_CARDDAV, XC ("principal-address"),
NULL);
if (message) {
soup_session_send_message (session, message);
/* Ignore errors here */
e_webdav_discover_process_addressbook_home_set (session, message, source, out_certificate_pem,
out_certificate_errors, &addressbooks, cancellable, NULL);
}
}
}
if (calendars || addressbooks) {
success = TRUE;
g_clear_error (&local_error);
} else if (local_error) {
g_propagate_error (error, local_error);
}
if (out_discovered_sources) {
if (calendars)
*out_discovered_sources = g_slist_concat (*out_discovered_sources, calendars);
if (addressbooks)
*out_discovered_sources = g_slist_concat (*out_discovered_sources, addressbooks);
} else {
e_webdav_discover_free_discovered_sources (calendars);
e_webdav_discover_free_discovered_sources (addressbooks);
}
if (out_calendar_user_addresses && *out_calendar_user_addresses)
*out_calendar_user_addresses = g_slist_reverse (*out_calendar_user_addresses);
if (out_discovered_sources && *out_discovered_sources)
*out_discovered_sources = g_slist_reverse (*out_discovered_sources);
} else {
success = FALSE;
}
if (cancellable && cancelled_handler_id)
g_cancellable_disconnect (cancellable, cancelled_handler_id);
if (authenticate_handler_id)
g_signal_handler_disconnect (session, authenticate_handler_id);
soup_uri_free (soup_uri);
g_clear_object (&message);
g_object_unref (session);
return success;
}