pidgin/pidgin

1896a80ff8e3
Route GLib debug logging directly to the Finch debug window

Instead of flowing through purple debug, this merges some bits of the existing GLib log handler, and the purple debug printer.

Testing Done:
Open the Debug window an see some `GLib-*` outputs.

Reviewed at https://reviews.imfreedom.org/r/1057/
/*
* purple - Jabber Protocol Plugin
*
* Purple is the legal property of its developers, whose names are too numerous
* to list here. Please refer to the COPYRIGHT file distributed with this
* source distribution.
*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
*
*/
#include <glib/gi18n-lib.h>
#include <purple.h>
#include "caps.h"
#include "iq.h"
#include "presence.h"
#include "xdata.h"
#define JABBER_CAPS_FILENAME "xmpp-caps.xml"
typedef struct {
gchar *var;
GList *values;
} JabberDataFormField;
static GHashTable *capstable = NULL; /* JabberCapsTuple -> JabberCapsClientInfo */
static GHashTable *nodetable = NULL; /* char *node -> JabberCapsNodeExts */
static guint save_timer = 0;
/* Free a GList of allocated char* */
static void
free_string_glist(GList *list)
{
g_list_free_full(list, g_free);
}
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 purple_strequal(name1->node, name2->node) &&
purple_strequal(name1->ver, name2->ver) &&
purple_strequal(name1->hash, name2->hash);
}
static void
jabber_caps_client_info_destroy(JabberCapsClientInfo *info)
{
if (info == NULL)
return;
g_list_free_full(info->identities, (GDestroyNotify)jabber_identity_free);
free_string_glist(info->features);
g_list_free_full(info->forms, (GDestroyNotify)purple_xmlnode_free);
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;
PurpleXmlNode *client = user_data, *ext, *feature;
ext = purple_xmlnode_new_child(client, "ext");
purple_xmlnode_set_attrib(ext, "identifier", identifier);
for (node = features; node; node = node->next) {
feature = purple_xmlnode_new_child(ext, "feature");
purple_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;
PurpleXmlNode *root = user_data;
PurpleXmlNode *client = purple_xmlnode_new_child(root, "client");
GList *iter;
purple_xmlnode_set_attrib(client, "node", tuple->node);
purple_xmlnode_set_attrib(client, "ver", tuple->ver);
if (tuple->hash)
purple_xmlnode_set_attrib(client, "hash", tuple->hash);
for(iter = props->identities; iter; iter = g_list_next(iter)) {
JabberIdentity *id = iter->data;
PurpleXmlNode *identity = purple_xmlnode_new_child(client, "identity");
purple_xmlnode_set_attrib(identity, "category", id->category);
purple_xmlnode_set_attrib(identity, "type", id->type);
if (id->name)
purple_xmlnode_set_attrib(identity, "name", id->name);
if (id->lang)
purple_xmlnode_set_attrib(identity, "lang", id->lang);
}
for(iter = props->features; iter; iter = g_list_next(iter)) {
const char *feat = iter->data;
PurpleXmlNode *feature = purple_xmlnode_new_child(client, "feature");
purple_xmlnode_set_attrib(feature, "var", feat);
}
for(iter = props->forms; iter; iter = g_list_next(iter)) {
/* FIXME: See #7814 */
PurpleXmlNode *xdata = iter->data;
purple_xmlnode_insert_child(client, purple_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;
PurpleXmlNode *root = purple_xmlnode_new("capabilities");
g_hash_table_foreach(capstable, jabber_caps_store_client, root);
str = purple_xmlnode_to_formatted_str(root, &length);
purple_xmlnode_free(root);
purple_util_write_data_to_cache_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 = g_timeout_add_seconds(5, do_jabber_caps_store, NULL);
}
static void
jabber_caps_load(void)
{
PurpleXmlNode *capsdata = purple_util_read_xml_from_cache_file(JABBER_CAPS_FILENAME, "XMPP capabilities cache");
PurpleXmlNode *client;
if(!capsdata)
return;
if (!purple_strequal(capsdata->name, "capabilities")) {
purple_xmlnode_free(capsdata);
return;
}
for (client = capsdata->child; client; client = client->next) {
if (client->type != PURPLE_XMLNODE_TYPE_TAG)
continue;
if (purple_strequal(client->name, "client")) {
JabberCapsClientInfo *value = g_new0(JabberCapsClientInfo, 1);
JabberCapsTuple *key = (JabberCapsTuple*)&value->tuple;
PurpleXmlNode *child;
JabberCapsNodeExts *exts = NULL;
key->node = g_strdup(purple_xmlnode_get_attrib(client,"node"));
key->ver = g_strdup(purple_xmlnode_get_attrib(client,"ver"));
key->hash = g_strdup(purple_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 != PURPLE_XMLNODE_TYPE_TAG)
continue;
if (purple_strequal(child->name, "feature")) {
const char *var = purple_xmlnode_get_attrib(child, "var");
if(!var)
continue;
value->features = g_list_append(value->features,g_strdup(var));
} else if (purple_strequal(child->name, "identity")) {
const char *category = purple_xmlnode_get_attrib(child, "category");
const char *type = purple_xmlnode_get_attrib(child, "type");
const char *name = purple_xmlnode_get_attrib(child, "name");
const char *lang = purple_xmlnode_get_attrib(child, "lang");
JabberIdentity *id;
if (!category || !type)
continue;
id = jabber_identity_new(category, type, lang, name);
value->identities = g_list_append(value->identities,id);
} else if (purple_strequal(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
* purple_xmlnode_from_str, not the output version... */
value->forms = g_list_append(value->forms, purple_xmlnode_copy(child));
} else if (purple_strequal(child->name, "ext")) {
if (key->hash != NULL)
purple_debug_warning("jabber", "Ignoring exts when reading new-style caps\n");
else {
/* TODO: Do we care about reading in the identities listed here? */
const char *identifier = purple_xmlnode_get_attrib(child, "identifier");
PurpleXmlNode *node;
GList *features = NULL;
if (!identifier)
continue;
for (node = child->child; node; node = node->next) {
if (node->type != PURPLE_XMLNODE_TYPE_TAG)
continue;
if (purple_strequal(node->name, "feature")) {
const char *var = purple_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);
}
}
purple_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) {
g_source_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) {
if (!info->exts ||
!g_hash_table_lookup(info->exts->exts, exts[i]))
return FALSE;
}
return TRUE;
}
typedef struct {
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, PurpleXmlNode *packet, gpointer data)
{
PurpleXmlNode *query = purple_xmlnode_get_child_with_namespace(packet, "query",
NS_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;
GChecksumType hash_type;
gboolean supported_hash = TRUE;
if (purple_strequal(userdata->hash, "sha-1")) {
hash_type = G_CHECKSUM_SHA1;
} else if (purple_strequal(userdata->hash, "md5")) {
hash_type = G_CHECKSUM_MD5;
} else {
supported_hash = FALSE;
}
if (supported_hash) {
hash = jabber_caps_calculate_hash(info, hash_type);
}
if (!hash || !purple_strequal(hash, userdata->ver)) {
purple_debug_warning("jabber", "Could not validate caps info from "
"%s. Expected %s, got %s\n",
purple_xmlnode_get_attrib(packet, "from"),
userdata->ver, hash ? hash : "(null)");
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 = NULL;
if (G_UNLIKELY(info == NULL)) {
g_warn_if_reached();
return;
}
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);
}
static void
jabber_caps_ext_iqcb(JabberStream *js, const char *from, JabberIqType type,
const char *id, PurpleXmlNode *packet, gpointer data)
{
PurpleXmlNode *query = purple_xmlnode_get_child_with_namespace(packet, "query",
NS_DISCO_INFO);
PurpleXmlNode *child;
PurpleKeyValuePair *cbdata = data;
jabber_caps_cbplususerdata *userdata = cbdata->value;
GList *features = NULL;
JabberCapsNodeExts *node_exts;
if (!query || type == JABBER_IQ_ERROR) {
purple_key_value_pair_free(cbdata);
return;
}
node_exts = (userdata->info ? userdata->info->exts : userdata->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", userdata->info);
/* Try once more to find the exts and then fail */
node_exts = jabber_caps_find_exts_by_node(userdata->node);
if (node_exts) {
purple_debug_info("jabber", "Found the exts on the second try.\n");
if (userdata->info) {
userdata->info->exts = node_exts;
} else {
userdata->node_exts = node_exts;
}
} else {
purple_key_value_pair_free(cbdata);
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->extOutstanding;
for (child = purple_xmlnode_get_child(query, "feature"); child;
child = purple_xmlnode_get_next_twin(child)) {
const char *var = purple_xmlnode_get_attrib(child, "var");
if (var)
features = g_list_prepend(features, g_strdup(var));
}
g_hash_table_insert(node_exts->exts, g_strdup(cbdata->key), features);
schedule_caps_save();
/* Are we done? */
if (userdata->info && userdata->extOutstanding == 0) {
jabber_caps_get_info_complete(userdata);
}
purple_key_value_pair_free(cbdata);
}
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_misc("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;
PurpleXmlNode *query;
char *nodever;
iq = jabber_iq_new_query(js, JABBER_IQ_GET, NS_DISCO_INFO);
query = purple_xmlnode_get_child_with_namespace(iq->node, "query",
NS_DISCO_INFO);
nodever = g_strdup_printf("%s#%s", node, ver);
purple_xmlnode_set_attrib(query, "node", nodever);
g_free(nodever);
purple_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;
PurpleXmlNode *query;
char *nodeext;
PurpleKeyValuePair *cbdata;
iq = jabber_iq_new_query(js, JABBER_IQ_GET, NS_DISCO_INFO);
query = purple_xmlnode_get_child_with_namespace(iq->node, "query",
NS_DISCO_INFO);
nodeext = g_strdup_printf("%s#%s", node, exts[i]);
purple_xmlnode_set_attrib(query, "node", nodeext);
g_free(nodeext);
purple_xmlnode_set_attrib(iq->node, "to", who);
cbdata = purple_key_value_pair_new_full(exts[i], cbplususerdata_ref(userdata),
(GDestroyNotify)cbplususerdata_unref);
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_xdata_compare(gconstpointer a, gconstpointer b)
{
const PurpleXmlNode *aformtypefield = a;
const PurpleXmlNode *bformtypefield = b;
char *aformtype;
char *bformtype;
int result;
aformtype = jabber_x_data_get_formtype(aformtypefield);
bformtype = jabber_x_data_get_formtype(bformtypefield);
result = strcmp(aformtype, bformtype);
g_free(aformtype);
g_free(bformtype);
return result;
}
JabberCapsClientInfo *jabber_caps_parse_client_info(PurpleXmlNode *query)
{
PurpleXmlNode *child;
JabberCapsClientInfo *info;
if (!query || !purple_strequal(query->name, "query") ||
!purple_strequal(query->xmlns, NS_DISCO_INFO))
return NULL;
info = g_new0(JabberCapsClientInfo, 1);
for(child = query->child; child; child = child->next) {
if (child->type != PURPLE_XMLNODE_TYPE_TAG)
continue;
if (purple_strequal(child->name, "identity")) {
/* parse identity */
const char *category = purple_xmlnode_get_attrib(child, "category");
const char *type = purple_xmlnode_get_attrib(child, "type");
const char *name = purple_xmlnode_get_attrib(child, "name");
const char *lang = purple_xmlnode_get_attrib(child, "lang");
JabberIdentity *id;
if (!category || !type)
continue;
id = jabber_identity_new(category, type, lang, name);
info->identities = g_list_append(info->identities, id);
} else if (purple_strequal(child->name, "feature")) {
/* parse feature */
const char *var = purple_xmlnode_get_attrib(child, "var");
if (var)
info->features = g_list_prepend(info->features, g_strdup(var));
} else if (purple_strequal(child->name, "x")) {
if (purple_strequal(child->xmlns, "jabber:x:data")) {
/* x-data form */
PurpleXmlNode *dataform = purple_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 PurpleXmlNode *x)
{
GList *fields = NULL;
PurpleXmlNode *field;
if (!x)
return NULL;
for (field = purple_xmlnode_get_child(x, "field"); field; field = purple_xmlnode_get_next_twin(field)) {
PurpleXmlNode *value;
JabberDataFormField *xdatafield = g_new0(JabberDataFormField, 1);
xdatafield->var = g_strdup(purple_xmlnode_get_attrib(field, "var"));
for (value = purple_xmlnode_get_child(field, "value"); value; value = purple_xmlnode_get_next_twin(value)) {
gchar *val = purple_xmlnode_get_data(value);
xdatafield->values = g_list_prepend(xdatafield->values, val);
}
xdatafield->values = g_list_sort(xdatafield->values, (GCompareFunc)strcmp);
fields = g_list_prepend(fields, xdatafield);
}
fields = g_list_sort(fields, jabber_caps_xdata_field_compare);
return fields;
}
static void
append_escaped_string(GChecksum *hash, const gchar *str)
{
g_return_if_fail(hash != NULL);
if (str && *str) {
char *tmp = g_markup_escape_text(str, -1);
g_checksum_update(hash, (const guchar *)tmp, -1);
g_free(tmp);
}
g_checksum_update(hash, (const guchar *)"<", -1);
}
gchar *jabber_caps_calculate_hash(JabberCapsClientInfo *info,
GChecksumType hash_type)
{
GChecksum *hash;
GList *node;
guint8 *checksum;
gsize checksum_size;
gchar *ret;
if (!info)
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);
hash = g_checksum_new(hash_type);
if (hash == NULL) {
return NULL;
}
/* Add identities to the hash data */
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;
char *tmp;
if (id->lang)
lang = g_markup_escape_text(id->lang, -1);
if (id->name)
name = g_markup_escape_text(id->name, -1);
tmp = g_strconcat(category, "/", type, "/", lang ? lang : "",
"/", name ? name : "", "<", NULL);
g_checksum_update(hash, (const guchar *)tmp, -1);
g_free(tmp);
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) {
append_escaped_string(hash, node->data);
}
/* concat x-data forms to the verification string */
for(node = info->forms; node; node = node->next) {
PurpleXmlNode *data = (PurpleXmlNode *)node->data;
gchar *formtype = jabber_x_data_get_formtype(data);
GList *fields = jabber_caps_xdata_get_fields(data);
/* append FORM_TYPE's field value to the verification string */
append_escaped_string(hash, formtype);
g_free(formtype);
while (fields) {
JabberDataFormField *field = (JabberDataFormField*)fields->data;
if (!purple_strequal(field->var, "FORM_TYPE")) {
/* Append the "var" attribute */
append_escaped_string(hash, field->var);
/* Append <value/> elements' cdata */
while (field->values) {
append_escaped_string(hash, field->values->data);
g_free(field->values->data);
field->values = g_list_delete_link(field->values,
field->values);
}
} else {
g_list_free_full(field->values, g_free);
}
g_free(field->var);
g_free(field);
fields = g_list_delete_link(fields, fields);
}
}
checksum_size = g_checksum_type_get_length(hash_type);
checksum = g_new(guint8, checksum_size);
/* generate hash */
g_checksum_get_digest(hash, checksum, &checksum_size);
ret = g_base64_encode(checksum, checksum_size);
g_free(checksum);
g_checksum_free(hash);
return ret;
}
void jabber_caps_calculate_own_hash(JabberStream *js) {
JabberCapsClientInfo info;
GList *iter = NULL;
GList *features = NULL;
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;
/* TODO: This copy can go away, I think, since jabber_identities
* is pre-sorted, so the sort in calculate_hash should be idempotent.
* However, I want to test that. --darkrain
*/
info.identities = g_list_copy(jabber_identities);
info.forms = NULL;
g_free(js->caps_hash);
js->caps_hash = jabber_caps_calculate_hash(&info, G_CHECKSUM_SHA1);
g_list_free(info.identities);
g_list_free(info.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 *protocol_id = purple_account_get_protocol_id(account);
if (purple_strequal("prpl-jabber", protocol_id) && purple_account_is_connected(account)) {
PurpleConnection *gc = purple_account_get_connection(account);
jabber_presence_send(purple_connection_get_protocol_data(gc), TRUE);
}
}
g_list_free(accounts);
}