switch from rustxml to minidom, doesn't work
This commit is contained in:
parent
fa08591162
commit
d4bd64370c
11 changed files with 241 additions and 205 deletions
|
|
@ -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 = "*"
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) => {
|
|
||||||
e = Some(child.name.clone());
|
|
||||||
break
|
break
|
||||||
},
|
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
}
|
let e = e.unwrap_or_else(|| "Authentication failure");
|
||||||
let e = e.unwrap_or_else(|| "Authentication failure".to_owned());
|
Err(e.to_owned())
|
||||||
Err(e)
|
|
||||||
},
|
},
|
||||||
Ok(Async::Ready(event)) => {
|
Ok(Async::Ready(event)) => {
|
||||||
println!("ClientAuth ignore {:?}", event);
|
println!("ClientAuth ignore {:?}", event);
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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> {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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("&"),
|
||||||
|
'<' => result.push_str("<"),
|
||||||
|
'>' => result.push_str(">"),
|
||||||
|
'\'' => result.push_str("'"),
|
||||||
|
'"' => result.push_str("""),
|
||||||
|
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,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue