# HG changeset patch # User Hermitifier # Date 1317650434 -7200 # Node ID 51f032d5ca2218b308a2fcd02dda3cc62fa0f37f # Parent 41667bc0288316689f2373f341c6af2ad73046e3 Add support for XEP-0115 Entity Capabilities, with offline cache diff -r 41667bc02883 -r 51f032d5ca22 mcabber/mcabber/api.h --- a/mcabber/mcabber/api.h Sun Jul 24 13:30:47 2011 +0200 +++ b/mcabber/mcabber/api.h Mon Oct 03 16:00:34 2011 +0200 @@ -3,8 +3,8 @@ #include // For MCABBER_BRANCH -#define MCABBER_API_VERSION 20 -#define MCABBER_API_MIN 19 +#define MCABBER_API_VERSION 21 +#define MCABBER_API_MIN 21 #define MCABBER_BRANCH_DEV 1 diff -r 41667bc02883 -r 51f032d5ca22 mcabber/mcabber/caps.c --- a/mcabber/mcabber/caps.c Sun Jul 24 13:30:47 2011 +0200 +++ b/mcabber/mcabber/caps.c Mon Oct 03 16:00:34 2011 +0200 @@ -20,12 +20,28 @@ */ #include +#include +#include +#include +#include + +#include "settings.h" +#include "utils.h" typedef struct { char *category; + char *type; char *name; - char *type; +} identity; + +typedef struct { + GHashTable *fields; +} dataform; + +typedef struct { + GHashTable *identities; GHashTable *features; + GHashTable *forms; } caps; static GHashTable *caps_cache = NULL; @@ -33,13 +49,34 @@ 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->identities); g_hash_table_destroy(c->features); + g_hash_table_destroy(c->forms); g_free(c); } +void identity_destroy(gpointer data) +{ + identity *i = data; + g_free(i->category); + g_free(i->type); + g_free(i->name); + g_free(i); +} + +void form_destroy(gpointer data) +{ + dataform *f = data; + g_hash_table_destroy(f->fields); + g_free(f); +} + +void field_destroy(gpointer data) +{ + GList *v = data; + g_list_free_full(v, g_free); +} + void caps_init(void) { if (!caps_cache) @@ -55,18 +92,76 @@ } } -void caps_add(char *hash) +void caps_add(const 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); + c->identities = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, identity_destroy); + c->forms = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, form_destroy); g_hash_table_insert(caps_cache, g_strdup(hash), c); } -int caps_has_hash(const char *hash) +void caps_remove(const char *hash) +{ + if (!hash) + return; + g_hash_table_remove(caps_cache, hash); +} + +void caps_move_to_local(char *hash, char *bjid) +{ + char *orig_hash; + caps *c = NULL; + if (!hash || !bjid) + return; + g_hash_table_lookup_extended(caps_cache, hash, (gpointer*)&orig_hash, (gpointer*)&c); + if (c) { + g_hash_table_steal(caps_cache, hash); + g_free(orig_hash); + g_hash_table_replace(caps_cache, g_strdup_printf("%s/#%s", bjid, hash), c); + // solidus is guaranteed to never appear in bare jid + // hash will not appear in base64 encoded hash + // sequence "/#" is deterministic separator, and allows to identify local cache entry + } +} + +int caps_has_hash(const char *hash, const char *bjid) { - return (hash != NULL && (g_hash_table_lookup(caps_cache, hash) != NULL)); + caps *c = NULL; + if (!hash) + return 0; + c = g_hash_table_lookup(caps_cache, hash); + if (!c && bjid) { + char *key = g_strdup_printf("%s/#%s", bjid, hash); + c = g_hash_table_lookup(caps_cache, key); + g_free(key); + } + return (c != NULL); +} + +void caps_add_identity(const char *hash, + const char *category, + const char *name, + const char *type, + const char *lang) +{ + caps *c; + if (!hash || !category || !type) + return; + if (!lang) + lang = ""; + + c = g_hash_table_lookup(caps_cache, hash); + if (c) { + identity *i = g_new0(identity, 1); + + i->category = g_strdup(category); + i->name = g_strdup(name); + i->type = g_strdup(type); + g_hash_table_replace(c->identities, g_strdup(lang), i); + } } void caps_set_identity(char *hash, @@ -74,19 +169,56 @@ const char *name, const char *type) { + caps_add_identity(hash, category, name, type, NULL); +} + +void caps_add_dataform(const char *hash, const char *formtype) +{ caps *c; - if (!hash || !category || !type) + if (!formtype) 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); + dataform *d = g_new0(dataform, 1); + char *f = g_strdup(formtype); + + d->fields = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, field_destroy); + g_hash_table_replace(c->forms, f, d); } } -void caps_add_feature(char *hash, const char *feature) +gint _strcmp_sort(gconstpointer a, gconstpointer b) +{ + return g_strcmp0(a, b); +} + +void caps_add_dataform_field(const char *hash, const char *formtype, + const char *field, const char *value) +{ + caps *c; + if (!formtype || !field || !value) + return; + c = g_hash_table_lookup(caps_cache, hash); + if (c) { + dataform *d; + d = g_hash_table_lookup(c->forms, formtype); + if (d) { + gpointer key, val; + char *f; + GList *v = NULL; + if (g_hash_table_lookup_extended(d->fields, field, &key, &val)) { + g_hash_table_steal(d->fields, field); + g_free(key); + v = val; + } + f = g_strdup(field); + v = g_list_insert_sorted(v, g_strdup(value), _strcmp_sort); + g_hash_table_replace(d->fields, f, v); + } + } +} + +void caps_add_feature(const char *hash, const char *feature) { caps *c; if (!hash || !feature) @@ -98,12 +230,17 @@ } } -int caps_has_feature(char *hash, char *feature) +int caps_has_feature(const char *hash, char *feature, char *bjid) { - caps *c; + caps *c = NULL; if (!hash || !feature) return 0; c = g_hash_table_lookup(caps_cache, hash); + if (!c && bjid) { + char *key = g_strdup_printf("%s/#%s", bjid, hash); + c = g_hash_table_lookup(caps_cache, key); + g_free(key); + } if (c) return (g_hash_table_lookup(c->features, feature) != NULL); return 0; @@ -129,16 +266,10 @@ 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; + GList *features, *langs; GChecksum *sha1; guint8 digest[20]; gsize digest_size = 20; @@ -148,10 +279,22 @@ 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 ? c->name : ""); - g_checksum_update(sha1, (guchar*)identity, -1); - g_free(identity); + + langs = g_hash_table_get_keys(c->identities); + langs = g_list_sort(langs, _strcmp_sort); + { + identity *i; + GList *lang; + char *identity_S; + for (lang=langs; lang; lang=lang->next) { + i = g_hash_table_lookup(c->identities, lang->data); + identity_S = g_strdup_printf("%s/%s/%s/%s<", i->category, i->type, + (char *)lang->data, i->name ? i->name : ""); + g_checksum_update(sha1, (guchar *)identity_S, -1); + g_free(identity_S); + } + } + g_list_free(langs); features = g_hash_table_get_values(c->features); features = g_list_sort(features, _strcmp_sort); @@ -176,4 +319,301 @@ return hash; } +gboolean caps_verify(const char *hash, char *function) +{ + GList *features, *langs, *forms; + GChecksum *checksum; + guint8 digest[20]; + gsize digest_size = 20; + gchar *local_hash; + gboolean match = FALSE; + caps *c = g_hash_table_lookup(caps_cache, hash); + + if (!g_strcmp0(function, "sha-1")) { + checksum = g_checksum_new(G_CHECKSUM_SHA1); + } else if (!g_strcmp0(function, "md5")) { + checksum = g_checksum_new(G_CHECKSUM_MD5); + digest_size = 16; + } else + return FALSE; + + langs = g_hash_table_get_keys(c->identities); + langs = g_list_sort(langs, _strcmp_sort); + { + identity *i; + GList *lang; + char *identity_S; + for (lang=langs; lang; lang=lang->next) { + i = g_hash_table_lookup(c->identities, lang->data); + identity_S = g_strdup_printf("%s/%s/%s/%s<", i->category, i->type, + (char *)lang->data, i->name ? i->name : ""); + g_checksum_update(checksum, (guchar *)identity_S, -1); + g_free(identity_S); + } + } + g_list_free(langs); + + features = g_hash_table_get_values(c->features); + features = g_list_sort(features, _strcmp_sort); + { + GList *feature; + for (feature=features; feature; feature=feature->next) { + g_checksum_update(checksum, feature->data, -1); + g_checksum_update(checksum, (guchar *)"<", -1); + } + } + g_list_free(features); + + forms = g_hash_table_get_keys(c->forms); + forms = g_list_sort(forms, _strcmp_sort); + { + dataform *d; + GList *form, *fields; + for (form=forms; form; form=form->next) { + d = g_hash_table_lookup(c->forms, form->data); + g_checksum_update(checksum, form->data, -1); + g_checksum_update(checksum, (guchar *)"<", -1); + fields = g_hash_table_get_keys(d->fields); + fields = g_list_sort(fields, _strcmp_sort); + { + GList *field; + GList *values; + for (field=fields; field; field=field->next) { + g_checksum_update(checksum, field->data, -1); + g_checksum_update(checksum, (guchar *)"<", -1); + values = g_hash_table_lookup(d->fields, field->data); + { + GList *value; + for (value=values; value; value=value->next) { + g_checksum_update(checksum, value->data, -1); + g_checksum_update(checksum, (guchar *)"<", -1); + } + } + } + } + g_list_free(fields); + } + } + g_list_free(forms); + + g_checksum_get_digest(checksum, digest, &digest_size); + local_hash = g_base64_encode(digest, digest_size); + g_checksum_free(checksum); + + match = !g_strcmp0(hash, local_hash); + + g_free(local_hash); + return match; +} + +static gchar* caps_get_filename(const char* hash) +{ + gchar *hash_fs = g_strdup (hash); + gchar *dir = (gchar *) settings_opt_get ("caps_directory"); + gchar *file = NULL; + + if (!dir) + goto caps_filename_return; + + { + const gchar *valid_fs = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+="; + g_strcanon(hash_fs, valid_fs, '-'); + } + + dir = expand_filename (dir); + file = g_strdup_printf ("%s/%s.ini", dir, hash_fs); + g_free(dir); + +caps_filename_return: + g_free(hash_fs); + return file; +} + +void caps_copy_to_persistent(const char* hash, char* xml) +{ + gchar *file; + GList *features, *langs, *forms; + GKeyFile *key_file; + caps *c; + int fd; + + g_free (xml); + + c = g_hash_table_lookup (caps_cache, hash); + if (!c) + goto caps_copy_return; + + file = caps_get_filename (hash); + if (!file) + goto caps_copy_return; + + fd = open (file, O_WRONLY|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR); + if (fd == -1) + goto caps_copy_exists; + + key_file = g_key_file_new (); + + langs = g_hash_table_get_keys (c->identities); + { + identity *i; + GList *lang; + gchar *group; + for (lang=langs; lang; lang=lang->next) { + i = g_hash_table_lookup (c->identities, lang->data); + group = g_strdup_printf("identity_%s", (gchar *)lang->data); + g_key_file_set_string (key_file, group, "category", i->category); + g_key_file_set_string (key_file, group, "type", i->type); + g_key_file_set_string (key_file, group, "name", i->name); + g_free (group); + } + } + g_list_free (langs); + + features = g_hash_table_get_values (c->features); + { + GList *feature; + gchar **string_list; + gint i; + + i = g_list_length (features); + string_list = g_new (gchar*, i + 1); + i = 0; + for (feature=features; feature; feature=feature->next) { + string_list[i] = g_strdup(feature->data); + ++i; + } + string_list[i] = NULL; + + g_key_file_set_string_list (key_file, "features", "features", + (const gchar**)string_list, i); + g_strfreev (string_list); + } + g_list_free (features); + + forms = g_hash_table_get_keys(c->forms); + { + dataform *d; + GList *form, *fields; + gchar *group; + for (form=forms; form; form=form->next) { + d = g_hash_table_lookup (c->forms, form->data); + group = g_strdup_printf ("form_%s", (gchar *)form->data); + fields = g_hash_table_get_keys(d->fields); + { + GList *field; + GList *values; + for (field=fields; field; field=field->next) { + values = g_hash_table_lookup (d->fields, field->data); + { + GList *value; + gchar **string_list; + gint i; + i = g_list_length (values); + string_list = g_new (gchar*, i + 1); + i = 0; + for (value=values; value; value=value->next) { + string_list[i] = g_strdup(value->data); + ++i; + } + string_list[i] = NULL; + + g_key_file_set_string_list (key_file, group, field->data, + (const gchar**)string_list, i); + + g_strfreev (string_list); + } + } + } + g_list_free(fields); + g_free (group); + } + } + g_list_free (forms); + + { + gchar *data; + gsize length; + data = g_key_file_to_data (key_file, &length, NULL); + write (fd, data, length); + g_free(data); + close (fd); + } + + g_key_file_free(key_file); +caps_copy_exists: + g_free(file); +caps_copy_return: + return; +} + +gboolean caps_restore_from_persistent (const char* hash) +{ + gchar *file; + GKeyFile *key_file; + gchar **groups, **group; + gboolean restored = FALSE; + + file = caps_get_filename (hash); + if (!file) + goto caps_restore_no_file; + + key_file = g_key_file_new (); + if (!g_key_file_load_from_file (key_file, file, G_KEY_FILE_NONE, NULL)) + goto caps_restore_bad_file; + + caps_add(hash); + + groups = g_key_file_get_groups (key_file, NULL); + for (group = groups; *group; ++group) { + if (!g_strcmp0(*group, "features")) { + gchar **features, **feature; + features = g_key_file_get_string_list (key_file, *group, "features", + NULL, NULL); + for (feature = features; *feature; ++feature) { + caps_add_feature(hash, *feature); + } + + g_strfreev (features); + } else if (g_str_has_prefix (*group, "identity_")) { + gchar *category, *type, *name, *lang; + + category = g_key_file_get_string(key_file, *group, "category", NULL); + type = g_key_file_get_string(key_file, *group, "type", NULL); + name = g_key_file_get_string(key_file, *group, "name", NULL); + lang = *group + 9; /* "identity_" */ + + caps_add_identity(hash, category, name, type, lang); + g_free(category); + g_free(type); + g_free(name); + } else if (g_str_has_prefix (*group, "form_")) { + gchar *formtype; + gchar **fields, **field; + formtype = *group + 5; /* "form_" */ + caps_add_dataform (hash, formtype); + + fields = g_key_file_get_keys(key_file, *group, NULL, NULL); + for (field = fields; *field; ++field) { + gchar **values, **value; + values = g_key_file_get_string_list (key_file, *group, *field, + NULL, NULL); + for (value = values; *value; ++value) { + caps_add_dataform_field (hash, formtype, *field, *value); + } + g_strfreev (values); + } + g_strfreev (fields); + } + } + g_strfreev(groups); + restored = TRUE; + +caps_restore_bad_file: + g_key_file_free (key_file); + g_free (file); +caps_restore_no_file: + return restored; +} + /* vim: set expandtab cindent cinoptions=>2\:2(0 sw=2 ts=2: For Vim users... */ diff -r 41667bc02883 -r 51f032d5ca22 mcabber/mcabber/caps.h --- a/mcabber/mcabber/caps.h Sun Jul 24 13:30:47 2011 +0200 +++ b/mcabber/mcabber/caps.h Mon Oct 03 16:00:34 2011 +0200 @@ -5,17 +5,30 @@ void caps_init(void); void caps_free(void); -void caps_add(char *hash); -int caps_has_hash(const char *hash); +void caps_add(const char *hash); +void caps_remove(const char *hash); +void caps_move_to_local(const char *hash, char *bjid); +int caps_has_hash(const char *hash, const char *bjid); +void caps_add_identity(const char *hash, + const char *category, + const char *name, + const char *type, + const char *lang); void caps_set_identity(char *hash, const char *category, const char *name, const char *type); +void caps_add_dataform(const char *hash, const char *formtype); +void caps_add_dataform_field(const char *hash, const char *formtype, + const char *field, const char *value); void caps_add_feature(char *hash, const char *feature); -int caps_has_feature(char *hash, char *feature); +int caps_has_feature(char *hash, char *feature, char *bjid); void caps_foreach_feature(const char *hash, GFunc func, gpointer user_data); char *caps_generate(void); +gboolean caps_verify(const char *hash, char *function); +void caps_copy_to_persistent(const char *hash, char *xml); +gboolean caps_restore_from_persistent(const char *hash); #endif /* __MCABBER_CAPS_H__ */ diff -r 41667bc02883 -r 51f032d5ca22 mcabber/mcabber/xmpp.c --- a/mcabber/mcabber/xmpp.c Sun Jul 24 13:30:47 2011 +0200 +++ b/mcabber/mcabber/xmpp.c Mon Oct 03 16:00:34 2011 +0200 @@ -318,8 +318,9 @@ #ifdef HAVE_LIBOTR int otr_msg = 0; #endif + char *barejid; #if defined HAVE_GPGME || defined XEP0022 || defined XEP0085 - char *rname, *barejid; + char *rname; GSList *sl_buddy; #endif #if defined XEP0022 || defined XEP0085 @@ -349,10 +350,10 @@ subtype = LM_MESSAGE_SUB_TYPE_CHAT; } + barejid = jidtodisp(fjid); #if defined HAVE_GPGME || defined HAVE_LIBOTR || \ defined XEP0022 || defined XEP0085 rname = strchr(fjid, JID_RESOURCE_SEPARATOR); - barejid = jidtodisp(fjid); sl_buddy = roster_find(barejid, jidsearch, ROSTER_TYPE_USER); // If we can get a resource name, we use it. Else we use NULL, @@ -406,7 +407,6 @@ } #endif // HAVE_GPGME - g_free(barejid); #endif // HAVE_GPGME || defined XEP0022 || defined XEP0085 x = lm_message_new_with_sub_type(fjid, LM_MESSAGE_TYPE_MESSAGE, subtype); @@ -428,12 +428,13 @@ // XEP-0184: Message Receipts if (sl_buddy && xep184 && caps_has_feature(buddy_resource_getcaps(sl_buddy->data, rname), - NS_RECEIPTS)) { + NS_RECEIPTS, barejid)) { lm_message_node_set_attribute (lm_message_node_add_child(x->node, "request", NULL), "xmlns", NS_RECEIPTS); *xep184 = lm_message_handler_new(cb_xep184, NULL, NULL); } + g_free(barejid); #if defined XEP0022 || defined XEP0085 // If typing notifications are disabled, we can skip all this stuff... @@ -1326,28 +1327,99 @@ LmMessage *m, gpointer user_data) { char *ver = user_data; + char *hash; + const char *from = lm_message_get_from(m); + char *bjid = jidtodisp(from); LmMessageSubType mstype = lm_message_get_sub_type(m); - caps_add(ver); - if (mstype == LM_MESSAGE_SUB_TYPE_ERROR) { - display_server_error(lm_message_node_get_child(m->node, "error"), - lm_message_get_from(m)); - } else if (mstype == LM_MESSAGE_SUB_TYPE_RESULT) { + hash = strchr(ver, ','); + if (hash) + *hash++ = '\0'; + + if (mstype == LM_MESSAGE_SUB_TYPE_RESULT) { LmMessageNode *info; LmMessageNode *query = lm_message_node_get_child(m->node, "query"); + if (caps_has_hash(ver, bjid)) + goto caps_callback_return; + + 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")); + while (info) { + if (!g_strcmp0(info->name, "identity")) + caps_add_identity(ver, lm_message_node_get_attribute(info, "category"), + lm_message_node_get_attribute(info, "name"), + lm_message_node_get_attribute(info, "type"), + lm_message_node_get_attribute(info, "xml:lang")); + info = info->next; + } + 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; } + + info = lm_message_node_get_child(query, "x"); + { + LmMessageNode *field; + LmMessageNode *value; + const char *formtype, *var; + while (info) { + if (!g_strcmp0(info->name, "x") + && !g_strcmp0(lm_message_node_get_attribute(info, "type"), + "result") + && !g_strcmp0(lm_message_node_get_attribute(info, "xmlns"), + "jabber:x:data")) { + field = lm_message_node_get_child(info, "field"); + formtype = NULL; + while (field) { + if (!g_strcmp0(field->name, "field") + && !g_strcmp0(lm_message_node_get_attribute(field, "var"), + "FORM_TYPE") + && !g_strcmp0(lm_message_node_get_attribute(field, "type"), + "hidden")) { + value = lm_message_node_get_child(field, "value"); + if (value) + formtype = lm_message_node_get_value(value); + } + field = field->next; + } + if (formtype) { + caps_add_dataform(ver, formtype); + field = lm_message_node_get_child(info, "field"); + while (field) { + var = lm_message_node_get_attribute(field, "var"); + if (!g_strcmp0(field->name, "field") + && (g_strcmp0(var, "FORM_TYPE") + || g_strcmp0(lm_message_node_get_attribute(field, "type"), + "hidden"))) { + value = lm_message_node_get_child(field, "value"); + while (value) { + if (!g_strcmp0(value->name, "value")) + caps_add_dataform_field(ver, formtype, var, + lm_message_node_get_value(value)); + value = value->next; + } + } + field = field->next; + } + } + } + info = info->next; + } + } + + if (caps_verify(ver, hash)) + caps_copy_to_persistent(ver, lm_message_node_to_string(query)); + else + caps_move_to_local(ver, bjid); } + +caps_callback_return: + g_free(bjid); g_free(ver); return LM_HANDLER_RESULT_REMOVE_MESSAGE; } @@ -1462,12 +1534,15 @@ caps = lm_message_node_find_xmlns(m->node, NS_CAPS); if (caps && ust != offline) { const char *ver = lm_message_node_get_attribute(caps, "ver"); + const char *hash = lm_message_node_get_attribute(caps, "hash"); GSList *sl_buddy = NULL; - if (!ver) { - scr_LogPrint(LPRINT_LOGNORM, "Error: malformed caps version (%s)", bjid); + if (!hash) { + // No support for legacy format goto handle_presence_return; } + if (!ver || !g_strcmp0(ver, "") || !g_strcmp0(hash, "")) + goto handle_presence_return; if (rname) sl_buddy = roster_find(bjid, jidsearch, ROSTER_TYPE_USER); @@ -1475,7 +1550,7 @@ if (sl_buddy && buddy_getonserverflag(sl_buddy->data)) { buddy_resource_setcaps(sl_buddy->data, rname, ver); - if (!caps_has_hash(ver)) { + if (!caps_has_hash(ver, bjid) && !caps_restore_from_persistent(ver)) { char *node; LmMessageHandler *handler; LmMessage *iq = lm_message_new_with_sub_type(from, LM_MESSAGE_TYPE_IQ, @@ -1489,7 +1564,9 @@ "node", node, NULL); g_free(node); - handler = lm_message_handler_new(cb_caps, g_strdup(ver), NULL); + handler = lm_message_handler_new(cb_caps, + g_strdup_printf("%s,%s",ver,hash), + NULL); lm_connection_send_with_reply(connection, iq, handler, NULL); lm_message_unref(iq); lm_message_handler_unref(handler); diff -r 41667bc02883 -r 51f032d5ca22 mcabber/mcabber/xmpp_helper.c --- a/mcabber/mcabber/xmpp_helper.c Sun Jul 24 13:30:47 2011 +0200 +++ b/mcabber/mcabber/xmpp_helper.c Mon Oct 03 16:00:34 2011 +0200 @@ -224,6 +224,7 @@ caps_add(""); caps_set_identity("", "client", PACKAGE_STRING, "pc"); caps_add_feature("", NS_DISCO_INFO); + caps_add_feature("", NS_CAPS); caps_add_feature("", NS_MUC); // advertise ChatStates only if they aren't disabled if (!settings_opt_get_int("disable_chatstates")) diff -r 41667bc02883 -r 51f032d5ca22 mcabber/mcabber/xmpp_iq.c --- a/mcabber/mcabber/xmpp_iq.c Sun Jul 24 13:30:47 2011 +0200 +++ b/mcabber/mcabber/xmpp_iq.c Mon Oct 03 16:00:34 2011 +0200 @@ -521,14 +521,10 @@ if (entitycaps) caps_foreach_feature(entitycaps, _disco_add_feature_helper, ansquery); - else { + 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_CAPS); - } } LmHandlerResult handle_iq_disco_info(LmMessageHandler *h,