Merge branch 'ibb' into 'master'

Add an IBB plugin, implementing XEP-0047

See merge request !12
This commit is contained in:
lumi 2017-05-27 21:32:01 +00:00
commit cbff1a7e9b
6 changed files with 180 additions and 176 deletions

View file

@ -1,7 +1,7 @@
[package]
name = "xmpp"
version = "0.2.0"
authors = ["lumi <lumi@pew.im>"]
authors = ["lumi <lumi@pew.im>", "Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>"]
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"

View file

@ -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);

View file

@ -1,3 +1 @@
pub mod stanza_error;
pub mod sasl_error;
//pub mod stream_error;

View file

@ -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<ErrorType, ()> {
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<String>),
InternalServerError,
ItemNotFound,
JidMalformed,
NotAcceptable,
NotAllowed,
NotAuthorized,
PolicyViolation,
RecipientUnavailable,
Redirect(Option<String>),
RegistrationRequired,
RemoteServerNotFound,
RemoteServerTimeout,
ResourceConstraint,
ServiceUnavailable,
SubscriptionRequired,
UndefinedCondition,
UnexpectedRequest,
}
impl FromParentElement for Condition {
type Err = ();
fn from_parent_element(elem: &Element) -> Result<Condition, ()> {
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<String>,
condition: Condition,
}
impl StanzaError {
pub fn new(error_type: ErrorType, text: Option<String>, condition: Condition) -> StanzaError {
StanzaError {
error_type: error_type,
text: text,
condition: condition,
}
}
}
impl FromElement for StanzaError {
type Err = ();
fn from_element(elem: &Element) -> Result<StanzaError, ()> {
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(())
}
}
}

175
src/plugins/ibb.rs Normal file
View file

@ -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<u8>,
}
#[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<Mutex<HashMap<(Jid, String), Session>>>,
}
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 doesnt 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 doesnt 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,
]);

View file

@ -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;