[evolution-data-server/gnome-3-16] Bug 663828 - Auto-add all Google calendars for GOA accounts
- From: Milan Crha <mcrha src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [evolution-data-server/gnome-3-16] Bug 663828 - Auto-add all Google calendars for GOA accounts
- Date: Fri, 29 May 2015 08:47:29 +0000 (UTC)
commit 00f63d86424128f976591553d530d84ab93053a6
Author: Milan Crha <mcrha redhat com>
Date: Fri May 29 10:47:01 2015 +0200
Bug 663828 - Auto-add all Google calendars for GOA accounts
modules/google-backend/Makefile.am | 2 +
modules/google-backend/e-webdav-discover.c | 1943 ++++++++++++++++++++++++
modules/google-backend/e-webdav-discover.h | 75 +
modules/google-backend/module-google-backend.c | 423 ++++--
4 files changed, 2338 insertions(+), 105 deletions(-)
---
diff --git a/modules/google-backend/Makefile.am b/modules/google-backend/Makefile.am
index 3063f4e..f9ec66c 100644
--- a/modules/google-backend/Makefile.am
+++ b/modules/google-backend/Makefile.am
@@ -13,6 +13,8 @@ module_google_backend_la_CPPFLAGS = \
module_google_backend_la_SOURCES = \
module-google-backend.c \
+ e-webdav-discover.h \
+ e-webdav-discover.c \
$(NULL)
module_google_backend_la_LIBADD = \
diff --git a/modules/google-backend/e-webdav-discover.c b/modules/google-backend/e-webdav-discover.c
new file mode 100644
index 0000000..4d8ea82
--- /dev/null
+++ b/modules/google-backend/e-webdav-discover.c
@@ -0,0 +1,1943 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include <libsoup/soup.h>
+
+#include <libxml/tree.h>
+#include <libxml/xpath.h>
+#include <libxml/xpathInternals.h>
+
+#include <libedataserver/libedataserver.h>
+#include <libebackend/libebackend.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)
+ 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);
+
+ 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)
+ 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);
+
+ 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;
+}
diff --git a/modules/google-backend/e-webdav-discover.h b/modules/google-backend/e-webdav-discover.h
new file mode 100644
index 0000000..240d35d
--- /dev/null
+++ b/modules/google-backend/e-webdav-discover.h
@@ -0,0 +1,75 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef E_WEBDAV_DISCOVER_H
+#define E_WEBDAV_DISCOVER_H
+
+#include <glib.h>
+
+#include <libedataserver/libedataserver.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+ E_WEBDAV_DISCOVER_SUPPORTS_NONE = 0,
+ E_WEBDAV_DISCOVER_SUPPORTS_CONTACTS = 1 << 0,
+ E_WEBDAV_DISCOVER_SUPPORTS_EVENTS = 1 << 1,
+ E_WEBDAV_DISCOVER_SUPPORTS_MEMOS = 1 << 2,
+ E_WEBDAV_DISCOVER_SUPPORTS_TASKS = 1 << 3
+} EWebDAVDiscoverSupports;
+
+typedef struct _EWebDAVDiscoveredSource {
+ gchar *href;
+ guint32 supports;
+ gchar *display_name;
+ gchar *description;
+ gchar *color;
+} EWebDAVDiscoveredSource;
+
+void e_webdav_discover_free_discovered_sources
+ (GSList *discovered_sources);
+
+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);
+
+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);
+
+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);
+
+G_END_DECLS
+
+#endif /* E_WEBDAV_DISCOVER_H */
diff --git a/modules/google-backend/module-google-backend.c b/modules/google-backend/module-google-backend.c
index d4d468c..de3ae6c 100644
--- a/modules/google-backend/module-google-backend.c
+++ b/modules/google-backend/module-google-backend.c
@@ -20,6 +20,8 @@
#include <libebackend/libebackend.h>
+#include "e-webdav-discover.h"
+
#ifdef HAVE_GOOGLE
#include <gdata/gdata.h>
#endif
@@ -52,18 +54,6 @@
#define GOOGLE_SMTP_PORT 587
#define GOOGLE_SMTP_SECURITY_METHOD METHOD (STARTTLS_ON_STANDARD_PORT)
-/* Calendar Configuration Details */
-#define GOOGLE_CALENDAR_BACKEND_NAME "caldav"
-#define GOOGLE_CALENDAR_RESOURCE_ID "Calendar"
-
-/* CalDAV v1 Configuration Details */
-#define GOOGLE_CALDAV_V1_HOST "www.google.com"
-#define GOOGLE_CALDAV_V1_PATH "/calendar/dav/%s/events"
-
-/* CalDAV v2 Configuration Details */
-#define GOOGLE_CALDAV_V2_HOST "apidata.googleusercontent.com"
-#define GOOGLE_CALDAV_V2_PATH "/caldav/v2/%s/events"
-
/* Contacts Configuration Details */
#define GOOGLE_CONTACTS_BACKEND_NAME "google"
#define GOOGLE_CONTACTS_HOST "www.google.com"
@@ -118,44 +108,22 @@ google_backend_calendar_update_auth_method (ESource *source)
{
EOAuth2Support *oauth2_support;
ESourceAuthentication *auth_extension;
- ESourceWebdav *webdav_extension;
- const gchar *extension_name;
- const gchar *host;
const gchar *method;
- const gchar *path_format;
- gchar *path;
- gchar *user;
- oauth2_support = e_server_side_source_ref_oauth2_support (
- E_SERVER_SIDE_SOURCE (source));
+ oauth2_support = e_server_side_source_ref_oauth2_support (E_SERVER_SIDE_SOURCE (source));
/* The host name and WebDAV resource path depend on the
* authentication method used, so update those here too. */
if (oauth2_support != NULL) {
method = "OAuth2";
- host = GOOGLE_CALDAV_V2_HOST;
- path_format = GOOGLE_CALDAV_V2_PATH;
} else {
method = "plain/password";
- host = GOOGLE_CALDAV_V1_HOST;
- path_format = GOOGLE_CALDAV_V1_PATH;
}
- extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
- auth_extension = e_source_get_extension (source, extension_name);
- e_source_authentication_set_host (auth_extension, host);
+ auth_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION);
e_source_authentication_set_method (auth_extension, method);
- extension_name = E_SOURCE_EXTENSION_WEBDAV_BACKEND;
- webdav_extension = e_source_get_extension (source, extension_name);
-
- user = e_source_authentication_dup_user (auth_extension);
- path = g_strdup_printf (path_format, (user != NULL) ? user : "");
- e_source_webdav_set_resource_path (webdav_extension, path);
- g_free (path);
- g_free (user);
-
g_clear_object (&oauth2_support);
}
@@ -179,80 +147,283 @@ google_backend_contacts_update_auth_method (ESource *source)
}
static void
-google_backend_add_calendar (ECollectionBackend *backend)
+google_add_uid_to_hashtable (gpointer source,
+ gpointer known_sources)
+{
+ ESourceResource *resource;
+ gchar *uid, *rid;
+
+ if (!e_source_has_extension (source, E_SOURCE_EXTENSION_RESOURCE))
+ return;
+
+ resource = e_source_get_extension (source, E_SOURCE_EXTENSION_RESOURCE);
+
+ uid = e_source_dup_uid (source);
+ if (!uid || !*uid) {
+ g_free (uid);
+ return;
+ }
+
+ rid = e_source_resource_dup_identity (resource);
+ if (!rid || !*rid) {
+ g_free (rid);
+ g_free (uid);
+ return;
+ }
+
+ g_hash_table_insert (known_sources, rid, uid);
+}
+
+static void
+google_remove_unknown_sources_cb (gpointer resource_id,
+ gpointer uid,
+ gpointer user_data)
{
+ ESourceRegistryServer *server = user_data;
ESource *source;
- ESource *collection_source;
+
+ source = e_source_registry_server_ref_source (server, uid);
+
+ if (source) {
+ e_source_registry_server_remove_source (server, source);
+ g_object_unref (source);
+ }
+}
+
+static void
+google_add_found_source (ECollectionBackend *collection,
+ EWebDAVDiscoverSupports source_type,
+ SoupURI *uri,
+ const gchar *display_name,
+ const gchar *color,
+ GHashTable *known_sources)
+{
ESourceRegistryServer *server;
- ESourceExtension *extension;
- ESourceCollection *collection_extension;
- const gchar *backend_name;
- const gchar *extension_name;
- const gchar *resource_id;
+ ESourceBackend *backend;
+ ESource *source = NULL;
+ const gchar *backend_name = NULL;
+ const gchar *provider = NULL;
+ const gchar *identity_prefix = NULL;
+ const gchar *source_uid;
+ gboolean is_new;
+ gchar *url;
+ gchar *identity;
+
+ g_return_if_fail (collection != NULL);
+ g_return_if_fail (uri != NULL);
+ g_return_if_fail (display_name != NULL);
+ g_return_if_fail (known_sources != NULL);
+
+ switch (source_type) {
+ case E_WEBDAV_DISCOVER_SUPPORTS_CONTACTS:
+ backend_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
+ provider = "webdav";
+ identity_prefix = "contacts";
+ break;
+ case E_WEBDAV_DISCOVER_SUPPORTS_EVENTS:
+ backend_name = E_SOURCE_EXTENSION_CALENDAR;
+ provider = "caldav";
+ identity_prefix = "events";
+ break;
+ case E_WEBDAV_DISCOVER_SUPPORTS_MEMOS:
+ backend_name = E_SOURCE_EXTENSION_MEMO_LIST;
+ provider = "caldav";
+ identity_prefix = "memos";
+ break;
+ case E_WEBDAV_DISCOVER_SUPPORTS_TASKS:
+ backend_name = E_SOURCE_EXTENSION_TASK_LIST;
+ provider = "caldav";
+ identity_prefix = "tasks";
+ break;
+ default:
+ g_warn_if_reached ();
+ return;
+ }
- /* FIXME As a future enhancement, we should query Google
- * for a list of user calendars and add them to the
- * collection with matching display names and colors. */
+ g_return_if_fail (backend_name != NULL);
- /* NOTE: Host name and WebDAV resource path are set in
- * google_backend_calendar_update_auth_method(),
- * since they depend on the auth method used. */
+ server = e_collection_backend_ref_server (collection);
- collection_source = e_backend_get_source (E_BACKEND (backend));
+ url = soup_uri_to_string (uri, FALSE);
+ identity = g_strconcat (identity_prefix, "::", url, NULL);
+ source_uid = g_hash_table_lookup (known_sources, identity);
+ is_new = !source_uid;
+ if (is_new) {
+ ESource *master_source;
- resource_id = GOOGLE_CALENDAR_RESOURCE_ID;
- source = e_collection_backend_new_child (backend, resource_id);
- e_source_set_display_name (source, _("Calendar"));
+ source = e_collection_backend_new_child (collection, identity);
+ g_warn_if_fail (source != NULL);
- collection_extension = e_source_get_extension (
- collection_source, E_SOURCE_EXTENSION_COLLECTION);
+ if (source) {
+ ESourceCollection *collection_extension;
+ ESourceAuthentication *child_auth;
+ ESourceResource *resource;
+ ESourceWebdav *master_webdav, *child_webdav;
- /* Configure the calendar source. */
+ master_source = e_backend_get_source (E_BACKEND (collection));
+ master_webdav = e_source_get_extension (master_source,
E_SOURCE_EXTENSION_WEBDAV_BACKEND);
+ collection_extension = e_source_get_extension (master_source,
E_SOURCE_EXTENSION_COLLECTION);
+ child_auth = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION);
+ child_webdav = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
+ resource = e_source_get_extension (source, E_SOURCE_EXTENSION_RESOURCE);
- backend_name = GOOGLE_CALENDAR_BACKEND_NAME;
+ e_source_authentication_set_user (child_auth, e_source_collection_get_identity
(collection_extension));
+ e_source_webdav_set_soup_uri (child_webdav, uri);
+ e_source_resource_set_identity (resource, identity);
- extension_name = E_SOURCE_EXTENSION_CALENDAR;
- extension = e_source_get_extension (source, extension_name);
+ /* inherit ssl trust options */
+ e_source_webdav_set_ssl_trust (child_webdav, e_source_webdav_get_ssl_trust
(master_webdav));
+ }
+ } else {
+ source = e_source_registry_server_ref_source (server, source_uid);
+ g_warn_if_fail (source != NULL);
- e_source_backend_set_backend_name (
- E_SOURCE_BACKEND (extension), backend_name);
+ g_hash_table_remove (known_sources, identity);
+ }
- extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
- extension = e_source_get_extension (source, extension_name);
+ g_free (identity);
+ g_free (url);
- e_binding_bind_property (
- collection_extension, "identity",
- extension, "user",
- G_BINDING_SYNC_CREATE);
+ /* these properties are synchronized always */
+ if (source) {
+ gint rr, gg, bb;
- /* Make sure the WebDAV resource path is up-to-date, since
- * it's built from the "user" property that we just set. */
- google_backend_calendar_update_auth_method (source);
+ backend = e_source_get_extension (source, backend_name);
+ e_source_backend_set_backend_name (backend, provider);
- extension_name = E_SOURCE_EXTENSION_SECURITY;
- extension = e_source_get_extension (source, extension_name);
+ e_source_set_display_name (source, display_name);
- e_source_security_set_secure (
- E_SOURCE_SECURITY (extension), TRUE);
+ /* Also check whether the color format is as expected; it cannot
+ be used gdk_rgba_parse here, because it required gdk/gtk. */
+ if (is_new && source_type != E_WEBDAV_DISCOVER_SUPPORTS_CONTACTS && color &&
+ sscanf (color, "#%02x%02x%02x", &rr, &gg, &bb) == 3) {
+ gchar *safe_color;
- extension_name = E_SOURCE_EXTENSION_ALARMS;
- extension = e_source_get_extension (source, extension_name);
- if (!e_source_alarms_get_last_notified (E_SOURCE_ALARMS (extension))) {
- GTimeVal today_tv;
- gchar *today;
-
- g_get_current_time (&today_tv);
- today = g_time_val_to_iso8601 (&today_tv);
- e_source_alarms_set_last_notified (
- E_SOURCE_ALARMS (extension), today);
- g_free (today);
+ /* In case an #RRGGBBAA is returned */
+ safe_color = g_strdup_printf ("#%02x%02x%02x", rr, gg, bb);
+
+ e_source_selectable_set_color (E_SOURCE_SELECTABLE (backend), safe_color);
+
+ g_free (safe_color);
+ }
+
+ if (is_new)
+ e_source_registry_server_add_source (server, source);
+
+ g_object_unref (source);
}
- server = e_collection_backend_ref_server (backend);
- e_source_registry_server_add_source (server, source);
g_object_unref (server);
+}
- g_object_unref (source);
+static ESourceAuthenticationResult
+google_backend_authenticate_sync (EBackend *backend,
+ const ENamedParameters *credentials,
+ gchar **out_certificate_pem,
+ GTlsCertificateFlags *out_certificate_errors,
+ GCancellable *cancellable,
+ GError **error)
+{
+ ECollectionBackend *collection = E_COLLECTION_BACKEND (backend);
+ ESourceCollection *collection_extension;
+ ESourceGoa *goa_extension;
+ ESource *source;
+ ESourceAuthenticationResult result;
+ GHashTable *known_sources;
+ GList *sources;
+ GSList *discovered_sources = NULL;
+ ENamedParameters *credentials_copy = NULL;
+ gboolean any_success = FALSE;
+ GError *local_error = NULL;
+
+ g_return_val_if_fail (collection != NULL, E_SOURCE_AUTHENTICATION_ERROR);
+
+ source = e_backend_get_source (backend);
+ collection_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_COLLECTION);
+ goa_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_GOA);
+
+ g_return_val_if_fail (e_source_collection_get_calendar_enabled (collection_extension) ||
+ e_source_collection_get_contacts_enabled (collection_extension),
E_SOURCE_AUTHENTICATION_ERROR);
+
+ if (credentials && !e_named_parameters_get (credentials, E_SOURCE_CREDENTIAL_USERNAME)) {
+ credentials_copy = e_named_parameters_new_clone (credentials);
+ e_named_parameters_set (credentials_copy, E_SOURCE_CREDENTIAL_USERNAME,
e_source_collection_get_identity (collection_extension));
+ credentials = credentials_copy;
+ }
+
+ /* resource-id => source's UID */
+ known_sources = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+ sources = e_collection_backend_list_calendar_sources (collection);
+ g_list_foreach (sources, google_add_uid_to_hashtable, known_sources);
+ g_list_free_full (sources, g_object_unref);
+
+ google_backend_calendar_update_auth_method (source);
+
+ if (e_source_collection_get_calendar_enabled (collection_extension) && e_source_goa_get_calendar_url
(goa_extension) &&
+ e_webdav_discover_sources_sync (source, e_source_goa_get_calendar_url (goa_extension),
E_WEBDAV_DISCOVER_SUPPORTS_NONE,
+ credentials, out_certificate_pem, out_certificate_errors,
+ &discovered_sources, NULL, cancellable, &local_error)) {
+ EWebDAVDiscoverSupports source_types[] = {
+ E_WEBDAV_DISCOVER_SUPPORTS_EVENTS,
+ E_WEBDAV_DISCOVER_SUPPORTS_MEMOS,
+ E_WEBDAV_DISCOVER_SUPPORTS_TASKS
+ };
+ GSList *link;
+ gint ii;
+
+ for (link = discovered_sources; link; link = g_slist_next (link)) {
+ EWebDAVDiscoveredSource *discovered_source = link->data;
+ SoupURI *soup_uri;
+
+ if (!discovered_source || !discovered_source->href ||
!discovered_source->display_name)
+ continue;
+
+ soup_uri = soup_uri_new (discovered_source->href);
+ if (!soup_uri)
+ continue;
+
+ for (ii = 0; ii < G_N_ELEMENTS (source_types); ii++) {
+ if ((discovered_source->supports & source_types[ii]) == source_types[ii])
+ google_add_found_source (collection, source_types[ii], soup_uri,
+ discovered_source->display_name, discovered_source->color,
known_sources);
+ }
+
+ soup_uri_free (soup_uri);
+ }
+
+ any_success = TRUE;
+ }
+
+ if (any_success) {
+ ESourceRegistryServer *server;
+
+ server = e_collection_backend_ref_server (collection);
+
+ g_hash_table_foreach (known_sources, google_remove_unknown_sources_cb, server);
+
+ g_object_unref (server);
+ }
+
+ if (local_error == NULL) {
+ result = E_SOURCE_AUTHENTICATION_ACCEPTED;
+ e_collection_backend_authenticate_children (collection, credentials);
+ } else if (g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_UNAUTHORIZED) ||
+ g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_FORBIDDEN)) {
+ result = E_SOURCE_AUTHENTICATION_REJECTED;
+ g_clear_error (&local_error);
+ } else if (g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_SSL_FAILED)) {
+ result = E_SOURCE_AUTHENTICATION_ERROR_SSL_FAILED;
+ g_propagate_error (error, local_error);
+ } else {
+ result = E_SOURCE_AUTHENTICATION_ERROR;
+ g_propagate_error (error, local_error);
+ }
+
+ g_hash_table_destroy (known_sources);
+ e_named_parameters_free (credentials_copy);
+
+ return result;
}
#if GDATA_CHECK_VERSION(0,15,1)
@@ -369,23 +540,40 @@ static void
google_backend_populate (ECollectionBackend *backend)
{
GList *list, *link;
- gboolean have_calendar = FALSE, have_tasks = FALSE;
+ gboolean have_tasks = FALSE;
+ ESourceRegistryServer *server;
+ ESourceCollection *collection_extension;
+ ESource *source;
- list = e_collection_backend_list_calendar_sources (backend);
+ server = e_collection_backend_ref_server (backend);
+ list = e_collection_backend_claim_all_resources (backend);
for (link = list; link; link = g_list_next (link)) {
ESource *source = link->data;
- have_calendar = have_calendar || e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR);
- have_tasks = have_tasks || e_source_has_extension (source, E_SOURCE_EXTENSION_TASK_LIST);
+ if (e_source_has_extension (source, E_SOURCE_EXTENSION_RESOURCE)) {
+ ESourceResource *resource;
+ ESource *child;
+
+ resource = e_source_get_extension (source, E_SOURCE_EXTENSION_RESOURCE);
+ child = e_collection_backend_new_child (backend, e_source_resource_get_identity
(resource));
+ if (child) {
+ e_source_registry_server_add_source (server, source);
+ g_object_unref (child);
+ }
+ }
+ }
+
+ g_list_free_full (list, g_object_unref);
+ g_object_unref (server);
- if (have_calendar && have_tasks)
- break;
+ list = e_collection_backend_list_calendar_sources (backend);
+ for (link = list; link && !have_tasks; link = g_list_next (link)) {
+ ESource *source = link->data;
+
+ have_tasks = have_tasks || e_source_has_extension (source, E_SOURCE_EXTENSION_TASK_LIST);
}
g_list_free_full (list, (GDestroyNotify) g_object_unref);
- if (!have_calendar)
- google_backend_add_calendar (backend);
-
#if GDATA_CHECK_VERSION(0,15,1)
if (!have_tasks)
google_backend_add_tasks (backend);
@@ -397,8 +585,15 @@ google_backend_populate (ECollectionBackend *backend)
g_list_free_full (list, (GDestroyNotify) g_object_unref);
/* Chain up to parent's populate() method. */
- E_COLLECTION_BACKEND_CLASS (e_google_backend_parent_class)->
- populate (backend);
+ E_COLLECTION_BACKEND_CLASS (e_google_backend_parent_class)->populate (backend);
+
+ source = e_backend_get_source (E_BACKEND (backend));
+ collection_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_COLLECTION);
+
+ if (e_source_collection_get_calendar_enabled (collection_extension)) {
+ e_backend_schedule_credentials_required (E_BACKEND (backend),
+ E_SOURCE_CREDENTIALS_REASON_REQUIRED, NULL, 0, NULL, NULL, G_STRFUNC);
+ }
}
static gchar *
@@ -412,7 +607,7 @@ google_backend_dup_resource_id (ECollectionBackend *backend,
extension_name = E_SOURCE_EXTENSION_CALENDAR;
if (e_source_has_extension (child_source, extension_name))
- return g_strdup (GOOGLE_CALENDAR_RESOURCE_ID);
+ return E_COLLECTION_BACKEND_CLASS (e_google_backend_parent_class)->dup_resource_id (backend,
child_source);
extension_name = E_SOURCE_EXTENSION_TASK_LIST;
if (e_source_has_extension (child_source, extension_name))
@@ -484,6 +679,20 @@ google_backend_child_added (ECollectionBackend *backend,
* Many-to-one property bindinds tend not to work so well. */
extension_name = E_SOURCE_EXTENSION_CALENDAR;
if (e_source_has_extension (child_source, extension_name)) {
+ ESourceAlarms *alarms_extension;
+
+ /* To not notify about past reminders. */
+ alarms_extension = e_source_get_extension (child_source, E_SOURCE_EXTENSION_ALARMS);
+ if (!e_source_alarms_get_last_notified (alarms_extension)) {
+ GTimeVal today_tv;
+ gchar *today;
+
+ g_get_current_time (&today_tv);
+ today = g_time_val_to_iso8601 (&today_tv);
+ e_source_alarms_set_last_notified (alarms_extension, today);
+ g_free (today);
+ }
+
google_backend_calendar_update_auth_method (child_source);
g_signal_connect (
child_source, "notify::oauth2-support",
@@ -509,12 +718,16 @@ google_backend_child_added (ECollectionBackend *backend,
static void
e_google_backend_class_init (EGoogleBackendClass *class)
{
- ECollectionBackendClass *backend_class;
+ EBackendClass *backend_class;
+ ECollectionBackendClass *collection_backend_class;
+
+ backend_class = E_BACKEND_CLASS (class);
+ backend_class->authenticate_sync = google_backend_authenticate_sync;
- backend_class = E_COLLECTION_BACKEND_CLASS (class);
- backend_class->populate = google_backend_populate;
- backend_class->dup_resource_id = google_backend_dup_resource_id;
- backend_class->child_added = google_backend_child_added;
+ collection_backend_class = E_COLLECTION_BACKEND_CLASS (class);
+ collection_backend_class->populate = google_backend_populate;
+ collection_backend_class->dup_resource_id = google_backend_dup_resource_id;
+ collection_backend_class->child_added = google_backend_child_added;
}
static void
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]