diff --git a/Cargo.toml b/Cargo.toml index 0475a1b091c76b6fbdea81b789f5e21dccc5c157..3a5c30d804b07830ba089d90509919f3e7ac2f7c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "xmpp" version = "0.2.0" -authors = ["lumi "] +authors = ["lumi ", "Emmanuel Gil Peyrot "] description = "A type-safe rust XMPP library. Still under development." homepage = "https://gitlab.com/lumi/xmpp-rs" repository = "https://gitlab.com/lumi/xmpp-rs" @@ -20,7 +20,7 @@ xmpp-parsers = "0.3.0" openssl = "0.9.12" base64 = "0.5.2" minidom = "0.4.1" -jid = "0.2.0" +jid = "0.2.1" sasl = "0.4.0" sha-1 = "0.3.3" diff --git a/examples/client.rs b/examples/client.rs index 9b8f66827311f2303b633425cc9c278bfb36a97d..49548b18f39a7ce75a12db084b378356f023a078 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -6,6 +6,7 @@ use xmpp::plugins::stanza::StanzaPlugin; use xmpp::plugins::unhandled_iq::UnhandledIqPlugin; use xmpp::plugins::messaging::{MessagingPlugin, MessageEvent}; use xmpp::plugins::presence::{PresencePlugin, Type}; +use xmpp::plugins::ibb::IbbPlugin; use xmpp::plugins::ping::{PingPlugin, PingEvent}; use xmpp::event::{Priority, Propagation}; @@ -22,6 +23,7 @@ fn main() { client.register_plugin(UnhandledIqPlugin::new()); client.register_plugin(MessagingPlugin::new()); client.register_plugin(PresencePlugin::new()); + client.register_plugin(IbbPlugin::new()); client.register_plugin(PingPlugin::new()); client.register_handler(Priority::Max, |e: &MessageEvent| { println!("{:?}", e); diff --git a/src/components/mod.rs b/src/components/mod.rs index 045a9d2fc797e2d1e73c902732aef4312d50b3f2..1d7dcd7d7307dd54a38ce9d549aa9e3c12d46020 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -1,3 +1 @@ -pub mod stanza_error; pub mod sasl_error; -//pub mod stream_error; diff --git a/src/components/stanza_error.rs b/src/components/stanza_error.rs deleted file mode 100644 index 3622b62ee8413ffb15e5b9eb3971816e435b3fd7..0000000000000000000000000000000000000000 --- a/src/components/stanza_error.rs +++ /dev/null @@ -1,172 +0,0 @@ -use ns; -use minidom::Element; -use util::{FromElement, FromParentElement}; -use std::str::FromStr; - -#[derive(Copy, Clone, Debug)] -pub enum ErrorType { - Auth, - Cancel, - Continue, - Modify, - Wait, -} - -impl FromStr for ErrorType { - type Err = (); - - fn from_str(s: &str) -> Result { - Ok(match s { - "auth" => ErrorType::Auth, - "cancel" => ErrorType::Cancel, - "continue" => ErrorType::Continue, - "modify" => ErrorType::Modify, - "wait" => ErrorType::Wait, - _ => { return Err(()); }, - }) - } -} - -#[derive(Clone, Debug)] -pub enum Condition { - BadRequest, - Conflict, - FeatureNotImplemented, - Forbidden, - Gone(Option), - InternalServerError, - ItemNotFound, - JidMalformed, - NotAcceptable, - NotAllowed, - NotAuthorized, - PolicyViolation, - RecipientUnavailable, - Redirect(Option), - RegistrationRequired, - RemoteServerNotFound, - RemoteServerTimeout, - ResourceConstraint, - ServiceUnavailable, - SubscriptionRequired, - UndefinedCondition, - UnexpectedRequest, -} - -impl FromParentElement for Condition { - type Err = (); - - fn from_parent_element(elem: &Element) -> Result { - if elem.has_child("bad-request", ns::STANZAS) { - Ok(Condition::BadRequest) - } - else if elem.has_child("conflict", ns::STANZAS) { - Ok(Condition::Conflict) - } - else if elem.has_child("feature-not-implemented", ns::STANZAS) { - Ok(Condition::FeatureNotImplemented) - } - else if elem.has_child("forbidden", ns::STANZAS) { - Ok(Condition::Forbidden) - } - else if let Some(alt) = elem.get_child("gone", ns::STANZAS) { - let text = alt.text(); - let inner = if text == "" { None } else { Some(text) }; - Ok(Condition::Gone(inner)) - } - else if elem.has_child("internal-server-error", ns::STANZAS) { - Ok(Condition::InternalServerError) - } - else if elem.has_child("item-not-found", ns::STANZAS) { - Ok(Condition::ItemNotFound) - } - else if elem.has_child("jid-malformed", ns::STANZAS) { - Ok(Condition::JidMalformed) - } - else if elem.has_child("not-acceptable", ns::STANZAS) { - Ok(Condition::NotAcceptable) - } - else if elem.has_child("not-allowed", ns::STANZAS) { - Ok(Condition::NotAllowed) - } - else if elem.has_child("not-authorized", ns::STANZAS) { - Ok(Condition::NotAuthorized) - } - else if elem.has_child("policy-violation", ns::STANZAS) { - Ok(Condition::PolicyViolation) - } - else if elem.has_child("recipient-unavailable", ns::STANZAS) { - Ok(Condition::RecipientUnavailable) - } - else if let Some(alt) = elem.get_child("redirect", ns::STANZAS) { - let text = alt.text(); - let inner = if text == "" { None } else { Some(text) }; - Ok(Condition::Redirect(inner)) - } - else if elem.has_child("registration-required", ns::STANZAS) { - Ok(Condition::RegistrationRequired) - } - else if elem.has_child("remote-server-not-found", ns::STANZAS) { - Ok(Condition::RemoteServerNotFound) - } - else if elem.has_child("remote-server-timeout", ns::STANZAS) { - Ok(Condition::RemoteServerTimeout) - } - else if elem.has_child("resource-constraint", ns::STANZAS) { - Ok(Condition::ResourceConstraint) - } - else if elem.has_child("service-unavailable", ns::STANZAS) { - Ok(Condition::ServiceUnavailable) - } - else if elem.has_child("subscription-required", ns::STANZAS) { - Ok(Condition::SubscriptionRequired) - } - else if elem.has_child("undefined-condition", ns::STANZAS) { - Ok(Condition::UndefinedCondition) - } - else if elem.has_child("unexpected-request", ns::STANZAS) { - Ok(Condition::UnexpectedRequest) - } - else { - Err(()) - } - } -} - -#[derive(Clone, Debug)] -pub struct StanzaError { - error_type: ErrorType, - text: Option, - condition: Condition, -} - -impl StanzaError { - pub fn new(error_type: ErrorType, text: Option, condition: Condition) -> StanzaError { - StanzaError { - error_type: error_type, - text: text, - condition: condition, - } - } -} - -impl FromElement for StanzaError { - type Err = (); - - fn from_element(elem: &Element) -> Result { - if elem.is("error", ns::STANZAS) { - let error_type = elem.attr("type").ok_or(())?; - let err: ErrorType = error_type.parse().map_err(|_| ())?; - let condition: Condition = Condition::from_parent_element(elem)?; - let text = elem.get_child("text", ns::STANZAS).map(|c| c.text()); - Ok(StanzaError { - error_type: err, - text: text, - condition: condition, - }) - } - else { - Err(()) - } - } -} diff --git a/src/plugins/ibb.rs b/src/plugins/ibb.rs new file mode 100644 index 0000000000000000000000000000000000000000..800003879541d716352c4e55f14f5b9191f05090 --- /dev/null +++ b/src/plugins/ibb.rs @@ -0,0 +1,175 @@ +use std::collections::{HashMap, BTreeMap}; +use std::collections::hash_map::Entry; +use std::convert::TryFrom; +use std::sync::{Mutex, Arc}; + +use plugin::PluginProxy; +use event::{Event, Priority, Propagation}; +use jid::Jid; + +use plugins::stanza::Iq; +use xmpp_parsers::iq::{IqType, IqPayload}; +use xmpp_parsers::ibb::{IBB, Stanza}; +use xmpp_parsers::stanza_error::{StanzaError, ErrorType, DefinedCondition}; + +#[derive(Debug, Clone)] +pub struct Session { + stanza: Stanza, + block_size: u16, + cur_seq: u16, +} + +#[derive(Debug)] +pub struct IbbOpen { + pub session: Session, +} + +#[derive(Debug)] +pub struct IbbData { + pub session: Session, + pub data: Vec, +} + +#[derive(Debug)] +pub struct IbbClose { + pub session: Session, +} + +impl Event for IbbOpen {} +impl Event for IbbData {} +impl Event for IbbClose {} + +fn generate_error(type_: ErrorType, defined_condition: DefinedCondition, text: &str) -> StanzaError { + StanzaError { + type_: type_, + defined_condition: defined_condition, + texts: { + let mut texts = BTreeMap::new(); + texts.insert(String::new(), String::from(text)); + texts + }, + by: None, + other: None, + } +} + +pub struct IbbPlugin { + proxy: PluginProxy, + sessions: Arc>>, +} + +impl IbbPlugin { + pub fn new() -> IbbPlugin { + IbbPlugin { + proxy: PluginProxy::new(), + sessions: Arc::new(Mutex::new(HashMap::new())), + } + } + + fn handle_ibb(&self, from: Jid, ibb: IBB) -> Result<(), StanzaError> { + let mut sessions = self.sessions.lock().unwrap(); + match ibb { + IBB::Open { block_size, sid, stanza } => { + match sessions.entry((from.clone(), sid.clone())) { + Entry::Vacant(_) => Ok(()), + Entry::Occupied(_) => Err(generate_error( + ErrorType::Cancel, + DefinedCondition::NotAcceptable, + "This session is already open." + )), + }?; + let session = Session { + stanza, + block_size, + cur_seq: 65535u16, + }; + sessions.insert((from, sid), session.clone()); + self.proxy.dispatch(IbbOpen { + session: session, + }); + }, + IBB::Data { seq, sid, data } => { + let entry = match sessions.entry((from, sid)) { + Entry::Occupied(entry) => Ok(entry), + Entry::Vacant(_) => Err(generate_error( + ErrorType::Cancel, + DefinedCondition::ItemNotFound, + "This session doesn’t exist." + )), + }?; + let mut session = entry.into_mut(); + if session.stanza != Stanza::Iq { + return Err(generate_error( + ErrorType::Cancel, + DefinedCondition::NotAcceptable, + "Wrong stanza type." + )) + } + let cur_seq = session.cur_seq.wrapping_add(1); + if seq != cur_seq { + return Err(generate_error( + ErrorType::Cancel, + DefinedCondition::NotAcceptable, + "Wrong seq number." + )) + } + session.cur_seq = cur_seq; + self.proxy.dispatch(IbbData { + session: session.clone(), + data, + }); + }, + IBB::Close { sid } => { + let entry = match sessions.entry((from, sid)) { + Entry::Occupied(entry) => Ok(entry), + Entry::Vacant(_) => Err(generate_error( + ErrorType::Cancel, + DefinedCondition::ItemNotFound, + "This session doesn’t exist." + )), + }?; + let session = entry.remove(); + self.proxy.dispatch(IbbClose { + session, + }); + }, + } + Ok(()) + } + + fn handle_iq(&self, iq: &Iq) -> Propagation { + let iq = iq.clone(); + if let IqType::Set(payload) = iq.payload { + let from = iq.from.unwrap(); + let id = iq.id.unwrap(); + // TODO: use an intermediate plugin to parse this payload. + let payload = match IqPayload::try_from(payload) { + Ok(IqPayload::IBB(ibb)) => { + match self.handle_ibb(from.clone(), ibb) { + Ok(_) => IqType::Result(None), + Err(error) => IqType::Error(error), + } + }, + Err(err) => IqType::Error(generate_error( + ErrorType::Cancel, + DefinedCondition::NotAcceptable, + format!("{:?}", err).as_ref() + )), + Ok(_) => return Propagation::Continue, + }; + self.proxy.send(Iq { + from: None, + to: Some(from), + id: Some(id), + payload: payload, + }.into()); + Propagation::Stop + } else { + Propagation::Continue + } + } +} + +impl_plugin!(IbbPlugin, proxy, [ + (Iq, Priority::Default) => handle_iq, +]); diff --git a/src/plugins/mod.rs b/src/plugins/mod.rs index 8bbb3af8f13d027f6ff1b4b81bc9f68f112f64a9..d2846f53f77ec92472b6b81146db449789cc02a8 100644 --- a/src/plugins/mod.rs +++ b/src/plugins/mod.rs @@ -1,6 +1,7 @@ pub mod messaging; pub mod presence; pub mod ping; +pub mod ibb; pub mod stanza; pub mod stanza_debug; pub mod unhandled_iq;