The new source discoverer is a complete overhaul of the current code. It is async-only, correctly threaded, and easier to maintain. A major new feature of this web source discoverer is that it now pings the server to see if the URL feeds us back with an iCalendar file. Furthermore, it continues to use E-D-S WebDAV discovery functions as a fallback mechanism. Fixes #380
331 lines
8.8 KiB
C
331 lines
8.8 KiB
C
/* gcal-simple-server.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 "GcalSimpleServer"
|
|
|
|
#include "gcal-simple-server.h"
|
|
|
|
struct _GcalSimpleServer
|
|
{
|
|
GObject parent;
|
|
|
|
/* Only valid while running */
|
|
GThread *thread;
|
|
GMainLoop *thread_mainloop;
|
|
SoupServer *server;
|
|
gchar *uri;
|
|
|
|
GMutex running_mutex;
|
|
GCond running_cond;
|
|
gboolean running;
|
|
};
|
|
|
|
G_DEFINE_TYPE (GcalSimpleServer, gcal_simple_server, G_TYPE_OBJECT)
|
|
|
|
|
|
/*
|
|
* Auxiliary methods
|
|
*/
|
|
|
|
static void
|
|
process_get (SoupMessage *message,
|
|
const gchar *prefix,
|
|
const gchar *path)
|
|
{
|
|
g_autofree gchar *calendar_path = g_strdup_printf ("%s/calendar", prefix);
|
|
g_autofree gchar *calendar_file = g_strdup_printf ("%s/calendar.ics", prefix);
|
|
|
|
if (g_strcmp0 (path, calendar_path) == 0)
|
|
{
|
|
g_debug ("Serving empty calendar");
|
|
soup_message_set_response (message,
|
|
"text/calendar",
|
|
SOUP_MEMORY_STATIC,
|
|
GCAL_TEST_SERVER_EMPTY_CALENDAR,
|
|
strlen (GCAL_TEST_SERVER_EMPTY_CALENDAR));
|
|
}
|
|
|
|
if (g_strcmp0 (path, calendar_file) == 0)
|
|
{
|
|
g_autoptr (SoupBuffer) buffer = NULL;
|
|
GMappedFile *mapping;
|
|
|
|
g_debug ("Serving calendar.ics");
|
|
|
|
mapping = g_mapped_file_new (path, FALSE, NULL);
|
|
if (!mapping)
|
|
{
|
|
soup_message_set_status (message, SOUP_STATUS_INTERNAL_SERVER_ERROR);
|
|
return;
|
|
}
|
|
|
|
buffer = soup_buffer_new_with_owner (g_mapped_file_get_contents (mapping),
|
|
g_mapped_file_get_length (mapping),
|
|
mapping,
|
|
(GDestroyNotify)g_mapped_file_unref);
|
|
soup_message_body_append_buffer (message->response_body, buffer);
|
|
}
|
|
}
|
|
|
|
static void
|
|
process_caldav (SoupMessage *message,
|
|
const gchar *path)
|
|
{
|
|
g_debug ("Processing CalDAV request");
|
|
}
|
|
|
|
|
|
/*
|
|
* Callbacks
|
|
*/
|
|
|
|
static gboolean
|
|
idle_quit_server_cb (gpointer user_data)
|
|
{
|
|
GcalSimpleServer *self = user_data;
|
|
|
|
g_main_loop_quit (self->thread_mainloop);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
no_auth_handler_cb (SoupServer *server,
|
|
SoupMessage *message,
|
|
const gchar *path,
|
|
GHashTable *query,
|
|
SoupClientContext *client,
|
|
gpointer user_data)
|
|
{
|
|
g_debug ("No authenticaton needed");
|
|
|
|
if (message->method == SOUP_METHOD_GET)
|
|
process_get (message, "/public", path);
|
|
else if (message->method == SOUP_METHOD_PROPFIND)
|
|
process_caldav (message, path);
|
|
else
|
|
soup_message_set_status (message, SOUP_STATUS_NOT_IMPLEMENTED);
|
|
}
|
|
|
|
static void
|
|
auth_handler_cb (SoupServer *server,
|
|
SoupMessage *message,
|
|
const gchar *path,
|
|
GHashTable *query,
|
|
SoupClientContext *client,
|
|
gpointer user_data)
|
|
{
|
|
g_debug ("Needs authenticaton");
|
|
|
|
if (message->method == SOUP_METHOD_GET)
|
|
process_get (message, "/secret-area", path);
|
|
else if (message->method == SOUP_METHOD_PROPFIND)
|
|
process_caldav (message, path);
|
|
else
|
|
soup_message_set_status (message, SOUP_STATUS_NOT_IMPLEMENTED);
|
|
}
|
|
|
|
static gboolean
|
|
authorize_cb (SoupAuthDomain *domain,
|
|
SoupMessage *message,
|
|
const char *username,
|
|
const char *password,
|
|
gpointer user_data)
|
|
{
|
|
const struct {
|
|
const gchar *username;
|
|
const gchar *password;
|
|
} valid_credentials[] = {
|
|
{ "unicorn", "iamnotahorse" },
|
|
{ "feaneron", "idonotmaintainanything" },
|
|
{ "purism", "FINISHTHEPHONE!" },
|
|
};
|
|
guint i;
|
|
|
|
g_debug ("Authorizing username: '%s', password: '%s'", username, password);
|
|
|
|
if (!username && !password)
|
|
return FALSE;
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (valid_credentials); i++)
|
|
{
|
|
if (g_strcmp0 (valid_credentials[i].username, username) == 0 &&
|
|
g_strcmp0 (valid_credentials[i].password, password) == 0)
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gpointer
|
|
run_server_in_thread (gpointer data)
|
|
{
|
|
g_autoptr (SoupAuthDomain) domain = NULL;
|
|
g_autoptr (GMainContext) context = NULL;
|
|
g_autoptr (SoupServer) server = NULL;
|
|
g_autoptr (GMainLoop) mainloop = NULL;
|
|
g_autoslist (SoupURI) uris = NULL;
|
|
g_autoptr (GError) error = NULL;
|
|
GcalSimpleServer *self = data;
|
|
g_autofree gchar *uri = NULL;
|
|
|
|
context = g_main_context_new ();
|
|
g_main_context_push_thread_default (context);
|
|
mainloop = g_main_loop_new (context, FALSE);
|
|
self->thread_mainloop = mainloop;
|
|
|
|
server = soup_server_new (SOUP_SERVER_SERVER_HEADER, "gcal-simple-server",
|
|
NULL);
|
|
|
|
/* Anything under /secret-area and /private requires authentication */
|
|
domain = soup_auth_domain_basic_new (SOUP_AUTH_DOMAIN_REALM, "GcalSimpleServer",
|
|
SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK, authorize_cb,
|
|
SOUP_AUTH_DOMAIN_BASIC_AUTH_DATA, self,
|
|
SOUP_AUTH_DOMAIN_ADD_PATH, "/secret-area",
|
|
NULL);
|
|
soup_server_add_auth_domain (server, domain);
|
|
|
|
soup_server_listen_local (server, 0, SOUP_SERVER_LISTEN_IPV4_ONLY, &error);
|
|
if (error)
|
|
{
|
|
g_critical ("Error starting up server: %s", error->message);
|
|
return NULL;
|
|
}
|
|
|
|
soup_server_add_handler (server, NULL, no_auth_handler_cb, NULL, NULL);
|
|
soup_server_add_handler (server, "/public", no_auth_handler_cb, NULL, NULL);
|
|
soup_server_add_handler (server, "/secret-area", auth_handler_cb, NULL, NULL);
|
|
|
|
uris = soup_server_get_uris (server);
|
|
g_assert_cmpint (g_slist_length (uris), ==, 1);
|
|
|
|
uri = soup_uri_to_string (uris->data, FALSE);
|
|
self->uri = g_strdup (uri);
|
|
|
|
g_debug ("Listening on %s", uri);
|
|
|
|
g_mutex_lock (&self->running_mutex);
|
|
self->running = TRUE;
|
|
g_cond_signal (&self->running_cond);
|
|
g_mutex_unlock (&self->running_mutex);
|
|
|
|
g_main_loop_run (mainloop);
|
|
|
|
g_debug ("Stopping");
|
|
|
|
soup_server_disconnect (server);
|
|
|
|
g_main_context_pop_thread_default (context);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
* GObject overrides
|
|
*/
|
|
|
|
static void
|
|
gcal_simple_server_finalize (GObject *object)
|
|
{
|
|
GcalSimpleServer *self = (GcalSimpleServer *)object;
|
|
|
|
if (self->thread)
|
|
gcal_simple_server_stop (self);
|
|
|
|
g_clear_pointer (&self->uri, g_free);
|
|
|
|
G_OBJECT_CLASS (gcal_simple_server_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
gcal_simple_server_class_init (GcalSimpleServerClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
object_class->finalize = gcal_simple_server_finalize;
|
|
}
|
|
|
|
static void
|
|
gcal_simple_server_init (GcalSimpleServer *self)
|
|
{
|
|
}
|
|
|
|
GcalSimpleServer *
|
|
gcal_simple_server_new (void)
|
|
{
|
|
return g_object_new (GCAL_TYPE_SIMPLE_SERVER, NULL);
|
|
}
|
|
|
|
void
|
|
gcal_simple_server_start (GcalSimpleServer *self)
|
|
{
|
|
g_return_if_fail (GCAL_IS_SIMPLE_SERVER (self));
|
|
|
|
if (self->thread)
|
|
{
|
|
g_warning ("Server already started");
|
|
return;
|
|
}
|
|
|
|
self->thread = g_thread_new ("gcal_simple_server_thread", run_server_in_thread, self);
|
|
|
|
g_mutex_lock (&self->running_mutex);
|
|
while (!self->running)
|
|
g_cond_wait (&self->running_cond, &self->running_mutex);
|
|
g_mutex_unlock (&self->running_mutex);
|
|
}
|
|
|
|
void
|
|
gcal_simple_server_stop (GcalSimpleServer *self)
|
|
{
|
|
GMainContext *context;
|
|
|
|
g_return_if_fail (GCAL_IS_SIMPLE_SERVER (self));
|
|
|
|
if (!self->thread)
|
|
{
|
|
g_warning ("Server not running");
|
|
return;
|
|
}
|
|
|
|
context = g_main_loop_get_context (self->thread_mainloop);
|
|
soup_add_completion (context, idle_quit_server_cb, self);
|
|
|
|
g_mutex_lock (&self->running_mutex);
|
|
self->running = FALSE;
|
|
g_cond_signal (&self->running_cond);
|
|
g_mutex_unlock (&self->running_mutex);
|
|
|
|
g_thread_join (self->thread);
|
|
self->thread = NULL;
|
|
}
|
|
|
|
SoupURI*
|
|
gcal_simple_server_get_uri (GcalSimpleServer *self)
|
|
{
|
|
g_return_val_if_fail (GCAL_IS_SIMPLE_SERVER (self), NULL);
|
|
g_return_val_if_fail (self->thread != NULL, NULL);
|
|
|
|
return soup_uri_new (self->uri);
|
|
}
|