pidgin/pidgin
Clone
Summary
Browse
Changes
Graph
Route GLib debug logging directly to the Finch debug window
2021-10-18, Elliott Sales de Andrade
1896a80ff8e3
Route GLib debug logging directly to the Finch debug window
Instead of flowing through purple debug, this merges some bits of the existing GLib log handler, and the purple debug printer.
Testing Done:
Open the Debug window an see some `GLib-*` outputs.
Reviewed at https://reviews.imfreedom.org/r/1057/
/*
* purple - Jabber Protocol Plugin
*
* Purple is the legal property of its developers, whose names are too numerous
* to list here. Please refer to the COPYRIGHT file distributed with this
* source distribution.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
*
*/
#include
<glib/gi18n-lib.h>
#include
<purple.h>
#include
"caps.h"
#include
"iq.h"
#include
"presence.h"
#include
"xdata.h"
#define JABBER_CAPS_FILENAME "xmpp-caps.xml"
typedef
struct
{
gchar
*
var
;
GList
*
values
;
}
JabberDataFormField
;
static
GHashTable
*
capstable
=
NULL
;
/* JabberCapsTuple -> JabberCapsClientInfo */
static
GHashTable
*
nodetable
=
NULL
;
/* char *node -> JabberCapsNodeExts */
static
guint
save_timer
=
0
;
/* Free a GList of allocated char* */
static
void
free_string_glist
(
GList
*
list
)
{
g_list_free_full
(
list
,
g_free
);
}
static
JabberCapsNodeExts
*
jabber_caps_node_exts_ref
(
JabberCapsNodeExts
*
exts
)
{
g_return_val_if_fail
(
exts
!=
NULL
,
NULL
);
++
exts
->
ref
;
return
exts
;
}
static
void
jabber_caps_node_exts_unref
(
JabberCapsNodeExts
*
exts
)
{
if
(
exts
==
NULL
)
return
;
g_return_if_fail
(
exts
->
ref
!=
0
);
if
(
--
exts
->
ref
!=
0
)
return
;
g_hash_table_destroy
(
exts
->
exts
);
g_free
(
exts
);
}
static
guint
jabber_caps_hash
(
gconstpointer
data
)
{
const
JabberCapsTuple
*
key
=
data
;
guint
nodehash
=
g_str_hash
(
key
->
node
);
guint
verhash
=
g_str_hash
(
key
->
ver
);
/*
* 'hash' was optional in XEP-0115 v1.4 and g_str_hash crashes on NULL >:O.
* Okay, maybe I've played too much Zelda, but that looks like
* a Deku Shrub...
*/
guint
hashhash
=
(
key
->
hash
?
g_str_hash
(
key
->
hash
)
:
0
);
return
nodehash
^
verhash
^
hashhash
;
}
static
gboolean
jabber_caps_compare
(
gconstpointer
v1
,
gconstpointer
v2
)
{
const
JabberCapsTuple
*
name1
=
v1
;
const
JabberCapsTuple
*
name2
=
v2
;
return
purple_strequal
(
name1
->
node
,
name2
->
node
)
&&
purple_strequal
(
name1
->
ver
,
name2
->
ver
)
&&
purple_strequal
(
name1
->
hash
,
name2
->
hash
);
}
static
void
jabber_caps_client_info_destroy
(
JabberCapsClientInfo
*
info
)
{
if
(
info
==
NULL
)
return
;
g_list_free_full
(
info
->
identities
,
(
GDestroyNotify
)
jabber_identity_free
);
free_string_glist
(
info
->
features
);
g_list_free_full
(
info
->
forms
,
(
GDestroyNotify
)
purple_xmlnode_free
);
jabber_caps_node_exts_unref
(
info
->
exts
);
g_free
((
char
*
)
info
->
tuple
.
node
);
g_free
((
char
*
)
info
->
tuple
.
ver
);
g_free
((
char
*
)
info
->
tuple
.
hash
);
g_free
(
info
);
}
/* NOTE: Takes a reference to the exts, unref it if you don't really want to
* keep it around. */
static
JabberCapsNodeExts
*
jabber_caps_find_exts_by_node
(
const
char
*
node
)
{
JabberCapsNodeExts
*
exts
;
if
(
NULL
==
(
exts
=
g_hash_table_lookup
(
nodetable
,
node
)))
{
exts
=
g_new0
(
JabberCapsNodeExts
,
1
);
exts
->
exts
=
g_hash_table_new_full
(
g_str_hash
,
g_str_equal
,
g_free
,
(
GDestroyNotify
)
free_string_glist
);
g_hash_table_insert
(
nodetable
,
g_strdup
(
node
),
jabber_caps_node_exts_ref
(
exts
));
}
return
jabber_caps_node_exts_ref
(
exts
);
}
static
void
exts_to_xmlnode
(
gconstpointer
key
,
gconstpointer
value
,
gpointer
user_data
)
{
const
char
*
identifier
=
key
;
const
GList
*
features
=
value
,
*
node
;
PurpleXmlNode
*
client
=
user_data
,
*
ext
,
*
feature
;
ext
=
purple_xmlnode_new_child
(
client
,
"ext"
);
purple_xmlnode_set_attrib
(
ext
,
"identifier"
,
identifier
);
for
(
node
=
features
;
node
;
node
=
node
->
next
)
{
feature
=
purple_xmlnode_new_child
(
ext
,
"feature"
);
purple_xmlnode_set_attrib
(
feature
,
"var"
,
(
const
gchar
*
)
node
->
data
);
}
}
static
void
jabber_caps_store_client
(
gpointer
key
,
gpointer
value
,
gpointer
user_data
)
{
const
JabberCapsTuple
*
tuple
=
key
;
const
JabberCapsClientInfo
*
props
=
value
;
PurpleXmlNode
*
root
=
user_data
;
PurpleXmlNode
*
client
=
purple_xmlnode_new_child
(
root
,
"client"
);
GList
*
iter
;
purple_xmlnode_set_attrib
(
client
,
"node"
,
tuple
->
node
);
purple_xmlnode_set_attrib
(
client
,
"ver"
,
tuple
->
ver
);
if
(
tuple
->
hash
)
purple_xmlnode_set_attrib
(
client
,
"hash"
,
tuple
->
hash
);
for
(
iter
=
props
->
identities
;
iter
;
iter
=
g_list_next
(
iter
))
{
JabberIdentity
*
id
=
iter
->
data
;
PurpleXmlNode
*
identity
=
purple_xmlnode_new_child
(
client
,
"identity"
);
purple_xmlnode_set_attrib
(
identity
,
"category"
,
id
->
category
);
purple_xmlnode_set_attrib
(
identity
,
"type"
,
id
->
type
);
if
(
id
->
name
)
purple_xmlnode_set_attrib
(
identity
,
"name"
,
id
->
name
);
if
(
id
->
lang
)
purple_xmlnode_set_attrib
(
identity
,
"lang"
,
id
->
lang
);
}
for
(
iter
=
props
->
features
;
iter
;
iter
=
g_list_next
(
iter
))
{
const
char
*
feat
=
iter
->
data
;
PurpleXmlNode
*
feature
=
purple_xmlnode_new_child
(
client
,
"feature"
);
purple_xmlnode_set_attrib
(
feature
,
"var"
,
feat
);
}
for
(
iter
=
props
->
forms
;
iter
;
iter
=
g_list_next
(
iter
))
{
/* FIXME: See #7814 */
PurpleXmlNode
*
xdata
=
iter
->
data
;
purple_xmlnode_insert_child
(
client
,
purple_xmlnode_copy
(
xdata
));
}
/* TODO: Ideally, only save this once-per-node... */
if
(
props
->
exts
)
g_hash_table_foreach
(
props
->
exts
->
exts
,
(
GHFunc
)
exts_to_xmlnode
,
client
);
}
static
gboolean
do_jabber_caps_store
(
gpointer
data
)
{
char
*
str
;
int
length
=
0
;
PurpleXmlNode
*
root
=
purple_xmlnode_new
(
"capabilities"
);
g_hash_table_foreach
(
capstable
,
jabber_caps_store_client
,
root
);
str
=
purple_xmlnode_to_formatted_str
(
root
,
&
length
);
purple_xmlnode_free
(
root
);
purple_util_write_data_to_cache_file
(
JABBER_CAPS_FILENAME
,
str
,
length
);
g_free
(
str
);
save_timer
=
0
;
return
FALSE
;
}
static
void
schedule_caps_save
(
void
)
{
if
(
save_timer
==
0
)
save_timer
=
g_timeout_add_seconds
(
5
,
do_jabber_caps_store
,
NULL
);
}
static
void
jabber_caps_load
(
void
)
{
PurpleXmlNode
*
capsdata
=
purple_util_read_xml_from_cache_file
(
JABBER_CAPS_FILENAME
,
"XMPP capabilities cache"
);
PurpleXmlNode
*
client
;
if
(
!
capsdata
)
return
;
if
(
!
purple_strequal
(
capsdata
->
name
,
"capabilities"
))
{
purple_xmlnode_free
(
capsdata
);
return
;
}
for
(
client
=
capsdata
->
child
;
client
;
client
=
client
->
next
)
{
if
(
client
->
type
!=
PURPLE_XMLNODE_TYPE_TAG
)
continue
;
if
(
purple_strequal
(
client
->
name
,
"client"
))
{
JabberCapsClientInfo
*
value
=
g_new0
(
JabberCapsClientInfo
,
1
);
JabberCapsTuple
*
key
=
(
JabberCapsTuple
*
)
&
value
->
tuple
;
PurpleXmlNode
*
child
;
JabberCapsNodeExts
*
exts
=
NULL
;
key
->
node
=
g_strdup
(
purple_xmlnode_get_attrib
(
client
,
"node"
));
key
->
ver
=
g_strdup
(
purple_xmlnode_get_attrib
(
client
,
"ver"
));
key
->
hash
=
g_strdup
(
purple_xmlnode_get_attrib
(
client
,
"hash"
));
/* v1.3 capabilities */
if
(
key
->
hash
==
NULL
)
exts
=
jabber_caps_find_exts_by_node
(
key
->
node
);
for
(
child
=
client
->
child
;
child
;
child
=
child
->
next
)
{
if
(
child
->
type
!=
PURPLE_XMLNODE_TYPE_TAG
)
continue
;
if
(
purple_strequal
(
child
->
name
,
"feature"
))
{
const
char
*
var
=
purple_xmlnode_get_attrib
(
child
,
"var"
);
if
(
!
var
)
continue
;
value
->
features
=
g_list_append
(
value
->
features
,
g_strdup
(
var
));
}
else
if
(
purple_strequal
(
child
->
name
,
"identity"
))
{
const
char
*
category
=
purple_xmlnode_get_attrib
(
child
,
"category"
);
const
char
*
type
=
purple_xmlnode_get_attrib
(
child
,
"type"
);
const
char
*
name
=
purple_xmlnode_get_attrib
(
child
,
"name"
);
const
char
*
lang
=
purple_xmlnode_get_attrib
(
child
,
"lang"
);
JabberIdentity
*
id
;
if
(
!
category
||
!
type
)
continue
;
id
=
jabber_identity_new
(
category
,
type
,
lang
,
name
);
value
->
identities
=
g_list_append
(
value
->
identities
,
id
);
}
else
if
(
purple_strequal
(
child
->
name
,
"x"
))
{
/* TODO: See #7814 -- this might cause problems if anyone
* ever actually specifies forms. In fact, for this to
* work properly, that bug needs to be fixed in
* purple_xmlnode_from_str, not the output version... */
value
->
forms
=
g_list_append
(
value
->
forms
,
purple_xmlnode_copy
(
child
));
}
else
if
(
purple_strequal
(
child
->
name
,
"ext"
))
{
if
(
key
->
hash
!=
NULL
)
purple_debug_warning
(
"jabber"
,
"Ignoring exts when reading new-style caps
\n
"
);
else
{
/* TODO: Do we care about reading in the identities listed here? */
const
char
*
identifier
=
purple_xmlnode_get_attrib
(
child
,
"identifier"
);
PurpleXmlNode
*
node
;
GList
*
features
=
NULL
;
if
(
!
identifier
)
continue
;
for
(
node
=
child
->
child
;
node
;
node
=
node
->
next
)
{
if
(
node
->
type
!=
PURPLE_XMLNODE_TYPE_TAG
)
continue
;
if
(
purple_strequal
(
node
->
name
,
"feature"
))
{
const
char
*
var
=
purple_xmlnode_get_attrib
(
node
,
"var"
);
if
(
!
var
)
continue
;
features
=
g_list_prepend
(
features
,
g_strdup
(
var
));
}
}
if
(
features
)
{
g_hash_table_insert
(
exts
->
exts
,
g_strdup
(
identifier
),
features
);
}
else
purple_debug_warning
(
"jabber"
,
"Caps ext %s had no features.
\n
"
,
identifier
);
}
}
}
value
->
exts
=
exts
;
g_hash_table_replace
(
capstable
,
key
,
value
);
}
}
purple_xmlnode_free
(
capsdata
);
}
void
jabber_caps_init
(
void
)
{
nodetable
=
g_hash_table_new_full
(
g_str_hash
,
g_str_equal
,
g_free
,
(
GDestroyNotify
)
jabber_caps_node_exts_unref
);
capstable
=
g_hash_table_new_full
(
jabber_caps_hash
,
jabber_caps_compare
,
NULL
,
(
GDestroyNotify
)
jabber_caps_client_info_destroy
);
jabber_caps_load
();
}
void
jabber_caps_uninit
(
void
)
{
if
(
save_timer
!=
0
)
{
g_source_remove
(
save_timer
);
save_timer
=
0
;
do_jabber_caps_store
(
NULL
);
}
g_hash_table_destroy
(
capstable
);
g_hash_table_destroy
(
nodetable
);
capstable
=
nodetable
=
NULL
;
}
gboolean
jabber_caps_exts_known
(
const
JabberCapsClientInfo
*
info
,
char
**
exts
)
{
int
i
;
g_return_val_if_fail
(
info
!=
NULL
,
FALSE
);
if
(
!
exts
)
return
TRUE
;
for
(
i
=
0
;
exts
[
i
];
++
i
)
{
if
(
!
info
->
exts
||
!
g_hash_table_lookup
(
info
->
exts
->
exts
,
exts
[
i
]))
return
FALSE
;
}
return
TRUE
;
}
typedef
struct
{
guint
ref
;
jabber_caps_get_info_cb
cb
;
gpointer
cb_data
;
char
*
who
;
char
*
node
;
char
*
ver
;
char
*
hash
;
JabberCapsClientInfo
*
info
;
GList
*
exts
;
guint
extOutstanding
;
JabberCapsNodeExts
*
node_exts
;
}
jabber_caps_cbplususerdata
;
static
jabber_caps_cbplususerdata
*
cbplususerdata_ref
(
jabber_caps_cbplususerdata
*
data
)
{
g_return_val_if_fail
(
data
!=
NULL
,
NULL
);
++
data
->
ref
;
return
data
;
}
static
void
cbplususerdata_unref
(
jabber_caps_cbplususerdata
*
data
)
{
if
(
data
==
NULL
)
return
;
g_return_if_fail
(
data
->
ref
!=
0
);
if
(
--
data
->
ref
>
0
)
return
;
g_free
(
data
->
who
);
g_free
(
data
->
node
);
g_free
(
data
->
ver
);
g_free
(
data
->
hash
);
/* If we have info here, it's already in the capstable, so don't free it */
if
(
data
->
exts
)
free_string_glist
(
data
->
exts
);
if
(
data
->
node_exts
)
jabber_caps_node_exts_unref
(
data
->
node_exts
);
g_free
(
data
);
}
static
void
jabber_caps_get_info_complete
(
jabber_caps_cbplususerdata
*
userdata
)
{
if
(
userdata
->
cb
)
{
userdata
->
cb
(
userdata
->
info
,
userdata
->
exts
,
userdata
->
cb_data
);
userdata
->
info
=
NULL
;
userdata
->
exts
=
NULL
;
}
if
(
userdata
->
ref
!=
1
)
purple_debug_warning
(
"jabber"
,
"Lost a reference to caps cbdata: %d
\n
"
,
userdata
->
ref
);
}
static
void
jabber_caps_client_iqcb
(
JabberStream
*
js
,
const
char
*
from
,
JabberIqType
type
,
const
char
*
id
,
PurpleXmlNode
*
packet
,
gpointer
data
)
{
PurpleXmlNode
*
query
=
purple_xmlnode_get_child_with_namespace
(
packet
,
"query"
,
NS_DISCO_INFO
);
jabber_caps_cbplususerdata
*
userdata
=
data
;
JabberCapsClientInfo
*
info
=
NULL
,
*
value
;
JabberCapsTuple
key
;
if
(
!
query
||
type
==
JABBER_IQ_ERROR
)
{
/* Any outstanding exts will be dealt with via ref-counting */
userdata
->
cb
(
NULL
,
NULL
,
userdata
->
cb_data
);
cbplususerdata_unref
(
userdata
);
return
;
}
/* check hash */
info
=
jabber_caps_parse_client_info
(
query
);
/* Only validate if these are v1.5 capabilities */
if
(
userdata
->
hash
)
{
gchar
*
hash
=
NULL
;
GChecksumType
hash_type
;
gboolean
supported_hash
=
TRUE
;
if
(
purple_strequal
(
userdata
->
hash
,
"sha-1"
))
{
hash_type
=
G_CHECKSUM_SHA1
;
}
else
if
(
purple_strequal
(
userdata
->
hash
,
"md5"
))
{
hash_type
=
G_CHECKSUM_MD5
;
}
else
{
supported_hash
=
FALSE
;
}
if
(
supported_hash
)
{
hash
=
jabber_caps_calculate_hash
(
info
,
hash_type
);
}
if
(
!
hash
||
!
purple_strequal
(
hash
,
userdata
->
ver
))
{
purple_debug_warning
(
"jabber"
,
"Could not validate caps info from "
"%s. Expected %s, got %s
\n
"
,
purple_xmlnode_get_attrib
(
packet
,
"from"
),
userdata
->
ver
,
hash
?
hash
:
"(null)"
);
userdata
->
cb
(
NULL
,
NULL
,
userdata
->
cb_data
);
jabber_caps_client_info_destroy
(
info
);
cbplususerdata_unref
(
userdata
);
g_free
(
hash
);
return
;
}
g_free
(
hash
);
}
if
(
!
userdata
->
hash
&&
userdata
->
node_exts
)
{
/* If the ClientInfo doesn't have information about the exts, give them
* ours (along with our ref) */
info
->
exts
=
userdata
->
node_exts
;
userdata
->
node_exts
=
NULL
;
}
key
.
node
=
userdata
->
node
;
key
.
ver
=
userdata
->
ver
;
key
.
hash
=
userdata
->
hash
;
/* Use the copy of this data already in the table if it exists or insert
* a new one if we need to */
if
((
value
=
g_hash_table_lookup
(
capstable
,
&
key
)))
{
jabber_caps_client_info_destroy
(
info
);
info
=
value
;
}
else
{
JabberCapsTuple
*
n_key
=
NULL
;
if
(
G_UNLIKELY
(
info
==
NULL
))
{
g_warn_if_reached
();
return
;
}
n_key
=
(
JabberCapsTuple
*
)
&
info
->
tuple
;
n_key
->
node
=
userdata
->
node
;
n_key
->
ver
=
userdata
->
ver
;
n_key
->
hash
=
userdata
->
hash
;
userdata
->
node
=
userdata
->
ver
=
userdata
->
hash
=
NULL
;
/* The capstable gets a reference */
g_hash_table_insert
(
capstable
,
n_key
,
info
);
schedule_caps_save
();
}
userdata
->
info
=
info
;
if
(
userdata
->
extOutstanding
==
0
)
jabber_caps_get_info_complete
(
userdata
);
cbplususerdata_unref
(
userdata
);
}
static
void
jabber_caps_ext_iqcb
(
JabberStream
*
js
,
const
char
*
from
,
JabberIqType
type
,
const
char
*
id
,
PurpleXmlNode
*
packet
,
gpointer
data
)
{
PurpleXmlNode
*
query
=
purple_xmlnode_get_child_with_namespace
(
packet
,
"query"
,
NS_DISCO_INFO
);
PurpleXmlNode
*
child
;
PurpleKeyValuePair
*
cbdata
=
data
;
jabber_caps_cbplususerdata
*
userdata
=
cbdata
->
value
;
GList
*
features
=
NULL
;
JabberCapsNodeExts
*
node_exts
;
if
(
!
query
||
type
==
JABBER_IQ_ERROR
)
{
purple_key_value_pair_free
(
cbdata
);
return
;
}
node_exts
=
(
userdata
->
info
?
userdata
->
info
->
exts
:
userdata
->
node_exts
);
/* TODO: I don't see how this can actually happen, but it crashed khc. */
if
(
!
node_exts
)
{
purple_debug_error
(
"jabber"
,
"Couldn't find JabberCapsNodeExts. If you "
"see this, please tell darkrain42 and save your debug log.
\n
"
"JabberCapsClientInfo = %p"
,
userdata
->
info
);
/* Try once more to find the exts and then fail */
node_exts
=
jabber_caps_find_exts_by_node
(
userdata
->
node
);
if
(
node_exts
)
{
purple_debug_info
(
"jabber"
,
"Found the exts on the second try.
\n
"
);
if
(
userdata
->
info
)
{
userdata
->
info
->
exts
=
node_exts
;
}
else
{
userdata
->
node_exts
=
node_exts
;
}
}
else
{
purple_key_value_pair_free
(
cbdata
);
g_return_if_reached
();
}
}
/* So, we decrement this after checking for an error, which means that
* if there *is* an error, we'll never call the callback passed to
* jabber_caps_get_info. We will still free all of our data, though.
*/
--
userdata
->
extOutstanding
;
for
(
child
=
purple_xmlnode_get_child
(
query
,
"feature"
);
child
;
child
=
purple_xmlnode_get_next_twin
(
child
))
{
const
char
*
var
=
purple_xmlnode_get_attrib
(
child
,
"var"
);
if
(
var
)
features
=
g_list_prepend
(
features
,
g_strdup
(
var
));
}
g_hash_table_insert
(
node_exts
->
exts
,
g_strdup
(
cbdata
->
key
),
features
);
schedule_caps_save
();
/* Are we done? */
if
(
userdata
->
info
&&
userdata
->
extOutstanding
==
0
)
{
jabber_caps_get_info_complete
(
userdata
);
}
purple_key_value_pair_free
(
cbdata
);
}
void
jabber_caps_get_info
(
JabberStream
*
js
,
const
char
*
who
,
const
char
*
node
,
const
char
*
ver
,
const
char
*
hash
,
char
**
exts
,
jabber_caps_get_info_cb
cb
,
gpointer
user_data
)
{
JabberCapsClientInfo
*
info
;
JabberCapsTuple
key
;
jabber_caps_cbplususerdata
*
userdata
;
if
(
exts
&&
hash
)
{
purple_debug_misc
(
"jabber"
,
"Ignoring exts in new-style caps from %s
\n
"
,
who
);
g_strfreev
(
exts
);
exts
=
NULL
;
}
/* Using this in a read-only fashion, so the cast is OK */
key
.
node
=
(
char
*
)
node
;
key
.
ver
=
(
char
*
)
ver
;
key
.
hash
=
(
char
*
)
hash
;
info
=
g_hash_table_lookup
(
capstable
,
&
key
);
if
(
info
&&
hash
)
{
/* v1.5 - We already have all the information we care about */
if
(
cb
)
cb
(
info
,
NULL
,
user_data
);
return
;
}
userdata
=
g_new0
(
jabber_caps_cbplususerdata
,
1
);
/* We start out with 0 references. Every query takes one */
userdata
->
cb
=
cb
;
userdata
->
cb_data
=
user_data
;
userdata
->
who
=
g_strdup
(
who
);
userdata
->
node
=
g_strdup
(
node
);
userdata
->
ver
=
g_strdup
(
ver
);
userdata
->
hash
=
g_strdup
(
hash
);
if
(
info
)
{
userdata
->
info
=
info
;
}
else
{
/* If we don't have the basic information about the client, we need
* to fetch it. */
JabberIq
*
iq
;
PurpleXmlNode
*
query
;
char
*
nodever
;
iq
=
jabber_iq_new_query
(
js
,
JABBER_IQ_GET
,
NS_DISCO_INFO
);
query
=
purple_xmlnode_get_child_with_namespace
(
iq
->
node
,
"query"
,
NS_DISCO_INFO
);
nodever
=
g_strdup_printf
(
"%s#%s"
,
node
,
ver
);
purple_xmlnode_set_attrib
(
query
,
"node"
,
nodever
);
g_free
(
nodever
);
purple_xmlnode_set_attrib
(
iq
->
node
,
"to"
,
who
);
cbplususerdata_ref
(
userdata
);
jabber_iq_set_callback
(
iq
,
jabber_caps_client_iqcb
,
userdata
);
jabber_iq_send
(
iq
);
}
/* Are there any exts that we don't recognize? */
if
(
exts
)
{
JabberCapsNodeExts
*
node_exts
;
int
i
;
if
(
info
)
{
if
(
info
->
exts
)
node_exts
=
info
->
exts
;
else
node_exts
=
info
->
exts
=
jabber_caps_find_exts_by_node
(
node
);
}
else
/* We'll put it in later once we have the client info */
node_exts
=
userdata
->
node_exts
=
jabber_caps_find_exts_by_node
(
node
);
for
(
i
=
0
;
exts
[
i
];
++
i
)
{
userdata
->
exts
=
g_list_prepend
(
userdata
->
exts
,
exts
[
i
]);
/* Look it up if we don't already know what it means */
if
(
!
g_hash_table_lookup
(
node_exts
->
exts
,
exts
[
i
]))
{
JabberIq
*
iq
;
PurpleXmlNode
*
query
;
char
*
nodeext
;
PurpleKeyValuePair
*
cbdata
;
iq
=
jabber_iq_new_query
(
js
,
JABBER_IQ_GET
,
NS_DISCO_INFO
);
query
=
purple_xmlnode_get_child_with_namespace
(
iq
->
node
,
"query"
,
NS_DISCO_INFO
);
nodeext
=
g_strdup_printf
(
"%s#%s"
,
node
,
exts
[
i
]);
purple_xmlnode_set_attrib
(
query
,
"node"
,
nodeext
);
g_free
(
nodeext
);
purple_xmlnode_set_attrib
(
iq
->
node
,
"to"
,
who
);
cbdata
=
purple_key_value_pair_new_full
(
exts
[
i
],
cbplususerdata_ref
(
userdata
),
(
GDestroyNotify
)
cbplususerdata_unref
);
jabber_iq_set_callback
(
iq
,
jabber_caps_ext_iqcb
,
cbdata
);
jabber_iq_send
(
iq
);
++
userdata
->
extOutstanding
;
}
exts
[
i
]
=
NULL
;
}
/* All the strings are now part of the GList, so don't need
* g_strfreev. */
g_free
(
exts
);
}
if
(
userdata
->
info
&&
userdata
->
extOutstanding
==
0
)
{
/* Start with 1 ref so the below functions are happy */
userdata
->
ref
=
1
;
/* We have everything we need right now */
jabber_caps_get_info_complete
(
userdata
);
cbplususerdata_unref
(
userdata
);
}
}
static
gint
jabber_xdata_compare
(
gconstpointer
a
,
gconstpointer
b
)
{
const
PurpleXmlNode
*
aformtypefield
=
a
;
const
PurpleXmlNode
*
bformtypefield
=
b
;
char
*
aformtype
;
char
*
bformtype
;
int
result
;
aformtype
=
jabber_x_data_get_formtype
(
aformtypefield
);
bformtype
=
jabber_x_data_get_formtype
(
bformtypefield
);
result
=
strcmp
(
aformtype
,
bformtype
);
g_free
(
aformtype
);
g_free
(
bformtype
);
return
result
;
}
JabberCapsClientInfo
*
jabber_caps_parse_client_info
(
PurpleXmlNode
*
query
)
{
PurpleXmlNode
*
child
;
JabberCapsClientInfo
*
info
;
if
(
!
query
||
!
purple_strequal
(
query
->
name
,
"query"
)
||
!
purple_strequal
(
query
->
xmlns
,
NS_DISCO_INFO
))
return
NULL
;
info
=
g_new0
(
JabberCapsClientInfo
,
1
);
for
(
child
=
query
->
child
;
child
;
child
=
child
->
next
)
{
if
(
child
->
type
!=
PURPLE_XMLNODE_TYPE_TAG
)
continue
;
if
(
purple_strequal
(
child
->
name
,
"identity"
))
{
/* parse identity */
const
char
*
category
=
purple_xmlnode_get_attrib
(
child
,
"category"
);
const
char
*
type
=
purple_xmlnode_get_attrib
(
child
,
"type"
);
const
char
*
name
=
purple_xmlnode_get_attrib
(
child
,
"name"
);
const
char
*
lang
=
purple_xmlnode_get_attrib
(
child
,
"lang"
);
JabberIdentity
*
id
;
if
(
!
category
||
!
type
)
continue
;
id
=
jabber_identity_new
(
category
,
type
,
lang
,
name
);
info
->
identities
=
g_list_append
(
info
->
identities
,
id
);
}
else
if
(
purple_strequal
(
child
->
name
,
"feature"
))
{
/* parse feature */
const
char
*
var
=
purple_xmlnode_get_attrib
(
child
,
"var"
);
if
(
var
)
info
->
features
=
g_list_prepend
(
info
->
features
,
g_strdup
(
var
));
}
else
if
(
purple_strequal
(
child
->
name
,
"x"
))
{
if
(
purple_strequal
(
child
->
xmlns
,
"jabber:x:data"
))
{
/* x-data form */
PurpleXmlNode
*
dataform
=
purple_xmlnode_copy
(
child
);
info
->
forms
=
g_list_append
(
info
->
forms
,
dataform
);
}
}
}
return
info
;
}
static
gint
jabber_caps_xdata_field_compare
(
gconstpointer
a
,
gconstpointer
b
)
{
const
JabberDataFormField
*
ac
=
a
;
const
JabberDataFormField
*
bc
=
b
;
return
strcmp
(
ac
->
var
,
bc
->
var
);
}
static
GList
*
jabber_caps_xdata_get_fields
(
const
PurpleXmlNode
*
x
)
{
GList
*
fields
=
NULL
;
PurpleXmlNode
*
field
;
if
(
!
x
)
return
NULL
;
for
(
field
=
purple_xmlnode_get_child
(
x
,
"field"
);
field
;
field
=
purple_xmlnode_get_next_twin
(
field
))
{
PurpleXmlNode
*
value
;
JabberDataFormField
*
xdatafield
=
g_new0
(
JabberDataFormField
,
1
);
xdatafield
->
var
=
g_strdup
(
purple_xmlnode_get_attrib
(
field
,
"var"
));
for
(
value
=
purple_xmlnode_get_child
(
field
,
"value"
);
value
;
value
=
purple_xmlnode_get_next_twin
(
value
))
{
gchar
*
val
=
purple_xmlnode_get_data
(
value
);
xdatafield
->
values
=
g_list_prepend
(
xdatafield
->
values
,
val
);
}
xdatafield
->
values
=
g_list_sort
(
xdatafield
->
values
,
(
GCompareFunc
)
strcmp
);
fields
=
g_list_prepend
(
fields
,
xdatafield
);
}
fields
=
g_list_sort
(
fields
,
jabber_caps_xdata_field_compare
);
return
fields
;
}
static
void
append_escaped_string
(
GChecksum
*
hash
,
const
gchar
*
str
)
{
g_return_if_fail
(
hash
!=
NULL
);
if
(
str
&&
*
str
)
{
char
*
tmp
=
g_markup_escape_text
(
str
,
-1
);
g_checksum_update
(
hash
,
(
const
guchar
*
)
tmp
,
-1
);
g_free
(
tmp
);
}
g_checksum_update
(
hash
,
(
const
guchar
*
)
"<"
,
-1
);
}
gchar
*
jabber_caps_calculate_hash
(
JabberCapsClientInfo
*
info
,
GChecksumType
hash_type
)
{
GChecksum
*
hash
;
GList
*
node
;
guint8
*
checksum
;
gsize
checksum_size
;
gchar
*
ret
;
if
(
!
info
)
return
NULL
;
/* sort identities, features and x-data forms */
info
->
identities
=
g_list_sort
(
info
->
identities
,
jabber_identity_compare
);
info
->
features
=
g_list_sort
(
info
->
features
,
(
GCompareFunc
)
strcmp
);
info
->
forms
=
g_list_sort
(
info
->
forms
,
jabber_xdata_compare
);
hash
=
g_checksum_new
(
hash_type
);
if
(
hash
==
NULL
)
{
return
NULL
;
}
/* Add identities to the hash data */
for
(
node
=
info
->
identities
;
node
;
node
=
node
->
next
)
{
JabberIdentity
*
id
=
(
JabberIdentity
*
)
node
->
data
;
char
*
category
=
g_markup_escape_text
(
id
->
category
,
-1
);
char
*
type
=
g_markup_escape_text
(
id
->
type
,
-1
);
char
*
lang
=
NULL
;
char
*
name
=
NULL
;
char
*
tmp
;
if
(
id
->
lang
)
lang
=
g_markup_escape_text
(
id
->
lang
,
-1
);
if
(
id
->
name
)
name
=
g_markup_escape_text
(
id
->
name
,
-1
);
tmp
=
g_strconcat
(
category
,
"/"
,
type
,
"/"
,
lang
?
lang
:
""
,
"/"
,
name
?
name
:
""
,
"<"
,
NULL
);
g_checksum_update
(
hash
,
(
const
guchar
*
)
tmp
,
-1
);
g_free
(
tmp
);
g_free
(
category
);
g_free
(
type
);
g_free
(
lang
);
g_free
(
name
);
}
/* concat features to the verification string */
for
(
node
=
info
->
features
;
node
;
node
=
node
->
next
)
{
append_escaped_string
(
hash
,
node
->
data
);
}
/* concat x-data forms to the verification string */
for
(
node
=
info
->
forms
;
node
;
node
=
node
->
next
)
{
PurpleXmlNode
*
data
=
(
PurpleXmlNode
*
)
node
->
data
;
gchar
*
formtype
=
jabber_x_data_get_formtype
(
data
);
GList
*
fields
=
jabber_caps_xdata_get_fields
(
data
);
/* append FORM_TYPE's field value to the verification string */
append_escaped_string
(
hash
,
formtype
);
g_free
(
formtype
);
while
(
fields
)
{
JabberDataFormField
*
field
=
(
JabberDataFormField
*
)
fields
->
data
;
if
(
!
purple_strequal
(
field
->
var
,
"FORM_TYPE"
))
{
/* Append the "var" attribute */
append_escaped_string
(
hash
,
field
->
var
);
/* Append <value/> elements' cdata */
while
(
field
->
values
)
{
append_escaped_string
(
hash
,
field
->
values
->
data
);
g_free
(
field
->
values
->
data
);
field
->
values
=
g_list_delete_link
(
field
->
values
,
field
->
values
);
}
}
else
{
g_list_free_full
(
field
->
values
,
g_free
);
}
g_free
(
field
->
var
);
g_free
(
field
);
fields
=
g_list_delete_link
(
fields
,
fields
);
}
}
checksum_size
=
g_checksum_type_get_length
(
hash_type
);
checksum
=
g_new
(
guint8
,
checksum_size
);
/* generate hash */
g_checksum_get_digest
(
hash
,
checksum
,
&
checksum_size
);
ret
=
g_base64_encode
(
checksum
,
checksum_size
);
g_free
(
checksum
);
g_checksum_free
(
hash
);
return
ret
;
}
void
jabber_caps_calculate_own_hash
(
JabberStream
*
js
)
{
JabberCapsClientInfo
info
;
GList
*
iter
=
NULL
;
GList
*
features
=
NULL
;
if
(
!
jabber_identities
&&
!
jabber_features
)
{
/* This really shouldn't ever happen */
purple_debug_warning
(
"jabber"
,
"No features or identities, cannot calculate own caps hash.
\n
"
);
g_free
(
js
->
caps_hash
);
js
->
caps_hash
=
NULL
;
return
;
}
/* build the currently-supported list of features */
if
(
jabber_features
)
{
for
(
iter
=
jabber_features
;
iter
;
iter
=
iter
->
next
)
{
JabberFeature
*
feat
=
iter
->
data
;
if
(
!
feat
->
is_enabled
||
feat
->
is_enabled
(
js
,
feat
->
namespace
))
{
features
=
g_list_append
(
features
,
feat
->
namespace
);
}
}
}
info
.
features
=
features
;
/* TODO: This copy can go away, I think, since jabber_identities
* is pre-sorted, so the sort in calculate_hash should be idempotent.
* However, I want to test that. --darkrain
*/
info
.
identities
=
g_list_copy
(
jabber_identities
);
info
.
forms
=
NULL
;
g_free
(
js
->
caps_hash
);
js
->
caps_hash
=
jabber_caps_calculate_hash
(
&
info
,
G_CHECKSUM_SHA1
);
g_list_free
(
info
.
identities
);
g_list_free
(
info
.
features
);
}
const
gchar
*
jabber_caps_get_own_hash
(
JabberStream
*
js
)
{
if
(
!
js
->
caps_hash
)
jabber_caps_calculate_own_hash
(
js
);
return
js
->
caps_hash
;
}
void
jabber_caps_broadcast_change
()
{
GList
*
node
,
*
accounts
=
purple_accounts_get_all_active
();
for
(
node
=
accounts
;
node
;
node
=
node
->
next
)
{
PurpleAccount
*
account
=
node
->
data
;
const
char
*
protocol_id
=
purple_account_get_protocol_id
(
account
);
if
(
purple_strequal
(
"prpl-jabber"
,
protocol_id
)
&&
purple_account_is_connected
(
account
))
{
PurpleConnection
*
gc
=
purple_account_get_connection
(
account
);
jabber_presence_send
(
purple_connection_get_protocol_data
(
gc
),
TRUE
);
}
}
g_list_free
(
accounts
);
}