# HG changeset patch # User franky # Date 1222160365 -7200 # Node ID c5ee395fbc8c05c31636c871fbdbc2196a9db231 # Parent dcd5d4c75199fd6836b6ca5e3fa21bca30f9da6f Updated Entity Capabilities support (XEP-0115) diff -r dcd5d4c75199 -r c5ee395fbc8c mcabber/src/Makefile.am --- a/mcabber/src/Makefile.am Sun Oct 11 15:39:32 2009 +0200 +++ b/mcabber/src/Makefile.am Tue Sep 23 10:59:25 2008 +0200 @@ -5,7 +5,7 @@ settings.c settings.h hooks.c hooks.h utf8.c utf8.h \ histolog.c histolog.h utils.c utils.h pgp.c pgp.h \ fifo.c fifo.h help.c help.h \ - xmpp.c xmpp.h xmpp_helper.h + xmpp.c xmpp.h xmpp_helper.h caps.c caps.h if OTR mcabber_SOURCES += otr.c otr.h nohtml.c nohtml.h diff -r dcd5d4c75199 -r c5ee395fbc8c mcabber/src/caps.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/src/caps.c Tue Sep 23 10:59:25 2008 +0200 @@ -0,0 +1,176 @@ +/* + * caps.c -- Entity Capabilities Cache for mcabber + * + * Copyright (C) 2008 Frank Zschockelt + * + * 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 + +typedef struct { + char *category; + char *name; + char *type; + GHashTable *features; +} caps; + +static GHashTable *caps_cache = NULL; + +void caps_destroy(gpointer data) +{ + caps *c = data; + g_free(c->category); + g_free(c->name); + g_free(c->type); + g_hash_table_destroy(c->features); + g_free(c); +} + +void caps_init(void) +{ + if (!caps_cache) + caps_cache = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, caps_destroy); +} + +void caps_free(void) +{ + if (caps_cache) { + g_hash_table_destroy(caps_cache); + caps_cache = NULL; + } +} + +void caps_add(char *hash) +{ + if (!hash) + return; + caps *c = g_new0(caps, 1); + c->features = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + g_hash_table_insert(caps_cache, g_strdup(hash), c); +} + +int caps_has_hash(const char *hash) +{ + return (hash != NULL && (g_hash_table_lookup(caps_cache, hash) != NULL)); +} + +void caps_set_identity(char *hash, + const char *category, + const char *name, + const char *type) +{ + caps *c; + if (!hash) + return; + + c = g_hash_table_lookup(caps_cache, hash); + if (c) { + c->category = g_strdup(category); + c->name = g_strdup(name); + c->type = g_strdup(type); + } +} + +void caps_add_feature(char *hash, const char *feature) +{ + caps *c; + if (!hash) + return; + c = g_hash_table_lookup(caps_cache, hash); + if (c) { + char *f = g_strdup(feature); + g_hash_table_insert(c->features, f, f); + } +} + +int caps_has_feature(char *hash, char *feature) +{ + caps *c; + if (!hash) + return 0; + c = g_hash_table_lookup(caps_cache, hash); + if (c) + return (g_hash_table_lookup(c->features, feature) != NULL); + return 0; +} + +static GFunc _foreach_function; + +void _caps_foreach_helper(gpointer key, gpointer value, gpointer user_data) +{ + // GFunc func = (GFunc)user_data; + _foreach_function(value, user_data); +} + +void caps_foreach_feature(const char *hash, GFunc func, gpointer user_data) +{ + caps *c; + if (!hash) + return; + c = g_hash_table_lookup(caps_cache, hash); + if (!c) + return; + _foreach_function = func; + g_hash_table_foreach(c->features, _caps_foreach_helper, user_data); +} + +gint _strcmp_sort(gconstpointer a, gconstpointer b) +{ + return g_strcmp0(a, b); +} + +//generates the sha1 hash for the special capability "" and returns it +const char *caps_generate(void) +{ + char *identity; + GList *features; + GChecksum *sha1; + guint8 digest[20]; + gsize digest_size = 20; + gchar *hash, *old_hash = NULL; + caps *old_caps; + unsigned int i; + caps *c = g_hash_table_lookup(caps_cache, ""); + + g_hash_table_steal(caps_cache, ""); + sha1 = g_checksum_new(G_CHECKSUM_SHA1); + identity = g_strdup_printf("%s/%s/%s<", c->category, c->type, c->name); + g_checksum_update(sha1, (guchar*)identity, -1); + g_free(identity); + + features = g_list_copy(g_hash_table_get_values(c->features)); + features = g_list_sort(features, _strcmp_sort); + for (i=0; i < g_list_length(features); i++) { + g_checksum_update(sha1, g_list_nth_data(features, i), -1); + g_checksum_update(sha1, (guchar *)"<", -1); + } + g_list_free(features); + + g_checksum_get_digest(sha1, digest, &digest_size); + hash = g_base64_encode(digest, digest_size); + g_checksum_free(sha1); + g_hash_table_lookup_extended(caps_cache, hash, + (gpointer *)&old_hash, (gpointer *)&old_caps); + g_hash_table_insert(caps_cache, hash, c); + if (old_hash) + return old_hash; + else + return hash; +} + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r dcd5d4c75199 -r c5ee395fbc8c mcabber/src/caps.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/src/caps.h Tue Sep 23 10:59:25 2008 +0200 @@ -0,0 +1,22 @@ +#ifndef __CAPS_H__ +#define __CAPS_H__ 1 + +#include + +void caps_init(void); +void caps_free(void); +void caps_add(char *hash); +int caps_has_hash(const char *hash); +void caps_set_identity(char *hash, + const char *category, + const char *name, + const char *type); +void caps_add_feature(char *hash, const char *feature); +int caps_has_feature(char *hash, char *feature); +void caps_foreach_feature(const char *hash, GFunc func, gpointer user_data); + +char *caps_generate(void); + +#endif /* __CAPS_H__ */ + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r dcd5d4c75199 -r c5ee395fbc8c mcabber/src/main.c --- a/mcabber/src/main.c Sun Oct 11 15:39:32 2009 +0200 +++ b/mcabber/src/main.c Tue Sep 23 10:59:25 2008 +0200 @@ -31,6 +31,7 @@ #include #include +#include "caps.h" #include "screen.h" #include "settings.h" #include "roster.h" @@ -318,6 +319,7 @@ roster_init(); settings_init(); scr_init_bindings(); + caps_init(); /* Initialize charset */ scr_InitLocaleCharSet(); @@ -429,6 +431,7 @@ #endif /* Save pending message state */ hlog_save_state(); + caps_free(); printf("\n\nThanks for using mcabber!\n"); diff -r dcd5d4c75199 -r c5ee395fbc8c mcabber/src/roster.c --- a/mcabber/src/roster.c Sun Oct 11 15:39:32 2009 +0200 +++ b/mcabber/src/roster.c Tue Sep 23 10:59:25 2008 +0200 @@ -67,6 +67,7 @@ enum imaffiliation affil; gchar *realjid; /* for chatrooms, if buddy's real jid is known */ guint events; + char *caps; #ifdef JEP0022 struct jep0022 jep22; #endif @@ -150,6 +151,7 @@ #ifdef HAVE_GPGME g_free(p_res->pgpdata.sign_keyid); #endif + g_free(p_res->caps); g_free(p_res); } @@ -1196,6 +1198,26 @@ p_res->events = events; } +char *buddy_resource_getcaps(gpointer rosterdata, const char *resname) +{ + roster *roster_usr = rosterdata; + res *p_res = get_resource(roster_usr, resname); + if (p_res) + return p_res->caps; + return NULL; +} + +void buddy_resource_setcaps(gpointer rosterdata, const char *resname, + const char *caps) +{ + roster *roster_usr = rosterdata; + res *p_res = get_resource(roster_usr, resname); + if (p_res) { + g_free(p_res->caps); + p_res->caps = g_strdup(caps); + } +} + struct jep0022 *buddy_resource_jep22(gpointer rosterdata, const char *resname) { #ifdef JEP0022 diff -r dcd5d4c75199 -r c5ee395fbc8c mcabber/src/roster.h --- a/mcabber/src/roster.h Sun Oct 11 15:39:32 2009 +0200 +++ b/mcabber/src/roster.h Tue Sep 23 10:59:25 2008 +0200 @@ -212,6 +212,9 @@ void buddy_resource_setevents(gpointer rosterdata, const char *resname, guint event); guint buddy_resource_getevents(gpointer rosterdata, const char *resname); +void buddy_resource_setcaps(gpointer rosterdata, const char *resname, + const char *caps); +char *buddy_resource_getcaps(gpointer rosterdata, const char *resname); struct jep0022 *buddy_resource_jep22(gpointer rosterdata, const char *resname); struct jep0085 *buddy_resource_jep85(gpointer rosterdata, const char *resname); struct pgp_data *buddy_resource_pgp(gpointer rosterdata, const char *resname); diff -r dcd5d4c75199 -r c5ee395fbc8c mcabber/src/xmpp.c --- a/mcabber/src/xmpp.c Sun Oct 11 15:39:32 2009 +0200 +++ b/mcabber/src/xmpp.c Tue Sep 23 10:59:25 2008 +0200 @@ -25,6 +25,7 @@ #include #include +#include "caps.h" #include "commands.h" #include "events.h" #include "histolog.h" @@ -1253,6 +1254,34 @@ return LM_HANDLER_RESULT_REMOVE_MESSAGE; } +static LmHandlerResult cb_caps(LmMessageHandler *h, LmConnection *c, + LmMessage *m, gpointer user_data) +{ + char *ver = user_data; + + if (lm_message_get_sub_type(m) == LM_MESSAGE_SUB_TYPE_ERROR) { + display_server_error(lm_message_node_get_child(m->node, "error")); + } else if (lm_message_get_sub_type(m) == LM_MESSAGE_SUB_TYPE_RESULT) { + LmMessageNode *info; + LmMessageNode *query = lm_message_node_get_child(m->node, "query"); + + caps_add(ver); + info = lm_message_node_get_child(query, "identity"); + if (info) + caps_set_identity(ver, lm_message_node_get_attribute(info, "category"), + lm_message_node_get_attribute(info, "name"), + lm_message_node_get_attribute(info, "type")); + info = lm_message_node_get_child(query, "feature"); + while (info) { + if (!g_strcmp0(info->name, "feature")) + caps_add_feature(ver, lm_message_node_get_attribute(info, "var")); + info = info->next; + } + } + g_free(ver); + return LM_HANDLER_RESULT_REMOVE_MESSAGE; +} + static LmHandlerResult handle_presence(LmMessageHandler *handler, LmConnection *connection, LmMessage *m, gpointer user_data) @@ -1262,7 +1291,7 @@ enum imstatus ust; char bpprio; time_t timestamp = 0L; - LmMessageNode *muc_packet; + LmMessageNode *muc_packet, *caps; //Check for MUC presence packet muc_packet = lm_message_node_find_xmlns @@ -1345,6 +1374,39 @@ ustmsg); } + //XEP-0115 Entity Capabilities + caps = lm_message_node_find_xmlns(m->node, NS_CAPS); + if (caps) { + const char *ver = lm_message_node_get_attribute(caps, "ver"); + GSList *sl_buddy = NULL; + if (rname) + sl_buddy = roster_find(r, jidsearch, ROSTER_TYPE_USER); + //only cache the caps if the user is on the roster + if (sl_buddy && buddy_getonserverflag(sl_buddy->data)) { + buddy_resource_setcaps(sl_buddy->data, rname, ver); + + if (!caps_has_hash(ver)) { + char *node; + LmMessageHandler *handler; + LmMessage *iq = lm_message_new_with_sub_type(from, LM_MESSAGE_TYPE_IQ, + LM_MESSAGE_SUB_TYPE_GET); + node = g_strdup_printf("%s#%s", + lm_message_node_get_attribute(caps, "node"), + ver); + lm_message_node_set_attributes + (lm_message_node_add_child(iq->node, "query", NULL), + "xmlns", NS_DISCO_INFO, + "node", node, + NULL); + g_free(node); + handler = lm_message_handler_new(cb_caps, g_strdup(ver), NULL); + lm_connection_send_with_reply(connection, iq, handler, NULL); + lm_message_unref(iq); + lm_message_handler_unref(handler); + } + } + } + g_free(r); return LM_HANDLER_RESULT_REMOVE_MESSAGE; } @@ -1693,34 +1755,16 @@ // insert_entity_capabilities(presence_stanza) // Entity Capabilities (XEP-0115) -static void insert_entity_capabilities(LmMessageNode * x) +static void insert_entity_capabilities(LmMessageNode *x, enum imstatus status) { LmMessageNode *y; - const char *ver = entity_version(); - char *exts, *exts2; - - exts = NULL; + const char *ver = entity_version(status); y = lm_message_node_add_child(x, "c", NULL); lm_message_node_set_attribute(y, "xmlns", NS_CAPS); + lm_message_node_set_attribute(y, "hash", "sha-1"); lm_message_node_set_attribute(y, "node", MCABBER_CAPS_NODE); lm_message_node_set_attribute(y, "ver", ver); -#ifdef JEP0085 - if (!chatstates_disabled) { - exts2 = g_strjoin(" ", "csn", exts, NULL); - g_free(exts); - exts = exts2; - } -#endif - if (!settings_opt_get_int("iq_last_disable")) { - exts2 = g_strjoin(" ", "iql", exts, NULL); - g_free(exts); - exts = exts2; - } - if (exts) { - lm_message_node_set_attribute(y, "ext", exts); - g_free(exts); - } } void xmpp_disconnect(void) @@ -1765,7 +1809,7 @@ if (lm_connection_is_authenticated(lconnection)) { const char *s_msg = (st != invisible ? msg : NULL); m = lm_message_new_presence(st, recipient, s_msg); - insert_entity_capabilities(m->node); // Entity Capabilities (XEP-0115) + insert_entity_capabilities(m->node, st); // Entity Capabilities (XEP-0115) #ifdef HAVE_GPGME if (!do_not_sign && gpg_enabled()) { char *signature; diff -r dcd5d4c75199 -r c5ee395fbc8c mcabber/src/xmpp_defines.h --- a/mcabber/src/xmpp_defines.h Sun Oct 11 15:39:32 2009 +0200 +++ b/mcabber/src/xmpp_defines.h Tue Sep 23 10:59:25 2008 +0200 @@ -1,6 +1,8 @@ #ifndef __XMPP_DEFINES_H__ #define __XMPP_DEFINES_H__ 1 +#define MCABBER_CAPS_NODE "http://mcabber.com/caps" + #define NS_CLIENT "jabber:client" #define NS_SERVER "jabber:server" #define NS_DIALBACK "jabber:server:dialback" diff -r dcd5d4c75199 -r c5ee395fbc8c mcabber/src/xmpp_helper.c --- a/mcabber/src/xmpp_helper.c Sun Oct 11 15:39:32 2009 +0200 +++ b/mcabber/src/xmpp_helper.c Tue Sep 23 10:59:25 2008 +0200 @@ -125,25 +125,43 @@ return new; } -// entity_version() +// entity_version(enum imstatus status) // Return a static version string for Entity Capabilities. // It should be specific to the client version, please change the id // if you alter mcabber's disco support (or add something to the version // number) so that it doesn't conflict with the official client. -const char *entity_version(void) +const char *entity_version(enum imstatus status) { - static char *ver; - const char *PVERSION = PACKAGE_VERSION; // "+xxx"; + static char *ver, *ver_notavail; - if (ver) + if (ver && (status != notavail)) return ver; + if (ver_notavail) + return ver_notavail; -#ifdef HGCSET - ver = g_strdup_printf("%s-%s", PVERSION, HGCSET); -#else - ver = g_strdup(PVERSION); -#endif + caps_add(""); + caps_set_identity("", "client", PACKAGE_STRING, "pc"); + caps_add_feature("", NS_DISCO_INFO); + caps_add_feature("", NS_MUC); + // advertise ChatStates only if they aren't disabled + if (!settings_opt_get_int("disable_chatstates")) + caps_add_feature("", NS_CHATSTATES); + caps_add_feature("", NS_TIME); + caps_add_feature("", NS_XMPP_TIME); + caps_add_feature("", NS_VERSION); + caps_add_feature("", NS_PING); + caps_add_feature("", NS_COMMANDS); + if (!settings_opt_get_int("iq_last_disable") && + (!settings_opt_get_int("iq_last_disable_when_notavail") || + status != notavail)) + caps_add_feature("", NS_LAST); + if (status == notavail) { + ver_notavail = caps_generate(); + return ver_notavail; + } + + ver = caps_generate(); return ver; } diff -r dcd5d4c75199 -r c5ee395fbc8c mcabber/src/xmpp_helper.h --- a/mcabber/src/xmpp_helper.h Sun Oct 11 15:39:32 2009 +0200 +++ b/mcabber/src/xmpp_helper.h Tue Sep 23 10:59:25 2008 +0200 @@ -17,8 +17,7 @@ void lm_message_node_deep_ref(LmMessageNode * node); /* XEP-0115 (Entity Capabilities) node */ -#define MCABBER_CAPS_NODE "http://mcabber.com/caps" -const char *entity_version(void); +const char *entity_version(enum imstatus status); #endif diff -r dcd5d4c75199 -r c5ee395fbc8c mcabber/src/xmpp_iq.c --- a/mcabber/src/xmpp_iq.c Sun Oct 11 15:39:32 2009 +0200 +++ b/mcabber/src/xmpp_iq.c Tue Sep 23 10:59:25 2008 +0200 @@ -461,82 +461,47 @@ } -// disco_info_set_ext(ansquery, ext) -// Add features attributes to ansquery for extension ext. -static void disco_info_set_ext(LmMessageNode *ansquery, const char *ext) +void _disco_add_feature_helper(gpointer data, gpointer user_data) { - char *nodename = g_strdup_printf("%s#%s", MCABBER_CAPS_NODE, ext); - lm_message_node_set_attribute(ansquery, "node", nodename); - g_free(nodename); - if (!strcasecmp(ext, "csn")) { - // I guess it's ok to send this even if it's not compiled in. - lm_message_node_set_attribute(lm_message_node_add_child(ansquery, - "feature", NULL), - "var", NS_CHATSTATES); - } - if (!strcasecmp(ext, "iql")) { - // I guess it's ok to send this even if it's not compiled in. - lm_message_node_set_attribute(lm_message_node_add_child(ansquery, - "feature", NULL), - "var", NS_LAST); - } + LmMessageNode *node = user_data; + lm_message_node_set_attribute + (lm_message_node_add_child(node, "feature", NULL), "var", data); } -// disco_info_set_default(ansquery, entitycaps) -// Add features attributes to ansquery. If entitycaps is TRUE, assume -// that we're answering an Entity Caps request (if not, the request was -// a basic discovery query). +// disco_info_set_caps(ansquery, entitycaps) +// Add features attributes to ansquery. entitycaps should either be a +// valid capabilities hash or NULL. If it is NULL, the node attribute won't +// be added to the query child and Entity Capabilities will be announced +// as a feature. // Please change the entity version string if you modify mcabber disco // source code, so that it doesn't conflict with the upstream client. -static void disco_info_set_default(LmMessageNode *ansquery, guint entitycaps) +static void disco_info_set_caps(LmMessageNode *ansquery, + const char *entitycaps) { - LmMessageNode *y; - char *eversion; - - eversion = g_strdup_printf("%s#%s", MCABBER_CAPS_NODE, entity_version()); - lm_message_node_set_attribute(ansquery, "node", eversion); - g_free(eversion); - - y = lm_message_node_add_child(ansquery, "identity", NULL); + if (entitycaps) { + char *eversion; + eversion = g_strdup_printf("%s#%s", MCABBER_CAPS_NODE, entitycaps); + lm_message_node_set_attribute(ansquery, "node", eversion); + g_free(eversion); + } - lm_message_node_set_attributes(y, - "category", "client", - "type", "pc", - "name", PACKAGE_NAME, - NULL); + lm_message_node_set_attributes + (lm_message_node_add_child(ansquery, "identity", NULL), + "category", "client", + "name", PACKAGE_STRING, + "type", "pc", + NULL); - lm_message_node_set_attribute - (lm_message_node_add_child(ansquery, "feature", NULL), - "var", NS_DISCO_INFO); - lm_message_node_set_attribute - (lm_message_node_add_child(ansquery, "feature", NULL), - "var", NS_MUC); -#ifdef JEP0085 - // Advertise ChatStates only if we're not using Entity Capabilities - if (!entitycaps) + if (entitycaps) + caps_foreach_feature(entitycaps, _disco_add_feature_helper, ansquery); + else { + caps_foreach_feature(entity_version(xmpp_getstatus()), + _disco_add_feature_helper, + ansquery); lm_message_node_set_attribute (lm_message_node_add_child(ansquery, "feature", NULL), - "var", NS_CHATSTATES); -#endif - lm_message_node_set_attribute - (lm_message_node_add_child(ansquery, "feature", NULL), - "var", NS_TIME); - lm_message_node_set_attribute - (lm_message_node_add_child(ansquery, "feature", NULL), - "var", NS_XMPP_TIME); - lm_message_node_set_attribute - (lm_message_node_add_child(ansquery, "feature", NULL), - "var", NS_VERSION); - lm_message_node_set_attribute - (lm_message_node_add_child(ansquery, "feature", NULL), - "var", NS_PING); - lm_message_node_set_attribute - (lm_message_node_add_child(ansquery, "feature", NULL), - "var", NS_COMMANDS); - if (!entitycaps) - lm_message_node_set_attribute - (lm_message_node_add_child(ansquery, "feature", NULL), - "var", NS_LAST); + "var", NS_CAPS); + } } static LmHandlerResult handle_iq_disco_info(LmMessageHandler *h, @@ -546,23 +511,21 @@ LmMessage *r; LmMessageNode *query, *tmp; const char *node = NULL; + const char *param = NULL; r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT); query = lm_message_node_add_child(r->node, "query", NULL); lm_message_node_set_attribute(query, "xmlns", NS_DISCO_INFO); tmp = lm_message_node_find_child(m->node, "query"); - if (tmp) + if (tmp) { node = lm_message_node_get_attribute(tmp, "node"); - if (node && startswith(node, MCABBER_CAPS_NODE "#", FALSE)) { - const char *param = node+strlen(MCABBER_CAPS_NODE)+1; - if (!strcmp(param, entity_version())) - disco_info_set_default(query, TRUE); // client#version - else - disco_info_set_ext(query, param); // client#extension - } else { + param = node+strlen(MCABBER_CAPS_NODE)+1; + } + if (node && startswith(node, MCABBER_CAPS_NODE "#", FALSE)) + disco_info_set_caps(query, param); // client#version + else // Basic discovery request - disco_info_set_default(query, FALSE); - } + disco_info_set_caps(query, NULL); lm_connection_send(c, r, NULL); lm_message_unref(r);