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
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
33 #define JABBER_CAPS_FILENAME "xmpp-caps.xml"
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* */
46 free_string_glist(GList
*list
)
50 list
= g_list_delete_link(list
, list
);
54 static JabberCapsNodeExts
*
55 jabber_caps_node_exts_ref(JabberCapsNodeExts
*exts
)
57 g_return_val_if_fail(exts
!= NULL
, NULL
);
64 jabber_caps_node_exts_unref(JabberCapsNodeExts
*exts
)
69 g_return_if_fail(exts
->ref
!= 0);
74 g_hash_table_destroy(exts
->exts
);
78 static guint
jabber_caps_hash(gconstpointer data
) {
79 const JabberCapsTuple
*key
= data
;
80 guint nodehash
= g_str_hash(key
->node
);
81 guint verhash
= g_str_hash(key
->ver
);
83 * 'hash' was optional in XEP-0115 v1.4 and g_str_hash crashes on NULL >:O.
84 * Okay, maybe I've played too much Zelda, but that looks like
87 guint hashhash
= (key
->hash
? g_str_hash(key
->hash
) : 0);
88 return nodehash
^ verhash
^ hashhash
;
91 static gboolean
jabber_caps_compare(gconstpointer v1
, gconstpointer v2
) {
92 const JabberCapsTuple
*name1
= v1
;
93 const JabberCapsTuple
*name2
= v2
;
95 return purple_strequal(name1
->node
, name2
->node
) &&
96 purple_strequal(name1
->ver
, name2
->ver
) &&
97 purple_strequal(name1
->hash
, name2
->hash
);
101 jabber_caps_client_info_destroy(JabberCapsClientInfo
*info
)
106 while(info
->identities
) {
107 JabberIdentity
*id
= info
->identities
->data
;
108 g_free(id
->category
);
113 info
->identities
= g_list_delete_link(info
->identities
, info
->identities
);
116 free_string_glist(info
->features
);
118 while (info
->forms
) {
119 purple_xmlnode_free(info
->forms
->data
);
120 info
->forms
= g_list_delete_link(info
->forms
, info
->forms
);
123 jabber_caps_node_exts_unref(info
->exts
);
125 g_free((char *)info
->tuple
.node
);
126 g_free((char *)info
->tuple
.ver
);
127 g_free((char *)info
->tuple
.hash
);
132 /* NOTE: Takes a reference to the exts, unref it if you don't really want to
134 static JabberCapsNodeExts
*
135 jabber_caps_find_exts_by_node(const char *node
)
137 JabberCapsNodeExts
*exts
;
138 if (NULL
== (exts
= g_hash_table_lookup(nodetable
, node
))) {
139 exts
= g_new0(JabberCapsNodeExts
, 1);
140 exts
->exts
= g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
,
141 (GDestroyNotify
)free_string_glist
);
142 g_hash_table_insert(nodetable
, g_strdup(node
), jabber_caps_node_exts_ref(exts
));
145 return jabber_caps_node_exts_ref(exts
);
149 exts_to_xmlnode(gconstpointer key
, gconstpointer value
, gpointer user_data
)
151 const char *identifier
= key
;
152 const GList
*features
= value
, *node
;
153 PurpleXmlNode
*client
= user_data
, *ext
, *feature
;
155 ext
= purple_xmlnode_new_child(client
, "ext");
156 purple_xmlnode_set_attrib(ext
, "identifier", identifier
);
158 for (node
= features
; node
; node
= node
->next
) {
159 feature
= purple_xmlnode_new_child(ext
, "feature");
160 purple_xmlnode_set_attrib(feature
, "var", (const gchar
*)node
->data
);
164 static void jabber_caps_store_client(gpointer key
, gpointer value
, gpointer user_data
) {
165 const JabberCapsTuple
*tuple
= key
;
166 const JabberCapsClientInfo
*props
= value
;
167 PurpleXmlNode
*root
= user_data
;
168 PurpleXmlNode
*client
= purple_xmlnode_new_child(root
, "client");
171 purple_xmlnode_set_attrib(client
, "node", tuple
->node
);
172 purple_xmlnode_set_attrib(client
, "ver", tuple
->ver
);
174 purple_xmlnode_set_attrib(client
, "hash", tuple
->hash
);
175 for(iter
= props
->identities
; iter
; iter
= g_list_next(iter
)) {
176 JabberIdentity
*id
= iter
->data
;
177 PurpleXmlNode
*identity
= purple_xmlnode_new_child(client
, "identity");
178 purple_xmlnode_set_attrib(identity
, "category", id
->category
);
179 purple_xmlnode_set_attrib(identity
, "type", id
->type
);
181 purple_xmlnode_set_attrib(identity
, "name", id
->name
);
183 purple_xmlnode_set_attrib(identity
, "lang", id
->lang
);
186 for(iter
= props
->features
; iter
; iter
= g_list_next(iter
)) {
187 const char *feat
= iter
->data
;
188 PurpleXmlNode
*feature
= purple_xmlnode_new_child(client
, "feature");
189 purple_xmlnode_set_attrib(feature
, "var", feat
);
192 for(iter
= props
->forms
; iter
; iter
= g_list_next(iter
)) {
193 /* FIXME: See #7814 */
194 PurpleXmlNode
*xdata
= iter
->data
;
195 purple_xmlnode_insert_child(client
, purple_xmlnode_copy(xdata
));
198 /* TODO: Ideally, only save this once-per-node... */
200 g_hash_table_foreach(props
->exts
->exts
, (GHFunc
)exts_to_xmlnode
, client
);
204 do_jabber_caps_store(gpointer data
)
208 PurpleXmlNode
*root
= purple_xmlnode_new("capabilities");
210 g_hash_table_foreach(capstable
, jabber_caps_store_client
, root
);
211 str
= purple_xmlnode_to_formatted_str(root
, &length
);
212 purple_xmlnode_free(root
);
213 purple_util_write_data_to_cache_file(JABBER_CAPS_FILENAME
, str
, length
);
221 schedule_caps_save(void)
224 save_timer
= g_timeout_add_seconds(5, do_jabber_caps_store
, NULL
);
228 jabber_caps_load(void)
230 PurpleXmlNode
*capsdata
= purple_util_read_xml_from_cache_file(JABBER_CAPS_FILENAME
, "XMPP capabilities cache");
231 PurpleXmlNode
*client
;
236 if (!purple_strequal(capsdata
->name
, "capabilities")) {
237 purple_xmlnode_free(capsdata
);
241 for (client
= capsdata
->child
; client
; client
= client
->next
) {
242 if (client
->type
!= PURPLE_XMLNODE_TYPE_TAG
)
244 if (purple_strequal(client
->name
, "client")) {
245 JabberCapsClientInfo
*value
= g_new0(JabberCapsClientInfo
, 1);
246 JabberCapsTuple
*key
= (JabberCapsTuple
*)&value
->tuple
;
247 PurpleXmlNode
*child
;
248 JabberCapsNodeExts
*exts
= NULL
;
249 key
->node
= g_strdup(purple_xmlnode_get_attrib(client
,"node"));
250 key
->ver
= g_strdup(purple_xmlnode_get_attrib(client
,"ver"));
251 key
->hash
= g_strdup(purple_xmlnode_get_attrib(client
,"hash"));
253 /* v1.3 capabilities */
254 if (key
->hash
== NULL
)
255 exts
= jabber_caps_find_exts_by_node(key
->node
);
257 for (child
= client
->child
; child
; child
= child
->next
) {
258 if (child
->type
!= PURPLE_XMLNODE_TYPE_TAG
)
260 if (purple_strequal(child
->name
, "feature")) {
261 const char *var
= purple_xmlnode_get_attrib(child
, "var");
264 value
->features
= g_list_append(value
->features
,g_strdup(var
));
265 } else if (purple_strequal(child
->name
, "identity")) {
266 const char *category
= purple_xmlnode_get_attrib(child
, "category");
267 const char *type
= purple_xmlnode_get_attrib(child
, "type");
268 const char *name
= purple_xmlnode_get_attrib(child
, "name");
269 const char *lang
= purple_xmlnode_get_attrib(child
, "lang");
272 if (!category
|| !type
)
275 id
= g_new0(JabberIdentity
, 1);
276 id
->category
= g_strdup(category
);
277 id
->type
= g_strdup(type
);
278 id
->name
= g_strdup(name
);
279 id
->lang
= g_strdup(lang
);
281 value
->identities
= g_list_append(value
->identities
,id
);
282 } else if (purple_strequal(child
->name
, "x")) {
283 /* TODO: See #7814 -- this might cause problems if anyone
284 * ever actually specifies forms. In fact, for this to
285 * work properly, that bug needs to be fixed in
286 * purple_xmlnode_from_str, not the output version... */
287 value
->forms
= g_list_append(value
->forms
, purple_xmlnode_copy(child
));
288 } else if (purple_strequal(child
->name
, "ext")) {
289 if (key
->hash
!= NULL
)
290 purple_debug_warning("jabber", "Ignoring exts when reading new-style caps\n");
292 /* TODO: Do we care about reading in the identities listed here? */
293 const char *identifier
= purple_xmlnode_get_attrib(child
, "identifier");
295 GList
*features
= NULL
;
300 for (node
= child
->child
; node
; node
= node
->next
) {
301 if (node
->type
!= PURPLE_XMLNODE_TYPE_TAG
)
303 if (purple_strequal(node
->name
, "feature")) {
304 const char *var
= purple_xmlnode_get_attrib(node
, "var");
307 features
= g_list_prepend(features
, g_strdup(var
));
312 g_hash_table_insert(exts
->exts
, g_strdup(identifier
),
315 purple_debug_warning("jabber", "Caps ext %s had no features.\n",
322 g_hash_table_replace(capstable
, key
, value
);
326 purple_xmlnode_free(capsdata
);
329 void jabber_caps_init(void)
331 nodetable
= g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, (GDestroyNotify
)jabber_caps_node_exts_unref
);
332 capstable
= g_hash_table_new_full(jabber_caps_hash
, jabber_caps_compare
, NULL
, (GDestroyNotify
)jabber_caps_client_info_destroy
);
336 void jabber_caps_uninit(void)
338 if (save_timer
!= 0) {
339 g_source_remove(save_timer
);
341 do_jabber_caps_store(NULL
);
343 g_hash_table_destroy(capstable
);
344 g_hash_table_destroy(nodetable
);
345 capstable
= nodetable
= NULL
;
348 gboolean
jabber_caps_exts_known(const JabberCapsClientInfo
*info
,
352 g_return_val_if_fail(info
!= NULL
, FALSE
);
357 for (i
= 0; exts
[i
]; ++i
) {
358 /* Hack since we advertise the ext along with v1.5 caps but don't
360 if (purple_strequal(exts
[i
], "voice-v1") && !info
->exts
)
363 !g_hash_table_lookup(info
->exts
->exts
, exts
[i
]))
373 jabber_caps_get_info_cb cb
;
381 JabberCapsClientInfo
*info
;
384 guint extOutstanding
;
385 JabberCapsNodeExts
*node_exts
;
386 } jabber_caps_cbplususerdata
;
388 static jabber_caps_cbplususerdata
*
389 cbplususerdata_ref(jabber_caps_cbplususerdata
*data
)
391 g_return_val_if_fail(data
!= NULL
, NULL
);
398 cbplususerdata_unref(jabber_caps_cbplususerdata
*data
)
403 g_return_if_fail(data
->ref
!= 0);
413 /* If we have info here, it's already in the capstable, so don't free it */
415 free_string_glist(data
->exts
);
417 jabber_caps_node_exts_unref(data
->node_exts
);
422 jabber_caps_get_info_complete(jabber_caps_cbplususerdata
*userdata
)
425 userdata
->cb(userdata
->info
, userdata
->exts
, userdata
->cb_data
);
426 userdata
->info
= NULL
;
427 userdata
->exts
= NULL
;
430 if (userdata
->ref
!= 1)
431 purple_debug_warning("jabber", "Lost a reference to caps cbdata: %d\n",
436 jabber_caps_client_iqcb(JabberStream
*js
, const char *from
, JabberIqType type
,
437 const char *id
, PurpleXmlNode
*packet
, gpointer data
)
439 PurpleXmlNode
*query
= purple_xmlnode_get_child_with_namespace(packet
, "query",
441 jabber_caps_cbplususerdata
*userdata
= data
;
442 JabberCapsClientInfo
*info
= NULL
, *value
;
445 if (!query
|| type
== JABBER_IQ_ERROR
) {
446 /* Any outstanding exts will be dealt with via ref-counting */
447 userdata
->cb(NULL
, NULL
, userdata
->cb_data
);
448 cbplususerdata_unref(userdata
);
453 info
= jabber_caps_parse_client_info(query
);
455 /* Only validate if these are v1.5 capabilities */
456 if (userdata
->hash
) {
458 GChecksumType hash_type
;
459 gboolean supported_hash
= TRUE
;
461 if (purple_strequal(userdata
->hash
, "sha-1")) {
462 hash_type
= G_CHECKSUM_SHA1
;
463 } else if (purple_strequal(userdata
->hash
, "md5")) {
464 hash_type
= G_CHECKSUM_MD5
;
466 supported_hash
= FALSE
;
469 if (supported_hash
) {
470 hash
= jabber_caps_calculate_hash(info
, hash_type
);
473 if (!hash
|| !purple_strequal(hash
, userdata
->ver
)) {
474 purple_debug_warning("jabber", "Could not validate caps info from "
475 "%s. Expected %s, got %s\n",
476 purple_xmlnode_get_attrib(packet
, "from"),
477 userdata
->ver
, hash
? hash
: "(null)");
479 userdata
->cb(NULL
, NULL
, userdata
->cb_data
);
480 jabber_caps_client_info_destroy(info
);
481 cbplususerdata_unref(userdata
);
489 if (!userdata
->hash
&& userdata
->node_exts
) {
490 /* If the ClientInfo doesn't have information about the exts, give them
491 * ours (along with our ref) */
492 info
->exts
= userdata
->node_exts
;
493 userdata
->node_exts
= NULL
;
496 key
.node
= userdata
->node
;
497 key
.ver
= userdata
->ver
;
498 key
.hash
= userdata
->hash
;
500 /* Use the copy of this data already in the table if it exists or insert
501 * a new one if we need to */
502 if ((value
= g_hash_table_lookup(capstable
, &key
))) {
503 jabber_caps_client_info_destroy(info
);
506 JabberCapsTuple
*n_key
= NULL
;
508 if (G_UNLIKELY(info
== NULL
)) {
513 n_key
= (JabberCapsTuple
*)&info
->tuple
;
514 n_key
->node
= userdata
->node
;
515 n_key
->ver
= userdata
->ver
;
516 n_key
->hash
= userdata
->hash
;
517 userdata
->node
= userdata
->ver
= userdata
->hash
= NULL
;
519 /* The capstable gets a reference */
520 g_hash_table_insert(capstable
, n_key
, info
);
521 schedule_caps_save();
524 userdata
->info
= info
;
526 if (userdata
->extOutstanding
== 0)
527 jabber_caps_get_info_complete(userdata
);
529 cbplususerdata_unref(userdata
);
534 jabber_caps_cbplususerdata
*data
;
538 jabber_caps_ext_iqcb(JabberStream
*js
, const char *from
, JabberIqType type
,
539 const char *id
, PurpleXmlNode
*packet
, gpointer data
)
541 PurpleXmlNode
*query
= purple_xmlnode_get_child_with_namespace(packet
, "query",
543 PurpleXmlNode
*child
;
544 ext_iq_data
*userdata
= data
;
545 GList
*features
= NULL
;
546 JabberCapsNodeExts
*node_exts
;
548 if (!query
|| type
== JABBER_IQ_ERROR
) {
549 cbplususerdata_unref(userdata
->data
);
554 node_exts
= (userdata
->data
->info
? userdata
->data
->info
->exts
:
555 userdata
->data
->node_exts
);
557 /* TODO: I don't see how this can actually happen, but it crashed khc. */
559 purple_debug_error("jabber", "Couldn't find JabberCapsNodeExts. If you "
560 "see this, please tell darkrain42 and save your debug log.\n"
561 "JabberCapsClientInfo = %p\n", userdata
->data
->info
);
564 /* Try once more to find the exts and then fail */
565 node_exts
= jabber_caps_find_exts_by_node(userdata
->data
->node
);
567 purple_debug_info("jabber", "Found the exts on the second try.\n");
568 if (userdata
->data
->info
)
569 userdata
->data
->info
->exts
= node_exts
;
571 userdata
->data
->node_exts
= node_exts
;
573 cbplususerdata_unref(userdata
->data
);
575 g_return_if_reached();
579 /* So, we decrement this after checking for an error, which means that
580 * if there *is* an error, we'll never call the callback passed to
581 * jabber_caps_get_info. We will still free all of our data, though.
583 --userdata
->data
->extOutstanding
;
585 for (child
= purple_xmlnode_get_child(query
, "feature"); child
;
586 child
= purple_xmlnode_get_next_twin(child
)) {
587 const char *var
= purple_xmlnode_get_attrib(child
, "var");
589 features
= g_list_prepend(features
, g_strdup(var
));
592 g_hash_table_insert(node_exts
->exts
, g_strdup(userdata
->name
), features
);
593 schedule_caps_save();
596 if (userdata
->data
->info
&& userdata
->data
->extOutstanding
== 0)
597 jabber_caps_get_info_complete(userdata
->data
);
599 cbplususerdata_unref(userdata
->data
);
603 void jabber_caps_get_info(JabberStream
*js
, const char *who
, const char *node
,
604 const char *ver
, const char *hash
, char **exts
,
605 jabber_caps_get_info_cb cb
, gpointer user_data
)
607 JabberCapsClientInfo
*info
;
609 jabber_caps_cbplususerdata
*userdata
;
612 purple_debug_misc("jabber", "Ignoring exts in new-style caps from %s\n",
618 /* Using this in a read-only fashion, so the cast is OK */
619 key
.node
= (char *)node
;
620 key
.ver
= (char *)ver
;
621 key
.hash
= (char *)hash
;
623 info
= g_hash_table_lookup(capstable
, &key
);
625 /* v1.5 - We already have all the information we care about */
627 cb(info
, NULL
, user_data
);
631 userdata
= g_new0(jabber_caps_cbplususerdata
, 1);
632 /* We start out with 0 references. Every query takes one */
634 userdata
->cb_data
= user_data
;
635 userdata
->who
= g_strdup(who
);
636 userdata
->node
= g_strdup(node
);
637 userdata
->ver
= g_strdup(ver
);
638 userdata
->hash
= g_strdup(hash
);
641 userdata
->info
= info
;
643 /* If we don't have the basic information about the client, we need
646 PurpleXmlNode
*query
;
649 iq
= jabber_iq_new_query(js
, JABBER_IQ_GET
, NS_DISCO_INFO
);
650 query
= purple_xmlnode_get_child_with_namespace(iq
->node
, "query",
652 nodever
= g_strdup_printf("%s#%s", node
, ver
);
653 purple_xmlnode_set_attrib(query
, "node", nodever
);
655 purple_xmlnode_set_attrib(iq
->node
, "to", who
);
657 cbplususerdata_ref(userdata
);
659 jabber_iq_set_callback(iq
, jabber_caps_client_iqcb
, userdata
);
663 /* Are there any exts that we don't recognize? */
665 JabberCapsNodeExts
*node_exts
;
670 node_exts
= info
->exts
;
672 node_exts
= info
->exts
= jabber_caps_find_exts_by_node(node
);
674 /* We'll put it in later once we have the client info */
675 node_exts
= userdata
->node_exts
= jabber_caps_find_exts_by_node(node
);
677 for (i
= 0; exts
[i
]; ++i
) {
678 userdata
->exts
= g_list_prepend(userdata
->exts
, exts
[i
]);
679 /* Look it up if we don't already know what it means */
680 if (!g_hash_table_lookup(node_exts
->exts
, exts
[i
])) {
682 PurpleXmlNode
*query
;
684 ext_iq_data
*cbdata
= g_new(ext_iq_data
, 1);
686 cbdata
->name
= exts
[i
];
687 cbdata
->data
= cbplususerdata_ref(userdata
);
689 iq
= jabber_iq_new_query(js
, JABBER_IQ_GET
, NS_DISCO_INFO
);
690 query
= purple_xmlnode_get_child_with_namespace(iq
->node
, "query",
692 nodeext
= g_strdup_printf("%s#%s", node
, exts
[i
]);
693 purple_xmlnode_set_attrib(query
, "node", nodeext
);
695 purple_xmlnode_set_attrib(iq
->node
, "to", who
);
697 jabber_iq_set_callback(iq
, jabber_caps_ext_iqcb
, cbdata
);
700 ++userdata
->extOutstanding
;
704 /* All the strings are now part of the GList, so don't need
709 if (userdata
->info
&& userdata
->extOutstanding
== 0) {
710 /* Start with 1 ref so the below functions are happy */
713 /* We have everything we need right now */
714 jabber_caps_get_info_complete(userdata
);
715 cbplususerdata_unref(userdata
);
720 jabber_xdata_compare(gconstpointer a
, gconstpointer b
)
722 const PurpleXmlNode
*aformtypefield
= a
;
723 const PurpleXmlNode
*bformtypefield
= b
;
728 aformtype
= jabber_x_data_get_formtype(aformtypefield
);
729 bformtype
= jabber_x_data_get_formtype(bformtypefield
);
731 result
= strcmp(aformtype
, bformtype
);
737 JabberCapsClientInfo
*jabber_caps_parse_client_info(PurpleXmlNode
*query
)
739 PurpleXmlNode
*child
;
740 JabberCapsClientInfo
*info
;
742 if (!query
|| !purple_strequal(query
->name
, "query") ||
743 !purple_strequal(query
->xmlns
, NS_DISCO_INFO
))
746 info
= g_new0(JabberCapsClientInfo
, 1);
748 for(child
= query
->child
; child
; child
= child
->next
) {
749 if (child
->type
!= PURPLE_XMLNODE_TYPE_TAG
)
751 if (purple_strequal(child
->name
, "identity")) {
753 const char *category
= purple_xmlnode_get_attrib(child
, "category");
754 const char *type
= purple_xmlnode_get_attrib(child
, "type");
755 const char *name
= purple_xmlnode_get_attrib(child
, "name");
756 const char *lang
= purple_xmlnode_get_attrib(child
, "lang");
759 if (!category
|| !type
)
762 id
= g_new0(JabberIdentity
, 1);
763 id
->category
= g_strdup(category
);
764 id
->type
= g_strdup(type
);
765 id
->name
= g_strdup(name
);
766 id
->lang
= g_strdup(lang
);
768 info
->identities
= g_list_append(info
->identities
, id
);
769 } else if (purple_strequal(child
->name
, "feature")) {
771 const char *var
= purple_xmlnode_get_attrib(child
, "var");
773 info
->features
= g_list_prepend(info
->features
, g_strdup(var
));
774 } else if (purple_strequal(child
->name
, "x")) {
775 if (purple_strequal(child
->xmlns
, "jabber:x:data")) {
777 PurpleXmlNode
*dataform
= purple_xmlnode_copy(child
);
778 info
->forms
= g_list_append(info
->forms
, dataform
);
785 static gint
jabber_caps_xdata_field_compare(gconstpointer a
, gconstpointer b
)
787 const JabberDataFormField
*ac
= a
;
788 const JabberDataFormField
*bc
= b
;
790 return strcmp(ac
->var
, bc
->var
);
793 static GList
* jabber_caps_xdata_get_fields(const PurpleXmlNode
*x
)
795 GList
*fields
= NULL
;
796 PurpleXmlNode
*field
;
801 for (field
= purple_xmlnode_get_child(x
, "field"); field
; field
= purple_xmlnode_get_next_twin(field
)) {
802 PurpleXmlNode
*value
;
803 JabberDataFormField
*xdatafield
= g_new0(JabberDataFormField
, 1);
804 xdatafield
->var
= g_strdup(purple_xmlnode_get_attrib(field
, "var"));
806 for (value
= purple_xmlnode_get_child(field
, "value"); value
; value
= purple_xmlnode_get_next_twin(value
)) {
807 gchar
*val
= purple_xmlnode_get_data(value
);
808 xdatafield
->values
= g_list_prepend(xdatafield
->values
, val
);
811 xdatafield
->values
= g_list_sort(xdatafield
->values
, (GCompareFunc
)strcmp
);
812 fields
= g_list_prepend(fields
, xdatafield
);
815 fields
= g_list_sort(fields
, jabber_caps_xdata_field_compare
);
820 append_escaped_string(GChecksum
*hash
, const gchar
*str
)
822 g_return_if_fail(hash
!= NULL
);
825 char *tmp
= g_markup_escape_text(str
, -1);
826 g_checksum_update(hash
, (const guchar
*)tmp
, -1);
830 g_checksum_update(hash
, (const guchar
*)"<", -1);
833 gchar
*jabber_caps_calculate_hash(JabberCapsClientInfo
*info
,
834 GChecksumType hash_type
)
845 /* sort identities, features and x-data forms */
846 info
->identities
= g_list_sort(info
->identities
, jabber_identity_compare
);
847 info
->features
= g_list_sort(info
->features
, (GCompareFunc
)strcmp
);
848 info
->forms
= g_list_sort(info
->forms
, jabber_xdata_compare
);
850 hash
= g_checksum_new(hash_type
);
856 /* Add identities to the hash data */
857 for (node
= info
->identities
; node
; node
= node
->next
) {
858 JabberIdentity
*id
= (JabberIdentity
*)node
->data
;
859 char *category
= g_markup_escape_text(id
->category
, -1);
860 char *type
= g_markup_escape_text(id
->type
, -1);
866 lang
= g_markup_escape_text(id
->lang
, -1);
868 name
= g_markup_escape_text(id
->name
, -1);
870 tmp
= g_strconcat(category
, "/", type
, "/", lang
? lang
: "",
871 "/", name
? name
: "", "<", NULL
);
873 g_checksum_update(hash
, (const guchar
*)tmp
, -1);
882 /* concat features to the verification string */
883 for (node
= info
->features
; node
; node
= node
->next
) {
884 append_escaped_string(hash
, node
->data
);
887 /* concat x-data forms to the verification string */
888 for(node
= info
->forms
; node
; node
= node
->next
) {
889 PurpleXmlNode
*data
= (PurpleXmlNode
*)node
->data
;
890 gchar
*formtype
= jabber_x_data_get_formtype(data
);
891 GList
*fields
= jabber_caps_xdata_get_fields(data
);
893 /* append FORM_TYPE's field value to the verification string */
894 append_escaped_string(hash
, formtype
);
898 JabberDataFormField
*field
= (JabberDataFormField
*)fields
->data
;
900 if (!purple_strequal(field
->var
, "FORM_TYPE")) {
901 /* Append the "var" attribute */
902 append_escaped_string(hash
, field
->var
);
903 /* Append <value/> elements' cdata */
904 while (field
->values
) {
905 append_escaped_string(hash
, field
->values
->data
);
906 g_free(field
->values
->data
);
907 field
->values
= g_list_delete_link(field
->values
,
911 g_list_free_full(field
->values
, g_free
);
917 fields
= g_list_delete_link(fields
, fields
);
921 checksum_size
= g_checksum_type_get_length(hash_type
);
922 checksum
= g_new(guint8
, checksum_size
);
925 g_checksum_get_digest(hash
, checksum
, &checksum_size
);
927 ret
= g_base64_encode(checksum
, checksum_size
);
933 void jabber_caps_calculate_own_hash(JabberStream
*js
) {
934 JabberCapsClientInfo info
;
936 GList
*features
= NULL
;
938 if (!jabber_identities
&& !jabber_features
) {
939 /* This really shouldn't ever happen */
940 purple_debug_warning("jabber", "No features or identities, cannot calculate own caps hash.\n");
941 g_free(js
->caps_hash
);
942 js
->caps_hash
= NULL
;
946 /* build the currently-supported list of features */
947 if (jabber_features
) {
948 for (iter
= jabber_features
; iter
; iter
= iter
->next
) {
949 JabberFeature
*feat
= iter
->data
;
950 if(!feat
->is_enabled
|| feat
->is_enabled(js
, feat
->namespace)) {
951 features
= g_list_append(features
, feat
->namespace);
956 info
.features
= features
;
957 /* TODO: This copy can go away, I think, since jabber_identities
958 * is pre-sorted, so the sort in calculate_hash should be idempotent.
959 * However, I want to test that. --darkrain
961 info
.identities
= g_list_copy(jabber_identities
);
964 g_free(js
->caps_hash
);
965 js
->caps_hash
= jabber_caps_calculate_hash(&info
, G_CHECKSUM_SHA1
);
966 g_list_free(info
.identities
);
967 g_list_free(info
.features
);
970 const gchar
* jabber_caps_get_own_hash(JabberStream
*js
)
973 jabber_caps_calculate_own_hash(js
);
975 return js
->caps_hash
;
978 void jabber_caps_broadcast_change()
980 GList
*node
, *accounts
= purple_accounts_get_all_active();
982 for (node
= accounts
; node
; node
= node
->next
) {
983 PurpleAccount
*account
= node
->data
;
984 const char *protocol_id
= purple_account_get_protocol_id(account
);
985 if (purple_strequal("prpl-jabber", protocol_id
) && purple_account_is_connected(account
)) {
986 PurpleConnection
*gc
= purple_account_get_connection(account
);
987 jabber_presence_send(purple_connection_get_protocol_data(gc
), TRUE
);
991 g_list_free(accounts
);