diff --git a/Cargo.toml b/Cargo.toml index 34dab765..3dbd0bc1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ authors = ["lumi "] [dependencies] xml-rs = "*" openssl = "*" +base64 = "*" [dependencies.minidom] git = "https://gitlab.com/lumi/minidom-rs.git" diff --git a/examples/client.rs b/examples/client.rs index ac56adb6..e1444336 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -2,10 +2,22 @@ extern crate xmpp; use xmpp::jid::Jid; use xmpp::client::ClientBuilder; +use xmpp::plugins::messaging::{MessagingPlugin, MessageEvent}; +use xmpp::plugins::presence::{PresencePlugin, Show}; use std::env; fn main() { let jid: Jid = env::var("JID").unwrap().parse().unwrap(); - let client = ClientBuilder::new(jid).connect().unwrap(); + let mut client = ClientBuilder::new(jid).connect().unwrap(); + client.register_plugin(MessagingPlugin::new()); + client.register_plugin(PresencePlugin::new()); + client.connect_plain(&env::var("PASS").unwrap()).unwrap(); + client.plugin::().set_presence(Show::Available, None).unwrap(); + loop { + let event = client.next_event().unwrap(); + if let Some(evt) = event.downcast::() { + println!("{:?}", evt); + } + } } diff --git a/src/client.rs b/src/client.rs index d01b9212..2b8f4398 100644 --- a/src/client.rs +++ b/src/client.rs @@ -2,10 +2,18 @@ use jid::Jid; use transport::{Transport, SslTransport}; use error::Error; use ns; +use plugin::{Plugin, PluginProxyBinding}; +use event::AbstractEvent; + +use base64; + +use minidom::Element; use xml::writer::XmlEvent as WriterEvent; use xml::reader::XmlEvent as ReaderEvent; +use std::sync::mpsc::{Receiver, channel}; + pub struct ClientBuilder { jid: Jid, host: Option, @@ -38,9 +46,15 @@ impl ClientBuilder { .attr("to", &self.jid.domain) .default_ns(ns::CLIENT) .ns("stream", ns::STREAM))?; + let (sender_out, sender_in) = channel(); + let (dispatcher_out, dispatcher_in) = channel(); Ok(Client { jid: self.jid, - transport: transport + transport: transport, + plugins: Vec::new(), + binding: PluginProxyBinding::new(sender_out, dispatcher_out), + sender_in: sender_in, + dispatcher_in: dispatcher_in, }) } } @@ -48,10 +62,116 @@ impl ClientBuilder { pub struct Client { jid: Jid, transport: SslTransport, + plugins: Vec>, + binding: PluginProxyBinding, + sender_in: Receiver, + dispatcher_in: Receiver, } impl Client { pub fn jid(&self) -> &Jid { &self.jid } + + pub fn register_plugin(&mut self, mut plugin: P) { + plugin.bind(self.binding.clone()); + self.plugins.push(Box::new(plugin)); + } + + pub fn plugin(&self) -> &P { + for plugin in &self.plugins { + let any = plugin.as_any(); + if let Some(ret) = any.downcast_ref::

() { + return ret; + } + } + panic!("plugin does not exist!"); + } + + pub fn next_event(&mut self) -> Result { + self.flush_send_queue()?; + loop { + if let Ok(evt) = self.dispatcher_in.try_recv() { + return Ok(evt); + } + let elem = self.transport.read_element()?; + for plugin in self.plugins.iter_mut() { + plugin.handle(&elem); + // TODO: handle plugin return + } + self.flush_send_queue()?; + } + } + + pub fn flush_send_queue(&mut self) -> Result<(), Error> { // TODO: not sure how great of an + // idea it is to flush in this + // manner… + while let Ok(elem) = self.sender_in.try_recv() { + self.transport.write_element(&elem)?; + } + Ok(()) + } + + pub fn connect_plain(&mut self, password: &str) -> Result<(), Error> { + // TODO: this is very ugly + loop { + let e = self.transport.read_event().unwrap(); + match e { + ReaderEvent::StartElement { .. } => { + break; + }, + _ => (), + } + } + let mut did_sasl = false; + loop { + let n = self.transport.read_element().unwrap(); + if n.is("features", ns::STREAM) { + if did_sasl { + let mut elem = Element::builder("iq") + .attr("id", "bind") + .attr("type", "set") + .build(); + let bind = Element::builder("bind") + .ns(ns::BIND) + .build(); + elem.append_child(bind); + self.transport.write_element(&elem)?; + } + else { + let mut auth = Vec::new(); + auth.push(0); + auth.extend(self.jid.node.as_ref().expect("JID has no node").bytes()); + auth.push(0); + auth.extend(password.bytes()); + let mut elem = Element::builder("auth") + .ns(ns::SASL) + .attr("mechanism", "PLAIN") + .build(); + elem.append_text_node(base64::encode(&auth)); + self.transport.write_element(&elem)?; + did_sasl = true; + } + } + else if n.is("success", ns::SASL) { + self.transport.reset_stream(); + self.transport.write_event(WriterEvent::start_element("stream:stream") + .attr("to", &self.jid.domain) + .default_ns(ns::CLIENT) + .ns("stream", ns::STREAM))?; + loop { + let e = self.transport.read_event()?; + match e { + ReaderEvent::StartElement { .. } => { + break; + }, + _ => (), + } + } + } + else if n.is("iq", ns::CLIENT) && n.has_child("bind", ns::BIND) { + return Ok(()); + } + } + } } diff --git a/src/event.rs b/src/event.rs new file mode 100644 index 00000000..7098c4e3 --- /dev/null +++ b/src/event.rs @@ -0,0 +1,25 @@ +use std::fmt::Debug; + +use std::any::Any; + +pub struct AbstractEvent { + inner: Box, +} + +impl AbstractEvent { + pub fn new(event: E) -> AbstractEvent { + AbstractEvent { + inner: Box::new(event), + } + } + + pub fn downcast(&self) -> Option<&E> { + self.inner.downcast_ref::() + } + + pub fn is(&self) -> bool { + self.inner.is::() + } +} + +pub trait Event: Any + Debug {} diff --git a/src/lib.rs b/src/lib.rs index 6baaae78..811c308a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,15 @@ extern crate xml; extern crate openssl; extern crate minidom; +extern crate base64; pub mod ns; pub mod transport; pub mod error; pub mod jid; pub mod client; +pub mod plugin; +pub mod event; +pub mod plugins; mod locked_io; diff --git a/src/plugin.rs b/src/plugin.rs new file mode 100644 index 00000000..a48c02f0 --- /dev/null +++ b/src/plugin.rs @@ -0,0 +1,89 @@ +use event::{Event, AbstractEvent}; + +use std::any::Any; + +use std::sync::mpsc::Sender; + +use std::mem; + +use minidom::Element; + +#[derive(Clone)] +pub struct PluginProxyBinding { + sender: Sender, + dispatcher: Sender, +} + +impl PluginProxyBinding { + pub fn new(sender: Sender, dispatcher: Sender) -> PluginProxyBinding { + PluginProxyBinding { + sender: sender, + dispatcher: dispatcher, + } + } +} + +pub enum PluginProxy { + Unbound, + BoundTo(PluginProxyBinding), +} + +impl PluginProxy { + pub fn new() -> PluginProxy { + PluginProxy::Unbound + } + + pub fn bind(&mut self, inner: PluginProxyBinding) { + if let PluginProxy::BoundTo(_) = *self { + panic!("trying to bind an already bound plugin proxy!"); + } + mem::replace(self, PluginProxy::BoundTo(inner)); + } + + fn with_binding R>(&self, f: F) -> R { + match *self { + PluginProxy::Unbound => { + panic!("trying to use an unbound plugin proxy!"); + }, + PluginProxy::BoundTo(ref binding) => { + f(binding) + }, + } + } + + pub fn dispatch(&self, event: E) { + self.with_binding(move |binding| { + binding.dispatcher.send(AbstractEvent::new(event)) + .unwrap(); // TODO: may want to return the error + }); + } + + pub fn send(&self, elem: Element) { + self.with_binding(move |binding| { + binding.sender.send(elem).unwrap(); // TODO: as above, may want to return the error + }); + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum PluginReturn { + Continue, + Unload, +} + +pub trait Plugin: Any + PluginAny { + fn get_proxy(&mut self) -> &mut PluginProxy; + fn handle(&mut self, _elem: &Element) -> PluginReturn { PluginReturn::Continue } + + fn bind(&mut self, inner: PluginProxyBinding) { + self.get_proxy().bind(inner); + } +} + +pub trait PluginAny { + fn as_any(&self) -> &Any; +} + +impl PluginAny for T { + fn as_any(&self) -> &Any { self } +} diff --git a/src/plugins/messaging.rs b/src/plugins/messaging.rs new file mode 100644 index 00000000..e06f6ba4 --- /dev/null +++ b/src/plugins/messaging.rs @@ -0,0 +1,45 @@ +use plugin::{Plugin, PluginReturn, PluginProxy}; +use event::Event; +use minidom::Element; +use jid::Jid; +use ns; + +#[derive(Debug)] +pub struct MessageEvent { + from: Jid, + to: Jid, + body: String, +} + +impl Event for MessageEvent {} + +pub struct MessagingPlugin { + proxy: PluginProxy, +} + +impl MessagingPlugin { + pub fn new() -> MessagingPlugin { + MessagingPlugin { + proxy: PluginProxy::new(), + } + } +} + +impl Plugin for MessagingPlugin { + fn get_proxy(&mut self) -> &mut PluginProxy { + &mut self.proxy + } + + fn handle(&mut self, elem: &Element) -> PluginReturn { + if elem.is("message", ns::CLIENT) && elem.attr("type") == Some("chat") { + if let Some(body) = elem.get_child("body", ns::CLIENT) { + self.proxy.dispatch(MessageEvent { // TODO: safety!!! + from: elem.attr("from").unwrap().parse().unwrap(), + to: elem.attr("to").unwrap().parse().unwrap(), + body: body.text(), + }); + } + } + PluginReturn::Continue + } +} diff --git a/src/plugins/mod.rs b/src/plugins/mod.rs new file mode 100644 index 00000000..c15a923f --- /dev/null +++ b/src/plugins/mod.rs @@ -0,0 +1,2 @@ +pub mod messaging; +pub mod presence; diff --git a/src/plugins/presence.rs b/src/plugins/presence.rs new file mode 100644 index 00000000..4dc9ca48 --- /dev/null +++ b/src/plugins/presence.rs @@ -0,0 +1,101 @@ +use error::Error; +use plugin::{Plugin, PluginProxy}; + +use minidom::Element; + +use ns; + +use std::fmt; + +use std::str::FromStr; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Show { + Available, + Away, + ExtendedAway, + DoNotDisturb, + Chat, + Unavailable, +} + +impl fmt::Display for Show { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match *self { + Show::Away => write!(fmt, "away"), + Show::ExtendedAway => write!(fmt, "xa"), + Show::DoNotDisturb => write!(fmt, "dnd"), + Show::Chat => write!(fmt, "chat"), + + // will never be seen inside a , maybe should crash? + Show::Available => write!(fmt, "available"), + Show::Unavailable => write!(fmt, "unavailable"), + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct InvalidShow; + +impl FromStr for Show { + type Err = InvalidShow; + + fn from_str(s: &str) -> Result { + Ok(match s { + "away" => Show::Away, + "xa" => Show::ExtendedAway, + "dnd" => Show::DoNotDisturb, + "chat" => Show::Chat, + + _ => { return Err(InvalidShow); } + }) + } +} + +pub struct PresencePlugin { + proxy: PluginProxy, +} + +impl PresencePlugin { + pub fn new() -> PresencePlugin { + PresencePlugin { + proxy: PluginProxy::new(), + } + } + + pub fn set_presence(&self, show: Show, status: Option) -> Result<(), Error> { + if show == Show::Unavailable { + self.proxy.send(Element::builder("presence") + .ns(ns::CLIENT) + .attr("type", "unavailable") + .build()); + } + else { + let mut stanza = Element::builder("presence") + .ns(ns::CLIENT) + .build(); + if let Some(stat) = status { + let mut elem = Element::builder("status") + .ns(ns::CLIENT) + .build(); + elem.append_text_node(stat); + stanza.append_child(elem); + } + let mut elem = Element::builder("show") + .ns(ns::CLIENT) + .build(); + if show != Show::Available { + elem.append_text_node(show.to_string()); + } + stanza.append_child(elem); + self.proxy.send(stanza); + } + Ok(()) + } +} + +impl Plugin for PresencePlugin { + fn get_proxy(&mut self) -> &mut PluginProxy { + &mut self.proxy + } +}