diff --git a/examples/client.rs b/examples/client.rs index db1567c..e4d7728 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -8,6 +8,7 @@ use xmpp::plugins::unhandled_iq::UnhandledIqPlugin; use xmpp::plugins::messaging::{MessagingPlugin, MessageEvent}; use xmpp::plugins::presence::{PresencePlugin, Type, Show}; use xmpp::plugins::disco::DiscoPlugin; +use xmpp::plugins::caps::CapsPlugin; use xmpp::plugins::ibb::IbbPlugin; use xmpp::plugins::ping::PingPlugin; use xmpp::event::{Priority, Propagation}; @@ -29,6 +30,7 @@ fn main() { client.register_plugin(MessagingPlugin::new()); client.register_plugin(PresencePlugin::new()); client.register_plugin(DiscoPlugin::new("client", "bot", "en", "xmpp-rs")); + client.register_plugin(CapsPlugin::new()); client.register_plugin(IbbPlugin::new()); client.register_plugin(PingPlugin::new()); client.plugin::().init(); diff --git a/src/plugins/caps.rs b/src/plugins/caps.rs new file mode 100644 index 0000000..1f44881 --- /dev/null +++ b/src/plugins/caps.rs @@ -0,0 +1,140 @@ +use std::collections::HashMap; +use std::convert::TryFrom; +use std::sync::{Mutex, Arc}; + +use plugin::PluginProxy; +use event::{Event, Priority, Propagation}; +use jid::Jid; +use base64; + +use plugins::stanza::{Presence, Iq}; +use plugins::disco::DiscoInfoResult; +use xmpp_parsers::presence::Type as PresenceType; +use xmpp_parsers::iq::IqType; +use xmpp_parsers::disco::Disco; +use xmpp_parsers::caps::Caps; + +#[derive(Debug)] +pub struct DiscoInfoRequest { + pub from: Jid, + pub id: String, + pub node: Option, +} + +impl Event for DiscoInfoRequest {} + +pub struct CapsPlugin { + proxy: PluginProxy, + pending: Arc>>, + cache: Arc>>, +} + +impl CapsPlugin { + pub fn new() -> CapsPlugin { + CapsPlugin { + proxy: PluginProxy::new(), + pending: Arc::new(Mutex::new(HashMap::new())), + cache: Arc::new(Mutex::new(HashMap::new())), + } + } + + fn handle_presence(&self, presence: &Presence) -> Propagation { + let presence = presence.clone(); + match presence.type_ { + PresenceType::None => for payload in presence.payloads { + let caps = match Caps::try_from(payload) { + Ok(caps) => caps, + Err(_) => continue, + }; + let recipient = presence.from.unwrap(); + let node = format!("{}#{}", caps.node, base64::encode(&caps.hash.hash)); + { + let cache = self.cache.lock().unwrap(); + if cache.contains_key(&(recipient.clone(), node.clone())) { + break; + } + } + let id = self.proxy.gen_id(); + { + let mut pending = self.pending.lock().unwrap(); + pending.insert(recipient.clone(), (id.clone(), node.clone())); + } + let disco = Disco { + node: Some(node), + identities: vec!(), + features: vec!(), + extensions: vec!(), + }; + self.proxy.send(Iq { + to: Some(recipient), + from: None, + id: Some(id), + payload: IqType::Get(disco.into()), + }.into()); + break; + }, + PresenceType::Unavailable + | PresenceType::Error => { + let recipient = presence.from.unwrap(); + let mut pending = self.pending.lock().unwrap(); + let previous = pending.remove(&recipient); + if previous.is_none() { + // This wasn’t one of our requests. + return Propagation::Continue; + } + // TODO: maybe add a negative cache? + }, + _ => (), + } + Propagation::Continue + } + + fn handle_result(&self, result: &DiscoInfoResult) -> Propagation { + let from = result.from.clone(); + let mut pending = self.pending.lock().unwrap(); + let previous = pending.remove(&from.clone()); + if let Some((id, node)) = previous { + if id != result.id { + return Propagation::Continue; + } + if Some(node.clone()) != result.disco.node { + // TODO: make that a debug log. + println!("Wrong node in result!"); + return Propagation::Continue; + } + { + let mut cache = self.cache.lock().unwrap(); + cache.insert((from, node), result.disco.clone()); + } + } else { + // TODO: make that a debug log. + println!("No such request from us."); + return Propagation::Continue; + } + Propagation::Stop + } + + // This is only for errors. + // TODO: also do the same thing for timeouts. + fn handle_iq(&self, iq: &Iq) -> Propagation { + let iq = iq.clone(); + if let IqType::Error(_) = iq.payload { + let from = iq.from.unwrap(); + let mut pending = self.pending.lock().unwrap(); + let previous = pending.remove(&from.clone()); + if previous.is_none() { + // This wasn’t one of our requests. + return Propagation::Continue; + } + // TODO: maybe add a negative cache? + return Propagation::Stop; + } + Propagation::Continue + } +} + +impl_plugin!(CapsPlugin, proxy, [ + (Presence, Priority::Default) => handle_presence, + (Iq, Priority::Default) => handle_iq, + (DiscoInfoResult, Priority::Default) => handle_result, +]); diff --git a/src/plugins/mod.rs b/src/plugins/mod.rs index 22b282e..a6c8b7c 100644 --- a/src/plugins/mod.rs +++ b/src/plugins/mod.rs @@ -2,6 +2,7 @@ pub mod messaging; pub mod presence; pub mod roster; pub mod disco; +pub mod caps; pub mod ping; pub mod ibb; pub mod stanza;