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
)
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
);
61 jabber_caps_node_exts_unref(JabberCapsNodeExts
*exts
)
66 g_return_if_fail(exts
->ref
!= 0);
71 g_hash_table_destroy(exts
->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
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
);
98 jabber_caps_client_info_destroy(JabberCapsClientInfo
*info
)
103 while(info
->identities
) {
104 JabberIdentity
*id
= info
->identities
->data
;
105 g_free(id
->category
);
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
);
126 /* NOTE: Takes a reference to the exts, unref it if you don't really want to
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
);
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");
165 purple_xmlnode_set_attrib(client
, "node", tuple
->node
);
166 purple_xmlnode_set_attrib(client
, "ver", tuple
->ver
);
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
);
175 purple_xmlnode_set_attrib(identity
, "name", id
->name
);
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... */
194 g_hash_table_foreach(props
->exts
->exts
, (GHFunc
)exts_to_xmlnode
, client
);
198 do_jabber_caps_store(gpointer data
)
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
);
215 schedule_caps_save(void)
218 save_timer
= g_timeout_add_seconds(5, do_jabber_caps_store
, NULL
);
222 jabber_caps_load(void)
224 PurpleXmlNode
*capsdata
= purple_util_read_xml_from_cache_file(JABBER_CAPS_FILENAME
, "XMPP capabilities cache");
225 PurpleXmlNode
*client
;
230 if (!purple_strequal(capsdata
->name
, "capabilities")) {
231 purple_xmlnode_free(capsdata
);
235 for (client
= capsdata
->child
; client
; client
= client
->next
) {
236 if (client
->type
!= PURPLE_XMLNODE_TYPE_TAG
)
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
)
254 if (purple_strequal(child
->name
, "feature")) {
255 const char *var
= purple_xmlnode_get_attrib(child
, "var");
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");
266 if (!category
|| !type
)
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");
286 /* TODO: Do we care about reading in the identities listed here? */
287 const char *identifier
= purple_xmlnode_get_attrib(child
, "identifier");
289 GList
*features
= NULL
;
294 for (node
= child
->child
; node
; node
= node
->next
) {
295 if (node
->type
!= PURPLE_XMLNODE_TYPE_TAG
)
297 if (purple_strequal(node
->name
, "feature")) {
298 const char *var
= purple_xmlnode_get_attrib(node
, "var");
301 features
= g_list_prepend(features
, g_strdup(var
));
306 g_hash_table_insert(exts
->exts
, g_strdup(identifier
),
309 purple_debug_warning("jabber", "Caps ext %s had no features.\n",
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
);
330 void jabber_caps_uninit(void)
332 if (save_timer
!= 0) {
333 g_source_remove(save_timer
);
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
,
346 g_return_val_if_fail(info
!= NULL
, FALSE
);
351 for (i
= 0; exts
[i
]; ++i
) {
352 /* Hack since we advertise the ext along with v1.5 caps but don't
354 if (purple_strequal(exts
[i
], "voice-v1") && !info
->exts
)
357 !g_hash_table_lookup(info
->exts
->exts
, exts
[i
]))
367 jabber_caps_get_info_cb cb
;
375 JabberCapsClientInfo
*info
;
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
);
392 cbplususerdata_unref(jabber_caps_cbplususerdata
*data
)
397 g_return_if_fail(data
->ref
!= 0);
407 /* If we have info here, it's already in the capstable, so don't free it */
409 free_string_glist(data
->exts
);
411 jabber_caps_node_exts_unref(data
->node_exts
);
416 jabber_caps_get_info_complete(jabber_caps_cbplususerdata
*userdata
)
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",
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",
435 jabber_caps_cbplususerdata
*userdata
= data
;
436 JabberCapsClientInfo
*info
= NULL
, *value
;
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
);
447 info
= jabber_caps_parse_client_info(query
);
449 /* Only validate if these are v1.5 capabilities */
450 if (userdata
->hash
) {
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
;
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
);
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
);
500 JabberCapsTuple
*n_key
= NULL
;
502 if (G_UNLIKELY(info
== NULL
)) {
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
);
528 jabber_caps_cbplususerdata
*data
;
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",
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
);
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. */
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
);
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
;
565 userdata
->data
->node_exts
= node_exts
;
567 cbplususerdata_unref(userdata
->data
);
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");
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();
590 if (userdata
->data
->info
&& userdata
->data
->extOutstanding
== 0)
591 jabber_caps_get_info_complete(userdata
->data
);
593 cbplususerdata_unref(userdata
->data
);
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
;
603 jabber_caps_cbplususerdata
*userdata
;
606 purple_debug_misc("jabber", "Ignoring exts in new-style caps from %s\n",
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
);
619 /* v1.5 - We already have all the information we care about */
621 cb(info
, NULL
, user_data
);
625 userdata
= g_new0(jabber_caps_cbplususerdata
, 1);
626 /* We start out with 0 references. Every query takes one */
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
);
635 userdata
->info
= info
;
637 /* If we don't have the basic information about the client, we need
640 PurpleXmlNode
*query
;
643 iq
= jabber_iq_new_query(js
, JABBER_IQ_GET
, NS_DISCO_INFO
);
644 query
= purple_xmlnode_get_child_with_namespace(iq
->node
, "query",
646 nodever
= g_strdup_printf("%s#%s", node
, ver
);
647 purple_xmlnode_set_attrib(query
, "node", 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
);
657 /* Are there any exts that we don't recognize? */
659 JabberCapsNodeExts
*node_exts
;
664 node_exts
= info
->exts
;
666 node_exts
= info
->exts
= jabber_caps_find_exts_by_node(node
);
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
])) {
676 PurpleXmlNode
*query
;
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",
686 nodeext
= g_strdup_printf("%s#%s", node
, exts
[i
]);
687 purple_xmlnode_set_attrib(query
, "node", nodeext
);
689 purple_xmlnode_set_attrib(iq
->node
, "to", who
);
691 jabber_iq_set_callback(iq
, jabber_caps_ext_iqcb
, cbdata
);
694 ++userdata
->extOutstanding
;
698 /* All the strings are now part of the GList, so don't need
703 if (userdata
->info
&& userdata
->extOutstanding
== 0) {
704 /* Start with 1 ref so the below functions are happy */
707 /* We have everything we need right now */
708 jabber_caps_get_info_complete(userdata
);
709 cbplususerdata_unref(userdata
);
714 jabber_xdata_compare(gconstpointer a
, gconstpointer b
)
716 const PurpleXmlNode
*aformtypefield
= a
;
717 const PurpleXmlNode
*bformtypefield
= b
;
722 aformtype
= jabber_x_data_get_formtype(aformtypefield
);
723 bformtype
= jabber_x_data_get_formtype(bformtypefield
);
725 result
= strcmp(aformtype
, bformtype
);
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
))
740 info
= g_new0(JabberCapsClientInfo
, 1);
742 for(child
= query
->child
; child
; child
= child
->next
) {
743 if (child
->type
!= PURPLE_XMLNODE_TYPE_TAG
)
745 if (purple_strequal(child
->name
, "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");
753 if (!category
|| !type
)
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")) {
765 const char *var
= purple_xmlnode_get_attrib(child
, "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")) {
771 PurpleXmlNode
*dataform
= purple_xmlnode_copy(child
);
772 info
->forms
= g_list_append(info
->forms
, dataform
);
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
;
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
);
814 append_escaped_string(GChecksum
*hash
, const gchar
*str
)
816 g_return_if_fail(hash
!= NULL
);
819 char *tmp
= g_markup_escape_text(str
, -1);
820 g_checksum_update(hash
, (const guchar
*)tmp
, -1);
824 g_checksum_update(hash
, (const guchar
*)"<", -1);
827 gchar
*jabber_caps_calculate_hash(JabberCapsClientInfo
*info
,
828 GChecksumType hash_type
)
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
);
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);
860 lang
= g_markup_escape_text(id
->lang
, -1);
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);
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
);
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
,
905 g_list_free_full(field
->values
, g_free
);
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
);
919 g_checksum_get_digest(hash
, checksum
, &checksum_size
);
921 ret
= g_base64_encode(checksum
, checksum_size
);
927 void jabber_caps_calculate_own_hash(JabberStream
*js
) {
928 JabberCapsClientInfo info
;
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
;
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
);
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
)
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
);