Mercurial > pidgin.yaz
view libpurple/protocols/jabber/caps.c @ 28133:3eef8392a54b
jabber: Add a BOSH send timer (queue up stanzas), fixes connecting to Prosody
This send timer shouldn't strictly be necessary, but Prosody currently
closes the TCP stream after every response (MattJ tells me there's a
pipelining bug that they couldn't fix, so they just disabled it), but in my
tests, the initial purple_ssl_read doesn't catch the EOF/disconnection
(the read returns EAGAIN), so the prpl, while processing the response from
the server, thinks that BOSH connection is still open, and tries to send
to send to it. *Then*, the write input watcher triggers and read returns 0
(EOF). The request that was sent is then lost.
Anyway, I've termed Prosody a fuzzer for BOSH. :-)
author | Paul Aurich <paul@darkrain42.org> |
---|---|
date | Sun, 02 Aug 2009 03:00:00 +0000 |
parents | 3b2a2469ffbf |
children | a40e0b43f57f |
line wrap: on
line source
/* * purple - Jabber Protocol Plugin * * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.com> * * 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 "internal.h" #include "debug.h" #include "caps.h" #include "cipher.h" #include "iq.h" #include "presence.h" #include "util.h" #define JABBER_CAPS_FILENAME "xmpp-caps.xml" typedef struct _JabberDataFormField { gchar *var; GList *values; } JabberDataFormField; static GHashTable *capstable = NULL; /* JabberCapsTuple -> 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. * * @param query A query object. * @return A JabberCapsClientInfo object. */ static JabberCapsClientInfo *jabber_caps_parse_client_info(xmlnode *query); /* 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 JabberCapsTuple *key = data; guint nodehash = g_str_hash(key->node); guint verhash = g_str_hash(key->ver); /* * 'hash' was optional in XEP-0115 v1.4 and 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 JabberCapsTuple *name1 = v1; const JabberCapsTuple *name2 = v2; return g_str_equal(name1->node, name2->node) && g_str_equal(name1->ver, name2->ver) && purple_strequal(name1->hash, name2->hash); } static void jabber_caps_client_info_destroy(JabberCapsClientInfo *info) { if (info == NULL) return; while(info->identities) { JabberIdentity *id = info->identities->data; g_free(id->category); g_free(id->type); g_free(id->name); g_free(id->lang); g_free(id); info->identities = g_list_delete_link(info->identities, info->identities); } free_string_glist(info->features); while (info->forms) { xmlnode_free(info->forms->data); info->forms = g_list_delete_link(info->forms, info->forms); } jabber_caps_node_exts_unref(info->exts); g_free((char *)info->tuple.node); g_free((char *)info->tuple.ver); g_free((char *)info->tuple.hash); 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); } 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) { const JabberCapsTuple *tuple = key; const JabberCapsClientInfo *props = value; xmlnode *root = user_data; xmlnode *client = xmlnode_new_child(root, "client"); GList *iter; xmlnode_set_attrib(client, "node", tuple->node); xmlnode_set_attrib(client, "ver", tuple->ver); if (tuple->hash) xmlnode_set_attrib(client, "hash", tuple->hash); for(iter = props->identities; iter; iter = g_list_next(iter)) { JabberIdentity *id = iter->data; xmlnode *identity = xmlnode_new_child(client, "identity"); xmlnode_set_attrib(identity, "category", id->category); xmlnode_set_attrib(identity, "type", id->type); if (id->name) xmlnode_set_attrib(identity, "name", id->name); if (id->lang) xmlnode_set_attrib(identity, "lang", id->lang); } 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); } 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 gboolean do_jabber_caps_store(gpointer data) { char *str; int length = 0; xmlnode *root = xmlnode_new("capabilities"); g_hash_table_foreach(capstable, jabber_caps_store_client, root); str = xmlnode_to_formatted_str(root, &length); 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); } static void jabber_caps_load(void) { xmlnode *capsdata = purple_util_read_xml_from_file(JABBER_CAPS_FILENAME, "XMPP capabilities cache"); xmlnode *client; if(!capsdata) return; if (strcmp(capsdata->name, "capabilities") != 0) { xmlnode_free(capsdata); return; } for(client = capsdata->child; client; client = client->next) { if(client->type != XMLNODE_TYPE_TAG) continue; if(!strcmp(client->name, "client")) { JabberCapsClientInfo *value = g_new0(JabberCapsClientInfo, 1); JabberCapsTuple *key = (JabberCapsTuple*)&value->tuple; xmlnode *child; JabberCapsNodeExts *exts = NULL; 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; 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"); const char *lang = xmlnode_get_attrib(child, "lang"); JabberIdentity *id; if (!category || !type) continue; id = g_new0(JabberIdentity, 1); id->category = g_strdup(category); id->type = g_strdup(type); id->name = g_strdup(name); id->lang = g_strdup(lang); value->identities = g_list_append(value->identities,id); } else if(!strcmp(child->name,"x")) { /* TODO: See #7814 -- this might 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); } 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, NULL, (GDestroyNotify)jabber_caps_client_info_destroy); 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 = nodetable = NULL; } gboolean jabber_caps_exts_known(const JabberCapsClientInfo *info, char **exts) { int i; g_return_val_if_fail(info != NULL, FALSE); if (!exts) return TRUE; for (i = 0; exts[i]; ++i) { /* Hack since we advertise the ext along with v1.5 caps but don't * store any exts */ if (g_str_equal(exts[i], "voice-v1") && !info->exts) continue; if (!info->exts || !g_hash_table_lookup(info->exts->exts, exts[i])) return FALSE; } return TRUE; } typedef struct _jabber_caps_cbplususerdata { guint ref; jabber_caps_get_info_cb cb; gpointer cb_data; char *who; char *node; char *ver; char *hash; 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 we have info here, it's already in the capstable, so don't free it */ if (data->exts) free_string_glist(data->exts); if (data->node_exts) jabber_caps_node_exts_unref(data->node_exts); g_free(data); } static void jabber_caps_get_info_complete(jabber_caps_cbplususerdata *userdata) { if (userdata->cb) { 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, const char *from, JabberIqType type, const char *id, 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; JabberCapsTuple key; if (!query || type == JABBER_IQ_ERROR) { /* 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); /* 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_destroy(info); cbplususerdata_unref(userdata); g_free(hash); return; } g_free(hash); } 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; } key.node = userdata->node; key.ver = userdata->ver; key.hash = userdata->hash; /* Use the copy of this data already in the table if it exists or insert * a new one if we need to */ if ((value = g_hash_table_lookup(capstable, &key))) { jabber_caps_client_info_destroy(info); info = value; } else { JabberCapsTuple *n_key = (JabberCapsTuple *)&info->tuple; n_key->node = userdata->node; n_key->ver = userdata->ver; n_key->hash = userdata->hash; userdata->node = userdata->ver = userdata->hash = NULL; /* The capstable gets a reference */ g_hash_table_insert(capstable, n_key, info); schedule_caps_save(); } userdata->info = info; if (userdata->extOutstanding == 0) jabber_caps_get_info_complete(userdata); cbplususerdata_unref(userdata); } typedef struct { const char *name; jabber_caps_cbplususerdata *data; } ext_iq_data; static void jabber_caps_ext_iqcb(JabberStream *js, const char *from, JabberIqType type, const char *id, 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; GList *features = NULL; JabberCapsNodeExts *node_exts; if (!query || type == JABBER_IQ_ERROR) { cbplususerdata_unref(userdata->data); g_free(userdata); return; } node_exts = (userdata->data->info ? userdata->data->info->exts : userdata->data->node_exts); /* TODO: I don't see how this can actually happen, but it crashed khc. */ if (!node_exts) { purple_debug_error("jabber", "Couldn't find JabberCapsNodeExts. If you " "see this, please tell darkrain42 and save your debug log.\n" "JabberCapsClientInfo = %p\n", userdata->data->info); /* Try once more to find the exts and then fail */ node_exts = jabber_caps_find_exts_by_node(userdata->data->node); if (node_exts) { purple_debug_info("jabber", "Found the exts on the second try.\n"); if (userdata->data->info) userdata->data->info->exts = node_exts; else userdata->data->node_exts = node_exts; } else { cbplususerdata_unref(userdata->data); g_free(userdata); g_return_if_reached(); } } /* 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)); } 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); } void jabber_caps_get_info(JabberStream *js, const char *who, const char *node, const char *ver, const char *hash, char **exts, jabber_caps_get_info_cb cb, gpointer user_data) { JabberCapsClientInfo *info; JabberCapsTuple key; jabber_caps_cbplususerdata *userdata; if (exts && hash) { purple_debug_info("jabber", "Ignoring exts in new-style caps from %s\n", who); g_strfreev(exts); exts = NULL; } /* 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 */ if (cb) cb(info, NULL, user_data); return; } userdata = g_new0(jabber_caps_cbplususerdata, 1); /* We start out with 0 references. Every query takes one */ 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) { userdata->info = info; } else { /* If we don't have the basic information about the client, we need * to fetch it. */ JabberIq *iq; xmlnode *query; char *nodever; 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, ver); xmlnode_set_attrib(query, "node", nodever); g_free(nodever); xmlnode_set_attrib(iq->node, "to", who); cbplususerdata_ref(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 (exts) { JabberCapsNodeExts *node_exts; 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; exts[i]; ++i) { userdata->exts = g_list_prepend(userdata->exts, exts[i]); /* Look it up if we don't already know what it means */ if (!g_hash_table_lookup(node_exts->exts, exts[i])) { JabberIq *iq; xmlnode *query; char *nodeext; ext_iq_data *cbdata = g_new(ext_iq_data, 1); cbdata->name = exts[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, exts[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; } exts[i] = NULL; } /* All the strings are now part of the GList, so don't need * g_strfreev. */ g_free(exts); } if (userdata->info && userdata->extOutstanding == 0) { /* Start with 1 ref so the below functions are happy */ userdata->ref = 1; /* We have everything we need right now */ jabber_caps_get_info_complete(userdata); cbplususerdata_unref(userdata); } } static gint jabber_identity_compare(gconstpointer a, gconstpointer b) { const JabberIdentity *ac; const JabberIdentity *bc; gint cat_cmp; gint typ_cmp; ac = a; bc = b; if ((cat_cmp = strcmp(ac->category, bc->category)) == 0) { if ((typ_cmp = strcmp(ac->type, bc->type)) == 0) { if (!ac->lang && !bc->lang) { return 0; } else if (ac->lang && !bc->lang) { return 1; } else if (!ac->lang && bc->lang) { return -1; } else { return strcmp(ac->lang, bc->lang); } } else { return typ_cmp; } } else { return cat_cmp; } } static gchar *jabber_caps_get_formtype(const xmlnode *x) { xmlnode *formtypefield; formtypefield = xmlnode_get_child(x, "field"); while (formtypefield && strcmp(xmlnode_get_attrib(formtypefield, "var"), "FORM_TYPE")) formtypefield = xmlnode_get_next_twin(formtypefield); formtypefield = xmlnode_get_child(formtypefield, "value"); return xmlnode_get_data(formtypefield);; } static gint jabber_xdata_compare(gconstpointer a, gconstpointer b) { const xmlnode *aformtypefield = a; const xmlnode *bformtypefield = b; char *aformtype; char *bformtype; int result; aformtype = jabber_caps_get_formtype(aformtypefield); bformtype = jabber_caps_get_formtype(bformtypefield); result = strcmp(aformtype, bformtype); g_free(aformtype); g_free(bformtype); return result; } static JabberCapsClientInfo *jabber_caps_parse_client_info(xmlnode *query) { xmlnode *child; JabberCapsClientInfo *info; if (!query || strcmp(query->xmlns, "http://jabber.org/protocol/disco#info")) return 0; info = g_new0(JabberCapsClientInfo, 1); for(child = query->child; child; child = child->next) { if (child->type != XMLNODE_TYPE_TAG) continue; if (!strcmp(child->name,"identity")) { /* parse 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"); const char *lang = xmlnode_get_attrib(child, "lang"); JabberIdentity *id; if (!category || !type) continue; id = g_new0(JabberIdentity, 1); id->category = g_strdup(category); id->type = g_strdup(type); id->name = g_strdup(name); id->lang = g_strdup(lang); info->identities = g_list_append(info->identities, id); } else if (!strcmp(child->name, "feature")) { /* parse feature */ const char *var = xmlnode_get_attrib(child, "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 */ xmlnode *dataform = xmlnode_copy(child); info->forms = g_list_append(info->forms, dataform); } } } return info; } static gint jabber_caps_xdata_field_compare(gconstpointer a, gconstpointer b) { const JabberDataFormField *ac = a; const JabberDataFormField *bc = b; return strcmp(ac->var, bc->var); } static GList* jabber_caps_xdata_get_fields(const xmlnode *x) { GList *fields = NULL; xmlnode *field; if (!x) return NULL; for (field = xmlnode_get_child(x, "field"); field; field = xmlnode_get_next_twin(field)) { xmlnode *value; JabberDataFormField *xdatafield = g_new0(JabberDataFormField, 1); xdatafield->var = g_strdup(xmlnode_get_attrib(field, "var")); for (value = xmlnode_get_child(field, "value"); value; value = xmlnode_get_next_twin(value)) { gchar *val = xmlnode_get_data(value); xdatafield->values = g_list_append(xdatafield->values, val); } xdatafield->values = g_list_sort(xdatafield->values, (GCompareFunc)strcmp); fields = g_list_append(fields, xdatafield); } fields = g_list_sort(fields, jabber_caps_xdata_field_compare); return fields; } static GString* jabber_caps_verification_append(GString *verification, const gchar *str) { char *tmp = g_markup_escape_text(str, -1); verification = g_string_append(verification, tmp); g_free(tmp); return g_string_append_c(verification, '<'); } gchar *jabber_caps_calculate_hash(JabberCapsClientInfo *info, const char *hash) { GList *node; GString *verification; PurpleCipherContext *context; guint8 checksum[20]; gsize checksum_size = 20; gboolean success; if (!info || !(context = purple_cipher_context_new_by_name(hash, NULL))) return NULL; /* sort identities, features and x-data forms */ info->identities = g_list_sort(info->identities, jabber_identity_compare); info->features = g_list_sort(info->features, (GCompareFunc)strcmp); info->forms = g_list_sort(info->forms, jabber_xdata_compare); verification = g_string_new(""); /* concat identities to the verification string */ for (node = info->identities; node; node = node->next) { JabberIdentity *id = (JabberIdentity*)node->data; char *category = g_markup_escape_text(id->category, -1); char *type = g_markup_escape_text(id->type, -1); char *lang = NULL; char *name = NULL; if (id->lang) lang = g_markup_escape_text(id->lang, -1); if (id->name) name = g_markup_escape_text(id->name, -1); g_string_append_printf(verification, "%s/%s/%s/%s<", category, type, lang ? lang : "", name ? name : ""); g_free(category); g_free(type); g_free(lang); g_free(name); } /* concat features to the verification string */ for (node = info->features; node; node = node->next) { verification = jabber_caps_verification_append(verification, node->data); } /* concat x-data forms to the verification string */ for(node = info->forms; node; node = node->next) { xmlnode *data = (xmlnode *)node->data; gchar *formtype = jabber_caps_get_formtype(data); GList *fields = jabber_caps_xdata_get_fields(data); /* append FORM_TYPE's field value to the verification string */ verification = jabber_caps_verification_append(verification, formtype); g_free(formtype); while (fields) { GList *value; JabberDataFormField *field = (JabberDataFormField*)fields->data; if (strcmp(field->var, "FORM_TYPE")) { /* Append the "var" attribute */ verification = jabber_caps_verification_append(verification, field->var); /* Append <value/> elements' cdata */ for(value = field->values; value; value = value->next) { verification = jabber_caps_verification_append(verification, value->data); g_free(value->data); } } g_free(field->var); g_list_free(field->values); fields = g_list_delete_link(fields, fields); } } /* generate hash */ purple_cipher_context_append(context, (guchar*)verification->str, verification->len); success = purple_cipher_context_digest(context, verification->len, checksum, &checksum_size); g_string_free(verification, TRUE); purple_cipher_context_destroy(context); return (success ? purple_base64_encode(checksum, checksum_size) : NULL); } void jabber_caps_calculate_own_hash(JabberStream *js) { JabberCapsClientInfo info; GList *iter = 0; GList *features = 0; if (!jabber_identities && !jabber_features) { /* This really shouldn't ever happen */ purple_debug_warning("jabber", "No features or identities, cannot calculate own caps hash.\n"); g_free(js->caps_hash); js->caps_hash = NULL; return; } /* build the currently-supported list of features */ if (jabber_features) { for (iter = jabber_features; iter; iter = iter->next) { JabberFeature *feat = iter->data; if(!feat->is_enabled || feat->is_enabled(js, feat->namespace)) { features = g_list_append(features, feat->namespace); } } } info.features = features; info.identities = g_list_copy(jabber_identities); info.forms = NULL; g_free(js->caps_hash); js->caps_hash = jabber_caps_calculate_hash(&info, "sha1"); g_list_free(info.identities); g_list_free(features); } const gchar* jabber_caps_get_own_hash(JabberStream *js) { if (!js->caps_hash) jabber_caps_calculate_own_hash(js); return js->caps_hash; } void jabber_caps_broadcast_change() { GList *node, *accounts = purple_accounts_get_all_active(); for (node = accounts; node; node = node->next) { PurpleAccount *account = node->data; const char *prpl_id = purple_account_get_protocol_id(account); if (!strcmp("prpl-jabber", prpl_id) && purple_account_is_connected(account)) { PurpleConnection *gc = purple_account_get_connection(account); jabber_presence_send(gc->proto_data, TRUE); } } g_list_free(accounts); }