# HG changeset patch # User Andreas Monitzer # Date 1182567441 0 # Node ID f88b3a093cbaad2cbf0c28c8009fc8642429c584 # Parent a8b1159fd95b060d6f618f12282951f839cd39cc Implemented ad-hoc commands for the buddy action menu (untested), implemented the receiving end of XEP-0115: Entity Capabilities. Note that this seems not to be reliable right now, since some clients seem to have a very broken [read: completely non-functional] implementation (most notably Gajim and the py-transports). diff -r a8b1159fd95b -r f88b3a093cba libpurple/protocols/jabber/adhoccommands.c --- a/libpurple/protocols/jabber/adhoccommands.c Fri Jun 22 11:52:50 2007 +0000 +++ b/libpurple/protocols/jabber/adhoccommands.c Sat Jun 23 02:57:21 2007 +0000 @@ -38,9 +38,61 @@ GList *actionslist; } JabberAdHocActionInfo; -/*static void do_adhoc_parse_iq(JabberStream *js, xmlnode *packet, gpointer data) { - jabber_adhoc_parse(js, packet); -}*/ +void jabber_adhoc_disco_result_cb(JabberStream *js, xmlnode *packet, gpointer data) { + const char *from = xmlnode_get_attrib(packet, "from"); + const char *type = xmlnode_get_attrib(packet, "type"); + const char *node; + xmlnode *query, *item; + JabberID *jabberid; + JabberBuddy *jb; + JabberBuddyResource *jbr = NULL; + + if(strcmp(type, "result")) + return; + + query = xmlnode_get_child_with_namespace(packet,"query","http://jabber.org/protocol/disco#items"); + if(!query) + return; + node = xmlnode_get_attrib(query,"node"); + if(!node || strcmp(node, "http://jabber.org/protocol/commands")) + return; + + if((jabberid = jabber_id_new(from))) { + if(jabberid->resource && (jb = jabber_buddy_find(js, from, TRUE))) + jbr = jabber_buddy_find_resource(jb, jabberid->resource); + jabber_id_free(jabberid); + } + + if(!jbr) + return; + + if(jbr->commands) { + /* since the list we just received is complete, wipe the old one */ + while(jbr->commands) { + JabberAdHocCommands *cmd = jbr->commands->data; + g_free(cmd->jid); + g_free(cmd->node); + g_free(cmd->name); + g_free(cmd); + jbr->commands = g_list_delete_link(jbr->commands, jbr->commands); + } + } + + for(item = query->child; item; item = item->next) { + JabberAdHocCommands *cmd; + if(item->type != XMLNODE_TYPE_TAG) + continue; + if(strcmp(item->name, "item")) + continue; + cmd = g_new0(JabberAdHocCommands, 1); + + cmd->jid = g_strdup(xmlnode_get_attrib(item,"jid")); + cmd->node = g_strdup(xmlnode_get_attrib(item,"node")); + cmd->name = g_strdup(xmlnode_get_attrib(item,"name")); + + jbr->commands = g_list_append(jbr->commands,cmd); + } +} static void do_adhoc_action_cb(JabberStream *js, xmlnode *result, const char *actionhandle, gpointer user_data) { xmlnode *command; @@ -128,3 +180,22 @@ jabber_x_data_request_with_actions(js,xdata,actionslist,actionindex,do_adhoc_action_cb,actionInfo); } } + +void jabber_adhoc_execute(PurpleBlistNode *node, gpointer data) { + if (PURPLE_BLIST_NODE_IS_BUDDY(node)) { + JabberAdHocCommands *cmd = data; + PurpleBuddy *buddy = (PurpleBuddy *) node; + JabberStream *js = purple_account_get_connection(buddy->account)->proto_data; + JabberIq *iq = jabber_iq_new(js, JABBER_IQ_SET); + xmlnode *command = xmlnode_new_child(iq->node,"command"); + xmlnode_set_attrib(iq->node,"to",cmd->jid); + xmlnode_set_namespace(command,"http://jabber.org/protocol/commands"); + xmlnode_set_attrib(command,"node",cmd->node); + xmlnode_set_attrib(command,"action","execute"); + + /* we don't need to set a callback, since jabber_adhoc_parse is run for all replies */ + + jabber_iq_send(iq); + } +} + diff -r a8b1159fd95b -r f88b3a093cba libpurple/protocols/jabber/adhoccommands.h --- a/libpurple/protocols/jabber/adhoccommands.h Fri Jun 22 11:52:50 2007 +0000 +++ b/libpurple/protocols/jabber/adhoccommands.h Sat Jun 23 02:57:21 2007 +0000 @@ -28,4 +28,8 @@ void jabber_adhoc_parse(JabberStream *js, xmlnode *packet); +void jabber_adhoc_disco_result_cb(JabberStream *js, xmlnode *packet, gpointer data); + +void jabber_adhoc_execute(PurpleBlistNode *node, gpointer data); + #endif /* _PURPLE_JABBER_ADHOCCOMMANDS_H_ */ diff -r a8b1159fd95b -r f88b3a093cba libpurple/protocols/jabber/buddy.c --- a/libpurple/protocols/jabber/buddy.c Fri Jun 22 11:52:50 2007 +0000 +++ b/libpurple/protocols/jabber/buddy.c Sat Jun 23 02:57:21 2007 +0000 @@ -35,6 +35,7 @@ #include "presence.h" #include "xdata.h" #include "pep.h" +#include "adhoccommands.h" typedef struct { long idle_seconds; @@ -117,7 +118,6 @@ int priority, JabberBuddyState state, const char *status) { JabberBuddyResource *jbr = jabber_buddy_find_resource(jb, resource); - if(!jbr) { jbr = g_new0(JabberBuddyResource, 1); jbr->jb = jb; @@ -142,6 +142,17 @@ g_return_if_fail(jbr != NULL); jbr->jb->resources = g_list_remove(jbr->jb->resources, jbr); + + while(jbr->commands) { + JabberAdHocCommands *cmd = jbr->commands->data; + g_free(cmd->jid); + g_free(cmd->node); + g_free(cmd->name); + g_free(cmd); + jbr->commands = g_list_delete_link(jbr->commands, jbr->commands); + } + + jabber_caps_free_clientinfo(jbr->caps); g_free(jbr->name); g_free(jbr->status); @@ -1625,6 +1636,7 @@ PurpleConnection *gc = purple_account_get_connection(buddy->account); JabberStream *js = gc->proto_data; JabberBuddy *jb = jabber_buddy_find(js, buddy->name, TRUE); + GList *jbrs; GList *m = NULL; PurpleMenuAction *act; @@ -1688,6 +1700,19 @@ NULL, NULL); m = g_list_append(m, act); } + + /* add all ad hoc commands to the action menu */ + for(jbrs = jb->resources; jbrs; jbrs = g_list_next(jbrs)) { + JabberBuddyResource *jbr = jbrs->data; + GList *commands; + if (!jbr->commands) + continue; + for(commands = jbr->commands; commands; commands = g_list_next(commands)) { + JabberAdHocCommands *cmd = commands->data; + act = purple_menu_action_new(cmd->name, PURPLE_CALLBACK(jabber_adhoc_execute), cmd, NULL); + m = g_list_append(m, act); + } + } return m; } diff -r a8b1159fd95b -r f88b3a093cba libpurple/protocols/jabber/buddy.h --- a/libpurple/protocols/jabber/buddy.h Fri Jun 22 11:52:50 2007 +0000 +++ b/libpurple/protocols/jabber/buddy.h Sat Jun 23 02:57:21 2007 +0000 @@ -34,6 +34,7 @@ } JabberBuddyState; #include "jabber.h" +#include "caps.h" #define AVATARNAMESPACEDATA "http://www.xmpp.org/extensions/xep-0084.html#ns-data" #define AVATARNAMESPACEMETA "http://www.xmpp.org/extensions/xep-0084.html#ns-metadata" @@ -56,6 +57,12 @@ } subscription; } JabberBuddy; +typedef struct _JabberAdHocCommands { + char *jid; + char *node; + char *name; +} JabberAdHocCommands; + typedef struct _JabberBuddyResource { JabberBuddy *jb; char *name; @@ -74,6 +81,8 @@ char *name; char *os; } client; + JabberCapsClientInfo *caps; + GList *commands; } JabberBuddyResource; void jabber_buddy_free(JabberBuddy *jb); diff -r a8b1159fd95b -r f88b3a093cba libpurple/protocols/jabber/caps.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/caps.c Sat Jun 23 02:57:21 2007 +0000 @@ -0,0 +1,529 @@ +/* + * purple - Jabber Protocol Plugin + * + * Copyright (C) 2007, Andreas Monitzer + * + * 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 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "caps.h" +#include +#include "internal.h" +#include "util.h" +#include "iq.h" + +#define JABBER_CAPS_FILENAME "xmpp-caps.xml" + +static GHashTable *capstable = NULL; /* JabberCapsKey -> JabberCapsValue */ + +typedef struct _JabberCapsKey { + char *node; + char *ver; +} JabberCapsKey; + +typedef struct _JabberCapsValueExt { + GList *identities; /* JabberCapsIdentity */ + GList *features; /* char * */ +} JabberCapsValueExt; + +typedef struct _JabberCapsValue { + GList *identities; /* JabberCapsIdentity */ + GList *features; /* char * */ + GHashTable *ext; /* char * -> JabberCapsValueExt */ +} JabberCapsValue; + +static guint jabber_caps_hash(gconstpointer key) { + const JabberCapsKey *name = key; + guint nodehash = g_str_hash(name->node); + guint verhash = g_str_hash(name->ver); + + return nodehash ^ verhash; +} + +static gboolean jabber_caps_compare(gconstpointer v1, gconstpointer v2) { + const JabberCapsKey *name1 = v1; + const JabberCapsKey *name2 = v2; + + return strcmp(name1->node,name2->node) == 0 && strcmp(name1->ver,name2->ver) == 0; +} + +static void jabber_caps_destroy_key(gpointer key) { + JabberCapsKey *keystruct = key; + g_free(keystruct->node); + g_free(keystruct->ver); + g_free(keystruct); +} + +static void jabber_caps_destroy_value(gpointer value) { + JabberCapsValue *valuestruct = value; + while(valuestruct->identities) { + JabberCapsIdentity *id = valuestruct->identities->data; + g_free(id->category); + g_free(id->type); + g_free(id->name); + g_free(id); + + valuestruct->identities = g_list_delete_link(valuestruct->identities,valuestruct->identities); + } + while(valuestruct->features) { + g_free(valuestruct->features->data); + valuestruct->features = g_list_delete_link(valuestruct->features,valuestruct->features); + } + g_hash_table_destroy(valuestruct->ext); + g_free(valuestruct); +} + +static void jabber_caps_ext_destroy_value(gpointer value) { + JabberCapsValueExt *valuestruct = value; + while(valuestruct->identities) { + JabberCapsIdentity *id = valuestruct->identities->data; + g_free(id->category); + g_free(id->type); + g_free(id->name); + g_free(id); + + valuestruct->identities = g_list_delete_link(valuestruct->identities,valuestruct->identities); + } + while(valuestruct->features) { + g_free(valuestruct->features->data); + valuestruct->features = g_list_delete_link(valuestruct->features,valuestruct->features); + } + g_free(valuestruct); +} + +static void jabber_caps_load(void); + +void jabber_caps_init(void) { + capstable = g_hash_table_new_full(jabber_caps_hash, jabber_caps_compare, jabber_caps_destroy_key, jabber_caps_destroy_value); + jabber_caps_load(); +} + +static void jabber_caps_load(void) { + xmlnode *capsdata = purple_util_read_xml_from_file(JABBER_CAPS_FILENAME, "XMPP capabilities cache"); + xmlnode *client; + if(!capsdata || strcmp(capsdata->name, "capabilities")) + return; + + for(client = capsdata->child; client; client = client->next) { + if(client->type != XMLNODE_TYPE_TAG) + continue; + if(!strcmp(client->name, "client")) { + JabberCapsKey *key = g_new0(JabberCapsKey, 1); + JabberCapsValue *value = g_new0(JabberCapsValue, 1); + xmlnode *child; + key->node = g_strdup(xmlnode_get_attrib(client,"node")); + key->ver = g_strdup(xmlnode_get_attrib(client,"ver")); + value->ext = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, jabber_caps_ext_destroy_value); + for(child = client->child; child; child = child->next) { + if(child->type != XMLNODE_TYPE_TAG) + continue; + if(!strcmp(child->name,"feature")) { + const char *var = xmlnode_get_attrib(child, "var"); + if(!var) + continue; + value->features = g_list_append(value->features,g_strdup(var)); + } else if(!strcmp(child->name,"identity")) { + const char *category = xmlnode_get_attrib(child, "category"); + const char *type = xmlnode_get_attrib(child, "type"); + const char *name = xmlnode_get_attrib(child, "name"); + + JabberCapsIdentity *id = g_new0(JabberCapsIdentity, 1); + id->category = g_strdup(category); + id->type = g_strdup(type); + id->name = g_strdup(name); + + value->identities = g_list_append(value->identities,id); + } else if(!strcmp(child->name,"ext")) { + const char *identifier = xmlnode_get_attrib(child, "identifier"); + if(identifier) { + xmlnode *extchild; + + JabberCapsValueExt *extvalue = g_new0(JabberCapsValueExt, 1); + + for(extchild = child->child; extchild; extchild = extchild->next) { + if(extchild->type != XMLNODE_TYPE_TAG) + continue; + if(!strcmp(extchild->name,"feature")) { + const char *var = xmlnode_get_attrib(extchild, "var"); + if(!var) + continue; + extvalue->features = g_list_append(extvalue->features,g_strdup(var)); + } else if(!strcmp(extchild->name,"identity")) { + const char *category = xmlnode_get_attrib(extchild, "category"); + const char *type = xmlnode_get_attrib(extchild, "type"); + const char *name = xmlnode_get_attrib(extchild, "name"); + + JabberCapsIdentity *id = g_new0(JabberCapsIdentity, 1); + id->category = g_strdup(category); + id->type = g_strdup(type); + id->name = g_strdup(name); + + extvalue->identities = g_list_append(extvalue->identities,id); + } + } + g_hash_table_replace(value->ext, g_strdup(identifier), extvalue); + } + } + } + g_hash_table_replace(capstable, key, value); + } + } +} + +static void jabber_caps_store_ext(gpointer key, gpointer value, gpointer user_data) { + const char *extname = key; + JabberCapsValueExt *props = value; + xmlnode *root = user_data; + xmlnode *ext = xmlnode_new_child(root,"ext"); + GList *iter; + + xmlnode_set_attrib(ext,"identifier",extname); + + for(iter = props->identities; iter; iter = g_list_next(iter)) { + JabberCapsIdentity *id = iter->data; + xmlnode *identity = xmlnode_new_child(ext, "identity"); + xmlnode_set_attrib(identity, "category", id->category); + xmlnode_set_attrib(identity, "type", id->type); + xmlnode_set_attrib(identity, "name", id->name); + } + + for(iter = props->features; iter; iter = g_list_next(iter)) { + const char *feat = iter->data; + xmlnode *feature = xmlnode_new_child(ext, "feature"); + xmlnode_set_attrib(feature, "var", feat); + } +} + +static void jabber_caps_store_client(gpointer key, gpointer value, gpointer user_data) { + JabberCapsKey *clientinfo = key; + JabberCapsValue *props = value; + xmlnode *root = user_data; + xmlnode *client = xmlnode_new_child(root,"client"); + GList *iter; + + xmlnode_set_attrib(client,"node",clientinfo->node); + xmlnode_set_attrib(client,"ver",clientinfo->ver); + + for(iter = props->identities; iter; iter = g_list_next(iter)) { + JabberCapsIdentity *id = iter->data; + xmlnode *identity = xmlnode_new_child(client, "identity"); + xmlnode_set_attrib(identity, "category", id->category); + xmlnode_set_attrib(identity, "type", id->type); + xmlnode_set_attrib(identity, "name", id->name); + } + + for(iter = props->features; iter; iter = g_list_next(iter)) { + const char *feat = iter->data; + xmlnode *feature = xmlnode_new_child(client, "feature"); + xmlnode_set_attrib(feature, "var", feat); + } + + g_hash_table_foreach(props->ext,jabber_caps_store_ext,client); +} + +static void jabber_caps_store(void) { + xmlnode *root = xmlnode_new("capabilities"); + g_hash_table_foreach(capstable, jabber_caps_store_client, root); + purple_util_write_data_to_file(JABBER_CAPS_FILENAME, xmlnode_to_formatted_str(root, NULL), -1); +} + +/* this function assumes that all information is available locally */ +static JabberCapsClientInfo *jabber_caps_collect_info(const char *node, const char *ver, GList *ext) { + JabberCapsClientInfo *result = g_new0(JabberCapsClientInfo, 1); + JabberCapsKey *key = g_new0(JabberCapsKey, 1); + JabberCapsValue *caps; + GList *iter; + + key->node = g_strdup(node); + key->ver = g_strdup(ver); + + caps = g_hash_table_lookup(capstable,key); + + g_free(key->node); + g_free(key->ver); + g_free(key); + + /* join all information */ + for(iter = caps->identities; iter; iter = g_list_next(iter)) { + JabberCapsIdentity *id = iter->data; + JabberCapsIdentity *newid = g_new0(JabberCapsIdentity, 1); + newid->category = g_strdup(id->category); + newid->type = g_strdup(id->type); + newid->name = g_strdup(id->name); + + result->identities = g_list_append(result->identities,newid); + } + for(iter = caps->features; iter; iter = g_list_next(iter)) { + const char *feat = iter->data; + char *newfeat = g_strdup(feat); + + result->features = g_list_append(result->features,newfeat); + } + + for(iter = ext; iter; iter = g_list_next(iter)) { + const char *ext = iter->data; + JabberCapsValueExt *extinfo = g_hash_table_lookup(caps->ext,ext); + + if(extinfo) { + for(iter = extinfo->identities; iter; iter = g_list_next(iter)) { + JabberCapsIdentity *id = iter->data; + JabberCapsIdentity *newid = g_new0(JabberCapsIdentity, 1); + newid->category = g_strdup(id->category); + newid->type = g_strdup(id->type); + newid->name = g_strdup(id->name); + + result->identities = g_list_append(result->identities,newid); + } + for(iter = extinfo->features; iter; iter = g_list_next(iter)) { + const char *feat = iter->data; + char *newfeat = g_strdup(feat); + + result->features = g_list_append(result->features,newfeat); + } + } + } + return result; +} + +void jabber_caps_free_clientinfo(JabberCapsClientInfo *clientinfo) { + if(!clientinfo) + return; + while(clientinfo->identities) { + JabberCapsIdentity *id = clientinfo->identities->data; + g_free(id->category); + g_free(id->type); + g_free(id->name); + g_free(id); + + clientinfo->identities = g_list_remove_link(clientinfo->identities,clientinfo->identities); + } + while(clientinfo->features) { + char *feat = clientinfo->features->data; + g_free(feat); + + clientinfo->features = g_list_remove_link(clientinfo->features,clientinfo->features); + } + + g_free(clientinfo); +} + +typedef struct _jabber_caps_cbplususerdata { + jabber_caps_get_info_cb cb; + gpointer user_data; + + char *who; + char *node; + char *ver; + GList *ext; + unsigned extOutstanding; +} jabber_caps_cbplususerdata; + +static void jabber_caps_get_info_check_completion(jabber_caps_cbplususerdata *userdata) { + if(userdata->extOutstanding == 0) { + userdata->cb(jabber_caps_collect_info(userdata->node, userdata->ver, userdata->ext), userdata->user_data); + g_free(userdata->who); + g_free(userdata->node); + g_free(userdata->ver); + while(userdata->ext) { + g_free(userdata->ext->data); + userdata->ext = g_list_remove_link(userdata->ext,userdata->ext); + } + g_free(userdata); + } +} + +static void jabber_caps_ext_iqcb(JabberStream *js, xmlnode *packet, gpointer data) { + /* collect data and fetch all exts */ + xmlnode *query = xmlnode_get_child_with_namespace(packet,"query","http://jabber.org/protocol/disco#info"); + xmlnode *child; + jabber_caps_cbplususerdata *userdata = data; + JabberCapsKey *clientkey = g_new0(JabberCapsKey, 1); + JabberCapsValue *client; + JabberCapsValueExt *value = g_new0(JabberCapsValueExt, 1); + const char *node = xmlnode_get_attrib(query, "node"); + const char *key; + + --userdata->extOutstanding; + + if(node) { + clientkey->node = g_strdup(userdata->node); + clientkey->ver = g_strdup(userdata->ver); + + client = g_hash_table_lookup(capstable,clientkey); + + g_free(clientkey->node); + g_free(clientkey->ver); + g_free(clientkey); + + /* split node by #, key either points to \0 or the correct ext afterwards */ + for(key = node; key[0] != '\0'; ++key) { + if(key[0] == '#') { + ++key; + break; + } + } + + for(child = query->child; child; child = child->next) { + if(child->type != XMLNODE_TYPE_TAG) + continue; + if(!strcmp(child->name,"feature")) { + const char *var = xmlnode_get_attrib(child, "var"); + if(!var) + continue; + value->features = g_list_append(value->features,g_strdup(var)); + } else if(!strcmp(child->name,"identity")) { + const char *category = xmlnode_get_attrib(child, "category"); + const char *type = xmlnode_get_attrib(child, "type"); + const char *name = xmlnode_get_attrib(child, "name"); + + JabberCapsIdentity *id = g_new0(JabberCapsIdentity, 1); + id->category = g_strdup(category); + id->type = g_strdup(type); + id->name = g_strdup(name); + + value->identities = g_list_append(value->identities,id); + } + } + g_hash_table_replace(client->ext, g_strdup(key), value); + + jabber_caps_store(); + } + + jabber_caps_get_info_check_completion(userdata); +} + +static void jabber_caps_client_iqcb(JabberStream *js, xmlnode *packet, gpointer data) { + /* collect data and fetch all exts */ + xmlnode *query = xmlnode_get_child_with_namespace(packet,"query","http://jabber.org/protocol/disco#info"); + xmlnode *child; + GList *iter; + jabber_caps_cbplususerdata *userdata = data; + JabberCapsKey *key = g_new0(JabberCapsKey, 1); + JabberCapsValue *value = g_new0(JabberCapsValue, 1); + key->node = g_strdup(userdata->node); + key->ver = g_strdup(userdata->ver); + + value->ext = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, jabber_caps_ext_destroy_value); + + for(child = query->child; child; child = child->next) { + if(child->type != XMLNODE_TYPE_TAG) + continue; + if(!strcmp(child->name,"feature")) { + const char *var = xmlnode_get_attrib(child, "var"); + if(!var) + continue; + value->features = g_list_append(value->features,g_strdup(var)); + } else if(!strcmp(child->name,"identity")) { + const char *category = xmlnode_get_attrib(child, "category"); + const char *type = xmlnode_get_attrib(child, "type"); + const char *name = xmlnode_get_attrib(child, "name"); + + JabberCapsIdentity *id = g_new0(JabberCapsIdentity, 1); + id->category = g_strdup(category); + id->type = g_strdup(type); + id->name = g_strdup(name); + + value->identities = g_list_append(value->identities,id); + } + } + g_hash_table_replace(capstable, key, value); + + /* fetch all exts */ + for(iter = userdata->ext; iter; iter = g_list_next(iter)) { + JabberIq *iq = jabber_iq_new_query(js,JABBER_IQ_GET,"http://jabber.org/protocol/disco#info"); + xmlnode *query = xmlnode_get_child_with_namespace(iq->node,"query","http://jabber.org/protocol/disco#info"); + char *node = g_strdup_printf("%s#%s", node, (const char*)iter->data); + xmlnode_set_attrib(query, "node", node); + g_free(node); + xmlnode_set_attrib(iq->node, "to", userdata->who); + + jabber_iq_set_callback(iq,jabber_caps_ext_iqcb,userdata); + jabber_iq_send(iq); + } + + jabber_caps_store(); + + jabber_caps_get_info_check_completion(userdata); +} + +void jabber_caps_get_info(JabberStream *js, const char *who, const char *node, const char *ver, const char *ext, jabber_caps_get_info_cb cb, gpointer user_data) { + JabberCapsValue *client; + JabberCapsKey *key = g_new0(JabberCapsKey, 1); + char *originalext = g_strdup(ext); + char *oneext, *ctx; + jabber_caps_cbplususerdata *userdata = g_new0(jabber_caps_cbplususerdata, 1); + userdata->cb = cb; + userdata->user_data = user_data; + userdata->who = g_strdup(who); + userdata->node = g_strdup(node); + userdata->ver = g_strdup(ver); + + if(originalext) + for(oneext = strtok_r(originalext, " ", &ctx); oneext; oneext = strtok_r(NULL, " ", &ctx)) { + userdata->ext = g_list_append(userdata->ext,g_strdup(oneext)); + ++userdata->extOutstanding; + } + g_free(originalext); + + key->node = g_strdup(node); + key->ver = g_strdup(ver); + + client = g_hash_table_lookup(capstable, key); + + g_free(key->node); + g_free(key->ver); + g_free(key); + + if(!client) { + JabberIq *iq = jabber_iq_new_query(js,JABBER_IQ_GET,"http://jabber.org/protocol/disco#info"); + xmlnode *query = xmlnode_get_child_with_namespace(iq->node,"query","http://jabber.org/protocol/disco#info"); + char *nodever = g_strdup_printf("%s#%s", node, ver); + xmlnode_set_attrib(query, "node", nodever); + g_free(nodever); + xmlnode_set_attrib(iq->node, "to", who); + + jabber_iq_set_callback(iq,jabber_caps_client_iqcb,userdata); + jabber_iq_send(iq); + } else { + GList *iter; + /* fetch unknown exts only */ + for(iter = userdata->ext; iter; iter = g_list_next(iter)) { + JabberCapsValueExt *extvalue = g_hash_table_lookup(client->ext, (const char*)iter->data); + JabberIq *iq; + xmlnode *query; + char *nodever; + + if(extvalue) { + /* we already have this ext, don't bother with it */ + --userdata->extOutstanding; + continue; + } + + iq = jabber_iq_new_query(js,JABBER_IQ_GET,"http://jabber.org/protocol/disco#info"); + query = xmlnode_get_child_with_namespace(iq->node,"query","http://jabber.org/protocol/disco#info"); + nodever = g_strdup_printf("%s#%s", node, (const char*)iter->data); + xmlnode_set_attrib(query, "node", nodever); + g_free(nodever); + xmlnode_set_attrib(iq->node, "to", who); + + jabber_iq_set_callback(iq,jabber_caps_ext_iqcb,userdata); + jabber_iq_send(iq); + } + /* maybe we have all data available anyways? This is the ideal case where no network traffic is necessary */ + jabber_caps_get_info_check_completion(userdata); + } +} + diff -r a8b1159fd95b -r f88b3a093cba libpurple/protocols/jabber/caps.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/caps.h Sat Jun 23 02:57:21 2007 +0000 @@ -0,0 +1,49 @@ +/* + * purple - Jabber Protocol Plugin + * + * Copyright (C) 2007, Andreas Monitzer + * + * 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 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _PURPLE_JABBER_CAPS_H_ +#define _PURPLE_JABBER_CAPS_H_ + +typedef struct _JabberCapsClientInfo JabberCapsClientInfo; + +#include "jabber.h" + +/* Implementation of XEP-0115 */ + +typedef struct _JabberCapsIdentity { + char *category; + char *type; + char *name; +} JabberCapsIdentity; + +struct _JabberCapsClientInfo { + GList *identities; /* JabberCapsIdentity */ + GList *features; /* char * */ +}; + +typedef void (*jabber_caps_get_info_cb)(JabberCapsClientInfo *info, gpointer user_data); + +void jabber_caps_init(void); + +void jabber_caps_get_info(JabberStream *js, const char *who, const char *node, const char *ver, const char *ext, jabber_caps_get_info_cb cb, gpointer user_data); +void jabber_caps_free_clientinfo(JabberCapsClientInfo *clientinfo); + +#endif /* _PURPLE_JABBER_CAPS_H_ */ diff -r a8b1159fd95b -r f88b3a093cba libpurple/protocols/jabber/disco.c --- a/libpurple/protocols/jabber/disco.c Fri Jun 22 11:52:50 2007 +0000 +++ b/libpurple/protocols/jabber/disco.c Sat Jun 23 02:57:21 2007 +0000 @@ -31,6 +31,8 @@ #include "presence.h" #include "roster.h" #include "pep.h" +#include "adhoccommands.h" + struct _jabber_disco_info_cb_data { gpointer data; @@ -210,6 +212,9 @@ capabilities |= JABBER_CAP_IQ_REGISTER; else if(!strcmp(var, "http://www.xmpp.org/extensions/xep-0199.html#ns")) capabilities |= JABBER_CAP_PING; + else if(!strcmp(var, "http://jabber.org/protocol/commands")) { + capabilities |= JABBER_CAP_ADHOC; + } } } diff -r a8b1159fd95b -r f88b3a093cba libpurple/protocols/jabber/jabber.h --- a/libpurple/protocols/jabber/jabber.h Fri Jun 22 11:52:50 2007 +0000 +++ b/libpurple/protocols/jabber/jabber.h Sat Jun 23 02:57:21 2007 +0000 @@ -41,6 +41,7 @@ JABBER_CAP_GOOGLE_ROSTER = 1 << 10, JABBER_CAP_PING = 1 << 11, + JABBER_CAP_ADHOC = 1 << 12, JABBER_CAP_RETRIEVED = 1 << 31 } JabberCapabilities; diff -r a8b1159fd95b -r f88b3a093cba libpurple/protocols/jabber/libxmpp.c --- a/libpurple/protocols/jabber/libxmpp.c Fri Jun 22 11:52:50 2007 +0000 +++ b/libpurple/protocols/jabber/libxmpp.c Sat Jun 23 02:57:21 2007 +0000 @@ -40,6 +40,7 @@ #include "google.h" #include "pep.h" #include "usertune.h" +#include "caps.h" static PurplePluginProtocolInfo prpl_info = { @@ -234,6 +235,7 @@ jabber_pep_init(); jabber_tune_init(); + jabber_caps_init(); jabber_add_feature("avatarmeta", AVATARNAMESPACEMETA, jabber_pep_namespace_only_when_pep_enabled_cb); jabber_add_feature("avatardata", AVATARNAMESPACEDATA, jabber_pep_namespace_only_when_pep_enabled_cb); diff -r a8b1159fd95b -r f88b3a093cba libpurple/protocols/jabber/presence.c --- a/libpurple/protocols/jabber/presence.c Fri Jun 22 11:52:50 2007 +0000 +++ b/libpurple/protocols/jabber/presence.c Sat Jun 23 02:57:21 2007 +0000 @@ -36,6 +36,7 @@ #include "presence.h" #include "iq.h" #include "jutil.h" +#include "adhoccommands.h" #include "usertune.h" @@ -337,6 +338,35 @@ } } +typedef struct _JabberPresenceCapabilities { + JabberStream *js; + JabberBuddyResource *jbr; + char *from; +} JabberPresenceCapabilities; + +static void jabber_presence_set_capabilities(JabberCapsClientInfo *info, gpointer user_data) { + JabberPresenceCapabilities *userdata = user_data; + GList *iter; + + if(userdata->jbr->caps) + jabber_caps_free_clientinfo(userdata->jbr->caps); + userdata->jbr->caps = info; + + for(iter = info->features; iter; iter = g_list_next(iter)) { + if(!strcmp((const char*)iter->data, "http://jabber.org/protocol/commands")) { + JabberIq *iq = jabber_iq_new_query(userdata->js, JABBER_IQ_GET, "http://jabber.org/protocol/disco#items"); + xmlnode *query = xmlnode_get_child_with_namespace(iq->node,"query","http://jabber.org/protocol/disco#items"); + xmlnode_set_attrib(iq->node, "to", userdata->from); + xmlnode_set_attrib(query, "node", "http://jabber.org/protocol/commands"); + + jabber_iq_set_callback(iq, jabber_adhoc_disco_result_cb, NULL); + jabber_iq_send(iq); + break; + } + } + g_free(user_data); +} + void jabber_presence_parse(JabberStream *js, xmlnode *packet) { const char *from = xmlnode_get_attrib(packet, "from"); @@ -358,6 +388,7 @@ xmlnode *y; gboolean muc = FALSE; char *avatar_hash = NULL; + xmlnode *caps = NULL; if(!(jb = jabber_buddy_find(js, from, TRUE))) return; @@ -420,8 +451,10 @@ for(y = packet->child; y; y = y->next) { + const char *xmlns; if(y->type != XMLNODE_TYPE_TAG) continue; + xmlns = xmlnode_get_namespace(y); if(!strcmp(y->name, "status")) { g_free(status); @@ -432,9 +465,11 @@ priority = atoi(p); g_free(p); } - } else if(!strcmp(y->name, "delay")) { + } else if(!strcmp(y->name, "delay") && !strcmp(xmlns, "urn:xmpp:delay")) { /* XXX: compare the time. jabber:x:delay can happen on presence packets that aren't really and truly delayed */ delayed = TRUE; + } else if(!strcmp(y->name, "c") && !strcmp(xmlns, "http://jabber.org/protocol/caps")) { + caps = y; /* store for later, when creating buddy resource */ } else if(!strcmp(y->name, "x")) { const char *xmlns = xmlnode_get_namespace(y); if(xmlns && !strcmp(xmlns, "jabber:x:delay")) { @@ -656,6 +691,19 @@ } else { jbr = jabber_buddy_track_resource(jb, jid->resource, priority, state, status); + if(caps) { + const char *node = xmlnode_get_attrib(caps,"node"); + const char *ver = xmlnode_get_attrib(caps,"ver"); + const char *ext = xmlnode_get_attrib(caps,"ext"); + + if(node && ver) { + JabberPresenceCapabilities *userdata = g_new0(JabberPresenceCapabilities, 1); + userdata->js = js; + userdata->jbr = jbr; + userdata->from = g_strdup(from); + jabber_caps_get_info(js, from, node, ver, ext, jabber_presence_set_capabilities, userdata); + } + } } if((found_jbr = jabber_buddy_find_resource(jb, NULL))) {