Replace functions which called once with their bodies
[pidgin-git.git] / libpurple / protocols / jabber / caps.c
blob96b1ba3342412ab990cde454c424a0b550a3f2ff
1 /*
2 * purple - Jabber Protocol Plugin
4 * Purple is the legal property of its developers, whose names are too numerous
5 * to list here. Please refer to the COPYRIGHT file distributed with this
6 * source distribution.
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
24 #include "internal.h"
26 #include "debug.h"
27 #include "caps.h"
28 #include "iq.h"
29 #include "presence.h"
30 #include "util.h"
31 #include "xdata.h"
33 #define JABBER_CAPS_FILENAME "xmpp-caps.xml"
35 typedef struct {
36 gchar *var;
37 GList *values;
38 } JabberDataFormField;
40 static GHashTable *capstable = NULL; /* JabberCapsTuple -> JabberCapsClientInfo */
41 static GHashTable *nodetable = NULL; /* char *node -> JabberCapsNodeExts */
42 static guint save_timer = 0;
44 /* Free a GList of allocated char* */
45 static void
46 free_string_glist(GList *list)
48 g_list_free_full(list, g_free);
51 static JabberCapsNodeExts*
52 jabber_caps_node_exts_ref(JabberCapsNodeExts *exts)
54 g_return_val_if_fail(exts != NULL, NULL);
56 ++exts->ref;
57 return exts;
60 static void
61 jabber_caps_node_exts_unref(JabberCapsNodeExts *exts)
63 if (exts == NULL)
64 return;
66 g_return_if_fail(exts->ref != 0);
68 if (--exts->ref != 0)
69 return;
71 g_hash_table_destroy(exts->exts);
72 g_free(exts);
75 static guint jabber_caps_hash(gconstpointer data) {
76 const JabberCapsTuple *key = data;
77 guint nodehash = g_str_hash(key->node);
78 guint verhash = g_str_hash(key->ver);
80 * 'hash' was optional in XEP-0115 v1.4 and g_str_hash crashes on NULL >:O.
81 * Okay, maybe I've played too much Zelda, but that looks like
82 * a Deku Shrub...
84 guint hashhash = (key->hash ? g_str_hash(key->hash) : 0);
85 return nodehash ^ verhash ^ hashhash;
88 static gboolean jabber_caps_compare(gconstpointer v1, gconstpointer v2) {
89 const JabberCapsTuple *name1 = v1;
90 const JabberCapsTuple *name2 = v2;
92 return purple_strequal(name1->node, name2->node) &&
93 purple_strequal(name1->ver, name2->ver) &&
94 purple_strequal(name1->hash, name2->hash);
97 static void
98 jabber_caps_client_info_destroy(JabberCapsClientInfo *info)
100 if (info == NULL)
101 return;
103 while(info->identities) {
104 JabberIdentity *id = info->identities->data;
105 g_free(id->category);
106 g_free(id->type);
107 g_free(id->name);
108 g_free(id->lang);
109 g_free(id);
110 info->identities = g_list_delete_link(info->identities, info->identities);
113 free_string_glist(info->features);
115 g_list_free_full(info->forms, (GDestroyNotify)purple_xmlnode_free);
117 jabber_caps_node_exts_unref(info->exts);
119 g_free((char *)info->tuple.node);
120 g_free((char *)info->tuple.ver);
121 g_free((char *)info->tuple.hash);
123 g_free(info);
126 /* NOTE: Takes a reference to the exts, unref it if you don't really want to
127 * keep it around. */
128 static JabberCapsNodeExts*
129 jabber_caps_find_exts_by_node(const char *node)
131 JabberCapsNodeExts *exts;
132 if (NULL == (exts = g_hash_table_lookup(nodetable, node))) {
133 exts = g_new0(JabberCapsNodeExts, 1);
134 exts->exts = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
135 (GDestroyNotify)free_string_glist);
136 g_hash_table_insert(nodetable, g_strdup(node), jabber_caps_node_exts_ref(exts));
139 return jabber_caps_node_exts_ref(exts);
142 static void
143 exts_to_xmlnode(gconstpointer key, gconstpointer value, gpointer user_data)
145 const char *identifier = key;
146 const GList *features = value, *node;
147 PurpleXmlNode *client = user_data, *ext, *feature;
149 ext = purple_xmlnode_new_child(client, "ext");
150 purple_xmlnode_set_attrib(ext, "identifier", identifier);
152 for (node = features; node; node = node->next) {
153 feature = purple_xmlnode_new_child(ext, "feature");
154 purple_xmlnode_set_attrib(feature, "var", (const gchar *)node->data);
158 static void jabber_caps_store_client(gpointer key, gpointer value, gpointer user_data) {
159 const JabberCapsTuple *tuple = key;
160 const JabberCapsClientInfo *props = value;
161 PurpleXmlNode *root = user_data;
162 PurpleXmlNode *client = purple_xmlnode_new_child(root, "client");
163 GList *iter;
165 purple_xmlnode_set_attrib(client, "node", tuple->node);
166 purple_xmlnode_set_attrib(client, "ver", tuple->ver);
167 if (tuple->hash)
168 purple_xmlnode_set_attrib(client, "hash", tuple->hash);
169 for(iter = props->identities; iter; iter = g_list_next(iter)) {
170 JabberIdentity *id = iter->data;
171 PurpleXmlNode *identity = purple_xmlnode_new_child(client, "identity");
172 purple_xmlnode_set_attrib(identity, "category", id->category);
173 purple_xmlnode_set_attrib(identity, "type", id->type);
174 if (id->name)
175 purple_xmlnode_set_attrib(identity, "name", id->name);
176 if (id->lang)
177 purple_xmlnode_set_attrib(identity, "lang", id->lang);
180 for(iter = props->features; iter; iter = g_list_next(iter)) {
181 const char *feat = iter->data;
182 PurpleXmlNode *feature = purple_xmlnode_new_child(client, "feature");
183 purple_xmlnode_set_attrib(feature, "var", feat);
186 for(iter = props->forms; iter; iter = g_list_next(iter)) {
187 /* FIXME: See #7814 */
188 PurpleXmlNode *xdata = iter->data;
189 purple_xmlnode_insert_child(client, purple_xmlnode_copy(xdata));
192 /* TODO: Ideally, only save this once-per-node... */
193 if (props->exts)
194 g_hash_table_foreach(props->exts->exts, (GHFunc)exts_to_xmlnode, client);
197 static gboolean
198 do_jabber_caps_store(gpointer data)
200 char *str;
201 int length = 0;
202 PurpleXmlNode *root = purple_xmlnode_new("capabilities");
204 g_hash_table_foreach(capstable, jabber_caps_store_client, root);
205 str = purple_xmlnode_to_formatted_str(root, &length);
206 purple_xmlnode_free(root);
207 purple_util_write_data_to_cache_file(JABBER_CAPS_FILENAME, str, length);
208 g_free(str);
210 save_timer = 0;
211 return FALSE;
214 static void
215 schedule_caps_save(void)
217 if (save_timer == 0)
218 save_timer = g_timeout_add_seconds(5, do_jabber_caps_store, NULL);
221 static void
222 jabber_caps_load(void)
224 PurpleXmlNode *capsdata = purple_util_read_xml_from_cache_file(JABBER_CAPS_FILENAME, "XMPP capabilities cache");
225 PurpleXmlNode *client;
227 if(!capsdata)
228 return;
230 if (!purple_strequal(capsdata->name, "capabilities")) {
231 purple_xmlnode_free(capsdata);
232 return;
235 for (client = capsdata->child; client; client = client->next) {
236 if (client->type != PURPLE_XMLNODE_TYPE_TAG)
237 continue;
238 if (purple_strequal(client->name, "client")) {
239 JabberCapsClientInfo *value = g_new0(JabberCapsClientInfo, 1);
240 JabberCapsTuple *key = (JabberCapsTuple*)&value->tuple;
241 PurpleXmlNode *child;
242 JabberCapsNodeExts *exts = NULL;
243 key->node = g_strdup(purple_xmlnode_get_attrib(client,"node"));
244 key->ver = g_strdup(purple_xmlnode_get_attrib(client,"ver"));
245 key->hash = g_strdup(purple_xmlnode_get_attrib(client,"hash"));
247 /* v1.3 capabilities */
248 if (key->hash == NULL)
249 exts = jabber_caps_find_exts_by_node(key->node);
251 for (child = client->child; child; child = child->next) {
252 if (child->type != PURPLE_XMLNODE_TYPE_TAG)
253 continue;
254 if (purple_strequal(child->name, "feature")) {
255 const char *var = purple_xmlnode_get_attrib(child, "var");
256 if(!var)
257 continue;
258 value->features = g_list_append(value->features,g_strdup(var));
259 } else if (purple_strequal(child->name, "identity")) {
260 const char *category = purple_xmlnode_get_attrib(child, "category");
261 const char *type = purple_xmlnode_get_attrib(child, "type");
262 const char *name = purple_xmlnode_get_attrib(child, "name");
263 const char *lang = purple_xmlnode_get_attrib(child, "lang");
264 JabberIdentity *id;
266 if (!category || !type)
267 continue;
269 id = g_new0(JabberIdentity, 1);
270 id->category = g_strdup(category);
271 id->type = g_strdup(type);
272 id->name = g_strdup(name);
273 id->lang = g_strdup(lang);
275 value->identities = g_list_append(value->identities,id);
276 } else if (purple_strequal(child->name, "x")) {
277 /* TODO: See #7814 -- this might cause problems if anyone
278 * ever actually specifies forms. In fact, for this to
279 * work properly, that bug needs to be fixed in
280 * purple_xmlnode_from_str, not the output version... */
281 value->forms = g_list_append(value->forms, purple_xmlnode_copy(child));
282 } else if (purple_strequal(child->name, "ext")) {
283 if (key->hash != NULL)
284 purple_debug_warning("jabber", "Ignoring exts when reading new-style caps\n");
285 else {
286 /* TODO: Do we care about reading in the identities listed here? */
287 const char *identifier = purple_xmlnode_get_attrib(child, "identifier");
288 PurpleXmlNode *node;
289 GList *features = NULL;
291 if (!identifier)
292 continue;
294 for (node = child->child; node; node = node->next) {
295 if (node->type != PURPLE_XMLNODE_TYPE_TAG)
296 continue;
297 if (purple_strequal(node->name, "feature")) {
298 const char *var = purple_xmlnode_get_attrib(node, "var");
299 if (!var)
300 continue;
301 features = g_list_prepend(features, g_strdup(var));
305 if (features) {
306 g_hash_table_insert(exts->exts, g_strdup(identifier),
307 features);
308 } else
309 purple_debug_warning("jabber", "Caps ext %s had no features.\n",
310 identifier);
315 value->exts = exts;
316 g_hash_table_replace(capstable, key, value);
320 purple_xmlnode_free(capsdata);
323 void jabber_caps_init(void)
325 nodetable = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)jabber_caps_node_exts_unref);
326 capstable = g_hash_table_new_full(jabber_caps_hash, jabber_caps_compare, NULL, (GDestroyNotify)jabber_caps_client_info_destroy);
327 jabber_caps_load();
330 void jabber_caps_uninit(void)
332 if (save_timer != 0) {
333 g_source_remove(save_timer);
334 save_timer = 0;
335 do_jabber_caps_store(NULL);
337 g_hash_table_destroy(capstable);
338 g_hash_table_destroy(nodetable);
339 capstable = nodetable = NULL;
342 gboolean jabber_caps_exts_known(const JabberCapsClientInfo *info,
343 char **exts)
345 int i;
346 g_return_val_if_fail(info != NULL, FALSE);
348 if (!exts)
349 return TRUE;
351 for (i = 0; exts[i]; ++i) {
352 /* Hack since we advertise the ext along with v1.5 caps but don't
353 * store any exts */
354 if (purple_strequal(exts[i], "voice-v1") && !info->exts)
355 continue;
356 if (!info->exts ||
357 !g_hash_table_lookup(info->exts->exts, exts[i]))
358 return FALSE;
361 return TRUE;
364 typedef struct {
365 guint ref;
367 jabber_caps_get_info_cb cb;
368 gpointer cb_data;
370 char *who;
371 char *node;
372 char *ver;
373 char *hash;
375 JabberCapsClientInfo *info;
377 GList *exts;
378 guint extOutstanding;
379 JabberCapsNodeExts *node_exts;
380 } jabber_caps_cbplususerdata;
382 static jabber_caps_cbplususerdata*
383 cbplususerdata_ref(jabber_caps_cbplususerdata *data)
385 g_return_val_if_fail(data != NULL, NULL);
387 ++data->ref;
388 return data;
391 static void
392 cbplususerdata_unref(jabber_caps_cbplususerdata *data)
394 if (data == NULL)
395 return;
397 g_return_if_fail(data->ref != 0);
399 if (--data->ref > 0)
400 return;
402 g_free(data->who);
403 g_free(data->node);
404 g_free(data->ver);
405 g_free(data->hash);
407 /* If we have info here, it's already in the capstable, so don't free it */
408 if (data->exts)
409 free_string_glist(data->exts);
410 if (data->node_exts)
411 jabber_caps_node_exts_unref(data->node_exts);
412 g_free(data);
415 static void
416 jabber_caps_get_info_complete(jabber_caps_cbplususerdata *userdata)
418 if (userdata->cb) {
419 userdata->cb(userdata->info, userdata->exts, userdata->cb_data);
420 userdata->info = NULL;
421 userdata->exts = NULL;
424 if (userdata->ref != 1)
425 purple_debug_warning("jabber", "Lost a reference to caps cbdata: %d\n",
426 userdata->ref);
429 static void
430 jabber_caps_client_iqcb(JabberStream *js, const char *from, JabberIqType type,
431 const char *id, PurpleXmlNode *packet, gpointer data)
433 PurpleXmlNode *query = purple_xmlnode_get_child_with_namespace(packet, "query",
434 NS_DISCO_INFO);
435 jabber_caps_cbplususerdata *userdata = data;
436 JabberCapsClientInfo *info = NULL, *value;
437 JabberCapsTuple key;
439 if (!query || type == JABBER_IQ_ERROR) {
440 /* Any outstanding exts will be dealt with via ref-counting */
441 userdata->cb(NULL, NULL, userdata->cb_data);
442 cbplususerdata_unref(userdata);
443 return;
446 /* check hash */
447 info = jabber_caps_parse_client_info(query);
449 /* Only validate if these are v1.5 capabilities */
450 if (userdata->hash) {
451 gchar *hash = NULL;
452 GChecksumType hash_type;
453 gboolean supported_hash = TRUE;
455 if (purple_strequal(userdata->hash, "sha-1")) {
456 hash_type = G_CHECKSUM_SHA1;
457 } else if (purple_strequal(userdata->hash, "md5")) {
458 hash_type = G_CHECKSUM_MD5;
459 } else {
460 supported_hash = FALSE;
463 if (supported_hash) {
464 hash = jabber_caps_calculate_hash(info, hash_type);
467 if (!hash || !purple_strequal(hash, userdata->ver)) {
468 purple_debug_warning("jabber", "Could not validate caps info from "
469 "%s. Expected %s, got %s\n",
470 purple_xmlnode_get_attrib(packet, "from"),
471 userdata->ver, hash ? hash : "(null)");
473 userdata->cb(NULL, NULL, userdata->cb_data);
474 jabber_caps_client_info_destroy(info);
475 cbplususerdata_unref(userdata);
476 g_free(hash);
477 return;
480 g_free(hash);
483 if (!userdata->hash && userdata->node_exts) {
484 /* If the ClientInfo doesn't have information about the exts, give them
485 * ours (along with our ref) */
486 info->exts = userdata->node_exts;
487 userdata->node_exts = NULL;
490 key.node = userdata->node;
491 key.ver = userdata->ver;
492 key.hash = userdata->hash;
494 /* Use the copy of this data already in the table if it exists or insert
495 * a new one if we need to */
496 if ((value = g_hash_table_lookup(capstable, &key))) {
497 jabber_caps_client_info_destroy(info);
498 info = value;
499 } else {
500 JabberCapsTuple *n_key = NULL;
502 if (G_UNLIKELY(info == NULL)) {
503 g_warn_if_reached();
504 return;
507 n_key = (JabberCapsTuple *)&info->tuple;
508 n_key->node = userdata->node;
509 n_key->ver = userdata->ver;
510 n_key->hash = userdata->hash;
511 userdata->node = userdata->ver = userdata->hash = NULL;
513 /* The capstable gets a reference */
514 g_hash_table_insert(capstable, n_key, info);
515 schedule_caps_save();
518 userdata->info = info;
520 if (userdata->extOutstanding == 0)
521 jabber_caps_get_info_complete(userdata);
523 cbplususerdata_unref(userdata);
526 typedef struct {
527 const char *name;
528 jabber_caps_cbplususerdata *data;
529 } ext_iq_data;
531 static void
532 jabber_caps_ext_iqcb(JabberStream *js, const char *from, JabberIqType type,
533 const char *id, PurpleXmlNode *packet, gpointer data)
535 PurpleXmlNode *query = purple_xmlnode_get_child_with_namespace(packet, "query",
536 NS_DISCO_INFO);
537 PurpleXmlNode *child;
538 ext_iq_data *userdata = data;
539 GList *features = NULL;
540 JabberCapsNodeExts *node_exts;
542 if (!query || type == JABBER_IQ_ERROR) {
543 cbplususerdata_unref(userdata->data);
544 g_free(userdata);
545 return;
548 node_exts = (userdata->data->info ? userdata->data->info->exts :
549 userdata->data->node_exts);
551 /* TODO: I don't see how this can actually happen, but it crashed khc. */
552 if (!node_exts) {
553 purple_debug_error("jabber", "Couldn't find JabberCapsNodeExts. If you "
554 "see this, please tell darkrain42 and save your debug log.\n"
555 "JabberCapsClientInfo = %p\n", userdata->data->info);
558 /* Try once more to find the exts and then fail */
559 node_exts = jabber_caps_find_exts_by_node(userdata->data->node);
560 if (node_exts) {
561 purple_debug_info("jabber", "Found the exts on the second try.\n");
562 if (userdata->data->info)
563 userdata->data->info->exts = node_exts;
564 else
565 userdata->data->node_exts = node_exts;
566 } else {
567 cbplususerdata_unref(userdata->data);
568 g_free(userdata);
569 g_return_if_reached();
573 /* So, we decrement this after checking for an error, which means that
574 * if there *is* an error, we'll never call the callback passed to
575 * jabber_caps_get_info. We will still free all of our data, though.
577 --userdata->data->extOutstanding;
579 for (child = purple_xmlnode_get_child(query, "feature"); child;
580 child = purple_xmlnode_get_next_twin(child)) {
581 const char *var = purple_xmlnode_get_attrib(child, "var");
582 if (var)
583 features = g_list_prepend(features, g_strdup(var));
586 g_hash_table_insert(node_exts->exts, g_strdup(userdata->name), features);
587 schedule_caps_save();
589 /* Are we done? */
590 if (userdata->data->info && userdata->data->extOutstanding == 0)
591 jabber_caps_get_info_complete(userdata->data);
593 cbplususerdata_unref(userdata->data);
594 g_free(userdata);
597 void jabber_caps_get_info(JabberStream *js, const char *who, const char *node,
598 const char *ver, const char *hash, char **exts,
599 jabber_caps_get_info_cb cb, gpointer user_data)
601 JabberCapsClientInfo *info;
602 JabberCapsTuple key;
603 jabber_caps_cbplususerdata *userdata;
605 if (exts && hash) {
606 purple_debug_misc("jabber", "Ignoring exts in new-style caps from %s\n",
607 who);
608 g_strfreev(exts);
609 exts = NULL;
612 /* Using this in a read-only fashion, so the cast is OK */
613 key.node = (char *)node;
614 key.ver = (char *)ver;
615 key.hash = (char *)hash;
617 info = g_hash_table_lookup(capstable, &key);
618 if (info && hash) {
619 /* v1.5 - We already have all the information we care about */
620 if (cb)
621 cb(info, NULL, user_data);
622 return;
625 userdata = g_new0(jabber_caps_cbplususerdata, 1);
626 /* We start out with 0 references. Every query takes one */
627 userdata->cb = cb;
628 userdata->cb_data = user_data;
629 userdata->who = g_strdup(who);
630 userdata->node = g_strdup(node);
631 userdata->ver = g_strdup(ver);
632 userdata->hash = g_strdup(hash);
634 if (info) {
635 userdata->info = info;
636 } else {
637 /* If we don't have the basic information about the client, we need
638 * to fetch it. */
639 JabberIq *iq;
640 PurpleXmlNode *query;
641 char *nodever;
643 iq = jabber_iq_new_query(js, JABBER_IQ_GET, NS_DISCO_INFO);
644 query = purple_xmlnode_get_child_with_namespace(iq->node, "query",
645 NS_DISCO_INFO);
646 nodever = g_strdup_printf("%s#%s", node, ver);
647 purple_xmlnode_set_attrib(query, "node", nodever);
648 g_free(nodever);
649 purple_xmlnode_set_attrib(iq->node, "to", who);
651 cbplususerdata_ref(userdata);
653 jabber_iq_set_callback(iq, jabber_caps_client_iqcb, userdata);
654 jabber_iq_send(iq);
657 /* Are there any exts that we don't recognize? */
658 if (exts) {
659 JabberCapsNodeExts *node_exts;
660 int i;
662 if (info) {
663 if (info->exts)
664 node_exts = info->exts;
665 else
666 node_exts = info->exts = jabber_caps_find_exts_by_node(node);
667 } else
668 /* We'll put it in later once we have the client info */
669 node_exts = userdata->node_exts = jabber_caps_find_exts_by_node(node);
671 for (i = 0; exts[i]; ++i) {
672 userdata->exts = g_list_prepend(userdata->exts, exts[i]);
673 /* Look it up if we don't already know what it means */
674 if (!g_hash_table_lookup(node_exts->exts, exts[i])) {
675 JabberIq *iq;
676 PurpleXmlNode *query;
677 char *nodeext;
678 ext_iq_data *cbdata = g_new(ext_iq_data, 1);
680 cbdata->name = exts[i];
681 cbdata->data = cbplususerdata_ref(userdata);
683 iq = jabber_iq_new_query(js, JABBER_IQ_GET, NS_DISCO_INFO);
684 query = purple_xmlnode_get_child_with_namespace(iq->node, "query",
685 NS_DISCO_INFO);
686 nodeext = g_strdup_printf("%s#%s", node, exts[i]);
687 purple_xmlnode_set_attrib(query, "node", nodeext);
688 g_free(nodeext);
689 purple_xmlnode_set_attrib(iq->node, "to", who);
691 jabber_iq_set_callback(iq, jabber_caps_ext_iqcb, cbdata);
692 jabber_iq_send(iq);
694 ++userdata->extOutstanding;
696 exts[i] = NULL;
698 /* All the strings are now part of the GList, so don't need
699 * g_strfreev. */
700 g_free(exts);
703 if (userdata->info && userdata->extOutstanding == 0) {
704 /* Start with 1 ref so the below functions are happy */
705 userdata->ref = 1;
707 /* We have everything we need right now */
708 jabber_caps_get_info_complete(userdata);
709 cbplususerdata_unref(userdata);
713 static gint
714 jabber_xdata_compare(gconstpointer a, gconstpointer b)
716 const PurpleXmlNode *aformtypefield = a;
717 const PurpleXmlNode *bformtypefield = b;
718 char *aformtype;
719 char *bformtype;
720 int result;
722 aformtype = jabber_x_data_get_formtype(aformtypefield);
723 bformtype = jabber_x_data_get_formtype(bformtypefield);
725 result = strcmp(aformtype, bformtype);
726 g_free(aformtype);
727 g_free(bformtype);
728 return result;
731 JabberCapsClientInfo *jabber_caps_parse_client_info(PurpleXmlNode *query)
733 PurpleXmlNode *child;
734 JabberCapsClientInfo *info;
736 if (!query || !purple_strequal(query->name, "query") ||
737 !purple_strequal(query->xmlns, NS_DISCO_INFO))
738 return NULL;
740 info = g_new0(JabberCapsClientInfo, 1);
742 for(child = query->child; child; child = child->next) {
743 if (child->type != PURPLE_XMLNODE_TYPE_TAG)
744 continue;
745 if (purple_strequal(child->name, "identity")) {
746 /* parse identity */
747 const char *category = purple_xmlnode_get_attrib(child, "category");
748 const char *type = purple_xmlnode_get_attrib(child, "type");
749 const char *name = purple_xmlnode_get_attrib(child, "name");
750 const char *lang = purple_xmlnode_get_attrib(child, "lang");
751 JabberIdentity *id;
753 if (!category || !type)
754 continue;
756 id = g_new0(JabberIdentity, 1);
757 id->category = g_strdup(category);
758 id->type = g_strdup(type);
759 id->name = g_strdup(name);
760 id->lang = g_strdup(lang);
762 info->identities = g_list_append(info->identities, id);
763 } else if (purple_strequal(child->name, "feature")) {
764 /* parse feature */
765 const char *var = purple_xmlnode_get_attrib(child, "var");
766 if (var)
767 info->features = g_list_prepend(info->features, g_strdup(var));
768 } else if (purple_strequal(child->name, "x")) {
769 if (purple_strequal(child->xmlns, "jabber:x:data")) {
770 /* x-data form */
771 PurpleXmlNode *dataform = purple_xmlnode_copy(child);
772 info->forms = g_list_append(info->forms, dataform);
776 return info;
779 static gint jabber_caps_xdata_field_compare(gconstpointer a, gconstpointer b)
781 const JabberDataFormField *ac = a;
782 const JabberDataFormField *bc = b;
784 return strcmp(ac->var, bc->var);
787 static GList* jabber_caps_xdata_get_fields(const PurpleXmlNode *x)
789 GList *fields = NULL;
790 PurpleXmlNode *field;
792 if (!x)
793 return NULL;
795 for (field = purple_xmlnode_get_child(x, "field"); field; field = purple_xmlnode_get_next_twin(field)) {
796 PurpleXmlNode *value;
797 JabberDataFormField *xdatafield = g_new0(JabberDataFormField, 1);
798 xdatafield->var = g_strdup(purple_xmlnode_get_attrib(field, "var"));
800 for (value = purple_xmlnode_get_child(field, "value"); value; value = purple_xmlnode_get_next_twin(value)) {
801 gchar *val = purple_xmlnode_get_data(value);
802 xdatafield->values = g_list_prepend(xdatafield->values, val);
805 xdatafield->values = g_list_sort(xdatafield->values, (GCompareFunc)strcmp);
806 fields = g_list_prepend(fields, xdatafield);
809 fields = g_list_sort(fields, jabber_caps_xdata_field_compare);
810 return fields;
813 static void
814 append_escaped_string(GChecksum *hash, const gchar *str)
816 g_return_if_fail(hash != NULL);
818 if (str && *str) {
819 char *tmp = g_markup_escape_text(str, -1);
820 g_checksum_update(hash, (const guchar *)tmp, -1);
821 g_free(tmp);
824 g_checksum_update(hash, (const guchar *)"<", -1);
827 gchar *jabber_caps_calculate_hash(JabberCapsClientInfo *info,
828 GChecksumType hash_type)
830 GChecksum *hash;
831 GList *node;
832 guint8 *checksum;
833 gsize checksum_size;
834 gchar *ret;
836 if (!info)
837 return NULL;
839 /* sort identities, features and x-data forms */
840 info->identities = g_list_sort(info->identities, jabber_identity_compare);
841 info->features = g_list_sort(info->features, (GCompareFunc)strcmp);
842 info->forms = g_list_sort(info->forms, jabber_xdata_compare);
844 hash = g_checksum_new(hash_type);
846 if (hash == NULL) {
847 return NULL;
850 /* Add identities to the hash data */
851 for (node = info->identities; node; node = node->next) {
852 JabberIdentity *id = (JabberIdentity*)node->data;
853 char *category = g_markup_escape_text(id->category, -1);
854 char *type = g_markup_escape_text(id->type, -1);
855 char *lang = NULL;
856 char *name = NULL;
857 char *tmp;
859 if (id->lang)
860 lang = g_markup_escape_text(id->lang, -1);
861 if (id->name)
862 name = g_markup_escape_text(id->name, -1);
864 tmp = g_strconcat(category, "/", type, "/", lang ? lang : "",
865 "/", name ? name : "", "<", NULL);
867 g_checksum_update(hash, (const guchar *)tmp, -1);
869 g_free(tmp);
870 g_free(category);
871 g_free(type);
872 g_free(lang);
873 g_free(name);
876 /* concat features to the verification string */
877 for (node = info->features; node; node = node->next) {
878 append_escaped_string(hash, node->data);
881 /* concat x-data forms to the verification string */
882 for(node = info->forms; node; node = node->next) {
883 PurpleXmlNode *data = (PurpleXmlNode *)node->data;
884 gchar *formtype = jabber_x_data_get_formtype(data);
885 GList *fields = jabber_caps_xdata_get_fields(data);
887 /* append FORM_TYPE's field value to the verification string */
888 append_escaped_string(hash, formtype);
889 g_free(formtype);
891 while (fields) {
892 JabberDataFormField *field = (JabberDataFormField*)fields->data;
894 if (!purple_strequal(field->var, "FORM_TYPE")) {
895 /* Append the "var" attribute */
896 append_escaped_string(hash, field->var);
897 /* Append <value/> elements' cdata */
898 while (field->values) {
899 append_escaped_string(hash, field->values->data);
900 g_free(field->values->data);
901 field->values = g_list_delete_link(field->values,
902 field->values);
904 } else {
905 g_list_free_full(field->values, g_free);
908 g_free(field->var);
909 g_free(field);
911 fields = g_list_delete_link(fields, fields);
915 checksum_size = g_checksum_type_get_length(hash_type);
916 checksum = g_new(guint8, checksum_size);
918 /* generate hash */
919 g_checksum_get_digest(hash, checksum, &checksum_size);
921 ret = g_base64_encode(checksum, checksum_size);
922 g_free(checksum);
924 return ret;
927 void jabber_caps_calculate_own_hash(JabberStream *js) {
928 JabberCapsClientInfo info;
929 GList *iter = NULL;
930 GList *features = NULL;
932 if (!jabber_identities && !jabber_features) {
933 /* This really shouldn't ever happen */
934 purple_debug_warning("jabber", "No features or identities, cannot calculate own caps hash.\n");
935 g_free(js->caps_hash);
936 js->caps_hash = NULL;
937 return;
940 /* build the currently-supported list of features */
941 if (jabber_features) {
942 for (iter = jabber_features; iter; iter = iter->next) {
943 JabberFeature *feat = iter->data;
944 if(!feat->is_enabled || feat->is_enabled(js, feat->namespace)) {
945 features = g_list_append(features, feat->namespace);
950 info.features = features;
951 /* TODO: This copy can go away, I think, since jabber_identities
952 * is pre-sorted, so the sort in calculate_hash should be idempotent.
953 * However, I want to test that. --darkrain
955 info.identities = g_list_copy(jabber_identities);
956 info.forms = NULL;
958 g_free(js->caps_hash);
959 js->caps_hash = jabber_caps_calculate_hash(&info, G_CHECKSUM_SHA1);
960 g_list_free(info.identities);
961 g_list_free(info.features);
964 const gchar* jabber_caps_get_own_hash(JabberStream *js)
966 if (!js->caps_hash)
967 jabber_caps_calculate_own_hash(js);
969 return js->caps_hash;
972 void jabber_caps_broadcast_change()
974 GList *node, *accounts = purple_accounts_get_all_active();
976 for (node = accounts; node; node = node->next) {
977 PurpleAccount *account = node->data;
978 const char *protocol_id = purple_account_get_protocol_id(account);
979 if (purple_strequal("prpl-jabber", protocol_id) && purple_account_is_connected(account)) {
980 PurpleConnection *gc = purple_account_get_connection(account);
981 jabber_presence_send(purple_connection_get_protocol_data(gc), TRUE);
985 g_list_free(accounts);