Merge branch 'ibb' into 'master'
Add an IBB plugin, implementing XEP-0047 See merge request !12
This commit is contained in:
commit
cbff1a7e9b
6 changed files with 180 additions and 176 deletions
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -1,3 +1 @@
|
|||
pub mod stanza_error;
|
||||
pub mod sasl_error;
|
||||
//pub mod stream_error;
|
||||
|
|
|
|||
|
|
@ -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
175
src/plugins/ibb.rs
Normal 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 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,
|
||||
]);
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue