/* * 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; }