cpw.darkrain42.xmpp.bosh: d71f3fcb: Support old (XEP v1.3) Entity Capabiliti...
paul at darkrain42.org
paul at darkrain42.org
Sat Jan 17 23:56:49 EST 2009
-----------------------------------------------------------------
Revision: d71f3fcb7f2ef61851e826ca66f475f540e713ea
Ancestor: a857c0576b3f4a14e1481eb94591a59bd61a4713
Author: paul at darkrain42.org
Date: 2008-12-19T04:11:07
Branch: im.pidgin.cpw.darkrain42.xmpp.bosh
URL: http://d.pidgin.im/viewmtn/revision/info/d71f3fcb7f2ef61851e826ca66f475f540e713ea
Modified files:
libpurple/protocols/jabber/buddy.c
libpurple/protocols/jabber/buddy.h
libpurple/protocols/jabber/caps.c
libpurple/protocols/jabber/caps.h
libpurple/protocols/jabber/jabber.c
libpurple/protocols/jabber/presence.c
ChangeLog:
Support old (XEP v1.3) Entity Capabilities alongside the new ones.
The ext structures from v1.3 are stored in a ref-counted structure that
is shared among all instances of the ClientInfo that share the same 'node'
(unique per client). exts are only used for v1.3-entity capabilities
clients and are not shared with caps that specify the 'hash' attribute
(required in v1.5).
The jabber_caps_cbplususerdata is also ref-counted and will never leak,
even if some disco#info responses from a client return errors.
-------------- next part --------------
============================================================
--- libpurple/protocols/jabber/buddy.c 03d8333c21099668ebf9ee4c399d57cc12e0fd95
+++ libpurple/protocols/jabber/buddy.c 4d0c7897c82d10209bfa260624e042ceacce7e42
@@ -181,7 +181,11 @@ void jabber_buddy_resource_free(JabberBu
jbr->commands = g_list_delete_link(jbr->commands, jbr->commands);
}
- jabber_caps_client_info_unref(jbr->caps);
+ jabber_caps_client_info_unref(jbr->caps.info);
+ if (jbr->caps.exts) {
+ g_list_foreach(jbr->caps.exts, (GFunc)g_free, NULL);
+ g_list_free(jbr->caps.exts);
+ }
g_free(jbr->name);
g_free(jbr->status);
g_free(jbr->thread_id);
@@ -2509,14 +2513,27 @@ jabber_resource_has_capability(const Jab
jabber_resource_has_capability(const JabberBuddyResource *jbr, const gchar *cap)
{
const GList *node = NULL;
+ const JabberCapsNodeExts *exts;
- if (!jbr->caps) {
+ if (!jbr->caps.info) {
purple_debug_error("jabber",
"Unable to find caps: nothing known about buddy\n");
return FALSE;
}
- node = g_list_find_custom(jbr->caps->features, cap, (GCompareFunc)strcmp);
+ node = g_list_find_custom(jbr->caps.info->features, cap, (GCompareFunc)strcmp);
+ if (!node && jbr->caps.exts && jbr->caps.info->exts) {
+ const GList *ext;
+ exts = jbr->caps.info->exts;
+ /* Walk through all the enabled caps, checking each list for the cap.
+ * Don't check it twice, though. */
+ for (ext = jbr->caps.exts; ext && !node; ext = ext->next) {
+ GList *features = g_hash_table_lookup(exts->exts, ext->data);
+ if (features)
+ node = g_list_find_custom(features, cap, (GCompareFunc)strcmp);
+ }
+ }
+
/* TODO: Are these messages actually useful? */
if (node)
purple_debug_info("jabber", "Found cap: %s\n", cap);
============================================================
--- libpurple/protocols/jabber/buddy.h b72ccdfffb3ac98b1f2cab34a3fddfef59c086e5
+++ libpurple/protocols/jabber/buddy.h 113481ac2c0146e2761280f10a1ff591e926d877
@@ -81,7 +81,10 @@ typedef struct _JabberBuddyResource {
char *name;
char *os;
} client;
- JabberCapsClientInfo *caps;
+ struct {
+ JabberCapsClientInfo *info;
+ GList *exts;
+ } caps;
GList *commands;
} JabberBuddyResource;
============================================================
--- libpurple/protocols/jabber/caps.c 8b22d3df09d6ac9532797939296958d5c3cb9bcc
+++ libpurple/protocols/jabber/caps.c 475eedb3ea5788f42f8e267c574bfd88f1481af2
@@ -42,6 +42,8 @@ static GHashTable *capstable = NULL; /*
} JabberCapsKey;
static GHashTable *capstable = NULL; /* JabberCapsKey -> JabberCapsClientInfo */
+static GHashTable *nodetable = NULL; /* char *node -> JabberCapsNodeExts */
+static guint save_timer = 0;
/**
* Processes a query-node and returns a JabberCapsClientInfo object with all relevant info.
@@ -59,21 +61,59 @@ typedef struct _JabberCapsValue {
} JabberCapsValue;
#endif
+/* Free a GList of allocated char* */
+static void
+free_string_glist(GList *list)
+{
+ g_list_foreach(list, (GFunc)g_free, NULL);
+ g_list_free(list);
+}
+
+static JabberCapsNodeExts*
+jabber_caps_node_exts_ref(JabberCapsNodeExts *exts)
+{
+ g_return_val_if_fail(exts != NULL, NULL);
+
+ ++exts->ref;
+ return exts;
+}
+
+static void
+jabber_caps_node_exts_unref(JabberCapsNodeExts *exts)
+{
+ if (exts == NULL)
+ return;
+
+ g_return_if_fail(exts->ref != 0);
+
+ if (--exts->ref != 0)
+ return;
+
+ g_hash_table_destroy(exts->exts);
+ g_free(exts);
+}
+
static guint jabber_caps_hash(gconstpointer data) {
const JabberCapsKey *key = data;
guint nodehash = g_str_hash(key->node);
- guint verhash = g_str_hash(key->ver);
- guint hashhash = g_str_hash(key->hash);
+ guint verhash = g_str_hash(key->ver);
+ /* 'hash' was optional in XEP-0115 v1.4 and I think g_str_hash crashes on
+ * NULL >:O. Okay, maybe I've played too much Zelda, but that looks like
+ * a Deku Shrub... */
+ guint hashhash = (key->hash ? g_str_hash(key->hash) : 0);
return nodehash ^ verhash ^ hashhash;
}
static gboolean jabber_caps_compare(gconstpointer v1, gconstpointer v2) {
const JabberCapsKey *name1 = v1;
const JabberCapsKey *name2 = v2;
-
+ /* Again, hash might be NULL and I *know* strcmp will crash on NULL. */
+ gboolean hasheq = ((!name1->hash && !name2->hash) ||
+ (name1->hash && name2->hash && !strcmp(name1->hash, name2->hash)));
+
return strcmp(name1->node, name2->node) == 0 &&
strcmp(name1->ver, name2->ver) == 0 &&
- strcmp(name1->hash, name2->hash) == 0;
+ hasheq;
}
void jabber_caps_destroy_key(gpointer data) {
@@ -84,11 +124,12 @@ void jabber_caps_destroy_key(gpointer da
g_free(key);
}
-void
+JabberCapsClientInfo *
jabber_caps_client_info_ref(JabberCapsClientInfo *info)
{
- g_return_if_fail(info != NULL);
+ g_return_val_if_fail(info != NULL, NULL);
++info->ref;
+ return info;
}
void
@@ -97,9 +138,9 @@ jabber_caps_client_info_unref(JabberCaps
if (info == NULL)
return;
- g_return_if_fail(info->ref > 0);
+ g_return_if_fail(info->ref != 0);
- if (--info->ref > 0)
+ if (--info->ref != 0)
return;
while(info->identities) {
@@ -112,11 +153,10 @@ jabber_caps_client_info_unref(JabberCaps
info->identities = g_list_delete_link(info->identities, info->identities);
}
- g_list_foreach(info->features, (GFunc)g_free, NULL);
- g_list_free(info->features);
+ free_string_glist(info->features);
+ free_string_glist(info->forms);
- g_list_foreach(info->forms, (GFunc)g_free, NULL);
- g_list_free(info->forms);
+ jabber_caps_node_exts_unref(info->exts);
#if 0
g_hash_table_destroy(valuestruct->ext);
@@ -125,6 +165,22 @@ jabber_caps_client_info_unref(JabberCaps
g_free(info);
}
+/* NOTE: Takes a reference to the exts, unref it if you don't really want to
+ * keep it around. */
+static JabberCapsNodeExts*
+jabber_caps_find_exts_by_node(const char *node)
+{
+ JabberCapsNodeExts *exts;
+ if (NULL == (exts = g_hash_table_lookup(nodetable, node))) {
+ exts = g_new0(JabberCapsNodeExts, 1);
+ exts->exts = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
+ (GDestroyNotify)free_string_glist);
+ g_hash_table_insert(nodetable, g_strdup(node), jabber_caps_node_exts_ref(exts));
+ }
+
+ return jabber_caps_node_exts_ref(exts);
+}
+
#if 0
static void jabber_caps_ext_destroy_value(gpointer value) {
JabberCapsValueExt *valuestruct = value;
@@ -146,16 +202,24 @@ static void jabber_caps_load(void);
#endif
static void jabber_caps_load(void);
+static gboolean do_jabber_caps_store(gpointer data);
void jabber_caps_init(void)
{
+ nodetable = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)jabber_caps_node_exts_unref);
capstable = g_hash_table_new_full(jabber_caps_hash, jabber_caps_compare, jabber_caps_destroy_key, (GDestroyNotify)jabber_caps_client_info_unref);
jabber_caps_load();
}
void jabber_caps_uninit(void)
{
+ if (save_timer != 0) {
+ purple_timeout_remove(save_timer);
+ save_timer = 0;
+ do_jabber_caps_store(NULL);
+ }
g_hash_table_destroy(capstable);
+ g_hash_table_destroy(nodetable);
capstable = NULL;
}
@@ -178,10 +242,16 @@ static void jabber_caps_load(void) {
JabberCapsKey *key = g_new0(JabberCapsKey, 1);
JabberCapsClientInfo *value = g_new0(JabberCapsClientInfo, 1);
xmlnode *child;
+ JabberCapsNodeExts *exts = NULL;
jabber_caps_client_info_ref(value);
key->node = g_strdup(xmlnode_get_attrib(client,"node"));
key->ver = g_strdup(xmlnode_get_attrib(client,"ver"));
key->hash = g_strdup(xmlnode_get_attrib(client,"hash"));
+
+ /* v1.3 capabilities */
+ if (key->hash == NULL)
+ exts = jabber_caps_find_exts_by_node(key->node);
+
for(child = client->child; child; child = child->next) {
if(child->type != XMLNODE_TYPE_TAG)
continue;
@@ -208,10 +278,45 @@ static void jabber_caps_load(void) {
value->identities = g_list_append(value->identities,id);
} else if(!strcmp(child->name,"x")) {
+ /* FIXME: See #7814 -- this will cause problems if anyone
+ * ever actually specifies forms. In fact, for this to
+ * work properly, that bug needs to be fixed in
+ * xmlnode_from_str, not the output version... */
value->forms = g_list_append(value->forms, xmlnode_copy(child));
+ } else if (!strcmp(child->name, "ext") && key->hash != NULL) {
+ purple_debug_warning("jabber", "Ignoring exts when reading new-style caps\n");
+ } else if (!strcmp(child->name, "ext")) {
+ /* TODO: Do we care about reading in the identities listed here? */
+ const char *identifier = xmlnode_get_attrib(child, "identifier");
+ xmlnode *node;
+ GList *features = NULL;
+
+ if (!identifier)
+ continue;
+
+ for (node = child->child; node; node = node->next) {
+ if (node->type != XMLNODE_TYPE_TAG)
+ continue;
+ if (!strcmp(node->name, "feature")) {
+ const char *var = xmlnode_get_attrib(node, "var");
+ if (!var)
+ continue;
+ features = g_list_prepend(features, g_strdup(var));
+ }
+ }
+
+ if (features) {
+ g_hash_table_insert(exts->exts, g_strdup(identifier),
+ features);
+ } else
+ purple_debug_warning("jabber", "Caps ext %s had no features.\n",
+ identifier);
}
}
+
+ value->exts = exts;
g_hash_table_replace(capstable, key, value);
+
}
}
xmlnode_free(capsdata);
@@ -244,6 +349,22 @@ static void jabber_caps_store_ext(gpoint
}
#endif
+static void
+exts_to_xmlnode(gconstpointer key, gconstpointer value, gpointer user_data)
+{
+ const char *identifier = key;
+ const GList *features = value, *node;
+ xmlnode *client = user_data, *ext, *feature;
+
+ ext = xmlnode_new_child(client, "ext");
+ xmlnode_set_attrib(ext, "identifier", identifier);
+
+ for (node = features; node; node = node->next) {
+ feature = xmlnode_new_child(ext, "feature");
+ xmlnode_set_attrib(feature, "var", (const gchar *)node->data);
+ }
+}
+
static void jabber_caps_store_client(gpointer key, gpointer value, gpointer user_data) {
JabberCapsKey *clientinfo = key;
JabberCapsClientInfo *props = value;
@@ -253,7 +374,8 @@ static void jabber_caps_store_client(gpo
xmlnode_set_attrib(client, "node", clientinfo->node);
xmlnode_set_attrib(client, "ver", clientinfo->ver);
- xmlnode_set_attrib(client, "hash", clientinfo->hash);
+ if (clientinfo->hash)
+ xmlnode_set_attrib(client, "hash", clientinfo->hash);
for(iter = props->identities; iter; iter = g_list_next(iter)) {
JabberIdentity *id = iter->data;
xmlnode *identity = xmlnode_new_child(client, "identity");
@@ -272,12 +394,19 @@ static void jabber_caps_store_client(gpo
}
for(iter = props->forms; iter; iter = g_list_next(iter)) {
+ /* FIXME: See #7814 */
xmlnode *xdata = iter->data;
xmlnode_insert_child(client, xmlnode_copy(xdata));
}
+
+ /* TODO: Ideally, only save this once-per-node... */
+ if (props->exts)
+ g_hash_table_foreach(props->exts->exts, (GHFunc)exts_to_xmlnode, client);
}
-static void jabber_caps_store(void) {
+static gboolean
+do_jabber_caps_store(gpointer data)
+{
char *str;
int length = 0;
xmlnode *root = xmlnode_new("capabilities");
@@ -286,8 +415,18 @@ static void jabber_caps_store(void) {
xmlnode_free(root);
purple_util_write_data_to_file(JABBER_CAPS_FILENAME, str, length);
g_free(str);
+
+ save_timer = 0;
+ return FALSE;
}
+static void
+schedule_caps_save(void)
+{
+ if (save_timer == 0)
+ save_timer = purple_timeout_add_seconds(5, do_jabber_caps_store, NULL);
+}
+
#if 0
/* this function assumes that all information is available locally */
static JabberCapsClientInfo *jabber_caps_collect_info(const char *node, const char *ver, GList *ext) {
@@ -354,6 +493,8 @@ typedef struct _jabber_caps_cbplususerda
#endif
typedef struct _jabber_caps_cbplususerdata {
+ guint ref;
+
jabber_caps_get_info_cb cb;
gpointer cb_data;
@@ -361,11 +502,48 @@ typedef struct _jabber_caps_cbplususerda
char *node;
char *ver;
char *hash;
-#if 0
- unsigned extOutstanding;
-#endif
+
+ JabberCapsClientInfo *info;
+
+ GList *exts;
+ guint extOutstanding;
+ JabberCapsNodeExts *node_exts;
} jabber_caps_cbplususerdata;
+static jabber_caps_cbplususerdata*
+cbplususerdata_ref(jabber_caps_cbplususerdata *data)
+{
+ g_return_val_if_fail(data != NULL, NULL);
+
+ ++data->ref;
+ return data;
+}
+
+static void
+cbplususerdata_unref(jabber_caps_cbplususerdata *data)
+{
+ if (data == NULL)
+ return;
+
+ g_return_if_fail(data->ref != 0);
+
+ if (--data->ref > 0)
+ return;
+
+ g_free(data->who);
+ g_free(data->node);
+ g_free(data->ver);
+ g_free(data->hash);
+
+ if (data->info)
+ jabber_caps_client_info_unref(data->info);
+ if (data->exts)
+ free_string_glist(data->exts);
+ if (data->node_exts)
+ jabber_caps_node_exts_unref(data->node_exts);
+ g_free(data);
+}
+
#if 0
typedef struct jabber_ext_userdata {
jabber_caps_cbplususerdata *userdata;
@@ -453,38 +631,68 @@ static void
#endif
static void
+jabber_caps_get_info_complete(jabber_caps_cbplususerdata *userdata)
+{
+ userdata->cb(userdata->info, userdata->exts, userdata->cb_data);
+ userdata->info = NULL;
+ userdata->exts = NULL;
+
+ if (userdata->ref != 1)
+ purple_debug_warning("jabber", "Lost a reference to caps cbdata: %d\n",
+ userdata->ref);
+}
+
+static void
jabber_caps_client_iqcb(JabberStream *js, xmlnode *packet, gpointer data)
{
xmlnode *query = xmlnode_get_child_with_namespace(packet, "query",
"http://jabber.org/protocol/disco#info");
jabber_caps_cbplususerdata *userdata = data;
JabberCapsClientInfo *info = NULL, *value;
- gchar *hash = NULL;
const char *type = xmlnode_get_attrib(packet, "type");
JabberCapsKey key;
if (!query || !strcmp(type, "error")) {
- userdata->cb(NULL, userdata->cb_data);
- goto out;
+ /* Any outstanding exts will be dealt with via ref-counting */
+ userdata->cb(NULL, NULL, userdata->cb_data);
+ cbplususerdata_unref(userdata);
+ return;
}
/* check hash */
info = jabber_caps_parse_client_info(query);
- if (!strcmp(userdata->hash, "sha-1")) {
- hash = jabber_caps_calculate_hash(info, "sha1");
- } else if (!strcmp(userdata->hash, "md5")) {
- hash = jabber_caps_calculate_hash(info, "md5");
+ /* Only validate if these are v1.5 capabilities */
+ if (userdata->hash) {
+ gchar *hash = NULL;
+ if (!strcmp(userdata->hash, "sha-1")) {
+ hash = jabber_caps_calculate_hash(info, "sha1");
+ } else if (!strcmp(userdata->hash, "md5")) {
+ hash = jabber_caps_calculate_hash(info, "md5");
+ }
+
+ if (!hash || strcmp(hash, userdata->ver)) {
+ purple_debug_warning("jabber", "Could not validate caps info from %s\n",
+ xmlnode_get_attrib(packet, "from"));
+
+ userdata->cb(NULL, NULL, userdata->cb_data);
+ jabber_caps_client_info_unref(info);
+ cbplususerdata_unref(userdata);
+ g_free(hash);
+ return;
+ }
+
+ g_free(hash);
}
- if (!hash || strcmp(hash, userdata->ver)) {
- purple_debug_warning("jabber", "Could not validate caps info from %s\n",
- xmlnode_get_attrib(packet, "from"));
+ if (!userdata->hash && userdata->node_exts) {
+ /* If the ClientInfo doesn't have information about the exts, give them
+ * ours (along with our ref) */
+ info->exts = userdata->node_exts;
+ userdata->node_exts = NULL;
+ }
- userdata->cb(NULL, userdata->cb_data);
- jabber_caps_client_info_unref(info);
- goto out;
- }
+ userdata->info = info;
key.node = userdata->node;
key.ver = userdata->ver;
@@ -503,51 +711,109 @@ jabber_caps_client_iqcb(JabberStream *js
n_key->hash = userdata->hash;
userdata->node = userdata->ver = userdata->hash = NULL;
- g_hash_table_insert(capstable, n_key, info);
- jabber_caps_store();
+ /* The capstable gets a reference */
+ g_hash_table_insert(capstable, n_key, jabber_caps_client_info_ref(info));
+ schedule_caps_save();
}
- userdata->cb(info, userdata->cb_data);
+ if (userdata->extOutstanding == 0)
+ jabber_caps_get_info_complete(userdata);
-out:
- g_free(userdata->who);
- g_free(userdata->node);
- g_free(userdata->ver);
- g_free(userdata->hash);
+ cbplususerdata_unref(userdata);
+}
+
+typedef struct {
+ const char *name;
+ jabber_caps_cbplususerdata *data;
+} ext_iq_data;
+
+static void
+jabber_caps_ext_iqcb(JabberStream *js, xmlnode *packet, gpointer data)
+{
+ xmlnode *query = xmlnode_get_child_with_namespace(packet, "query",
+ "http://jabber.org/protocol/disco#info");
+ xmlnode *child;
+ ext_iq_data *userdata = data;
+ const char *type = xmlnode_get_attrib(packet, "type");
+ GList *features = NULL;
+ JabberCapsNodeExts *node_exts;
+
+ if (!query || !strcmp(type, "error")) {
+ cbplususerdata_unref(userdata->data);
+ g_free(userdata);
+ return;
+ }
+
+ /* So, we decrement this after checking for an error, which means that
+ * if there *is* an error, we'll never call the callback passed to
+ * jabber_caps_get_info. We will still free all of our data, though.
+ */
+ --userdata->data->extOutstanding;
+
+ for (child = xmlnode_get_child(query, "feature"); child;
+ child = xmlnode_get_next_twin(child)) {
+ const char *var = xmlnode_get_attrib(child, "var");
+ if (var)
+ features = g_list_prepend(features, g_strdup(var));
+ }
+
+ node_exts = (userdata->data->info ? userdata->data->info->exts :
+ userdata->data->node_exts);
+ g_hash_table_insert(node_exts->exts, g_strdup(userdata->name), features);
+ schedule_caps_save();
+
+ /* Are we done? */
+ if (userdata->data->info && userdata->data->extOutstanding == 0)
+ jabber_caps_get_info_complete(userdata->data);
+
+ cbplususerdata_unref(userdata->data);
g_free(userdata);
- g_free(hash);
}
void jabber_caps_get_info(JabberStream *js, const char *who, const char *node,
- const char *ver, const char *hash, jabber_caps_get_info_cb cb,
- gpointer user_data)
+ const char *ver, const char *hash, const char *ext,
+ jabber_caps_get_info_cb cb, gpointer user_data)
{
JabberCapsClientInfo *info;
JabberCapsKey key;
+ jabber_caps_cbplususerdata *userdata;
+ if (ext && *ext && hash)
+ purple_debug_warning("jabber", "Ignoring exts in new-style caps from %s\n",
+ who);
+
/* Using this in a read-only fashion, so the cast is OK */
key.node = (char *)node;
key.ver = (char *)ver;
key.hash = (char *)hash;
info = g_hash_table_lookup(capstable, &key);
+ if (info && hash) {
+ /* v1.5 - We already have all the information we care about */
+ cb(jabber_caps_client_info_ref(info), NULL, user_data);
+ return;
+ }
+
+ userdata = g_new0(jabber_caps_cbplususerdata, 1);
+ /* This ref is given to fetching the basic node#ver info if we need it
+ * or unrefed at the bottom of this function */
+ cbplususerdata_ref(userdata);
+ userdata->cb = cb;
+ userdata->cb_data = user_data;
+ userdata->who = g_strdup(who);
+ userdata->node = g_strdup(node);
+ userdata->ver = g_strdup(ver);
+ userdata->hash = g_strdup(hash);
+
if (info) {
- jabber_caps_client_info_ref(info);
- cb(info, user_data);
+ userdata->info = jabber_caps_client_info_ref(info);
} else {
- jabber_caps_cbplususerdata *userdata;
+ /* If we don't have the basic information about the client, we need
+ * to fetch it. */
JabberIq *iq;
xmlnode *query;
char *nodever;
- userdata = g_new0(jabber_caps_cbplususerdata, 1);
- userdata->cb = cb;
- userdata->cb_data = user_data;
- userdata->who = g_strdup(who);
- userdata->node = g_strdup(node);
- userdata->ver = g_strdup(ver);
- userdata->hash = g_strdup(hash);
-
iq = jabber_iq_new_query(js, JABBER_IQ_GET,
"http://jabber.org/protocol/disco#info");
query = xmlnode_get_child_with_namespace(iq->node, "query",
@@ -557,9 +823,65 @@ void jabber_caps_get_info(JabberStream *
g_free(nodever);
xmlnode_set_attrib(iq->node, "to", who);
- jabber_iq_set_callback(iq,jabber_caps_client_iqcb,userdata);
+ jabber_iq_set_callback(iq, jabber_caps_client_iqcb, userdata);
jabber_iq_send(iq);
}
+
+ /* Are there any exts that we don't recognize? */
+ if (ext && *ext && !hash) {
+ JabberCapsNodeExts *node_exts;
+ gchar **splat = g_strsplit(ext, " ", 0);
+ int i;
+
+ if (info) {
+ if (info->exts)
+ node_exts = info->exts;
+ else
+ node_exts = info->exts = jabber_caps_find_exts_by_node(node);
+ } else
+ /* We'll put it in later once we have the client info */
+ node_exts = userdata->node_exts = jabber_caps_find_exts_by_node(node);
+
+ for (i = 0; splat[i]; ++i) {
+ userdata->exts = g_list_prepend(userdata->exts, splat[i]);
+ /* Look it up if we don't already know what it means */
+ if (!g_hash_table_lookup(node_exts->exts, splat[i])) {
+ JabberIq *iq;
+ xmlnode *query;
+ char *nodeext;
+ ext_iq_data *cbdata = g_new(ext_iq_data, 1);
+
+ cbdata->name = splat[i];
+ cbdata->data = cbplususerdata_ref(userdata);
+
+ 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");
+ nodeext = g_strdup_printf("%s#%s", node, splat[i]);
+ xmlnode_set_attrib(query, "node", nodeext);
+ g_free(nodeext);
+ xmlnode_set_attrib(iq->node, "to", who);
+
+ jabber_iq_set_callback(iq, jabber_caps_ext_iqcb, cbdata);
+ jabber_iq_send(iq);
+
+ ++userdata->extOutstanding;
+ }
+ splat[i] = NULL;
+ }
+ /* All the strings are now part of the GList, so don't need
+ * g_strfreev. */
+ g_free(splat);
+ }
+
+ if (userdata->info && userdata->extOutstanding == 0) {
+ jabber_caps_get_info_complete(userdata);
+ cbplususerdata_unref(userdata);
+ }
+
+ return;
+
#if 0
/* The above check was originally simply "if (!info)", so this was executed
* on info being non-null */
@@ -700,9 +1022,8 @@ static JabberCapsClientInfo *jabber_caps
} else if (!strcmp(child->name, "feature")) {
/* parse feature */
const char *var = xmlnode_get_attrib(child, "var");
- if(!var)
- continue;
- info->features = g_list_append(info->features, g_strdup(var));
+ if (var)
+ info->features = g_list_prepend(info->features, g_strdup(var));
} else if (!strcmp(child->name, "x")) {
if (child->xmlns && !strcmp(child->xmlns, "jabber:x:data")) {
/* x-data form */
============================================================
--- libpurple/protocols/jabber/caps.h d3e913179bd9674ddafb54f4d3faeae41a2bc1fe
+++ libpurple/protocols/jabber/caps.h 70955b8748e8db1d781874b5fc4beb78f6091fdd
@@ -28,26 +28,47 @@ typedef struct _JabberCapsClientInfo Jab
/* Implementation of XEP-0115 - Entity Capabilities */
+typedef struct _JabberCapsNodeExts JabberCapsNodeExts;
+
struct _JabberCapsClientInfo {
GList *identities; /* JabberIdentity */
GList *features; /* char * */
GList *forms; /* xmlnode * */
+ JabberCapsNodeExts *exts;
guint ref;
};
+/*
+ * This stores a set of exts "known" for a specific node (which indicates
+ * a specific client -- for reference, Pidgin, Finch, Meebo, et al share one
+ * node.) In XEP-0115 v1.3, exts are used for features that may or may not be
+ * present at a given time (PEP things, buzz might be disabled, etc).
+ *
+ * This structure is shared among all JabberCapsClientInfo instances matching
+ * a specific node (if the capstable key->hash == NULL, which indicates that
+ * the ClientInfo is using v1.3 caps as opposed to v1.5 caps).
+ *
+ * It's only exposed so that jabber_resource_has_capability can use it.
+ * Everyone else, STAY AWAY!
+ */
+struct _JabberCapsNodeExts {
+ guint ref;
+ GHashTable *exts; /* char *ext_name -> GList *features */
+};
+
/**
* Adjust the refcount for JabberCapsClientInfo. When the refcount reaches
* 0, the data will be destroyed.
*/
void jabber_caps_client_info_unref(JabberCapsClientInfo *info);
-void jabber_caps_client_info_ref(JabberCapsClientInfo *info);
+JabberCapsClientInfo* jabber_caps_client_info_ref(JabberCapsClientInfo *info);
#if 0
typedef struct _JabberCapsClientInfo JabberCapsValueExt;
#endif
-typedef void (*jabber_caps_get_info_cb)(JabberCapsClientInfo *info, gpointer user_data);
+typedef void (*jabber_caps_get_info_cb)(JabberCapsClientInfo *info, GList *exts, gpointer user_data);
void jabber_caps_init(void);
void jabber_caps_uninit(void);
@@ -57,10 +78,14 @@ void jabber_caps_destroy_key(gpointer va
/**
* Main entity capabilites function to get the capabilities of a contact.
*
- * The callback will be called synchronously if we already have the capabilities for
- * the specified (node,ver,hash).
+ * The callback will be called synchronously if we already have the
+ * capabilities for the specified (node,ver,hash) (and, if exts are specified,
+ * if we know what each means)
*/
-void jabber_caps_get_info(JabberStream *js, const char *who, const char *node, const char *ver, const char *hash, jabber_caps_get_info_cb cb, gpointer user_data);
+void jabber_caps_get_info(JabberStream *js, const char *who, const char *node,
+ const char *ver, const char *hash,
+ const char *ext, jabber_caps_get_info_cb cb,
+ gpointer user_data);
/**
* Takes a JabberCapsClientInfo pointer and returns the caps hash according to
@@ -68,8 +93,7 @@ void jabber_caps_get_info(JabberStream *
*
* @param info A JabberCapsClientInfo pointer.
* @param hash Hash cipher to be used. Either sha-1 or md5.
- * @return The base64 encoded SHA-1 hash; needs to be freed if not needed
- * any furthermore.
+ * @return The base64 encoded SHA-1 hash; must be freed by caller
*/
gchar *jabber_caps_calculate_hash(JabberCapsClientInfo *info, const char *hash);
============================================================
--- libpurple/protocols/jabber/jabber.c 731fa7238927b1c6eb72593b0caf6e03543852f0
+++ libpurple/protocols/jabber/jabber.c e3655108e7b9a6983260a097625a251b72ff9614
@@ -2425,7 +2425,7 @@ static gboolean _jabber_send_buzz(Jabber
}
/* Is this message sufficiently useful to not just fold it in with the tail error condition below? */
- if(!jbr->caps) {
+ if(!jbr->caps.info) {
*error = g_strdup_printf(_("Unable to buzz, because there is nothing known about user %s."), username);
return FALSE;
}
============================================================
--- libpurple/protocols/jabber/presence.c a161dc405a07a57da27e49599a6ca6c6c340bf81
+++ libpurple/protocols/jabber/presence.c 6c064fcf8285b104ee1223b6652edcd71efe509a
@@ -382,7 +382,9 @@ typedef struct _JabberPresenceCapabiliti
char *from;
} JabberPresenceCapabilities;
-static void jabber_presence_set_capabilities(JabberCapsClientInfo *info, JabberPresenceCapabilities *userdata)
+static void
+jabber_presence_set_capabilities(JabberCapsClientInfo *info, GList *exts,
+ JabberPresenceCapabilities *userdata)
{
JabberBuddyResource *jbr;
char *resource = g_utf8_strrchr(userdata->from, -1, '/');
@@ -392,12 +394,23 @@ static void jabber_presence_set_capabili
if (!jbr) {
g_free(userdata->from);
g_free(userdata);
+ jabber_caps_client_info_unref(info);
+ if (exts) {
+ g_list_foreach(exts, (GFunc)g_free, NULL);
+ g_list_free(exts);
+ }
return;
}
- jabber_caps_client_info_unref(jbr->caps);
- jbr->caps = info;
+ jabber_caps_client_info_unref(jbr->caps.info);
+ if (jbr->caps.exts) {
+ g_list_foreach(jbr->caps.exts, (GFunc)g_free, NULL);
+ g_list_free(jbr->caps.exts);
+ }
+ jbr->caps.info = info;
+ jbr->caps.exts = exts;
+
if (jabber_resource_has_capability(jbr, "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");
@@ -751,13 +764,16 @@ void jabber_presence_parse(JabberStream
const char *node = xmlnode_get_attrib(caps,"node");
const char *ver = xmlnode_get_attrib(caps,"ver");
const char *hash = xmlnode_get_attrib(caps,"hash");
-
- if(node && ver && hash) {
+ const char *ext = xmlnode_get_attrib(caps,"ext");
+
+ /* v1.3 uses: node, ver, and optionally ext.
+ * v1.5 uses: node, ver, and hash. */
+ if (node && ver) {
JabberPresenceCapabilities *userdata = g_new0(JabberPresenceCapabilities, 1);
userdata->js = js;
userdata->jb = jb;
userdata->from = g_strdup(from);
- jabber_caps_get_info(js, from, node, ver, hash,
+ jabber_caps_get_info(js, from, node, ver, hash, ext,
(jabber_caps_get_info_cb)jabber_presence_set_capabilities,
userdata);
}
More information about the Commits
mailing list