summaryrefslogtreecommitdiff
path: root/plugins/console/sidecar.c
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/console/sidecar.c')
-rw-r--r--plugins/console/sidecar.c548
1 files changed, 548 insertions, 0 deletions
diff --git a/plugins/console/sidecar.c b/plugins/console/sidecar.c
new file mode 100644
index 000000000..1e01234cd
--- /dev/null
+++ b/plugins/console/sidecar.c
@@ -0,0 +1,548 @@
+/* XML console plugin
+ *
+ * Copyright © 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * 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; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+#include "console/sidecar.h"
+
+#include <string.h>
+#include <wocky/wocky.h>
+#include <gabble/gabble.h>
+#include "extensions/extensions.h"
+
+#include "console/debug.h"
+
+enum {
+ PROP_0,
+ PROP_CONNECTION,
+ PROP_SESSION,
+ PROP_SPEW
+};
+
+struct _GabbleConsoleSidecarPrivate
+{
+ WockySession *session;
+ TpBaseConnection *connection;
+ WockyXmppReader *reader;
+ WockyXmppWriter *writer;
+
+ /* %TRUE if we should emit signals when sending or receiving stanzas */
+ gboolean spew;
+ /* 0 if spew is FALSE; or a WockyPorter handler id for all incoming stanzas
+ * if spew is TRUE. */
+ guint incoming_handler;
+ /* 0 if spew is FALSE; a GLib signal handler id for WockyPorter::sending if
+ * spew is TRUE.
+ */
+ gulong sending_id;
+};
+
+static void sidecar_iface_init (
+ gpointer g_iface,
+ gpointer data);
+static void console_iface_init (
+ gpointer g_iface,
+ gpointer data);
+static void gabble_console_sidecar_set_spew (
+ GabbleConsoleSidecar *self,
+ gboolean spew);
+
+G_DEFINE_TYPE_WITH_CODE (GabbleConsoleSidecar, gabble_console_sidecar,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (GABBLE_TYPE_SIDECAR, sidecar_iface_init);
+ G_IMPLEMENT_INTERFACE (GABBLE_TYPE_SVC_GABBLE_PLUGIN_CONSOLE,
+ console_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
+ tp_dbus_properties_mixin_iface_init);
+ )
+
+static void
+gabble_console_sidecar_init (GabbleConsoleSidecar *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GABBLE_TYPE_CONSOLE_SIDECAR,
+ GabbleConsoleSidecarPrivate);
+ self->priv->reader = wocky_xmpp_reader_new_no_stream_ns (
+ WOCKY_XMPP_NS_JABBER_CLIENT);
+ self->priv->writer = wocky_xmpp_writer_new_no_stream ();
+}
+
+static void
+gabble_console_sidecar_get_property (
+ GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GabbleConsoleSidecar *self = GABBLE_CONSOLE_SIDECAR (object);
+
+ switch (property_id)
+ {
+ case PROP_SPEW:
+ g_value_set_boolean (value, self->priv->spew);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ }
+}
+
+static void
+gabble_console_sidecar_set_property (
+ GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GabbleConsoleSidecar *self = GABBLE_CONSOLE_SIDECAR (object);
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ g_assert (self->priv->connection == NULL); /* construct-only */
+ self->priv->connection = g_value_dup_object (value);
+ break;
+
+ case PROP_SESSION:
+ g_assert (self->priv->session == NULL); /* construct-only */
+ self->priv->session = g_value_dup_object (value);
+ break;
+
+ case PROP_SPEW:
+ gabble_console_sidecar_set_spew (self, g_value_get_boolean (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ }
+}
+
+static void
+gabble_console_sidecar_dispose (GObject *object)
+{
+ void (*chain_up) (GObject *) =
+ G_OBJECT_CLASS (gabble_console_sidecar_parent_class)->dispose;
+ GabbleConsoleSidecar *self = GABBLE_CONSOLE_SIDECAR (object);
+
+ gabble_console_sidecar_set_spew (self, FALSE);
+
+ tp_clear_object (&self->priv->connection);
+ tp_clear_object (&self->priv->reader);
+ tp_clear_object (&self->priv->writer);
+ tp_clear_object (&self->priv->session);
+
+ if (chain_up != NULL)
+ chain_up (object);
+}
+
+static void
+gabble_console_sidecar_class_init (GabbleConsoleSidecarClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ static TpDBusPropertiesMixinPropImpl console_props[] = {
+ { "SpewStanzas", "spew-stanzas", "spew-stanzas" },
+ { NULL },
+ };
+ static TpDBusPropertiesMixinIfaceImpl interfaces[] = {
+ { GABBLE_IFACE_GABBLE_PLUGIN_CONSOLE,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ /* FIXME: if we were feeling clever, we'd override the setter so that
+ * we can monitor the bus name of any application which sets
+ * SpewStanzas to TRUE and flip it back to false when that application
+ * dies.
+ *
+ * Alternatively, we could just replace this sidecar with a channel.
+ */
+ tp_dbus_properties_mixin_setter_gobject_properties,
+ console_props
+ },
+ { NULL },
+ };
+
+ object_class->get_property = gabble_console_sidecar_get_property;
+ object_class->set_property = gabble_console_sidecar_set_property;
+ object_class->dispose = gabble_console_sidecar_dispose;
+
+ g_type_class_add_private (klass, sizeof (GabbleConsoleSidecarPrivate));
+
+ g_object_class_install_property (object_class, PROP_CONNECTION,
+ g_param_spec_object ("connection", "Connection",
+ "Gabble connection",
+ GABBLE_TYPE_PLUGIN_CONNECTION,
+ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_SESSION,
+ g_param_spec_object ("session", "Session",
+ "Wocky session",
+ WOCKY_TYPE_SESSION,
+ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_SPEW,
+ g_param_spec_boolean ("spew-stanzas", "SpewStanzas",
+ "If %TRUE, someone wants us to spit out a tonne of stanzas",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ klass->props_class.interfaces = interfaces;
+ tp_dbus_properties_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (GabbleConsoleSidecarClass, props_class));
+}
+
+static void sidecar_iface_init (
+ gpointer g_iface,
+ gpointer data)
+{
+ GabbleSidecarInterface *iface = g_iface;
+
+ iface->interface = GABBLE_IFACE_GABBLE_PLUGIN_CONSOLE;
+ iface->get_immutable_properties = NULL;
+}
+
+static gboolean
+incoming_cb (
+ WockyPorter *porter,
+ WockyStanza *stanza,
+ gpointer user_data)
+{
+ GabbleConsoleSidecar *self = GABBLE_CONSOLE_SIDECAR (user_data);
+ const guint8 *body;
+ gsize length;
+
+ wocky_xmpp_writer_write_stanza (self->priv->writer, stanza, &body, &length);
+ gabble_svc_gabble_plugin_console_emit_stanza_received (self,
+ (const gchar *) body);
+ return FALSE;
+}
+
+static void
+sending_cb (
+ WockyPorter *porter,
+ WockyStanza *stanza,
+ gpointer user_data)
+{
+ GabbleConsoleSidecar *self = GABBLE_CONSOLE_SIDECAR (user_data);
+
+ if (stanza != NULL)
+ {
+ const guint8 *body;
+ gsize length;
+
+ wocky_xmpp_writer_write_stanza (self->priv->writer, stanza, &body,
+ &length);
+ gabble_svc_gabble_plugin_console_emit_stanza_sent (self,
+ (const gchar *) body);
+ }
+}
+
+static void
+gabble_console_sidecar_set_spew (
+ GabbleConsoleSidecar *self,
+ gboolean spew)
+{
+ GabbleConsoleSidecarPrivate *priv = self->priv;
+
+ if (!spew != !priv->spew)
+ {
+ WockyPorter *porter = wocky_session_get_porter (self->priv->session);
+ const gchar *props[] = { "SpewStanzas", NULL };
+
+ priv->spew = spew;
+ tp_dbus_properties_mixin_emit_properties_changed (G_OBJECT (self),
+ GABBLE_IFACE_GABBLE_PLUGIN_CONSOLE, props);
+
+ if (spew)
+ {
+ g_return_if_fail (priv->incoming_handler == 0);
+ priv->incoming_handler = wocky_porter_register_handler_from_anyone (
+ porter, WOCKY_STANZA_TYPE_NONE, WOCKY_STANZA_SUB_TYPE_NONE,
+ WOCKY_PORTER_HANDLER_PRIORITY_MAX, incoming_cb, self, NULL);
+
+ g_return_if_fail (priv->sending_id == 0);
+ priv->sending_id = g_signal_connect (porter, "sending",
+ (GCallback) sending_cb, self);
+ }
+ else
+ {
+ g_return_if_fail (priv->incoming_handler != 0);
+ wocky_porter_unregister_handler (porter, priv->incoming_handler);
+ priv->incoming_handler = 0;
+
+ g_return_if_fail (priv->sending_id != 0);
+ g_signal_handler_disconnect (porter, priv->sending_id);
+ priv->sending_id = 0;
+ }
+ }
+}
+
+static void
+return_from_send_iq (
+ GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GabbleConsoleSidecar *self = GABBLE_CONSOLE_SIDECAR (source);
+ DBusGMethodInvocation *context = user_data;
+ GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
+ GError *error = NULL;
+
+ if (g_simple_async_result_propagate_error (simple, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ }
+ else
+ {
+ WockyStanza *reply = g_simple_async_result_get_op_res_gpointer (simple);
+ WockyStanzaSubType sub_type;
+ const guint8 *body;
+ gsize length;
+
+ wocky_stanza_get_type_info (reply, NULL, &sub_type);
+ wocky_xmpp_writer_write_stanza (self->priv->writer, reply, &body, &length);
+
+ /* woop woop */
+ gabble_svc_gabble_plugin_console_return_from_send_iq (context,
+ sub_type == WOCKY_STANZA_SUB_TYPE_RESULT ? "result" : "error",
+ (const gchar *) body);
+ }
+}
+
+static void
+console_iq_reply_cb (
+ GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ WockyPorter *porter = WOCKY_PORTER (source);
+ GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (user_data);
+ GError *error = NULL;
+ WockyStanza *reply = wocky_porter_send_iq_finish (porter, result, &error);
+
+ if (reply != NULL)
+ {
+ g_simple_async_result_set_op_res_gpointer (simple, reply, g_object_unref);
+ }
+ else
+ {
+ g_simple_async_result_set_from_error (simple, error);
+ g_error_free (error);
+ }
+
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+}
+
+static gboolean
+get_iq_type (const gchar *type_str,
+ WockyStanzaSubType *sub_type_out,
+ GError **error)
+{
+ if (!wocky_strdiff (type_str, "get"))
+ {
+ *sub_type_out = WOCKY_STANZA_SUB_TYPE_GET;
+ return TRUE;
+ }
+
+ if (!wocky_strdiff (type_str, "set"))
+ {
+ *sub_type_out = WOCKY_STANZA_SUB_TYPE_SET;
+ return TRUE;
+ }
+
+ g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
+ "Type must be 'get' or 'set', not '%s'", type_str);
+ return FALSE;
+}
+
+static gboolean
+validate_jid (const gchar **to,
+ GError **error)
+{
+ if (tp_str_empty (*to))
+ {
+ *to = NULL;
+ return TRUE;
+ }
+
+ if (wocky_decode_jid (*to, NULL, NULL, NULL))
+ return TRUE;
+
+ g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
+ "'%s' is not a valid (or empty) JID", *to);
+ return FALSE;
+}
+
+/*
+ * @xml: doesn't actually have to be a top-level stanza. It can be the body of
+ * an IQ or whatever. If it has no namespace, it's assumed to be in
+ * jabber:client.
+ */
+static gboolean
+parse_me_a_stanza (
+ GabbleConsoleSidecar *self,
+ const gchar *xml,
+ WockyStanza **stanza_out,
+ GError **error)
+{
+ GabbleConsoleSidecarPrivate *priv = self->priv;
+ WockyStanza *stanza;
+
+ wocky_xmpp_reader_reset (priv->reader);
+ wocky_xmpp_reader_push (priv->reader, (const guint8 *) xml, strlen (xml));
+
+ *error = wocky_xmpp_reader_get_error (priv->reader);
+
+ if (*error != NULL)
+ return FALSE;
+
+ stanza = wocky_xmpp_reader_pop_stanza (priv->reader);
+
+ if (stanza == NULL)
+ {
+ g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
+ "Incomplete stanza! Bad person.");
+ return FALSE;
+ }
+
+ *stanza_out = stanza;
+ return TRUE;
+}
+
+static void
+console_send_iq (
+ GabbleSvcGabblePluginConsole *sidecar,
+ const gchar *type_str,
+ const gchar *to,
+ const gchar *body,
+ DBusGMethodInvocation *context)
+{
+ GabbleConsoleSidecar *self = GABBLE_CONSOLE_SIDECAR (sidecar);
+ WockyPorter *porter = wocky_session_get_porter (self->priv->session);
+ WockyStanzaSubType sub_type;
+ WockyStanza *fragment;
+ GError *error = NULL;
+
+ if (get_iq_type (type_str, &sub_type, &error) &&
+ validate_jid (&to, &error) &&
+ parse_me_a_stanza (self, body, &fragment, &error))
+ {
+ GSimpleAsyncResult *simple = g_simple_async_result_new (G_OBJECT (self),
+ return_from_send_iq, context, console_send_iq);
+ WockyStanza *stanza = wocky_stanza_build (WOCKY_STANZA_TYPE_IQ, sub_type,
+ NULL, to, NULL);
+
+ wocky_node_add_node_tree (wocky_stanza_get_top_node (stanza),
+ WOCKY_NODE_TREE (fragment));
+ wocky_porter_send_iq_async (porter, stanza, NULL, console_iq_reply_cb, simple);
+ g_object_unref (fragment);
+ }
+ else
+ {
+ DEBUG ("%s", error->message);
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ }
+}
+
+static void
+console_stanza_sent_cb (
+ GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ WockyPorter *porter = WOCKY_PORTER (source);
+ DBusGMethodInvocation *context = user_data;
+ GError *error = NULL;
+
+ if (wocky_porter_send_finish (porter, result, &error))
+ {
+ gabble_svc_gabble_plugin_console_return_from_send_stanza (context);
+ }
+ else
+ {
+ dbus_g_method_return_error (context, error);
+ g_clear_error (&error);
+ }
+}
+
+static gboolean
+stanza_looks_coherent (
+ WockyStanza *stanza,
+ GError **error)
+{
+ WockyNode *top_node = wocky_stanza_get_top_node (stanza);
+ WockyStanzaType t;
+ WockyStanzaSubType st;
+
+ wocky_stanza_get_type_info (stanza, &t, &st);
+
+ if (t == WOCKY_STANZA_TYPE_UNKNOWN)
+ {
+ g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
+ "I don't know what a <%s xmlns='%s'/> is", top_node->name,
+ g_quark_to_string (top_node->ns));
+ return FALSE;
+ }
+ else if (st == WOCKY_STANZA_SUB_TYPE_UNKNOWN)
+ {
+ g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
+ "I don't know what type='%s' means",
+ wocky_node_get_attribute (top_node, "type"));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+console_send_stanza (
+ GabbleSvcGabblePluginConsole *sidecar,
+ const gchar *xml,
+ DBusGMethodInvocation *context)
+{
+ GabbleConsoleSidecar *self = GABBLE_CONSOLE_SIDECAR (sidecar);
+ WockyPorter *porter = wocky_session_get_porter (self->priv->session);
+ WockyStanza *stanza = NULL;
+ GError *error = NULL;
+
+ if (parse_me_a_stanza (self, xml, &stanza, &error) &&
+ stanza_looks_coherent (stanza, &error))
+ {
+ wocky_porter_send_async (porter, stanza, NULL, console_stanza_sent_cb,
+ context);
+ }
+ else
+ {
+ DEBUG ("%s", error->message);
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ }
+
+ tp_clear_object (&stanza);
+}
+
+static void
+console_iface_init (
+ gpointer klass,
+ gpointer data G_GNUC_UNUSED)
+{
+#define IMPLEMENT(x) gabble_svc_gabble_plugin_console_implement_##x (\
+ klass, console_##x)
+ IMPLEMENT (send_iq);
+ IMPLEMENT (send_stanza);
+#undef IMPLEMENT
+}