switch from rustxml to minidom, doesn't work

This commit is contained in:
Astro 2017-07-17 20:53:00 +02:00
commit d4bd64370c
11 changed files with 241 additions and 205 deletions

View file

@ -7,8 +7,10 @@ authors = ["Astro <astro@spaceboyz.net>"]
futures = "*" futures = "*"
tokio-core = "0.1.7" tokio-core = "0.1.7"
tokio-io = "*" tokio-io = "*"
bytes = "*" bytes = "0.4.4"
RustyXML = "*" xml5ever = "*"
tendril = "*"
minidom = { path = "../../programs/minidom-rs" }
native-tls = "*" native-tls = "*"
tokio-tls = "*" tokio-tls = "*"
sasl = "*" sasl = "*"

View file

@ -2,13 +2,14 @@ extern crate futures;
extern crate tokio_core; extern crate tokio_core;
extern crate tokio_xmpp; extern crate tokio_xmpp;
extern crate jid; extern crate jid;
extern crate xml; extern crate minidom;
use std::env::args; use std::env::args;
use std::process::exit; use std::process::exit;
use tokio_core::reactor::Core; use tokio_core::reactor::Core;
use futures::{Future, Stream, Sink, future}; use futures::{Future, Stream, Sink, future};
use tokio_xmpp::Client; use tokio_xmpp::Client;
use minidom::Element;
fn main() { fn main() {
let args: Vec<String> = args().collect(); let args: Vec<String> = args().collect();
@ -47,14 +48,14 @@ fn main() {
let presence = make_presence(); let presence = make_presence();
send(presence); send(presence);
} else if let Some(stanza) = event.as_stanza() { } else if let Some(stanza) = event.as_stanza() {
if stanza.name == "message" && if stanza.name() == "message" &&
stanza.get_attribute("type", None) != Some("error") { stanza.attr("type") != Some("error") {
// This is a message we'll echo // This is a message we'll echo
let from = stanza.get_attribute("from", None); let from = stanza.attr("from");
let body = stanza.get_child("body", Some("jabber:client")) let body = stanza.get_child("body", "jabber:client")
.map(|el| el.content_str()); .map(|el| el.text());
match (from.as_ref(), body) { match (from, body) {
(Some(from), Some(body)) => { (Some(from), Some(body)) => {
let reply = make_reply(from, body); let reply = make_reply(from, body);
send(reply); send(reply);
@ -78,24 +79,24 @@ fn main() {
} }
// Construct a <presence/> // Construct a <presence/>
fn make_presence() -> xml::Element { fn make_presence() -> Element {
let mut presence = xml::Element::new("presence".to_owned(), None, vec![]); Element::builder("presence")
presence.tag(xml::Element::new("status".to_owned(), None, vec![])) .append(Element::builder("status")
.text("chat".to_owned()); .append("chat")
presence.tag(xml::Element::new("show".to_owned(), None, vec![])) .build())
.text("Echoing messages".to_owned()); .append(Element::builder("show")
presence .append("Echoing messages")
.build())
.build()
} }
// Construct a chat <message/> // Construct a chat <message/>
fn make_reply(to: &str, body: String) -> xml::Element { fn make_reply(to: &str, body: String) -> Element {
let mut message = xml::Element::new( Element::builder("message")
"message".to_owned(), .attr("type", "chat")
None, .attr("to", to)
vec![("type".to_owned(), None, "chat".to_owned()), .append(Element::builder("body")
("to".to_owned(), None, to.to_owned())] .append(body)
); .build())
message.tag(xml::Element::new("body".to_owned(), None, vec![])) .build()
.text(body);
message
} }

View file

@ -2,7 +2,7 @@ use std::mem::replace;
use futures::*; use futures::*;
use futures::sink; use futures::sink;
use tokio_io::{AsyncRead, AsyncWrite}; use tokio_io::{AsyncRead, AsyncWrite};
use xml; use minidom::Element;
use sasl::common::Credentials; use sasl::common::Credentials;
use sasl::common::scram::*; use sasl::common::scram::*;
use sasl::client::Mechanism; use sasl::client::Mechanism;
@ -37,12 +37,13 @@ impl<S: AsyncWrite> ClientAuth<S> {
]; ];
let mech_names: Vec<String> = let mech_names: Vec<String> =
match stream.stream_features.get_child("mechanisms", Some(NS_XMPP_SASL)) { match stream.stream_features.get_child("mechanisms", NS_XMPP_SASL) {
None => None =>
return Err("No auth mechanisms".to_owned()), return Err("No auth mechanisms".to_owned()),
Some(mechs) => Some(mechs) =>
mechs.get_children("mechanism", Some(NS_XMPP_SASL)) mechs.children()
.map(|mech_el| mech_el.content_str()) .filter(|child| child.is("mechanism", NS_XMPP_SASL))
.map(|mech_el| mech_el.text())
.collect(), .collect(),
}; };
println!("SASL mechanisms offered: {:?}", mech_names); println!("SASL mechanisms offered: {:?}", mech_names);
@ -58,7 +59,7 @@ impl<S: AsyncWrite> ClientAuth<S> {
}; };
this.send( this.send(
stream, stream,
"auth", &[("mechanism".to_owned(), name)], "auth", &[("mechanism", &name)],
&initial &initial
); );
return Ok(this); return Ok(this);
@ -68,15 +69,13 @@ impl<S: AsyncWrite> ClientAuth<S> {
Err("No supported SASL mechanism available".to_owned()) Err("No supported SASL mechanism available".to_owned())
} }
fn send(&mut self, stream: XMPPStream<S>, nonza_name: &str, attrs: &[(String, String)], content: &[u8]) { fn send(&mut self, stream: XMPPStream<S>, nonza_name: &str, attrs: &[(&str, &str)], content: &[u8]) {
let mut nonza = xml::Element::new( let nonza = Element::builder(nonza_name)
nonza_name.to_owned(), .ns(NS_XMPP_SASL);
Some(NS_XMPP_SASL.to_owned()), let nonza = attrs.iter()
attrs.iter() .fold(nonza, |nonza, &(name, value)| nonza.attr(name, value))
.map(|&(ref name, ref value)| (name.clone(), None, value.clone())) .append(content.to_base64(base64::URL_SAFE))
.collect() .build();
);
nonza.text(content.to_base64(base64::URL_SAFE));
let send = stream.send(Packet::Stanza(nonza)); let send = stream.send(Packet::Stanza(nonza));
@ -108,11 +107,11 @@ impl<S: AsyncRead + AsyncWrite> Future for ClientAuth<S> {
ClientAuthState::WaitRecv(mut stream) => ClientAuthState::WaitRecv(mut stream) =>
match stream.poll() { match stream.poll() {
Ok(Async::Ready(Some(Packet::Stanza(ref stanza)))) Ok(Async::Ready(Some(Packet::Stanza(ref stanza))))
if stanza.name == "challenge" if stanza.name() == "challenge"
&& stanza.ns == Some(NS_XMPP_SASL.to_owned()) => && stanza.ns() == Some(NS_XMPP_SASL) =>
{ {
let content = try!( let content = try!(
stanza.content_str() stanza.text()
.from_base64() .from_base64()
.map_err(|e| format!("{}", e)) .map_err(|e| format!("{}", e))
); );
@ -121,29 +120,24 @@ impl<S: AsyncRead + AsyncWrite> Future for ClientAuth<S> {
self.poll() self.poll()
}, },
Ok(Async::Ready(Some(Packet::Stanza(ref stanza)))) Ok(Async::Ready(Some(Packet::Stanza(ref stanza))))
if stanza.name == "success" if stanza.name() == "success"
&& stanza.ns == Some(NS_XMPP_SASL.to_owned()) => && stanza.ns() == Some(NS_XMPP_SASL) =>
{ {
let start = stream.restart(); let start = stream.restart();
self.state = ClientAuthState::Start(start); self.state = ClientAuthState::Start(start);
self.poll() self.poll()
}, },
Ok(Async::Ready(Some(Packet::Stanza(ref stanza)))) Ok(Async::Ready(Some(Packet::Stanza(ref stanza))))
if stanza.name == "failure" if stanza.name() == "failure"
&& stanza.ns == Some(NS_XMPP_SASL.to_owned()) => && stanza.ns() == Some(NS_XMPP_SASL) =>
{ {
let mut e = None; let mut e = None;
for child in &stanza.children { for child in stanza.children() {
match child { e = Some(child.name().clone());
&xml::Xml::ElementNode(ref child) => { break
e = Some(child.name.clone());
break
},
_ => (),
}
} }
let e = e.unwrap_or_else(|| "Authentication failure".to_owned()); let e = e.unwrap_or_else(|| "Authentication failure");
Err(e) Err(e.to_owned())
}, },
Ok(Async::Ready(event)) => { Ok(Async::Ready(event)) => {
println!("ClientAuth ignore {:?}", event); println!("ClientAuth ignore {:?}", event);

View file

@ -4,8 +4,8 @@ use std::str::FromStr;
use futures::*; use futures::*;
use futures::sink; use futures::sink;
use tokio_io::{AsyncRead, AsyncWrite}; use tokio_io::{AsyncRead, AsyncWrite};
use xml;
use jid::Jid; use jid::Jid;
use minidom::Element;
use xmpp_codec::*; use xmpp_codec::*;
use xmpp_stream::*; use xmpp_stream::*;
@ -25,7 +25,7 @@ impl<S: AsyncWrite> ClientBind<S> {
/// the stream for anything else until the resource binding /// the stream for anything else until the resource binding
/// req/resp are done. /// req/resp are done.
pub fn new(stream: XMPPStream<S>) -> Self { pub fn new(stream: XMPPStream<S>) -> Self {
match stream.stream_features.get_child("bind", Some(NS_XMPP_BIND)) { match stream.stream_features.get_child("bind", NS_XMPP_BIND) {
None => None =>
// No resource binding available, // No resource binding available,
// return the (probably // usable) stream immediately // return the (probably // usable) stream immediately
@ -39,31 +39,22 @@ impl<S: AsyncWrite> ClientBind<S> {
} }
} }
fn make_bind_request(resource: Option<&String>) -> xml::Element { fn make_bind_request(resource: Option<&String>) -> Element {
let mut iq = xml::Element::new( let iq = Element::builder("iq")
"iq".to_owned(), .attr("type", "set")
None, .attr("id", BIND_REQ_ID);
vec![("type".to_owned(), None, "set".to_owned()), let mut bind_el = Element::builder("bind")
("id".to_owned(), None, BIND_REQ_ID.to_owned())] .ns(NS_XMPP_BIND);
); match resource {
{ Some(resource) => {
let bind_el = iq.tag( let resource_el = Element::builder("resource")
xml::Element::new( .append(resource);
"bind".to_owned(), bind_el = bind_el.append(resource_el.build());
Some(NS_XMPP_BIND.to_owned()), },
vec![] None => (),
));
resource.map(|resource| {
let resource_el = bind_el.tag(
xml::Element::new(
"resource".to_owned(),
Some(NS_XMPP_BIND.to_owned()),
vec![]
));
resource_el.text(resource.clone());
});
} }
iq iq.append(bind_el.build())
.build()
} }
impl<S: AsyncRead + AsyncWrite> Future for ClientBind<S> { impl<S: AsyncRead + AsyncWrite> Future for ClientBind<S> {
@ -93,9 +84,9 @@ impl<S: AsyncRead + AsyncWrite> Future for ClientBind<S> {
ClientBind::WaitRecv(mut stream) => { ClientBind::WaitRecv(mut stream) => {
match stream.poll() { match stream.poll() {
Ok(Async::Ready(Some(Packet::Stanza(ref iq)))) Ok(Async::Ready(Some(Packet::Stanza(ref iq))))
if iq.name == "iq" if iq.name() == "iq"
&& iq.get_attribute("id", None) == Some(BIND_REQ_ID) => { && iq.attr("id") == Some(BIND_REQ_ID) => {
match iq.get_attribute("type", None) { match iq.attr("type") {
Some("result") => { Some("result") => {
get_bind_response_jid(&iq) get_bind_response_jid(&iq)
.map(|jid| stream.jid = jid); .map(|jid| stream.jid = jid);
@ -123,13 +114,13 @@ impl<S: AsyncRead + AsyncWrite> Future for ClientBind<S> {
} }
} }
fn get_bind_response_jid(iq: &xml::Element) -> Option<Jid> { fn get_bind_response_jid(iq: &Element) -> Option<Jid> {
iq.get_child("bind", Some(NS_XMPP_BIND)) iq.get_child("bind", NS_XMPP_BIND)
.and_then(|bind_el| .and_then(|bind_el|
bind_el.get_child("jid", Some(NS_XMPP_BIND)) bind_el.get_child("jid", NS_XMPP_BIND)
) )
.and_then(|jid_el| .and_then(|jid_el|
Jid::from_str(&jid_el.content_str()) Jid::from_str(&jid_el.text())
.ok() .ok()
) )
} }

View file

@ -1,10 +1,10 @@
use xml; use minidom::Element;
#[derive(Debug)] #[derive(Debug)]
pub enum Event { pub enum Event {
Online, Online,
Disconnected, Disconnected,
Stanza(xml::Element), Stanza(Element),
} }
impl Event { impl Event {
@ -17,12 +17,12 @@ impl Event {
pub fn is_stanza(&self, name: &str) -> bool { pub fn is_stanza(&self, name: &str) -> bool {
match self { match self {
&Event::Stanza(ref stanza) => stanza.name == name, &Event::Stanza(ref stanza) => stanza.name() == name,
_ => false, _ => false,
} }
} }
pub fn as_stanza(&self) -> Option<&xml::Element> { pub fn as_stanza(&self) -> Option<&Element> {
match self { match self {
&Event::Stanza(ref stanza) => Some(stanza), &Event::Stanza(ref stanza) => Some(stanza),
_ => None, _ => None,

View file

@ -6,8 +6,8 @@ use tokio_core::net::TcpStream;
use tokio_io::{AsyncRead, AsyncWrite}; use tokio_io::{AsyncRead, AsyncWrite};
use tokio_tls::TlsStream; use tokio_tls::TlsStream;
use futures::*; use futures::*;
use minidom::Element;
use jid::{Jid, JidParseError}; use jid::{Jid, JidParseError};
use xml;
use sasl::common::{Credentials, ChannelBinding}; use sasl::common::{Credentials, ChannelBinding};
use super::xmpp_codec::Packet; use super::xmpp_codec::Packet;
@ -76,7 +76,7 @@ impl Client {
fn can_starttls<S>(stream: &xmpp_stream::XMPPStream<S>) -> bool { fn can_starttls<S>(stream: &xmpp_stream::XMPPStream<S>) -> bool {
stream.stream_features stream.stream_features
.get_child("starttls", Some(NS_XMPP_TLS)) .get_child("starttls", NS_XMPP_TLS)
.is_some() .is_some()
} }
@ -151,7 +151,7 @@ impl Stream for Client {
} }
impl Sink for Client { impl Sink for Client {
type SinkItem = xml::Element; type SinkItem = Element;
type SinkError = String; type SinkError = String;
fn start_send(&mut self, item: Self::SinkItem) -> StartSend<Self::SinkItem, Self::SinkError> { fn start_send(&mut self, item: Self::SinkItem) -> StartSend<Self::SinkItem, Self::SinkError> {

View file

@ -3,7 +3,9 @@ extern crate futures;
extern crate tokio_core; extern crate tokio_core;
extern crate tokio_io; extern crate tokio_io;
extern crate bytes; extern crate bytes;
extern crate xml; extern crate xml5ever;
extern crate tendril;
extern crate minidom;
extern crate native_tls; extern crate native_tls;
extern crate tokio_tls; extern crate tokio_tls;
extern crate sasl; extern crate sasl;

View file

@ -5,7 +5,7 @@ use futures::sink;
use tokio_io::{AsyncRead, AsyncWrite}; use tokio_io::{AsyncRead, AsyncWrite};
use tokio_tls::*; use tokio_tls::*;
use native_tls::TlsConnector; use native_tls::TlsConnector;
use xml; use minidom::Element;
use jid::Jid; use jid::Jid;
use xmpp_codec::*; use xmpp_codec::*;
@ -34,10 +34,9 @@ impl<S: AsyncRead + AsyncWrite> StartTlsClient<S> {
pub fn from_stream(xmpp_stream: XMPPStream<S>) -> Self { pub fn from_stream(xmpp_stream: XMPPStream<S>) -> Self {
let jid = xmpp_stream.jid.clone(); let jid = xmpp_stream.jid.clone();
let nonza = xml::Element::new( let nonza = Element::builder("starttls")
"starttls".to_owned(), Some(NS_XMPP_TLS.to_owned()), .ns(NS_XMPP_TLS)
vec![] .build();
);
let packet = Packet::Stanza(nonza); let packet = Packet::Stanza(nonza);
let send = xmpp_stream.send(packet); let send = xmpp_stream.send(packet);
@ -72,7 +71,7 @@ impl<S: AsyncRead + AsyncWrite> Future for StartTlsClient<S> {
StartTlsClientState::AwaitProceed(mut xmpp_stream) => StartTlsClientState::AwaitProceed(mut xmpp_stream) =>
match xmpp_stream.poll() { match xmpp_stream.poll() {
Ok(Async::Ready(Some(Packet::Stanza(ref stanza)))) Ok(Async::Ready(Some(Packet::Stanza(ref stanza))))
if stanza.name == "proceed" => if stanza.name() == "proceed" =>
{ {
let stream = xmpp_stream.stream.into_inner(); let stream = xmpp_stream.stream.into_inner();
let connect = TlsConnector::builder().unwrap() let connect = TlsConnector::builder().unwrap()

View file

@ -76,8 +76,8 @@ impl<S: AsyncRead + AsyncWrite> Future for StreamStart<S> {
StreamStartState::RecvFeatures(mut stream, stream_attrs) => StreamStartState::RecvFeatures(mut stream, stream_attrs) =>
match stream.poll() { match stream.poll() {
Ok(Async::Ready(Some(Packet::Stanza(stanza)))) => Ok(Async::Ready(Some(Packet::Stanza(stanza)))) =>
if stanza.name == "features" if stanza.name() == "features"
&& stanza.ns == Some(NS_XMPP_STREAM.to_owned()) { && stanza.ns() == Some(NS_XMPP_STREAM) {
let stream = XMPPStream::new(self.jid.clone(), stream, stream_attrs, stanza); let stream = XMPPStream::new(self.jid.clone(), stream, stream_attrs, stanza);
(StreamStartState::Invalid, Ok(Async::Ready(stream))) (StreamStartState::Invalid, Ok(Async::Ready(stream)))
} else { } else {

View file

@ -1,69 +1,139 @@
use std; use std;
use std::default::Default;
use std::iter::FromIterator;
use std::cell::RefCell;
use std::rc::Rc;
use std::fmt::Write; use std::fmt::Write;
use std::str::from_utf8; use std::str::from_utf8;
use std::io::{Error, ErrorKind}; use std::io::{Error, ErrorKind};
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::vec_deque::VecDeque;
use tokio_io::codec::{Encoder, Decoder}; use tokio_io::codec::{Encoder, Decoder};
use xml; use minidom::Element;
use xml5ever::tokenizer::{XmlTokenizer, TokenSink, Token, Tag, TagKind};
use bytes::*; use bytes::*;
const NS_XMLNS: &'static str = "http://www.w3.org/2000/xmlns/"; // const NS_XMLNS: &'static str = "http://www.w3.org/2000/xmlns/";
pub type Attributes = HashMap<(String, Option<String>), String>;
struct XMPPRoot {
builder: xml::ElementBuilder,
pub attributes: Attributes,
}
impl XMPPRoot {
fn new(root: xml::StartTag) -> Self {
let mut builder = xml::ElementBuilder::new();
let mut attributes = HashMap::new();
for (name_ns, value) in root.attributes {
match name_ns {
(ref name, None) if name == "xmlns" =>
builder.set_default_ns(value),
(ref prefix, Some(ref ns)) if ns == NS_XMLNS =>
builder.define_prefix(prefix.to_owned(), value),
_ => {
attributes.insert(name_ns, value);
},
}
}
XMPPRoot {
builder: builder,
attributes: attributes,
}
}
fn handle_event(&mut self, event: Result<xml::Event, xml::ParserError>)
-> Option<Result<xml::Element, xml::BuilderError>> {
self.builder.handle_event(event)
}
}
#[derive(Debug)] #[derive(Debug)]
pub enum Packet { pub enum Packet {
Error(Box<std::error::Error>), Error(Box<std::error::Error>),
StreamStart(HashMap<String, String>), StreamStart(HashMap<String, String>),
Stanza(xml::Element), Stanza(Element),
Text(String), Text(String),
StreamEnd, StreamEnd,
} }
struct ParserSink {
// Ready stanzas
queue: Rc<RefCell<VecDeque<Packet>>>,
// Parsing stack
stack: Vec<Element>,
}
impl ParserSink {
pub fn new(queue: Rc<RefCell<VecDeque<Packet>>>) -> Self {
ParserSink {
queue,
stack: vec![],
}
}
fn push_queue(&self, pkt: Packet) {
println!("push: {:?}", pkt);
self.queue.borrow_mut().push_back(pkt);
}
fn handle_start_tag(&mut self, tag: Tag) {
let el = tag_to_element(&tag);
self.stack.push(el);
}
fn handle_end_tag(&mut self) {
let el = self.stack.pop().unwrap();
match self.stack.len() {
// </stream:stream>
0 =>
self.push_queue(Packet::StreamEnd),
// </stanza>
1 =>
self.push_queue(Packet::Stanza(el)),
len => {
let parent = &mut self.stack[len - 1];
parent.append_child(el);
},
}
}
}
fn tag_to_element(tag: &Tag) -> Element {
let el_builder = Element::builder(tag.name.local.as_ref())
.ns(tag.name.ns.as_ref());
let el_builder = tag.attrs.iter().fold(
el_builder,
|el_builder, attr| el_builder.attr(
attr.name.local.as_ref(),
attr.value.as_ref()
)
);
el_builder.build()
}
impl TokenSink for ParserSink {
fn process_token(&mut self, token: Token) {
println!("Token: {:?}", token);
match token {
Token::TagToken(tag) => match tag.kind {
TagKind::StartTag =>
self.handle_start_tag(tag),
TagKind::EndTag =>
self.handle_end_tag(),
TagKind::EmptyTag => {
self.handle_start_tag(tag);
self.handle_end_tag();
},
TagKind::ShortTag =>
self.push_queue(Packet::Error(Box::new(Error::new(ErrorKind::InvalidInput, "ShortTag")))),
},
Token::CharacterTokens(tendril) =>
match self.stack.len() {
0 | 1 =>
self.push_queue(Packet::Text(tendril.into())),
len => {
let el = &mut self.stack[len - 1];
el.append_text_node(tendril);
},
},
Token::EOFToken =>
self.push_queue(Packet::StreamEnd),
Token::ParseError(s) => {
println!("ParseError: {:?}", s);
self.push_queue(Packet::Error(Box::new(Error::new(ErrorKind::InvalidInput, (*s).to_owned()))))
},
_ => (),
}
}
// fn end(&mut self) {
// }
}
pub struct XMPPCodec { pub struct XMPPCodec {
parser: xml::Parser, parser: XmlTokenizer<ParserSink>,
root: Option<XMPPRoot>, // For handling truncated utf8
buf: Vec<u8>, buf: Vec<u8>,
queue: Rc<RefCell<VecDeque<Packet>>>,
} }
impl XMPPCodec { impl XMPPCodec {
pub fn new() -> Self { pub fn new() -> Self {
let queue = Rc::new(RefCell::new((VecDeque::new())));
let sink = ParserSink::new(queue.clone());
// TODO: configure parser?
let parser = XmlTokenizer::new(sink, Default::default());
XMPPCodec { XMPPCodec {
parser: xml::Parser::new(), parser,
root: None, queue,
buf: vec![], buf: vec![],
} }
} }
@ -74,6 +144,7 @@ impl Decoder for XMPPCodec {
type Error = Error; type Error = Error;
fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> { fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
println!("decode {} bytes", buf.len());
let buf1: Box<AsRef<[u8]>> = let buf1: Box<AsRef<[u8]>> =
if self.buf.len() > 0 && buf.len() > 0 { if self.buf.len() > 0 && buf.len() > 0 {
let mut prefix = std::mem::replace(&mut self.buf, vec![]); let mut prefix = std::mem::replace(&mut self.buf, vec![]);
@ -87,7 +158,8 @@ impl Decoder for XMPPCodec {
Ok(s) => { Ok(s) => {
if s.len() > 0 { if s.len() > 0 {
println!("<< {}", s); println!("<< {}", s);
self.parser.feed_str(s); let tendril = FromIterator::from_iter(s.chars());
self.parser.feed(tendril);
} }
}, },
// Remedies for truncated utf8 // Remedies for truncated utf8
@ -110,57 +182,11 @@ impl Decoder for XMPPCodec {
}, },
} }
let mut new_root: Option<XMPPRoot> = None; let result = self.queue.borrow_mut().pop_front();
let mut result = None;
for event in &mut self.parser {
match self.root {
None => {
// Expecting <stream:stream>
match event {
Ok(xml::Event::ElementStart(start_tag)) => {
let mut attrs: HashMap<String, String> = HashMap::new();
for (&(ref name, _), value) in &start_tag.attributes {
attrs.insert(name.to_owned(), value.to_owned());
}
result = Some(Packet::StreamStart(attrs));
self.root = Some(XMPPRoot::new(start_tag));
break
},
Err(e) => {
result = Some(Packet::Error(Box::new(e)));
break
},
_ =>
(),
}
}
Some(ref mut root) => {
match root.handle_event(event) {
None => (),
Some(Ok(stanza)) => {
// Emit the stanza
result = Some(Packet::Stanza(stanza));
break
},
Some(Err(e)) => {
result = Some(Packet::Error(Box::new(e)));
break
}
};
},
}
match new_root.take() {
None => (),
Some(root) => self.root = Some(root),
}
}
Ok(result) Ok(result)
} }
fn decode_eof(&mut self, buf: &mut BytesMut) -> Result<Option<Self::Item>, Error> { fn decode_eof(&mut self, buf: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
self.decode(buf) self.decode(buf)
} }
} }
@ -170,35 +196,56 @@ impl Encoder for XMPPCodec {
type Error = Error; type Error = Error;
fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> { fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> {
println!("encode {:?}", item);
match item { match item {
Packet::StreamStart(start_attrs) => { Packet::StreamStart(start_attrs) => {
let mut buf = String::new(); let mut buf = String::new();
write!(buf, "<stream:stream").unwrap(); write!(buf, "<stream:stream").unwrap();
for (ref name, ref value) in &start_attrs { for (ref name, ref value) in &start_attrs {
write!(buf, " {}=\"{}\"", xml::escape(&name), xml::escape(&value)) write!(buf, " {}=\"{}\"", escape(&name), escape(&value))
.unwrap(); .unwrap();
} }
write!(buf, ">\n").unwrap(); write!(buf, ">\n").unwrap();
print!(">> {}", buf); print!(">> {}", buf);
write!(dst, "{}", buf) write!(dst, "{}", buf)
.map_err(|_| Error::from(ErrorKind::InvalidInput))
}, },
Packet::Stanza(stanza) => { Packet::Stanza(stanza) => {
println!(">> {}", stanza); println!(">> {:?}", stanza);
write!(dst, "{}", stanza) let mut root_ns = None; // TODO
stanza.write_to_inner(&mut dst.clone().writer(), &mut root_ns)
.map_err(|_| Error::from(ErrorKind::InvalidInput))
}, },
Packet::Text(text) => { Packet::Text(text) => {
let escaped = xml::escape(&text); let escaped = escape(&text);
println!(">> {}", escaped); println!(">> {}", escaped);
write!(dst, "{}", escaped) write!(dst, "{}", escaped)
.map_err(|_| Error::from(ErrorKind::InvalidInput))
}, },
// TODO: Implement all // TODO: Implement all
_ => Ok(()) _ => Ok(())
} }
.map_err(|_| Error::from(ErrorKind::InvalidInput))
} }
} }
/// Copied from RustyXML for now
pub fn escape(input: &str) -> String {
let mut result = String::with_capacity(input.len());
for c in input.chars() {
match c {
'&' => result.push_str("&amp;"),
'<' => result.push_str("&lt;"),
'>' => result.push_str("&gt;"),
'\'' => result.push_str("&apos;"),
'"' => result.push_str("&quot;"),
o => result.push(o)
}
}
result
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -240,8 +287,8 @@ mod tests {
let r = c.decode(&mut b); let r = c.decode(&mut b);
assert!(match r { assert!(match r {
Ok(Some(Packet::Stanza(ref el))) Ok(Some(Packet::Stanza(ref el)))
if el.name == "test" if el.name() == "test"
&& el.content_str() == "ß" && el.text() == "ß"
=> true, => true,
_ => false, _ => false,
}); });
@ -271,8 +318,8 @@ mod tests {
let r = c.decode(&mut b); let r = c.decode(&mut b);
assert!(match r { assert!(match r {
Ok(Some(Packet::Stanza(ref el))) Ok(Some(Packet::Stanza(ref el)))
if el.name == "test" if el.name() == "test"
&& el.content_str() == "ß" && el.text() == "ß"
=> true, => true,
_ => false, _ => false,
}); });

View file

@ -2,7 +2,7 @@ use std::collections::HashMap;
use futures::*; use futures::*;
use tokio_io::{AsyncRead, AsyncWrite}; use tokio_io::{AsyncRead, AsyncWrite};
use tokio_io::codec::Framed; use tokio_io::codec::Framed;
use xml; use minidom::Element;
use jid::Jid; use jid::Jid;
use xmpp_codec::*; use xmpp_codec::*;
@ -14,14 +14,14 @@ pub struct XMPPStream<S> {
pub jid: Jid, pub jid: Jid,
pub stream: Framed<S, XMPPCodec>, pub stream: Framed<S, XMPPCodec>,
pub stream_attrs: HashMap<String, String>, pub stream_attrs: HashMap<String, String>,
pub stream_features: xml::Element, pub stream_features: Element,
} }
impl<S: AsyncRead + AsyncWrite> XMPPStream<S> { impl<S: AsyncRead + AsyncWrite> XMPPStream<S> {
pub fn new(jid: Jid, pub fn new(jid: Jid,
stream: Framed<S, XMPPCodec>, stream: Framed<S, XMPPCodec>,
stream_attrs: HashMap<String, String>, stream_attrs: HashMap<String, String>,
stream_features: xml::Element) -> Self { stream_features: Element) -> Self {
XMPPStream { jid, stream, stream_attrs, stream_features } XMPPStream { jid, stream, stream_attrs, stream_features }
} }