2017-03-02 14:37:32 +00:00
using Gee ;
namespace Xmpp.Xep.EntityCapabilities {
private const string NS_URI = " http://jabber.org/protocol/caps " ;
public class Module : XmppStreamModule {
2017-03-19 11:55:36 +00:00
public static ModuleIdentity < Module > IDENTITY = new ModuleIdentity < Module > ( NS_URI , " 0115_entity_capabilities " ) ;
2017-03-02 14:37:32 +00:00
private string own_ver_hash ;
private Storage storage ;
2019-09-02 13:39:06 +00:00
private Regex sha1_base64_regex = / ^ [ A - Za - z0 - 9 + \ / ] { 27 } = $ / ;
2017-03-02 14:37:32 +00:00
public Module ( Storage storage ) {
this . storage = storage ;
}
private string get_own_hash ( XmppStream stream ) {
if ( own_ver_hash = = null ) {
2019-09-02 13:39:06 +00:00
own_ver_hash = compute_hash ( stream . get_module ( ServiceDiscovery . Module . IDENTITY ) . identities , stream . get_flag ( ServiceDiscovery . Flag . IDENTITY ) . features , new ArrayList < DataForms . DataForm > ( ) ) ;
2017-03-02 14:37:32 +00:00
}
return own_ver_hash ;
}
public override void attach ( XmppStream stream ) {
2017-03-11 00:40:42 +00:00
stream . get_module ( Presence . Module . IDENTITY ) . pre_send_presence_stanza . connect ( on_pre_send_presence_stanza ) ;
stream . get_module ( Presence . Module . IDENTITY ) . received_presence . connect ( on_received_presence ) ;
stream . get_module ( ServiceDiscovery . Module . IDENTITY ) . add_feature ( stream , NS_URI ) ;
2017-03-02 14:37:32 +00:00
}
public override void detach ( XmppStream stream ) {
2017-03-11 00:40:42 +00:00
stream . get_module ( Presence . Module . IDENTITY ) . pre_send_presence_stanza . disconnect ( on_pre_send_presence_stanza ) ;
stream . get_module ( Presence . Module . IDENTITY ) . received_presence . disconnect ( on_received_presence ) ;
2017-03-02 14:37:32 +00:00
}
public override string get_ns ( ) { return NS_URI ; }
2017-03-19 11:55:36 +00:00
public override string get_id ( ) { return IDENTITY . id ; }
2017-03-02 14:37:32 +00:00
private void on_pre_send_presence_stanza ( XmppStream stream , Presence . Stanza presence ) {
if ( presence . type_ = = Presence . Stanza . TYPE_AVAILABLE ) {
presence . stanza . put_node ( new StanzaNode . build ( " c " , NS_URI ) . add_self_xmlns ( )
. put_attribute ( " hash " , " sha-1 " )
2017-08-17 19:24:01 +00:00
. put_attribute ( " node " , " https://dino.im " )
2017-03-02 14:37:32 +00:00
. put_attribute ( " ver " , get_own_hash ( stream ) ) ) ;
}
}
private void on_received_presence ( XmppStream stream , Presence . Stanza presence ) {
StanzaNode ? c_node = presence . stanza . get_subnode ( " c " , NS_URI ) ;
if ( c_node ! = null ) {
2019-03-13 17:40:45 +00:00
string ? ver_attribute = c_node . get_attribute ( " ver " , NS_URI ) ;
2019-09-02 13:39:06 +00:00
if ( ver_attribute = = null | | ! sha1_base64_regex . match ( ver_attribute ) ) return ;
2017-08-02 15:29:55 +00:00
Gee . List < string > capabilities = storage . get_features ( ver_attribute ) ;
2017-03-02 14:37:32 +00:00
if ( capabilities . size = = 0 ) {
2017-06-13 16:14:59 +00:00
stream . get_module ( ServiceDiscovery . Module . IDENTITY ) . request_info ( stream , presence . from , ( stream , query_result ) = > {
store_entity_result ( stream , ver_attribute , query_result ) ;
} ) ;
2017-03-02 14:37:32 +00:00
} else {
2017-03-30 23:17:01 +00:00
stream . get_flag ( ServiceDiscovery . Flag . IDENTITY ) . set_entity_features ( presence . from , capabilities ) ;
2017-03-02 14:37:32 +00:00
}
}
}
2017-06-13 16:14:59 +00:00
private void store_entity_result ( XmppStream stream , string entity , ServiceDiscovery . InfoResult ? query_result ) {
2017-03-30 23:17:01 +00:00
if ( query_result = = null ) return ;
2019-09-02 13:39:06 +00:00
Gee . List < DataForms . DataForm > data_forms = new ArrayList < DataForms . DataForm > ( ) ;
foreach ( StanzaNode node in query_result . iq . stanza . get_deep_subnodes ( ServiceDiscovery . NS_URI_INFO + " :query " , DataForms . NS_URI + " :x " ) ) {
data_forms . add ( DataForms . DataForm . create_from_node ( stream , node , ( stream , node ) = > { } ) ) ;
}
if ( compute_hash ( query_result . identities , query_result . features , data_forms ) = = entity ) {
2017-03-11 22:04:58 +00:00
storage . store_features ( entity , query_result . features ) ;
2017-06-13 16:14:59 +00:00
stream . get_flag ( ServiceDiscovery . Flag . IDENTITY ) . set_entity_features ( query_result . iq . from , query_result . features ) ;
2017-03-02 14:37:32 +00:00
}
}
2019-09-02 13:39:06 +00:00
private static string compute_hash ( Gee . List < ServiceDiscovery . Identity > identities , Gee . List < string > features , Gee . List < DataForms . DataForm > data_forms ) {
2017-03-02 14:37:32 +00:00
identities . sort ( compare_identities ) ;
features . sort ( ) ;
string s = " " ;
foreach ( ServiceDiscovery . Identity identity in identities ) {
string s_identity = identity . category + " / " + identity . type_ + " // " ;
if ( identity . name ! = null ) s_identity + = identity . name ;
s_identity + = " < " ;
s + = s_identity ;
}
foreach ( string feature in features ) {
s + = feature + " < " ;
}
2019-09-02 13:39:06 +00:00
data_forms . sort ( compare_data_forms ) ;
foreach ( DataForms . DataForm data_form in data_forms ) {
if ( data_form . form_type = = null ) {
// If [..] the FORM_TYPE field is not of type "hidden" or the form does not include a FORM_TYPE field, ignore the form but continue processing. (XEP-0115)
continue ;
}
s + = data_form . form_type + " < " ;
data_form . fields . sort ( compare_data_fields ) ;
foreach ( DataForms . DataForm . Field field in data_form . fields ) {
s + = field . var + " < " ;
Gee . List < string > values = field . get_values ( ) ;
values . sort ( ) ;
foreach ( string value in values ) {
s + = value + " < " ;
}
}
}
2017-03-02 14:37:32 +00:00
Checksum c = new Checksum ( ChecksumType . SHA1 ) ;
c . update ( s . data , - 1 ) ;
size_t size = 20 ;
uint8 [ ] buf = new uint8 [ size ] ;
c . get_digest ( buf , ref size ) ;
return Base64 . encode ( buf ) ;
}
private static int compare_identities ( ServiceDiscovery . Identity a , ServiceDiscovery . Identity b ) {
int category_comp = a . category . collate ( b . category ) ;
if ( category_comp ! = 0 ) return category_comp ;
int type_comp = a . type_ . collate ( b . type_ ) ;
if ( type_comp ! = 0 ) return type_comp ;
// TODO lang
return 0 ;
}
2019-09-02 13:39:06 +00:00
private static int compare_data_forms ( DataForms . DataForm a , DataForms . DataForm b ) {
if ( a . form_type ! = null & & b . form_type ! = null ) {
return a . form_type . collate ( b . form_type ) ;
}
return 0 ;
}
private static int compare_data_fields ( DataForms . DataForm . Field a , DataForms . DataForm . Field b ) {
if ( a . var ! = null & & b . var ! = null ) {
return a . var . collate ( b . var ) ;
}
return 0 ;
}
2017-03-02 14:37:32 +00:00
}
public interface Storage : Object {
2018-01-10 14:12:54 +00:00
public abstract void store_features ( string entity , Gee . List < string > capabilities ) ;
public abstract Gee . List < string > get_features ( string entity ) ;
2017-03-02 14:37:32 +00:00
}
}