/* XML console plugin
*
* Copyright © 2011 Collabora Ltd.
*
* 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/channel.h"
#include
#include
#include
#include
#include
#include "extensions/extensions.h"
#include "console/debug.h"
enum {
PROP_0,
PROP_SPEW
};
struct _GabbleConsoleChannelPrivate
{
WockySession *session;
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 console_iface_init (
gpointer g_iface,
gpointer data);
static void gabble_console_channel_set_spew (
GabbleConsoleChannel *self,
gboolean spew);
gchar *gabble_console_channel_get_path (TpBaseChannel *chan);
static void gabble_console_channel_close (TpBaseChannel *chan);
G_DEFINE_TYPE_WITH_CODE (GabbleConsoleChannel, gabble_console_channel,
TP_TYPE_BASE_CHANNEL,
G_IMPLEMENT_INTERFACE (GABBLE_TYPE_SVC_GABBLE_PLUGIN_CONSOLE,
console_iface_init);
)
static void
gabble_console_channel_init (GabbleConsoleChannel *self)
{
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GABBLE_TYPE_CONSOLE_CHANNEL,
GabbleConsoleChannelPrivate);
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_channel_constructed (GObject *object)
{
GabbleConsoleChannel *self = GABBLE_CONSOLE_CHANNEL (object);
void (*chain_up)(GObject *) =
G_OBJECT_CLASS (gabble_console_channel_parent_class)->constructed;
if (chain_up != NULL)
chain_up (object);
self->priv->session = g_object_ref (
gabble_plugin_connection_get_session (
GABBLE_PLUGIN_CONNECTION (
tp_base_channel_get_connection (
TP_BASE_CHANNEL (self)))));
g_return_if_fail (self->priv->session != NULL);
}
static void
gabble_console_channel_get_property (
GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
GabbleConsoleChannel *self = GABBLE_CONSOLE_CHANNEL (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_channel_set_property (
GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GabbleConsoleChannel *self = GABBLE_CONSOLE_CHANNEL (object);
switch (property_id)
{
case PROP_SPEW:
gabble_console_channel_set_spew (self, g_value_get_boolean (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
}
}
static void
gabble_console_channel_dispose (GObject *object)
{
void (*chain_up) (GObject *) =
G_OBJECT_CLASS (gabble_console_channel_parent_class)->dispose;
GabbleConsoleChannel *self = GABBLE_CONSOLE_CHANNEL (object);
gabble_console_channel_set_spew (self, FALSE);
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_channel_class_init (GabbleConsoleChannelClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
TpBaseChannelClass *channel_class = TP_BASE_CHANNEL_CLASS (klass);
static TpDBusPropertiesMixinPropImpl console_props[] = {
{ "SpewStanzas", "spew-stanzas", "spew-stanzas" },
{ NULL },
};
object_class->constructed = gabble_console_channel_constructed;
object_class->get_property = gabble_console_channel_get_property;
object_class->set_property = gabble_console_channel_set_property;
object_class->dispose = gabble_console_channel_dispose;
channel_class->channel_type = GABBLE_IFACE_GABBLE_PLUGIN_CONSOLE;
channel_class->get_object_path_suffix = gabble_console_channel_get_path;
channel_class->close = gabble_console_channel_close;
g_type_class_add_private (klass, sizeof (GabbleConsoleChannelPrivate));
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));
tp_dbus_properties_mixin_implement_interface (object_class,
GABBLE_IFACE_QUARK_GABBLE_PLUGIN_CONSOLE,
tp_dbus_properties_mixin_getter_gobject_properties,
tp_dbus_properties_mixin_setter_gobject_properties,
console_props);
}
gchar *
gabble_console_channel_get_path (TpBaseChannel *chan)
{
return g_strdup_printf ("console%p", chan);
}
static void
gabble_console_channel_close (TpBaseChannel *chan)
{
GabbleConsoleChannel *self = GABBLE_CONSOLE_CHANNEL (chan);
gabble_console_channel_set_spew (self, FALSE);
tp_base_channel_destroyed (chan);
}
static gboolean
incoming_cb (
WockyPorter *porter,
WockyStanza *stanza,
gpointer user_data)
{
GabbleConsoleChannel *self = GABBLE_CONSOLE_CHANNEL (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)
{
GabbleConsoleChannel *self = GABBLE_CONSOLE_CHANNEL (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_channel_set_spew (
GabbleConsoleChannel *self,
gboolean spew)
{
GabbleConsoleChannelPrivate *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)
{
GabbleConsoleChannel *self = GABBLE_CONSOLE_CHANNEL (source);
GDBusMethodInvocation *context = user_data;
GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
GError *error = NULL;
if (g_simple_async_result_propagate_error (simple, &error))
{
g_dbus_method_invocation_return_gerror (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 (
GabbleConsoleChannel *self,
const gchar *xml,
WockyStanza **stanza_out,
GError **error)
{
GabbleConsoleChannelPrivate *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 *channel,
const gchar *type_str,
const gchar *to,
const gchar *body,
GDBusMethodInvocation *context)
{
GabbleConsoleChannel *self = GABBLE_CONSOLE_CHANNEL (channel);
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);
g_dbus_method_invocation_return_gerror (context, error);
g_error_free (error);
}
}
static void
console_stanza_sent_cb (
GObject *source,
GAsyncResult *result,
gpointer user_data)
{
WockyPorter *porter = WOCKY_PORTER (source);
GDBusMethodInvocation *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
{
g_dbus_method_invocation_return_gerror (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 *channel,
const gchar *xml,
GDBusMethodInvocation *context)
{
GabbleConsoleChannel *self = GABBLE_CONSOLE_CHANNEL (channel);
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);
g_dbus_method_invocation_return_gerror (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
}