/* gcal-source-discoverer.c * * Copyright 2019 Georges Basile Stavracas Neto * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * SPDX-License-Identifier: GPL-3.0-or-later */ #define G_LOG_DOMAIN "GcalSourceDiscoverer" #include #include #include "gcal-debug.h" #include "gcal-source-discoverer.h" #include "gcal-utils.h" G_DEFINE_QUARK (GcalSourceDiscoverer, gcal_source_discoverer_error); typedef struct { gchar *uri; gchar *username; gchar *password; } DiscovererData; /* * Auxiliary methods */ static void discoverer_data_free (gpointer data) { DiscovererData *discoverer_data = data; if (!discoverer_data) return; g_free (discoverer_data->uri); g_free (discoverer_data->username); g_free (discoverer_data->password); g_free (discoverer_data); } static ESource* create_source_for_uri (DiscovererData *data) { ESourceAuthentication *auth; g_autoptr (SoupURI) soup_uri = NULL; g_autofree gchar *display_name = NULL; g_autofree gchar *basename = NULL; ESourceExtension *ext; ESourceWebdav *webdav; ESource *source; const gchar *host; const gchar *path; soup_uri = soup_uri_new (data->uri); if (!soup_uri) GCAL_RETURN (NULL); host = soup_uri_get_host (soup_uri); /* Create the new source and add the needed extensions */ source = e_source_new (NULL, NULL, NULL); e_source_set_parent (source, "webcal-stub"); /* Display name */ path = soup_uri_get_path (soup_uri); basename = g_path_get_basename (path); display_name = gcal_utils_format_filename_for_display (basename); e_source_set_display_name (source, display_name); ext = e_source_get_extension (source, E_SOURCE_EXTENSION_CALENDAR); e_source_backend_set_backend_name (E_SOURCE_BACKEND (ext), "webcal"); /* Authentication */ auth = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION); e_source_authentication_set_host (auth, host); /* Webdav */ webdav = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND); e_source_webdav_set_soup_uri (webdav, soup_uri); return source; } static ESource* create_discovered_source (ESource *source, GSList *user_addresses, EWebDAVDiscoveredSource *discovered_source) { g_autoptr (SoupURI) soup_uri = NULL; ESourceSelectable *selectable; ESourceExtension *ext; ESourceWebdav *webdav; ESource *new_source; const gchar *resource_path; soup_uri = soup_uri_new (discovered_source->href); resource_path = soup_uri_get_path (soup_uri); new_source = e_source_new (NULL, NULL, NULL); e_source_set_parent (new_source, "local"); ext = e_source_get_extension (new_source, E_SOURCE_EXTENSION_CALENDAR); e_source_backend_set_backend_name (E_SOURCE_BACKEND (ext), "local"); /* Copy Authentication data */ if (e_source_has_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION)) { ESourceAuthentication *new_auth, *parent_auth; parent_auth = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION); new_auth = e_source_get_extension (new_source, E_SOURCE_EXTENSION_AUTHENTICATION); e_source_authentication_set_host (new_auth, e_source_authentication_get_host (parent_auth)); e_source_authentication_set_method (new_auth, e_source_authentication_get_method (parent_auth)); e_source_authentication_set_port (new_auth, e_source_authentication_get_port (parent_auth)); e_source_authentication_set_user (new_auth, e_source_authentication_get_user (parent_auth)); e_source_authentication_set_proxy_uid (new_auth, e_source_authentication_get_proxy_uid (parent_auth)); } /* Copy Webdav data */ if (e_source_has_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND)) { ESourceWebdav *new_webdav, *parent_webdav; parent_webdav = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND); new_webdav = e_source_get_extension (new_source, E_SOURCE_EXTENSION_WEBDAV_BACKEND); e_source_webdav_set_display_name (new_webdav, e_source_webdav_get_display_name (parent_webdav)); e_source_webdav_set_resource_path (new_webdav, e_source_webdav_get_resource_path (parent_webdav)); e_source_webdav_set_resource_query (new_webdav, e_source_webdav_get_resource_query (parent_webdav)); e_source_webdav_set_email_address (new_webdav, e_source_webdav_get_email_address (parent_webdav)); e_source_webdav_set_ssl_trust (new_webdav, e_source_webdav_get_ssl_trust (parent_webdav)); e_source_set_parent (new_source, "webcal-stub"); e_source_backend_set_backend_name (E_SOURCE_BACKEND (ext), "webcal"); } /* build up the new source */ e_source_set_display_name (new_source, discovered_source->display_name); webdav = e_source_get_extension (new_source, E_SOURCE_EXTENSION_WEBDAV_BACKEND); e_source_webdav_set_resource_path (webdav, resource_path); e_source_webdav_set_display_name (webdav, discovered_source->display_name); if (user_addresses) e_source_webdav_set_email_address (webdav, user_addresses->data); /* Setup the color */ selectable = e_source_get_extension (new_source, E_SOURCE_EXTENSION_CALENDAR); e_source_selectable_set_color (selectable, discovered_source->color); return new_source; } static gboolean is_authentication_error (gint code) { switch (code) { case SOUP_STATUS_UNAUTHORIZED: case SOUP_STATUS_FORBIDDEN: case SOUP_STATUS_METHOD_NOT_ALLOWED: return TRUE; } return FALSE; } /* * Callbacks */ static void on_soup_session_authenticate_cb (SoupSession *session, SoupMessage *message, SoupAuth *auth, gboolean retrying, gpointer user_data) { DiscovererData *data = user_data; if (data->username && data->password) soup_auth_authenticate (auth, data->username, data->password); else soup_message_set_status (message, SOUP_STATUS_UNAUTHORIZED); } typedef GPtrArray* (*DiscoverFunc) (DiscovererData *data, GCancellable *cancellable, GError **error); static GPtrArray* discover_file_in_thread (DiscovererData *data, GCancellable *cancellable, GError **error) { g_autoptr (GInputStream) input_stream = NULL; g_autoptr (SoupMessage) message = NULL; g_autoptr (SoupSession) session = NULL; g_autoptr (GPtrArray) source = NULL; g_autoptr (SoupURI) uri = NULL; g_autofree gchar *uri_str = NULL; const gchar *content_type; GCAL_ENTRY; uri = soup_uri_new (data->uri); if (g_strcmp0 (soup_uri_get_scheme (uri), "webcal") == 0) soup_uri_set_scheme (uri, SOUP_URI_SCHEME_HTTPS); uri_str = soup_uri_to_string (uri, FALSE); GCAL_TRACE_MSG ("Creating request for %s", uri_str); session = soup_session_new_with_options (SOUP_SESSION_TIMEOUT, 10, NULL); g_signal_connect (session, "authenticate", G_CALLBACK (on_soup_session_authenticate_cb), data); message = soup_message_new_from_uri ("GET", uri); input_stream = soup_session_send (session, message, cancellable, error); if (!input_stream) GCAL_RETURN (NULL); g_input_stream_close (input_stream, cancellable, error); content_type = soup_message_headers_get_content_type (message->response_headers, NULL); GCAL_TRACE_MSG ("Message retrieved, content type: %s, status code: %u", content_type, message->status_code); if (is_authentication_error (message->status_code)) { g_set_error (error, GCAL_SOURCE_DISCOVERER_ERROR, GCAL_SOURCE_DISCOVERER_ERROR_UNAUTHORIZED, "%s", soup_status_get_phrase (message->status_code)); GCAL_RETURN (NULL); } if (content_type && g_strstr_len (content_type, -1, "text/calendar") != NULL) { source = g_ptr_array_new_full (1, g_object_unref); g_ptr_array_add (source, create_source_for_uri (data)); } GCAL_RETURN (g_steal_pointer (&source)); } static GPtrArray* discover_webdav_in_thread (DiscovererData *data, GCancellable *cancellable, GError **error) { g_autoptr (ENamedParameters) credentials = NULL; g_autoptr (GPtrArray) sources = NULL; g_autoptr (ESource) source = NULL; g_autoptr (GError) local_error = NULL; g_autofree gchar *certificate_pem = NULL; GTlsCertificateFlags flags; GSList *discovered_sources = NULL; GSList *user_addresses = NULL; GSList *l; GCAL_ENTRY; credentials = e_named_parameters_new (); e_named_parameters_set (credentials, E_SOURCE_CREDENTIAL_USERNAME, data->username); e_named_parameters_set (credentials, E_SOURCE_CREDENTIAL_PASSWORD, data->password); source = create_source_for_uri (data); e_webdav_discover_sources_sync (source, data->uri, E_WEBDAV_DISCOVER_SUPPORTS_EVENTS, credentials, &certificate_pem, &flags, &discovered_sources, &user_addresses, cancellable, &local_error); if (local_error) { if (is_authentication_error (local_error->code)) { g_set_error (error, GCAL_SOURCE_DISCOVERER_ERROR, GCAL_SOURCE_DISCOVERER_ERROR_UNAUTHORIZED, "%s", soup_status_get_phrase (local_error->code)); } else { g_propagate_error (error, g_steal_pointer (&local_error)); } GCAL_RETURN (NULL); } sources = g_ptr_array_new_full (g_slist_length (discovered_sources), g_object_unref); for (l = discovered_sources; l; l = l->next) { EWebDAVDiscoveredSource *discovered_source; ESource *new_source; discovered_source = l->data; new_source = create_discovered_source (source, user_addresses, discovered_source); if (!new_source) continue; g_ptr_array_add (sources, new_source); } g_clear_pointer (&discovered_sources, e_webdav_discover_free_discovered_sources); g_slist_free_full (user_addresses, g_free); GCAL_RETURN (g_steal_pointer (&sources)); } static void discover_sources_in_thread_cb (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) { g_autoptr (GError) error = NULL; DiscovererData *data = task_data; guint i; const DiscoverFunc discover_funcs[] = { discover_file_in_thread, discover_webdav_in_thread, }; for (i = 0; i < G_N_ELEMENTS (discover_funcs); i++) { g_autoptr (GPtrArray) sources = NULL; g_autoptr (GError) local_error = NULL; sources = discover_funcs[i] (data, cancellable, &local_error); if (sources) { g_task_return_pointer (task, g_steal_pointer (&sources), NULL); return; } else if (local_error) { g_clear_error (&error); g_propagate_error (&error, g_steal_pointer (&local_error)); } } if (error) g_task_return_error (task, g_steal_pointer (&error)); else g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_UNKNOWN, "Unknown error"); } void gcal_discover_sources_from_uri (const gchar *uri, const gchar *username, const gchar *password, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { g_autoptr (GTask) task = NULL; DiscovererData *data; g_assert (uri != NULL); data = g_new0 (DiscovererData, 1); data->uri = g_strdup (uri); data->username = g_strdup (username); data->password = g_strdup (password); task = g_task_new (NULL, cancellable, callback, user_data); g_task_set_source_tag (task, gcal_discover_sources_from_uri); g_task_set_task_data (task, data, discoverer_data_free); g_task_run_in_thread (task, discover_sources_in_thread_cb); } GPtrArray* gcal_discover_sources_from_uri_finish (GAsyncResult *result, GError **error) { g_return_val_if_fail (g_task_is_valid (result, NULL), NULL); g_return_val_if_fail (!error || !*error, NULL); return g_task_propagate_pointer (G_TASK (result), error); }