Files
federated-calendar/src/utils/gcal-source-discoverer.c
T
Michael Catanzaro a3e9af23dc source-discoverer: add support for webcal:// URIs
We can implement webcal:// by simply rewriting the URI to https://.
Amazing!

Fixes #564
2020-04-14 15:16:24 -05:00

414 lines
13 KiB
C

/* gcal-source-discoverer.c
*
* Copyright 2019 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#define G_LOG_DOMAIN "GcalSourceDiscoverer"
#include <libecal/libecal.h>
#include <libsoup/soup.h>
#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);
}