Mercurial > pidgin.yaz
diff libpurple/protocols/jabber/caps.c @ 19694:1d2002a5735e
propagate from branch 'im.pidgin.pidgin' (head 996cf0c57149ba6e1c714ebb1f11d5d4bac8fb68)
to branch 'im.pidgin.soc.2007.xmpp' (head cdf63b6603891b8cd3e7f629ef5a9a927a153550)
author | Andreas Monitzer <pidgin@monitzer.com> |
---|---|
date | Wed, 05 Sep 2007 22:32:14 +0000 |
parents | ce04ca030a1b |
children | d32ed28cf645 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/caps.c Wed Sep 05 22:32:14 2007 +0000 @@ -0,0 +1,530 @@ +/* + * 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 "caps.h" +#include <string.h> +#include "internal.h" +#include "util.h" +#include "iq.h" + +#define JABBER_CAPS_FILENAME "xmpp-caps.xml" + +static GHashTable *capstable = NULL; /* JabberCapsKey -> JabberCapsValue */ + +typedef struct _JabberCapsKey { + char *node; + char *ver; +} JabberCapsKey; + +typedef struct _JabberCapsValueExt { + GList *identities; /* JabberCapsIdentity */ + GList *features; /* char * */ +} JabberCapsValueExt; + +typedef struct _JabberCapsValue { + GList *identities; /* JabberCapsIdentity */ + GList *features; /* char * */ + GHashTable *ext; /* char * -> JabberCapsValueExt */ +} JabberCapsValue; + +static guint jabber_caps_hash(gconstpointer key) { + const JabberCapsKey *name = key; + guint nodehash = g_str_hash(name->node); + guint verhash = g_str_hash(name->ver); + + return nodehash ^ verhash; +} + +static gboolean jabber_caps_compare(gconstpointer v1, gconstpointer v2) { + const JabberCapsKey *name1 = v1; + const JabberCapsKey *name2 = v2; + + return strcmp(name1->node,name2->node) == 0 && strcmp(name1->ver,name2->ver) == 0; +} + +static void jabber_caps_destroy_key(gpointer key) { + JabberCapsKey *keystruct = key; + g_free(keystruct->node); + g_free(keystruct->ver); + g_free(keystruct); +} + +static void jabber_caps_destroy_value(gpointer value) { + JabberCapsValue *valuestruct = value; + while(valuestruct->identities) { + JabberCapsIdentity *id = valuestruct->identities->data; + g_free(id->category); + g_free(id->type); + g_free(id->name); + g_free(id); + + valuestruct->identities = g_list_delete_link(valuestruct->identities,valuestruct->identities); + } + while(valuestruct->features) { + g_free(valuestruct->features->data); + valuestruct->features = g_list_delete_link(valuestruct->features,valuestruct->features); + } + g_hash_table_destroy(valuestruct->ext); + g_free(valuestruct); +} + +static void jabber_caps_ext_destroy_value(gpointer value) { + JabberCapsValueExt *valuestruct = value; + while(valuestruct->identities) { + JabberCapsIdentity *id = valuestruct->identities->data; + g_free(id->category); + g_free(id->type); + g_free(id->name); + g_free(id); + + valuestruct->identities = g_list_delete_link(valuestruct->identities,valuestruct->identities); + } + while(valuestruct->features) { + g_free(valuestruct->features->data); + valuestruct->features = g_list_delete_link(valuestruct->features,valuestruct->features); + } + g_free(valuestruct); +} + +static void jabber_caps_load(void); + +void jabber_caps_init(void) { + capstable = g_hash_table_new_full(jabber_caps_hash, jabber_caps_compare, jabber_caps_destroy_key, jabber_caps_destroy_value); + jabber_caps_load(); +} + +static void jabber_caps_load(void) { + xmlnode *capsdata = purple_util_read_xml_from_file(JABBER_CAPS_FILENAME, "XMPP capabilities cache"); + xmlnode *client; + if(!capsdata || strcmp(capsdata->name, "capabilities")) + return; + + for(client = capsdata->child; client; client = client->next) { + if(client->type != XMLNODE_TYPE_TAG) + continue; + if(!strcmp(client->name, "client")) { + JabberCapsKey *key = g_new0(JabberCapsKey, 1); + JabberCapsValue *value = g_new0(JabberCapsValue, 1); + xmlnode *child; + key->node = g_strdup(xmlnode_get_attrib(client,"node")); + key->ver = g_strdup(xmlnode_get_attrib(client,"ver")); + value->ext = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, jabber_caps_ext_destroy_value); + 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"); + + JabberCapsIdentity *id = g_new0(JabberCapsIdentity, 1); + id->category = g_strdup(category); + id->type = g_strdup(type); + id->name = g_strdup(name); + + value->identities = g_list_append(value->identities,id); + } else if(!strcmp(child->name,"ext")) { + const char *identifier = xmlnode_get_attrib(child, "identifier"); + if(identifier) { + xmlnode *extchild; + + JabberCapsValueExt *extvalue = g_new0(JabberCapsValueExt, 1); + + for(extchild = child->child; extchild; extchild = extchild->next) { + if(extchild->type != XMLNODE_TYPE_TAG) + continue; + if(!strcmp(extchild->name,"feature")) { + const char *var = xmlnode_get_attrib(extchild, "var"); + if(!var) + continue; + extvalue->features = g_list_append(extvalue->features,g_strdup(var)); + } else if(!strcmp(extchild->name,"identity")) { + const char *category = xmlnode_get_attrib(extchild, "category"); + const char *type = xmlnode_get_attrib(extchild, "type"); + const char *name = xmlnode_get_attrib(extchild, "name"); + + JabberCapsIdentity *id = g_new0(JabberCapsIdentity, 1); + id->category = g_strdup(category); + id->type = g_strdup(type); + id->name = g_strdup(name); + + extvalue->identities = g_list_append(extvalue->identities,id); + } + } + g_hash_table_replace(value->ext, g_strdup(identifier), extvalue); + } + } + } + g_hash_table_replace(capstable, key, value); + } + } +} + +static void jabber_caps_store_ext(gpointer key, gpointer value, gpointer user_data) { + const char *extname = key; + JabberCapsValueExt *props = value; + xmlnode *root = user_data; + xmlnode *ext = xmlnode_new_child(root,"ext"); + GList *iter; + + xmlnode_set_attrib(ext,"identifier",extname); + + for(iter = props->identities; iter; iter = g_list_next(iter)) { + JabberCapsIdentity *id = iter->data; + xmlnode *identity = xmlnode_new_child(ext, "identity"); + xmlnode_set_attrib(identity, "category", id->category); + xmlnode_set_attrib(identity, "type", id->type); + xmlnode_set_attrib(identity, "name", id->name); + } + + for(iter = props->features; iter; iter = g_list_next(iter)) { + const char *feat = iter->data; + xmlnode *feature = xmlnode_new_child(ext, "feature"); + xmlnode_set_attrib(feature, "var", feat); + } +} + +static void jabber_caps_store_client(gpointer key, gpointer value, gpointer user_data) { + JabberCapsKey *clientinfo = key; + JabberCapsValue *props = value; + xmlnode *root = user_data; + xmlnode *client = xmlnode_new_child(root,"client"); + GList *iter; + + xmlnode_set_attrib(client,"node",clientinfo->node); + xmlnode_set_attrib(client,"ver",clientinfo->ver); + + for(iter = props->identities; iter; iter = g_list_next(iter)) { + JabberCapsIdentity *id = iter->data; + xmlnode *identity = xmlnode_new_child(client, "identity"); + xmlnode_set_attrib(identity, "category", id->category); + xmlnode_set_attrib(identity, "type", id->type); + xmlnode_set_attrib(identity, "name", id->name); + } + + 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); + } + + g_hash_table_foreach(props->ext,jabber_caps_store_ext,client); +} + +static void jabber_caps_store(void) { + xmlnode *root = xmlnode_new("capabilities"); + g_hash_table_foreach(capstable, jabber_caps_store_client, root); + purple_util_write_data_to_file(JABBER_CAPS_FILENAME, xmlnode_to_formatted_str(root, NULL), -1); +} + +/* this function assumes that all information is available locally */ +static JabberCapsClientInfo *jabber_caps_collect_info(const char *node, const char *ver, GList *ext) { + JabberCapsClientInfo *result = g_new0(JabberCapsClientInfo, 1); + JabberCapsKey *key = g_new0(JabberCapsKey, 1); + JabberCapsValue *caps; + GList *iter; + + key->node = g_strdup(node); + key->ver = g_strdup(ver); + + caps = g_hash_table_lookup(capstable,key); + + g_free(key->node); + g_free(key->ver); + g_free(key); + + /* join all information */ + for(iter = caps->identities; iter; iter = g_list_next(iter)) { + JabberCapsIdentity *id = iter->data; + JabberCapsIdentity *newid = g_new0(JabberCapsIdentity, 1); + newid->category = g_strdup(id->category); + newid->type = g_strdup(id->type); + newid->name = g_strdup(id->name); + + result->identities = g_list_append(result->identities,newid); + } + for(iter = caps->features; iter; iter = g_list_next(iter)) { + const char *feat = iter->data; + char *newfeat = g_strdup(feat); + + result->features = g_list_append(result->features,newfeat); + } + + for(iter = ext; iter; iter = g_list_next(iter)) { + const char *extname = iter->data; + JabberCapsValueExt *extinfo = g_hash_table_lookup(caps->ext,extname); + + if(extinfo) { + GList *iter2; + for(iter2 = extinfo->identities; iter2; iter2 = g_list_next(iter2)) { + JabberCapsIdentity *id = iter2->data; + JabberCapsIdentity *newid = g_new0(JabberCapsIdentity, 1); + newid->category = g_strdup(id->category); + newid->type = g_strdup(id->type); + newid->name = g_strdup(id->name); + + result->identities = g_list_append(result->identities,newid); + } + for(iter2 = extinfo->features; iter2; iter2 = g_list_next(iter2)) { + const char *feat = iter2->data; + char *newfeat = g_strdup(feat); + + result->features = g_list_append(result->features,newfeat); + } + } + } + return result; +} + +void jabber_caps_free_clientinfo(JabberCapsClientInfo *clientinfo) { + if(!clientinfo) + return; + while(clientinfo->identities) { + JabberCapsIdentity *id = clientinfo->identities->data; + g_free(id->category); + g_free(id->type); + g_free(id->name); + g_free(id); + + clientinfo->identities = g_list_delete_link(clientinfo->identities,clientinfo->identities); + } + while(clientinfo->features) { + char *feat = clientinfo->features->data; + g_free(feat); + + clientinfo->features = g_list_delete_link(clientinfo->features,clientinfo->features); + } + + g_free(clientinfo); +} + +typedef struct _jabber_caps_cbplususerdata { + jabber_caps_get_info_cb cb; + gpointer user_data; + + char *who; + char *node; + char *ver; + GList *ext; + unsigned extOutstanding; +} jabber_caps_cbplususerdata; + +static void jabber_caps_get_info_check_completion(jabber_caps_cbplususerdata *userdata) { + if(userdata->extOutstanding == 0) { + userdata->cb(jabber_caps_collect_info(userdata->node, userdata->ver, userdata->ext), userdata->user_data); + g_free(userdata->who); + g_free(userdata->node); + g_free(userdata->ver); + while(userdata->ext) { + g_free(userdata->ext->data); + userdata->ext = g_list_delete_link(userdata->ext,userdata->ext); + } + g_free(userdata); + } +} + +static void jabber_caps_ext_iqcb(JabberStream *js, xmlnode *packet, gpointer data) { + /* collect data and fetch all exts */ + xmlnode *query = xmlnode_get_child_with_namespace(packet,"query","http://jabber.org/protocol/disco#info"); + xmlnode *child; + jabber_caps_cbplususerdata *userdata = data; + JabberCapsKey *clientkey = g_new0(JabberCapsKey, 1); + JabberCapsValue *client; + JabberCapsValueExt *value = g_new0(JabberCapsValueExt, 1); + const char *node = xmlnode_get_attrib(query, "node"); + const char *key; + + --userdata->extOutstanding; + + if(node) { + clientkey->node = g_strdup(userdata->node); + clientkey->ver = g_strdup(userdata->ver); + + client = g_hash_table_lookup(capstable,clientkey); + + g_free(clientkey->node); + g_free(clientkey->ver); + g_free(clientkey); + + /* split node by #, key either points to \0 or the correct ext afterwards */ + for(key = node; key[0] != '\0'; ++key) { + if(key[0] == '#') { + ++key; + break; + } + } + + for(child = query->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"); + + JabberCapsIdentity *id = g_new0(JabberCapsIdentity, 1); + id->category = g_strdup(category); + id->type = g_strdup(type); + id->name = g_strdup(name); + + value->identities = g_list_append(value->identities,id); + } + } + g_hash_table_replace(client->ext, g_strdup(key), value); + + jabber_caps_store(); + } + + jabber_caps_get_info_check_completion(userdata); +} + +static void jabber_caps_client_iqcb(JabberStream *js, xmlnode *packet, gpointer data) { + /* collect data and fetch all exts */ + xmlnode *query = xmlnode_get_child_with_namespace(packet,"query","http://jabber.org/protocol/disco#info"); + xmlnode *child; + GList *iter; + jabber_caps_cbplususerdata *userdata = data; + JabberCapsKey *key = g_new0(JabberCapsKey, 1); + JabberCapsValue *value = g_new0(JabberCapsValue, 1); + key->node = g_strdup(userdata->node); + key->ver = g_strdup(userdata->ver); + + value->ext = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, jabber_caps_ext_destroy_value); + + for(child = query->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"); + + JabberCapsIdentity *id = g_new0(JabberCapsIdentity, 1); + id->category = g_strdup(category); + id->type = g_strdup(type); + id->name = g_strdup(name); + + value->identities = g_list_append(value->identities,id); + } + } + g_hash_table_replace(capstable, key, value); + + /* fetch all exts */ + for(iter = userdata->ext; iter; iter = g_list_next(iter)) { + JabberIq *iq = jabber_iq_new_query(js,JABBER_IQ_GET,"http://jabber.org/protocol/disco#info"); + xmlnode *query = xmlnode_get_child_with_namespace(iq->node,"query","http://jabber.org/protocol/disco#info"); + char *node = g_strdup_printf("%s#%s", userdata->node, (const char*)iter->data); + xmlnode_set_attrib(query, "node", node); + g_free(node); + xmlnode_set_attrib(iq->node, "to", userdata->who); + + jabber_iq_set_callback(iq,jabber_caps_ext_iqcb,userdata); + jabber_iq_send(iq); + } + + jabber_caps_store(); + + jabber_caps_get_info_check_completion(userdata); +} + +void jabber_caps_get_info(JabberStream *js, const char *who, const char *node, const char *ver, const char *ext, jabber_caps_get_info_cb cb, gpointer user_data) { + JabberCapsValue *client; + JabberCapsKey *key = g_new0(JabberCapsKey, 1); + char *originalext = g_strdup(ext); + char *oneext, *ctx; + jabber_caps_cbplususerdata *userdata = g_new0(jabber_caps_cbplususerdata, 1); + userdata->cb = cb; + userdata->user_data = user_data; + userdata->who = g_strdup(who); + userdata->node = g_strdup(node); + userdata->ver = g_strdup(ver); + + if(originalext) + for(oneext = strtok_r(originalext, " ", &ctx); oneext; oneext = strtok_r(NULL, " ", &ctx)) { + userdata->ext = g_list_append(userdata->ext,g_strdup(oneext)); + ++userdata->extOutstanding; + } + g_free(originalext); + + key->node = g_strdup(node); + key->ver = g_strdup(ver); + + client = g_hash_table_lookup(capstable, key); + + g_free(key->node); + g_free(key->ver); + g_free(key); + + if(!client) { + JabberIq *iq = jabber_iq_new_query(js,JABBER_IQ_GET,"http://jabber.org/protocol/disco#info"); + xmlnode *query = xmlnode_get_child_with_namespace(iq->node,"query","http://jabber.org/protocol/disco#info"); + char *nodever = g_strdup_printf("%s#%s", node, ver); + xmlnode_set_attrib(query, "node", nodever); + g_free(nodever); + xmlnode_set_attrib(iq->node, "to", who); + + jabber_iq_set_callback(iq,jabber_caps_client_iqcb,userdata); + jabber_iq_send(iq); + } else { + GList *iter; + /* fetch unknown exts only */ + for(iter = userdata->ext; iter; iter = g_list_next(iter)) { + JabberCapsValueExt *extvalue = g_hash_table_lookup(client->ext, (const char*)iter->data); + JabberIq *iq; + xmlnode *query; + char *nodever; + + if(extvalue) { + /* we already have this ext, don't bother with it */ + --userdata->extOutstanding; + continue; + } + + 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, (const char*)iter->data); + xmlnode_set_attrib(query, "node", nodever); + g_free(nodever); + xmlnode_set_attrib(iq->node, "to", who); + + jabber_iq_set_callback(iq,jabber_caps_ext_iqcb,userdata); + jabber_iq_send(iq); + } + /* maybe we have all data available anyways? This is the ideal case where no network traffic is necessary */ + jabber_caps_get_info_check_completion(userdata); + } +} +