/* Copyright 2009 Myhailo Danylenko
*
* Service discovery requests
*
* This file is part of mcabber-disco
*
* mcabber-disco 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 2 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/>. */
#include <glib.h>
#include <gmodule.h>
#include <loudmouth/loudmouth.h>
#include <string.h>
#include <mcabber/commands.h>
#include <mcabber/logprint.h>
#include <mcabber/utils.h>
#include <mcabber/hooks.h>
#include <mcabber/xmpp.h>
#include <mcabber/compl.h>
#include <mcabber/xmpp_defines.h>
#include <mcabber/screen.h>
#include <mcabber/hbuf.h>
#include "disco.h"
//
// private types
//
// lm message handler userdata
typedef struct {
disco_info_handler_t handler;
gpointer data;
GDestroyNotify notify;
} disco_info_reply_handler_t;
// lm message handler userdata
typedef struct {
disco_items_handler_t handler;
gpointer data;
GDestroyNotify notify;
} disco_items_reply_handler_t;
// user request disco handler userdata (common for info and items)
typedef struct {
gchar *jid;
gchar *node;
} disco_handler_t;
//
// globals
//
static guint disco_cid = 0;
static GSList *reply_handlers = NULL;
//
// destroyers
//
static void disco_info_reply_handler_destroy_notify (gpointer data)
{
disco_info_reply_handler_t *cb = data;
if (cb -> notify)
cb -> notify (cb -> data);
g_free (cb);
return;
}
static void disco_items_reply_handler_destroy_notify (gpointer data)
{
disco_items_reply_handler_t *cb = data;
if (cb -> notify)
cb -> notify (cb -> data);
g_free (cb);
return;
}
static void disco_handler_destroy_notify (gpointer data)
{
disco_handler_t *cb = data;
g_free (cb -> jid);
g_free (cb -> node);
g_free (cb);
return;
}
//
// lm reply handlers
//
static LmHandlerResult disco_info_reply_handler (LmMessageHandler *handler, LmConnection *connection, LmMessage *message, gpointer udata)
{
disco_info_reply_handler_t *cb = udata;
reply_handlers = g_slist_remove (reply_handlers, handler);
switch (lm_message_get_sub_type (message)) {
case LM_MESSAGE_SUB_TYPE_RESULT:
{
LmMessageNode *node = lm_message_get_node (message);
const gchar *from = lm_message_node_get_attribute (node, "from");
GSList *identities = NULL;
GSList *features = NULL;
node = lm_message_node_get_child (node, "query");
// check xmlns
if (!node || g_strcmp0 (lm_message_node_get_attribute (node, "xmlns"), NS_DISCO_INFO))
break;
// parse request results
if (node->children)
for (node = node->children; node; node = node->next)
if (!strcasecmp (node->name, "identity")) {
disco_identity_t *identity = g_new (disco_identity_t, 1);
identity -> category = lm_message_node_get_attribute (node, "category");
identity -> type = lm_message_node_get_attribute (node, "type");
identity -> name = lm_message_node_get_attribute (node, "name");
identity -> reserved = NULL;
identities = g_slist_append (identities, identity);
} else if (!strcasecmp (node->name, "feature"))
features = g_slist_insert_sorted (features, (gpointer) lm_message_node_get_attribute (node, "var"), (GCompareFunc) g_strcmp0);
// call handler
cb -> handler (identities, features, cb -> data);
{ // free resources
GSList *iel;
for (iel = identities; iel; iel = iel -> next)
g_free (iel -> data);
g_slist_free (identities);
g_slist_free (features);
}
}
break;
case LM_MESSAGE_SUB_TYPE_ERROR:
{
LmMessageNode *node = lm_message_get_node (message);
const gchar *from = lm_message_node_get_attribute (node, "from");
const gchar *type;
const gchar *reason;
node = lm_message_node_get_child (node, "error");
type = lm_message_node_get_attribute (node, "type");
if (node->children)
reason = node->children->name;
else
reason = "undefined";
// XXX: we need to inform user, but do we really need to print this on every possible error?
scr_LogPrint (LPRINT_LOGNORM, "disco: Service info discovery for %s failed: %s - %s", from, type, reason);
cb -> handler (NULL, NULL, cb -> data);
}
break;
default:
return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
}
return LM_HANDLER_RESULT_REMOVE_MESSAGE;
}
static LmHandlerResult disco_items_reply_handler (LmMessageHandler *handler, LmConnection *connection, LmMessage *message, gpointer udata)
{
disco_items_reply_handler_t *cb = udata;
reply_handlers = g_slist_remove (reply_handlers, handler);
switch (lm_message_get_sub_type (message)) {
case LM_MESSAGE_SUB_TYPE_RESULT:
{
LmMessageNode *node = lm_message_get_node (message);
const gchar *from = lm_message_node_get_attribute (node, "from");
GSList *items = NULL;
node = lm_message_node_get_child (node, "query");
// check xmlns
if (!node || g_strcmp0 (lm_message_node_get_attribute (node, "xmlns"), NS_DISCO_ITEMS))
break;
// parse request results
if (node->children)
for (node = node->children; node; node = node->next)
if (!strcasecmp (node->name, "item")) {
disco_item_t *item = g_new (disco_item_t, 1);
item -> name = lm_message_node_get_attribute (node, "name");
item -> jid = lm_message_node_get_attribute (node, "jid");
item -> node = lm_message_node_get_attribute (node, "node");
items = g_slist_append (items, item);
}
// call handler
cb -> handler (items, cb -> data);
{ // free resources
GSList *iel;
for (iel = items; iel; iel = iel -> next)
g_free (iel -> data);
g_slist_free (items);
}
}
break;
case LM_MESSAGE_SUB_TYPE_ERROR:
{
LmMessageNode *node = lm_message_get_node (message);
const gchar *from = lm_message_node_get_attribute (node, "from");
const gchar *type;
const gchar *reason;
node = lm_message_node_get_child (node, "error");
type = lm_message_node_get_attribute (node, "type");
if (node->children)
reason = node->children->name;
else
reason = "undefined";
// XXX: we need to inform user, but do we really need to print this on every possible error?
scr_LogPrint (LPRINT_LOGNORM, "disco: Service items discovery for %s failed: %s - %s", from, type, reason);
cb -> handler (NULL, cb -> data);
}
break;
default:
return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
}
return LM_HANDLER_RESULT_REMOVE_MESSAGE;
}
//
// disco requests sending
//
static void disco_features_cb (gconstpointer data, gpointer userdata)
{
const gchar *feature = data;
GSList **features = userdata;
*features = g_slist_insert_sorted (*features, (gpointer) feature, (GCompareFunc) g_strcmp0);
return;
}
void disco_info_request (const gchar *jid, const gchar *dnode, disco_info_handler_t handler, gpointer userdata, GDestroyNotify notify)
{
if (!handler) {
if (notify)
notify (userdata);
return;
}
if (0 && !dnode) { // FIXME: no way to get identity(ies) from caps
gchar *bjid = jidtodisp (jid);
GSList *buddy = roster_find (bjid, jidsearch, ROSTER_TYPE_USER | ROSTER_TYPE_ROOM | ROSTER_TYPE_AGENT);
const gchar *resource = strchr (jid, JID_RESOURCE_SEPARATOR);
g_free (bjid);
if (buddy) {
const gchar *hash = buddy_resource_getcaps (BUDDATA(buddy), resource); // ?? will it all work?
if (hash) { // cached result
GSList *identities = NULL;
GSList *features = NULL;
caps_foreach_feature (hash, disco_features_cb, &features);
handler (identities, features, userdata);
if (notify)
notify (userdata);
return;
}
}
}
{ // send request
LmMessage *request;
LmMessageNode *node;
GError *error = NULL;
request = lm_message_new_with_sub_type (jid, LM_MESSAGE_TYPE_IQ, LM_MESSAGE_SUB_TYPE_GET);
node = lm_message_get_node (request);
node = lm_message_node_add_child (node, "query", NULL);
lm_message_node_set_attribute (node, "xmlns", NS_DISCO_INFO);
if (dnode)
lm_message_node_set_attribute (node, "node", dnode);
{
disco_info_reply_handler_t *cb = g_new (disco_info_reply_handler_t, 1);
LmMessageHandler *lhandler = lm_message_handler_new (disco_info_reply_handler, cb, disco_info_reply_handler_destroy_notify);
cb -> handler = handler;
cb -> data = userdata;
cb -> notify = notify;
reply_handlers = g_slist_append (reply_handlers, lhandler);
lm_connection_send_with_reply (lconnection, request, lhandler, &error);
if (error) {
scr_LogPrint (LPRINT_DEBUG, "disco: Error sending disco request: %s.", error -> message);
g_error_free (error);
}
lm_message_handler_unref (lhandler);
}
lm_message_unref (request);
}
return;
}
void disco_items_request (const gchar *jid, const gchar *dnode, disco_items_handler_t handler, gpointer userdata, GDestroyNotify notify)
{
if (!handler) {
if (notify)
notify (userdata);
return;
}
{ // send request
LmMessage *request;
LmMessageNode *node;
GError *error = NULL;
request = lm_message_new_with_sub_type (jid, LM_MESSAGE_TYPE_IQ, LM_MESSAGE_SUB_TYPE_GET);
node = lm_message_get_node (request);
node = lm_message_node_add_child (node, "query", NULL);
lm_message_node_set_attribute (node, "xmlns", NS_DISCO_ITEMS);
if (dnode)
lm_message_node_set_attribute (node, "node", dnode);
{
disco_items_reply_handler_t *cb = g_new (disco_items_reply_handler_t, 1);
LmMessageHandler *lhandler = lm_message_handler_new (disco_items_reply_handler, cb, disco_items_reply_handler_destroy_notify);
cb -> handler = handler;
cb -> data = userdata;
cb -> notify = notify;
reply_handlers = g_slist_append (reply_handlers, lhandler);
lm_connection_send_with_reply (lconnection, request, lhandler, &error);
if (error) {
scr_LogPrint (LPRINT_DEBUG, "disco: Error sending disco request: %s.", error -> message);
g_error_free (error);
}
lm_message_handler_unref (lhandler);
}
lm_message_unref (request);
}
return;
}
//
// user requests handlers (print results)
//
static void disco_info_handler (GSList *identities, GSList *features, gpointer udata)
{
disco_handler_t *cb = udata;
GString *info = g_string_new ("Service discovery info results for ");
g_string_append (info, cb -> jid);
if (cb -> node)
g_string_append_printf (info, " (%s):", cb -> node);
else
g_string_append_c (info, ':');
{
GString *tmp = g_string_new (NULL);
GSList *el;
// compose identities part
for (el = identities; el; el = el -> next) {
disco_identity_t *identity = el -> data;
g_string_append_printf (tmp, "\n [%s (%s)] %s",
identity -> category ? identity -> category : "none",
identity -> type ? identity -> type : "none",
identity -> name ? identity -> name : "");
}
if (tmp -> len) {
g_string_append_printf (info, "\n Identities:%s", tmp -> str);
g_string_truncate (tmp, 0);
}
// compose features part
for (el = features; el; el = el -> next) {
gchar *feature = el -> data;
g_string_append_printf (tmp, "\n [%s]", feature ? feature : "none");
}
if (tmp -> len)
g_string_append_printf (info, "\n Features:%s", tmp -> str);
g_string_free (tmp, TRUE);
}
{ // print to buddy's buffer
gchar *bjid = jidtodisp (cb -> jid);
scr_WriteIncomingMessage (bjid, info -> str, 0, HBB_PREFIX_INFO, 0); // NO conversion from utf-8
g_free (bjid);
}
g_string_free (info, TRUE);
return;
}
static void disco_items_handler (GSList *items, gpointer udata)
{
disco_handler_t *cb = udata;
GString *info = g_string_new ("Service discovery items results for ");
g_string_append (info, cb -> jid);
if (cb -> node)
g_string_append_printf (info, " (%s):", cb -> node);
else
g_string_append_c (info, ':');
{
GSList *el;
// add items info
for (el = items; el; el = el -> next) {
disco_item_t *item = el -> data;
if (item -> node)
g_string_append_printf (info, "\n [%s (%s)] %s",
item -> jid ? item -> jid : "none", item -> node,
item -> name ? item -> name : "");
else
g_string_append_printf (info, "\n [%s] %s",
item -> jid ? item -> jid : "none",
item -> name ? item -> name : "");
}
}
{ // print to buddy's buffer
gchar *bjid = jidtodisp (cb -> jid);
scr_WriteIncomingMessage (bjid, info -> str, 0, HBB_PREFIX_INFO, 0); // NO conversion from utf-8
g_free (bjid);
}
g_string_free (info, TRUE);
return;
}
//
// command
//
static void do_disco (char *arg)
{
char **args = split_arg (arg, 3, 0);
int info = -1;
if (!args[0] || !strcmp (args[0], "info"))
info = 1;
else if (!strcmp (args[0], "items"))
info = 0;
else
scr_LogPrint (LPRINT_NORMAL, "Unknown subcomand.");
if (info != -1) {
char *to = NULL;
char *dnode = NULL;
if (args[0] && args[1]) {
char *p = args[1];
if (*p == '.') {
if (*(p + 1) == JID_RESOURCE_SEPARATOR) {
char *rest = to_utf8 (p + 1);
to = g_strdup_printf ("%s%s", CURRENT_JID, rest);
g_free (rest);
} else if (*(p + 1))
to = to_utf8 (p);
} else
to = to_utf8 (p);
if (args[2])
dnode = to_utf8 (args[2]);
}
// XXX send to all resources/current resource?
{
disco_handler_t *cb = g_new (disco_handler_t, 1);
cb -> jid = to ? to : g_strdup (CURRENT_JID);
cb -> node = dnode;
if (info)
disco_info_request (cb -> jid, cb -> node, disco_info_handler, cb, disco_handler_destroy_notify);
else
disco_items_request (cb -> jid, cb -> node, disco_items_handler, cb, disco_handler_destroy_notify);
}
}
free_arg_lst (args);
return;
}
//
// module mechanics
//
static void disco_unregister_handlers (void)
{
GSList *hel;
for (hel = reply_handlers; hel; hel = hel -> next) {
LmMessageHandler *handler = hel -> data;
lm_message_handler_invalidate (handler);
}
g_slist_free (reply_handlers);
reply_handlers = NULL;
return;
}
static void disco_hh (guint32 htype, hk_arg_t *args, gpointer ignore)
{
#ifdef HOOK_PRE_DISCONNECT
disco_unregister_handlers ();
#else
hk_arg_t *arg;
for (arg = args; arg->name; ++arg)
if (!strcmp (arg->name, "hook")) {
if (!strcmp (arg->value, "hook-pre-disconnect"))
disco_unregister_handlers ();
return;
}
#endif
return;
}
const gchar *g_module_check_init(GModule *module)
{
// completion
disco_cid = compl_new_category ();
if (disco_cid) {
compl_add_category_word (disco_cid, "info");
compl_add_category_word (disco_cid, "items");
}
// hook handler
#ifdef HOOK_PRE_DISCONNECT
hk_add_handler (disco_hh, HOOK_PRE_DISCONNECT, NULL);
#else
hk_add_handler (disco_hh, HOOK_INTERNAL, NULL);
#endif
// command
cmd_add ("disco", "", disco_cid, COMPL_JID, do_disco, NULL);
return NULL;
}
void g_module_unload(GModule *module)
{
// command
cmd_del ("disco");
// completion
if (disco_cid)
compl_del_category (disco_cid);
// hook handler
hk_del_handler (disco_hh, NULL);
// unregister handlers
disco_unregister_handlers ();
return;
}
/* vim: se ts=4 sw=4: */