--- 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
--- /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 <mcabber@freakysoft.de>
+ *
+ * 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 <glib.h>
+
+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... */
--- /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 <glib.h>
+
+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... */
--- 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 <glib.h>
#include <config.h>
+#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");
--- 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
--- 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);
--- 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 <string.h>
#include <sys/utsname.h>
+#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;
--- 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"
--- 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;
}
--- 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
--- 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);