minidom, tokio-xmpp: switch xml parsing to rxml

This commit is contained in:
Astro 2022-03-22 23:29:25 +01:00
commit d4a5a8247b
33 changed files with 465 additions and 811 deletions

View file

@ -22,3 +22,4 @@ gitlab = { repository = "xmpp-rs/xmpp-rs" }
[dependencies] [dependencies]
quick-xml = "0.22.0" quick-xml = "0.22.0"
rxml = "^0.7.1"

View file

@ -17,18 +17,17 @@ use crate::error::{Error, Result};
use crate::namespaces::NSChoice; use crate::namespaces::NSChoice;
use crate::node::Node; use crate::node::Node;
use crate::prefixes::{Namespace, Prefix, Prefixes}; use crate::prefixes::{Namespace, Prefix, Prefixes};
use crate::tree_builder::TreeBuilder;
use std::collections::{btree_map, BTreeMap}; use std::collections::{btree_map, BTreeMap};
use std::io::Write; use std::io::{BufRead, Write};
use std::borrow::Cow; use std::borrow::Cow;
use std::str; use std::str;
use quick_xml::events::{BytesDecl, BytesEnd, BytesStart, Event}; use quick_xml::events::{BytesDecl, BytesEnd, BytesStart, Event};
use quick_xml::Reader as EventReader;
use quick_xml::Writer as EventWriter; use quick_xml::Writer as EventWriter;
use rxml::{EventRead, Lexer, PullDriver, RawParser};
use std::io::BufRead;
use std::str::FromStr; use std::str::FromStr;
@ -84,8 +83,9 @@ pub struct Element {
namespace: String, namespace: String,
/// This is only used when deserializing. If you have to use a custom prefix use /// This is only used when deserializing. If you have to use a custom prefix use
/// `ElementBuilder::prefix`. /// `ElementBuilder::prefix`.
prefix: Option<Prefix>, pub(crate) prefix: Option<Prefix>,
prefixes: Prefixes, /// Namespace declarations
pub prefixes: Prefixes,
attributes: BTreeMap<String, String>, attributes: BTreeMap<String, String>,
children: Vec<Node>, children: Vec<Node>,
} }
@ -102,8 +102,7 @@ impl FromStr for Element {
type Err = Error; type Err = Error;
fn from_str(s: &str) -> Result<Element> { fn from_str(s: &str) -> Result<Element> {
let mut reader = EventReader::from_str(s); Element::from_reader(s.as_bytes())
Element::from_reader(&mut reader)
} }
} }
@ -120,15 +119,8 @@ impl PartialEq for Element {
} }
} }
fn ensure_no_prefix<S: AsRef<str>>(s: &S) -> Result<()> {
match s.as_ref().split(':').count() {
1 => Ok(()),
_ => Err(Error::InvalidElement),
}
}
impl Element { impl Element {
fn new<P: Into<Prefixes>>( pub(crate) fn new<P: Into<Prefixes>>(
name: String, name: String,
namespace: String, namespace: String,
prefix: Option<Prefix>, prefix: Option<Prefix>,
@ -136,8 +128,6 @@ impl Element {
attributes: BTreeMap<String, String>, attributes: BTreeMap<String, String>,
children: Vec<Node>, children: Vec<Node>,
) -> Element { ) -> Element {
ensure_no_prefix(&name).unwrap();
// TODO: Return Result<Element> instead.
Element { Element {
name, name,
namespace, namespace,
@ -310,123 +300,18 @@ impl Element {
namespace.into().compare(self.namespace.as_ref()) namespace.into().compare(self.namespace.as_ref())
} }
/// Parse a document from an `EventReader`. /// Parse a document from a `BufRead`.
pub fn from_reader<R: BufRead>(reader: &mut EventReader<R>) -> Result<Element> { pub fn from_reader<R: BufRead>(reader: R) -> Result<Element> {
let mut buf = Vec::new(); let mut tree_builder = TreeBuilder::new();
let mut driver = PullDriver::wrap(reader, Lexer::new(), RawParser::new());
while let Some(event) = driver.read()? {
tree_builder.process_event(event)?;
let mut prefixes = BTreeMap::new(); if let Some(root) = tree_builder.root.take() {
let root: Element = loop { return Ok(root);
let e = reader.read_event(&mut buf)?;
match e {
Event::Empty(ref e) | Event::Start(ref e) => {
break build_element(reader, e, &mut prefixes)?;
}
Event::Eof => {
return Err(Error::EndOfDocument);
}
Event::Comment { .. } => {
return Err(Error::NoComments);
}
Event::Text { .. }
| Event::End { .. }
| Event::CData { .. }
| Event::Decl { .. }
| Event::PI { .. }
| Event::DocType { .. } => (), // TODO: may need more errors
}
};
let mut stack = vec![root];
let mut prefix_stack = vec![prefixes];
loop {
match reader.read_event(&mut buf)? {
Event::Empty(ref e) => {
let mut prefixes = prefix_stack.last().unwrap().clone();
let elem = build_element(reader, e, &mut prefixes)?;
// Since there is no Event::End after, directly append it to the current node
stack.last_mut().unwrap().append_child(elem);
}
Event::Start(ref e) => {
let mut prefixes = prefix_stack.last().unwrap().clone();
let elem = build_element(reader, e, &mut prefixes)?;
stack.push(elem);
prefix_stack.push(prefixes);
}
Event::End(ref e) => {
if stack.len() <= 1 {
break;
}
let prefixes = prefix_stack.pop().unwrap();
let elem = stack.pop().unwrap();
if let Some(to) = stack.last_mut() {
// TODO: check whether this is correct, we are comparing &[u8]s, not &strs
let elem_name = e.name();
let mut split_iter = elem_name.splitn(2, |u| *u == 0x3A);
let possible_prefix = split_iter.next().unwrap(); // Can't be empty.
let opening_prefix = {
let mut tmp: Option<Option<String>> = None;
for (prefix, ns) in prefixes {
if ns == elem.namespace {
tmp = Some(prefix.clone());
break;
} }
} }
match tmp { Err(Error::EndOfDocument)
Some(prefix) => prefix,
None => return Err(Error::InvalidPrefix),
}
};
match split_iter.next() {
// There is a prefix on the closing tag
Some(name) => {
// Does the closing prefix match the opening prefix?
match opening_prefix {
Some(prefix) if possible_prefix == prefix.as_bytes() => (),
_ => return Err(Error::InvalidElementClosed),
}
// Does the closing tag name match the opening tag name?
if name != elem.name().as_bytes() {
return Err(Error::InvalidElementClosed);
}
}
// There was no prefix on the closing tag
None => {
// Is there a prefix on the opening tag?
if opening_prefix.is_some() {
return Err(Error::InvalidElementClosed);
}
// Does the opening tag name match the closing one?
if possible_prefix != elem.name().as_bytes() {
return Err(Error::InvalidElementClosed);
}
}
}
to.append_child(elem);
}
}
Event::Text(s) => {
let text = s.unescape_and_decode(reader)?;
if !text.is_empty() {
let current_elem = stack.last_mut().unwrap();
current_elem.append_text_node(text);
}
}
Event::CData(s) => {
let text = s.unescape_and_decode(&reader)?;
if !text.is_empty() {
let current_elem = stack.last_mut().unwrap();
current_elem.append_text_node(text);
}
}
Event::Eof => {
break;
}
Event::Comment(_) => return Err(Error::NoComments),
Event::Decl { .. } | Event::PI { .. } | Event::DocType { .. } => (),
}
}
Ok(stack.pop().unwrap())
} }
/// Output a document to a `Writer`. /// Output a document to a `Writer`.
@ -822,68 +707,18 @@ impl Element {
})?; })?;
self.children.remove(idx).into_element() self.children.remove(idx).into_element()
} }
}
fn split_element_name<S: AsRef<str>>(s: S) -> Result<(Option<String>, String)> { /// Remove the leading nodes up to the first child element and
let name_parts = s.as_ref().split(':').collect::<Vec<&str>>(); /// return it
match name_parts.len() { pub fn unshift_child(&mut self) -> Option<Element> {
2 => Ok((Some(name_parts[0].to_owned()), name_parts[1].to_owned())), while self.children.len() > 0 {
1 => Ok((None, name_parts[0].to_owned())), if let Some(el) = self.children.remove(0).into_element() {
_ => Err(Error::InvalidElement), return Some(el);
} }
}
fn build_element<R: BufRead>(
reader: &EventReader<R>,
event: &BytesStart,
prefixes: &mut BTreeMap<Prefix, Namespace>,
) -> Result<Element> {
let (prefix, name) = split_element_name(str::from_utf8(event.name())?)?;
let mut local_prefixes = BTreeMap::new();
let attributes = event
.attributes()
.map(|o| {
let o = o?;
let key = str::from_utf8(o.key)?.to_owned();
let value = o.unescape_and_decode_value(reader)?;
Ok((key, value))
})
.filter(|o| match *o {
Ok((ref key, ref value)) if key == "xmlns" => {
local_prefixes.insert(None, value.clone());
prefixes.insert(None, value.clone());
false
} }
Ok((ref key, ref value)) if key.starts_with("xmlns:") => {
local_prefixes.insert(Some(key[6..].to_owned()), value.to_owned());
prefixes.insert(Some(key[6..].to_owned()), value.to_owned());
false
}
_ => true,
})
.collect::<Result<BTreeMap<String, String>>>()?;
let namespace: &String = { None
if let Some(namespace) = local_prefixes.get(&prefix) {
namespace
} else if let Some(namespace) = prefixes.get(&prefix) {
namespace
} else {
return Err(Error::MissingNamespace);
} }
};
Ok(Element::new(
name,
namespace.clone(),
// Note that this will always be Some(_) as we can't distinguish between the None case and
// Some(None). At least we make sure the prefix has a namespace associated.
Some(prefix),
local_prefixes,
attributes,
Vec::new(),
))
} }
/// An iterator over references to child elements of an `Element`. /// An iterator over references to child elements of an `Element`.
@ -1067,9 +902,8 @@ mod tests {
#[test] #[test]
fn test_from_reader_simple() { fn test_from_reader_simple() {
let xml = "<foo xmlns='ns1'></foo>"; let xml = b"<foo xmlns='ns1'></foo>";
let mut reader = EventReader::from_str(xml); let elem = Element::from_reader(&xml[..]);
let elem = Element::from_reader(&mut reader);
let elem2 = Element::builder("foo", "ns1").build(); let elem2 = Element::builder("foo", "ns1").build();
@ -1078,9 +912,8 @@ mod tests {
#[test] #[test]
fn test_from_reader_nested() { fn test_from_reader_nested() {
let xml = "<foo xmlns='ns1'><bar xmlns='ns1' baz='qxx' /></foo>"; let xml = b"<foo xmlns='ns1'><bar xmlns='ns1' baz='qxx' /></foo>";
let mut reader = EventReader::from_str(xml); let elem = Element::from_reader(&xml[..]);
let elem = Element::from_reader(&mut reader);
let nested = Element::builder("bar", "ns1").attr("baz", "qxx").build(); let nested = Element::builder("bar", "ns1").attr("baz", "qxx").build();
let elem2 = Element::builder("foo", "ns1").append(nested).build(); let elem2 = Element::builder("foo", "ns1").append(nested).build();
@ -1090,9 +923,8 @@ mod tests {
#[test] #[test]
fn test_from_reader_with_prefix() { fn test_from_reader_with_prefix() {
let xml = "<foo xmlns='ns1'><prefix:bar xmlns:prefix='ns1' baz='qxx' /></foo>"; let xml = b"<foo xmlns='ns1'><prefix:bar xmlns:prefix='ns1' baz='qxx' /></foo>";
let mut reader = EventReader::from_str(xml); let elem = Element::from_reader(&xml[..]);
let elem = Element::from_reader(&mut reader);
let nested = Element::builder("bar", "ns1").attr("baz", "qxx").build(); let nested = Element::builder("bar", "ns1").attr("baz", "qxx").build();
let elem2 = Element::builder("foo", "ns1").append(nested).build(); let elem2 = Element::builder("foo", "ns1").append(nested).build();
@ -1102,9 +934,8 @@ mod tests {
#[test] #[test]
fn test_from_reader_split_prefix() { fn test_from_reader_split_prefix() {
let xml = "<foo:bar xmlns:foo='ns1'/>"; let xml = b"<foo:bar xmlns:foo='ns1'/>";
let mut reader = EventReader::from_str(xml); let elem = Element::from_reader(&xml[..]).unwrap();
let elem = Element::from_reader(&mut reader).unwrap();
assert_eq!(elem.name(), String::from("bar")); assert_eq!(elem.name(), String::from("bar"));
assert_eq!(elem.ns(), String::from("ns1")); assert_eq!(elem.ns(), String::from("ns1"));
@ -1118,38 +949,32 @@ mod tests {
#[test] #[test]
fn parses_spectest_xml() { fn parses_spectest_xml() {
// From: https://gitlab.com/lumi/minidom-rs/issues/8 // From: https://gitlab.com/lumi/minidom-rs/issues/8
let xml = r#" let xml = br#"<rng:grammar xmlns:rng="http://relaxng.org/ns/structure/1.0">
<rng:grammar xmlns:rng="http://relaxng.org/ns/structure/1.0">
<rng:name xmlns:rng="http://relaxng.org/ns/structure/1.0"></rng:name> <rng:name xmlns:rng="http://relaxng.org/ns/structure/1.0"></rng:name>
</rng:grammar> </rng:grammar>
"#; "#;
let mut reader = EventReader::from_str(xml); let _ = Element::from_reader(&xml[..]).unwrap();
let _ = Element::from_reader(&mut reader).unwrap();
} }
#[test] #[test]
fn does_not_unescape_cdata() { fn does_not_unescape_cdata() {
let xml = "<test xmlns='test'><![CDATA[&apos;&gt;blah<blah>]]></test>"; let xml = b"<test xmlns='test'><![CDATA[&apos;&gt;blah<blah>]]></test>";
let mut reader = EventReader::from_str(xml); let elem = Element::from_reader(&xml[..]).unwrap();
let elem = Element::from_reader(&mut reader).unwrap();
assert_eq!(elem.text(), "&apos;&gt;blah<blah>"); assert_eq!(elem.text(), "&apos;&gt;blah<blah>");
} }
#[test] #[test]
fn test_compare_all_ns() { fn test_compare_all_ns() {
let xml = "<foo xmlns='foo' xmlns:bar='baz'><bar:meh xmlns:bar='baz' /></foo>"; let xml = b"<foo xmlns='foo' xmlns:bar='baz'><bar:meh xmlns:bar='baz' /></foo>";
let mut reader = EventReader::from_str(xml); let elem = Element::from_reader(&xml[..]).unwrap();
let elem = Element::from_reader(&mut reader).unwrap();
let elem2 = elem.clone(); let elem2 = elem.clone();
let xml3 = "<foo xmlns='foo'><bar:meh xmlns:bar='baz'/></foo>"; let xml3 = b"<foo xmlns='foo'><bar:meh xmlns:bar='baz'/></foo>";
let mut reader3 = EventReader::from_str(xml3); let elem3 = Element::from_reader(&xml3[..]).unwrap();
let elem3 = Element::from_reader(&mut reader3).unwrap();
let xml4 = "<prefix:foo xmlns:prefix='foo'><bar:meh xmlns:bar='baz'/></prefix:foo>"; let xml4 = b"<prefix:foo xmlns:prefix='foo'><bar:meh xmlns:bar='baz'/></prefix:foo>";
let mut reader4 = EventReader::from_str(xml4); let elem4 = Element::from_reader(&xml4[..]).unwrap();
let elem4 = Element::from_reader(&mut reader4).unwrap();
assert_eq!(elem, elem2); assert_eq!(elem, elem2);
assert_eq!(elem, elem3); assert_eq!(elem, elem3);

View file

@ -20,21 +20,12 @@ pub enum Error {
/// An error from quick_xml. /// An error from quick_xml.
XmlError(::quick_xml::Error), XmlError(::quick_xml::Error),
/// An UTF-8 conversion error. /// Error from rxml parsing
Utf8Error(::std::str::Utf8Error), ParserError(rxml::Error),
/// An I/O error, from std::io.
IoError(::std::io::Error),
/// An error which is returned when the end of the document was reached prematurely. /// An error which is returned when the end of the document was reached prematurely.
EndOfDocument, EndOfDocument,
/// An error which is returned when an element is closed when it shouldn't be
InvalidElementClosed,
/// An error which is returned when an elemet's name contains more colons than permitted
InvalidElement,
/// An error which is returned when an element being serialized doesn't contain a prefix /// An error which is returned when an element being serialized doesn't contain a prefix
/// (be it None or Some(_)). /// (be it None or Some(_)).
InvalidPrefix, InvalidPrefix,
@ -42,9 +33,6 @@ pub enum Error {
/// An error which is returned when an element doesn't contain a namespace /// An error which is returned when an element doesn't contain a namespace
MissingNamespace, MissingNamespace,
/// An error which is returned when a comment is to be parsed by minidom
NoComments,
/// An error which is returned when a prefixed is defined twice /// An error which is returned when a prefixed is defined twice
DuplicatePrefix, DuplicatePrefix,
} }
@ -53,14 +41,10 @@ impl StdError for Error {
fn cause(&self) -> Option<&dyn StdError> { fn cause(&self) -> Option<&dyn StdError> {
match self { match self {
Error::XmlError(e) => Some(e), Error::XmlError(e) => Some(e),
Error::Utf8Error(e) => Some(e), Error::ParserError(e) => Some(e),
Error::IoError(e) => Some(e),
Error::EndOfDocument => None, Error::EndOfDocument => None,
Error::InvalidElementClosed => None,
Error::InvalidElement => None,
Error::InvalidPrefix => None, Error::InvalidPrefix => None,
Error::MissingNamespace => None, Error::MissingNamespace => None,
Error::NoComments => None,
Error::DuplicatePrefix => None, Error::DuplicatePrefix => None,
} }
} }
@ -70,21 +54,12 @@ impl std::fmt::Display for Error {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
match self { match self {
Error::XmlError(e) => write!(fmt, "XML error: {}", e), Error::XmlError(e) => write!(fmt, "XML error: {}", e),
Error::Utf8Error(e) => write!(fmt, "UTF-8 error: {}", e), Error::ParserError(e) => write!(fmt, "XML parser error: {}", e),
Error::IoError(e) => write!(fmt, "IO error: {}", e),
Error::EndOfDocument => { Error::EndOfDocument => {
write!(fmt, "the end of the document has been reached prematurely") write!(fmt, "the end of the document has been reached prematurely")
} }
Error::InvalidElementClosed => {
write!(fmt, "the XML is invalid, an element was wrongly closed")
}
Error::InvalidElement => write!(fmt, "the XML element is invalid"),
Error::InvalidPrefix => write!(fmt, "the prefix is invalid"), Error::InvalidPrefix => write!(fmt, "the prefix is invalid"),
Error::MissingNamespace => write!(fmt, "the XML element is missing a namespace",), Error::MissingNamespace => write!(fmt, "the XML element is missing a namespace",),
Error::NoComments => write!(
fmt,
"a comment has been found even though comments are forbidden"
),
Error::DuplicatePrefix => write!(fmt, "the prefix is already defined"), Error::DuplicatePrefix => write!(fmt, "the prefix is already defined"),
} }
} }
@ -96,15 +71,9 @@ impl From<::quick_xml::Error> for Error {
} }
} }
impl From<::std::str::Utf8Error> for Error { impl From<rxml::Error> for Error {
fn from(err: ::std::str::Utf8Error) -> Error { fn from(err: rxml::Error) -> Error {
Error::Utf8Error(err) Error::ParserError(err)
}
}
impl From<::std::io::Error> for Error {
fn from(err: ::std::io::Error) -> Error {
Error::IoError(err)
} }
} }

View file

@ -83,6 +83,7 @@ pub mod error;
mod namespaces; mod namespaces;
pub mod node; pub mod node;
mod prefixes; mod prefixes;
pub mod tree_builder;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;

View file

@ -10,16 +10,16 @@
use crate::element::Element; use crate::element::Element;
use crate::error::{Error, ParserError, Result}; use crate::error::{Error, ParserError, Result};
use crate::tree_builder::TreeBuilder;
use bytes::BytesMut; use rxml::{PushDriver, RawParser};
use quick_xml::Reader as EventReader;
use std::cell::RefCell;
use std::str; use std::str;
/// Parser /// Parser
#[derive(Debug)] #[derive(Debug)]
pub struct Parser { pub struct Parser {
buffer: RefCell<BytesMut>, driver: PushDriver<RawParser>,
tree_builder: TreeBuilder,
state: ParserState, state: ParserState,
} }
@ -90,14 +90,17 @@ impl Parser {
/// Creates a new Parser /// Creates a new Parser
pub fn new() -> Parser { pub fn new() -> Parser {
Parser { Parser {
buffer: RefCell::new(BytesMut::new()), driver: PushDriver::default(),
tree_builder: TreeBuilder::new(),
state: ParserState::Empty, state: ParserState::Empty,
} }
} }
/// Feed bytes to the parser. /// Feed bytes to the parser.
pub fn feed(&mut self, bytes: BytesMut) -> Result<()> { pub fn feed(&mut self, bytes: BytesMut) -> Result<()> {
self.buffer.borrow_mut().unsplit(bytes); self.driver.feed(bytes);
bytes.clear();
let state = match self.state { let state = match self.state {
ParserState::Empty => { ParserState::Empty => {
// TODO: Try splitting xml prolog and stream header // TODO: Try splitting xml prolog and stream header

View file

@ -13,9 +13,7 @@
use crate::element::Element; use crate::element::Element;
use crate::error::Error; use crate::error::Error;
use quick_xml::Reader; const TEST_STRING: &'static [u8] = br#"<root xmlns="root_ns" a="b" xml:lang="en">meow<child c="d"/><child xmlns="child_ns" d="e" xml:lang="fr"/>nya</root>"#;
const TEST_STRING: &'static str = r#"<root xmlns="root_ns" a="b" xml:lang="en">meow<child c="d"/><child xmlns="child_ns" d="e" xml:lang="fr"/>nya</root>"#;
fn build_test_tree() -> Element { fn build_test_tree() -> Element {
let mut root = Element::builder("root", "root_ns") let mut root = Element::builder("root", "root_ns")
@ -36,9 +34,8 @@ fn build_test_tree() -> Element {
#[test] #[test]
fn reader_works() { fn reader_works() {
let mut reader = Reader::from_str(TEST_STRING);
assert_eq!( assert_eq!(
Element::from_reader(&mut reader).unwrap(), Element::from_reader(TEST_STRING).unwrap(),
build_test_tree() build_test_tree()
); );
} }
@ -143,7 +140,7 @@ fn writer_works() {
{ {
root.write_to(&mut writer).unwrap(); root.write_to(&mut writer).unwrap();
} }
assert_eq!(String::from_utf8(writer).unwrap(), TEST_STRING); assert_eq!(writer, TEST_STRING);
} }
#[test] #[test]
@ -153,7 +150,10 @@ fn writer_with_decl_works() {
{ {
root.write_to_decl(&mut writer).unwrap(); root.write_to_decl(&mut writer).unwrap();
} }
let result = format!(r#"<?xml version="1.0" encoding="utf-8"?>{}"#, TEST_STRING); let result = format!(
r#"<?xml version="1.0" encoding="utf-8"?>{}"#,
String::from_utf8(TEST_STRING.to_owned()).unwrap()
);
assert_eq!(String::from_utf8(writer).unwrap(), result); assert_eq!(String::from_utf8(writer).unwrap(), result);
} }
@ -348,8 +348,7 @@ fn two_elements_with_same_arguments_different_order_are_equal() {
#[test] #[test]
fn namespace_attributes_works() { fn namespace_attributes_works() {
let mut reader = Reader::from_str(TEST_STRING); let root = Element::from_reader(TEST_STRING).unwrap();
let root = Element::from_reader(&mut reader).unwrap();
assert_eq!("en", root.attr("xml:lang").unwrap()); assert_eq!("en", root.attr("xml:lang").unwrap());
assert_eq!( assert_eq!(
"fr", "fr",
@ -424,7 +423,7 @@ fn namespace_inherited_prefixed2() {
fn fail_comments() { fn fail_comments() {
let elem: Result<Element, Error> = "<foo xmlns='ns1'><!-- bar --></foo>".parse(); let elem: Result<Element, Error> = "<foo xmlns='ns1'><!-- bar --></foo>".parse();
match elem { match elem {
Err(Error::NoComments) => (), Err(_) => (),
_ => panic!(), _ => panic!(),
}; };
} }
@ -432,20 +431,16 @@ fn fail_comments() {
#[test] #[test]
fn xml_error() { fn xml_error() {
match "<a xmlns='ns1'></b>".parse::<Element>() { match "<a xmlns='ns1'></b>".parse::<Element>() {
Err(crate::error::Error::XmlError(_)) => (), Err(crate::error::Error::ParserError(rxml::Error::NotWellFormed(
rxml::error::WFError::ElementMismatch,
))) => (),
err => panic!("No or wrong error: {:?}", err), err => panic!("No or wrong error: {:?}", err),
} }
match "<a xmlns='ns1'></".parse::<Element>() { match "<a xmlns='ns1'></".parse::<Element>() {
Err(crate::error::Error::XmlError(_)) => (), Err(crate::error::Error::ParserError(rxml::Error::NotWellFormed(
err => panic!("No or wrong error: {:?}", err), rxml::error::WFError::InvalidEof(_),
} ))) => (),
}
#[test]
fn invalid_element_error() {
match "<a:b:c>".parse::<Element>() {
Err(crate::error::Error::InvalidElement) => (),
err => panic!("No or wrong error: {:?}", err), err => panic!("No or wrong error: {:?}", err),
} }
} }

154
minidom/src/tree_builder.rs Normal file
View file

@ -0,0 +1,154 @@
// Copyright (c) 2022 Astro <astro@spaceboyz.net>
//! SAX events to DOM tree conversion
use crate::prefixes::{Prefix, Prefixes};
use crate::{Element, Error};
use rxml::RawEvent;
use std::collections::BTreeMap;
/// Tree-building parser state
pub struct TreeBuilder {
next_tag: Option<(Prefix, String, Prefixes, BTreeMap<String, String>)>,
/// Parsing stack
stack: Vec<Element>,
/// Namespace set stack by prefix
prefixes_stack: Vec<Prefixes>,
/// Document root element if finished
pub root: Option<Element>,
}
impl TreeBuilder {
/// Create a new one
pub fn new() -> Self {
TreeBuilder {
next_tag: None,
stack: vec![],
prefixes_stack: vec![],
root: None,
}
}
/// Stack depth
pub fn depth(&self) -> usize {
self.stack.len()
}
/// Get the top-most element from the stack but don't remove it
pub fn top(&mut self) -> Option<&Element> {
self.stack.last()
}
/// Pop the top-most element from the stack
fn pop(&mut self) -> Option<Element> {
self.prefixes_stack.pop();
self.stack.pop()
}
/// Unshift the first child of the top element
pub fn unshift_child(&mut self) -> Option<Element> {
let depth = self.stack.len();
if depth > 0 {
self.stack[depth - 1].unshift_child()
} else {
None
}
}
/// Lookup XML namespace declaration for given prefix (or no prefix)
fn lookup_prefix(&self, prefix: &Option<String>) -> Option<&str> {
for nss in self.prefixes_stack.iter().rev() {
if let Some(ns) = nss.get(prefix) {
return Some(ns);
}
}
None
}
fn process_end_tag(&mut self) -> Result<(), Error> {
if let Some(el) = self.pop() {
if self.depth() > 0 {
let top = self.stack.len() - 1;
self.stack[top].append_child(el);
} else {
self.root = Some(el);
}
}
Ok(())
}
fn process_text(&mut self, text: String) {
if self.depth() > 0 {
let top = self.stack.len() - 1;
self.stack[top].append_text_node(text);
}
}
/// Process a Event that you got out of a RawParser
pub fn process_event(&mut self, event: RawEvent) -> Result<(), Error> {
match event {
RawEvent::XMLDeclaration(_, _) => {}
RawEvent::ElementHeadOpen(_, (prefix, name)) => {
self.next_tag = Some((
prefix.map(|prefix| prefix.as_str().to_owned()),
name.as_str().to_owned(),
Prefixes::default(),
BTreeMap::new(),
))
}
RawEvent::Attribute(_, (prefix, name), value) => {
self.next_tag
.as_mut()
.map(
|(_, _, ref mut prefixes, ref mut attrs)| match (prefix, name) {
(None, xmlns) if xmlns == "xmlns" => {
prefixes.insert(None, value);
}
(Some(xmlns), prefix) if xmlns.as_str() == "xmlns" => {
prefixes.insert(Some(prefix.as_str().to_owned()), value);
}
(Some(prefix), name) => {
attrs.insert(
format!("{}:{}", prefix, name),
value.as_str().to_owned(),
);
}
(None, name) => {
attrs.insert(name.as_str().to_owned(), value.as_str().to_owned());
}
},
);
}
RawEvent::ElementHeadClose(_) => {
if let Some((prefix, name, prefixes, attrs)) = self.next_tag.take() {
self.prefixes_stack.push(prefixes.clone());
let namespace = self
.lookup_prefix(&prefix.clone().map(|prefix| prefix.as_str().to_owned()))
.ok_or(Error::MissingNamespace)?
.to_owned();
let el = Element::new(
name.as_str().to_owned(),
namespace,
Some(prefix.map(|prefix| prefix.as_str().to_owned())),
prefixes,
attrs,
vec![],
);
self.stack.push(el);
}
}
RawEvent::ElementFoot(_) => self.process_end_tag()?,
RawEvent::Text(_, text) => self.process_text(text.as_str().to_owned()),
}
Ok(())
}
}

View file

@ -265,8 +265,7 @@ mod tests {
#[test] #[test]
fn test_xep_5_2() { fn test_xep_5_2() {
let elem: Element = r#" let elem: Element = r#"<query xmlns='http://jabber.org/protocol/disco#info'
<query xmlns='http://jabber.org/protocol/disco#info'
node='http://psi-im.org#q07IKJEyjvHSyhy//CH0CxmKi8w='> node='http://psi-im.org#q07IKJEyjvHSyhy//CH0CxmKi8w='>
<identity category='client' name='Exodus 0.9.1' type='pc'/> <identity category='client' name='Exodus 0.9.1' type='pc'/>
<feature var='http://jabber.org/protocol/caps'/> <feature var='http://jabber.org/protocol/caps'/>
@ -294,8 +293,7 @@ mod tests {
#[test] #[test]
fn test_xep_5_3() { fn test_xep_5_3() {
let elem: Element = r#" let elem: Element = r#"<query xmlns='http://jabber.org/protocol/disco#info'
<query xmlns='http://jabber.org/protocol/disco#info'
node='http://psi-im.org#q07IKJEyjvHSyhy//CH0CxmKi8w='> node='http://psi-im.org#q07IKJEyjvHSyhy//CH0CxmKi8w='>
<identity xml:lang='en' category='client' name='Psi 0.11' type='pc'/> <identity xml:lang='en' category='client' name='Psi 0.11' type='pc'/>
<identity xml:lang='el' category='client' name='Ψ 0.11' type='pc'/> <identity xml:lang='el' category='client' name='Ψ 0.11' type='pc'/>

View file

@ -176,8 +176,7 @@ mod tests {
#[test] #[test]
fn list() { fn list() {
let elem: Element = r#" let elem: Element = r#"<items xmlns='urn:xmpp:saslcert:1'>
<items xmlns='urn:xmpp:saslcert:1'>
<item> <item>
<name>Mobile Client</name> <name>Mobile Client</name>
<x509cert>AAAA</x509cert> <x509cert>AAAA</x509cert>

View file

@ -236,8 +236,7 @@ mod tests {
#[test] #[test]
fn test_xep_ex1() { fn test_xep_ex1() {
let elem: Element = r#" let elem: Element = r#"<query xmlns="http://jabber.org/protocol/disco#info">
<query xmlns="http://jabber.org/protocol/disco#info">
<identity category="client" name="BombusMod" type="mobile"/> <identity category="client" name="BombusMod" type="mobile"/>
<feature var="http://jabber.org/protocol/si"/> <feature var="http://jabber.org/protocol/si"/>
<feature var="http://jabber.org/protocol/bytestreams"/> <feature var="http://jabber.org/protocol/bytestreams"/>
@ -307,8 +306,7 @@ mod tests {
#[test] #[test]
fn test_xep_ex2() { fn test_xep_ex2() {
let elem: Element = r#" let elem: Element = r#"<query xmlns="http://jabber.org/protocol/disco#info">
<query xmlns="http://jabber.org/protocol/disco#info">
<identity category="client" name="Tkabber" type="pc" xml:lang="en"/> <identity category="client" name="Tkabber" type="pc" xml:lang="en"/>
<identity category="client" name="Ткаббер" type="pc" xml:lang="ru"/> <identity category="client" name="Ткаббер" type="pc" xml:lang="ru"/>
<feature var="games:board"/> <feature var="games:board"/>

View file

@ -137,8 +137,7 @@ mod tests {
#[test] #[test]
fn test_ex2() { fn test_ex2() {
let elem: Element = r#" let elem: Element = r#"<query xmlns='jabber:iq:register'>
<query xmlns='jabber:iq:register'>
<instructions> <instructions>
Choose a username and password for use with this service. Choose a username and password for use with this service.
Please also provide your email address. Please also provide your email address.

View file

@ -354,8 +354,7 @@ mod tests {
#[test] #[test]
fn test_description() { fn test_description() {
let elem: Element = r#" let elem: Element = r#"<description xmlns='urn:xmpp:jingle:apps:file-transfer:5'>
<description xmlns='urn:xmpp:jingle:apps:file-transfer:5'>
<file> <file>
<media-type>text/plain</media-type> <media-type>text/plain</media-type>
<name>test.txt</name> <name>test.txt</name>
@ -387,8 +386,7 @@ mod tests {
#[test] #[test]
fn test_request() { fn test_request() {
let elem: Element = r#" let elem: Element = r#"<description xmlns='urn:xmpp:jingle:apps:file-transfer:5'>
<description xmlns='urn:xmpp:jingle:apps:file-transfer:5'>
<file> <file>
<hash xmlns='urn:xmpp:hashes:2' <hash xmlns='urn:xmpp:hashes:2'
algo='sha-1'>w0mcJylzCn+AfvuGdqkty2+KP48=</hash> algo='sha-1'>w0mcJylzCn+AfvuGdqkty2+KP48=</hash>
@ -413,8 +411,7 @@ mod tests {
#[test] #[test]
fn test_descs() { fn test_descs() {
let elem: Element = r#" let elem: Element = r#"<description xmlns='urn:xmpp:jingle:apps:file-transfer:5'>
<description xmlns='urn:xmpp:jingle:apps:file-transfer:5'>
<file> <file>
<media-type>text/plain</media-type> <media-type>text/plain</media-type>
<desc xml:lang='fr'>Fichier secret!</desc> <desc xml:lang='fr'>Fichier secret!</desc>
@ -437,8 +434,7 @@ mod tests {
Desc(String::from("Fichier secret!")) Desc(String::from("Fichier secret!"))
); );
let elem: Element = r#" let elem: Element = r#"<description xmlns='urn:xmpp:jingle:apps:file-transfer:5'>
<description xmlns='urn:xmpp:jingle:apps:file-transfer:5'>
<file> <file>
<media-type>text/plain</media-type> <media-type>text/plain</media-type>
<desc xml:lang='fr'>Fichier secret!</desc> <desc xml:lang='fr'>Fichier secret!</desc>

View file

@ -72,8 +72,7 @@ mod tests {
#[test] #[test]
fn parse_group() { fn parse_group() {
let elem: Element = " let elem: Element = "<group xmlns='urn:xmpp:jingle:apps:grouping:0' semantics='BUNDLE'>
<group xmlns='urn:xmpp:jingle:apps:grouping:0' semantics='BUNDLE'>
<content name='voice'/> <content name='voice'/>
<content name='webcam'/> <content name='webcam'/>
</group>" </group>"

View file

@ -134,8 +134,7 @@ mod tests {
#[test] #[test]
fn test_gajim() { fn test_gajim() {
let elem: Element = " let elem: Element = "<transport xmlns='urn:xmpp:jingle:transports:ice-udp:1' pwd='wakMJ8Ydd5rqnPaFerws5o' ufrag='aeXX'>
<transport xmlns='urn:xmpp:jingle:transports:ice-udp:1' pwd='wakMJ8Ydd5rqnPaFerws5o' ufrag='aeXX'>
<candidate xmlns='urn:xmpp:jingle:transports:ice-udp:1' component='2' foundation='1' generation='0' id='11b72719-6a1b-4c51-8ae6-9f1538047568' ip='192.168.0.12' network='0' port='56715' priority='1010828030' protocol='tcp' type='host'/> <candidate xmlns='urn:xmpp:jingle:transports:ice-udp:1' component='2' foundation='1' generation='0' id='11b72719-6a1b-4c51-8ae6-9f1538047568' ip='192.168.0.12' network='0' port='56715' priority='1010828030' protocol='tcp' type='host'/>
<candidate xmlns='urn:xmpp:jingle:transports:ice-udp:1' component='2' foundation='1' generation='0' id='7e07b22d-db50-4e17-9ed9-eafeb96f4f63' ip='192.168.0.12' network='0' port='0' priority='1015022334' protocol='tcp' type='host'/> <candidate xmlns='urn:xmpp:jingle:transports:ice-udp:1' component='2' foundation='1' generation='0' id='7e07b22d-db50-4e17-9ed9-eafeb96f4f63' ip='192.168.0.12' network='0' port='0' priority='1015022334' protocol='tcp' type='host'/>
<candidate xmlns='urn:xmpp:jingle:transports:ice-udp:1' component='2' foundation='1' generation='0' id='431de362-c45f-40a8-bf10-9ed898a71d86' ip='192.168.0.12' network='0' port='36480' priority='2013266428' protocol='udp' type='host'/> <candidate xmlns='urn:xmpp:jingle:transports:ice-udp:1' component='2' foundation='1' generation='0' id='431de362-c45f-40a8-bf10-9ed898a71d86' ip='192.168.0.12' network='0' port='36480' priority='2013266428' protocol='udp' type='host'/>
@ -164,8 +163,7 @@ mod tests {
#[test] #[test]
fn test_jitsi_meet() { fn test_jitsi_meet() {
let elem: Element = " let elem: Element = "<transport ufrag='2acq51d4p07v2m' pwd='7lk9uul39gckit6t02oavv2r9j' xmlns='urn:xmpp:jingle:transports:ice-udp:1'>
<transport ufrag='2acq51d4p07v2m' pwd='7lk9uul39gckit6t02oavv2r9j' xmlns='urn:xmpp:jingle:transports:ice-udp:1'>
<fingerprint hash='sha-1' setup='actpass' xmlns='urn:xmpp:jingle:apps:dtls:0'>97:F2:B5:BE:DB:A6:00:B1:3E:40:B2:41:3C:0D:FC:E0:BD:B2:A0:E8</fingerprint> <fingerprint hash='sha-1' setup='actpass' xmlns='urn:xmpp:jingle:apps:dtls:0'>97:F2:B5:BE:DB:A6:00:B1:3E:40:B2:41:3C:0D:FC:E0:BD:B2:A0:E8</fingerprint>
<candidate type='host' protocol='udp' id='186cb069513c2bbe546192c93cc4ab3b05ab0d426' ip='2a05:d014:fc7:54a1:8bfc:7248:3d1c:51a4' component='1' port='10000' foundation='1' generation='0' priority='2130706431' network='0'/> <candidate type='host' protocol='udp' id='186cb069513c2bbe546192c93cc4ab3b05ab0d426' ip='2a05:d014:fc7:54a1:8bfc:7248:3d1c:51a4' component='1' port='10000' foundation='1' generation='0' priority='2130706431' network='0'/>
<candidate type='host' protocol='udp' id='186cb069513c2bbe546192c93cc4ab3b063daeefd' ip='10.15.1.120' component='1' port='10000' foundation='2' generation='0' priority='2130706431' network='0'/> <candidate type='host' protocol='udp' id='186cb069513c2bbe546192c93cc4ab3b063daeefd' ip='10.15.1.120' component='1' port='10000' foundation='2' generation='0' priority='2130706431' network='0'/>

View file

@ -78,8 +78,7 @@ mod tests {
#[test] #[test]
fn example_1() { fn example_1() {
let elem: Element = " let elem: Element = "<transport xmlns='urn:xmpp:jingle:transports:raw-udp:1'>
<transport xmlns='urn:xmpp:jingle:transports:raw-udp:1'>
<candidate component='1' <candidate component='1'
generation='0' generation='0'
id='a9j3mnbtu1' id='a9j3mnbtu1'

View file

@ -175,8 +175,7 @@ mod tests {
#[test] #[test]
fn test_simple() { fn test_simple() {
let elem: Element = " let elem: Element = "<description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'>
<description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'>
<payload-type xmlns='urn:xmpp:jingle:apps:rtp:1' channels='2' clockrate='48000' id='96' name='OPUS'/> <payload-type xmlns='urn:xmpp:jingle:apps:rtp:1' channels='2' clockrate='48000' id='96' name='OPUS'/>
<payload-type xmlns='urn:xmpp:jingle:apps:rtp:1' channels='1' clockrate='32000' id='105' name='SPEEX'/> <payload-type xmlns='urn:xmpp:jingle:apps:rtp:1' channels='1' clockrate='32000' id='105' name='SPEEX'/>
<payload-type xmlns='urn:xmpp:jingle:apps:rtp:1' channels='1' clockrate='8000' id='9' name='G722'/> <payload-type xmlns='urn:xmpp:jingle:apps:rtp:1' channels='1' clockrate='8000' id='9' name='G722'/>

View file

@ -74,8 +74,7 @@ mod tests {
#[test] #[test]
fn parse_exthdr() { fn parse_exthdr() {
let elem: Element = " let elem: Element = "<rtp-hdrext xmlns='urn:xmpp:jingle:apps:rtp:rtp-hdrext:0'
<rtp-hdrext xmlns='urn:xmpp:jingle:apps:rtp:rtp-hdrext:0'
uri='urn:ietf:params:rtp-hdrext:toffset' uri='urn:ietf:params:rtp-hdrext:toffset'
id='1'/>" id='1'/>"
.parse() .parse()

View file

@ -101,8 +101,7 @@ mod tests {
#[test] #[test]
fn parse_source() { fn parse_source() {
let elem: Element = " let elem: Element = "<source ssrc='1656081975' xmlns='urn:xmpp:jingle:apps:rtp:ssma:0'>
<source ssrc='1656081975' xmlns='urn:xmpp:jingle:apps:rtp:ssma:0'>
<parameter name='cname' value='Yv/wvbCdsDW2Prgd'/> <parameter name='cname' value='Yv/wvbCdsDW2Prgd'/>
<parameter name='msid' value='MLTJKIHilGn71fNQoszkQ4jlPTuS5vJyKVIv MLTJKIHilGn71fNQoszkQ4jlPTuS5vJyKVIva0'/> <parameter name='msid' value='MLTJKIHilGn71fNQoszkQ4jlPTuS5vJyKVIv MLTJKIHilGn71fNQoszkQ4jlPTuS5vJyKVIva0'/>
</source>" </source>"
@ -124,8 +123,7 @@ mod tests {
#[test] #[test]
fn parse_source_group() { fn parse_source_group() {
let elem: Element = " let elem: Element = "<ssrc-group semantics='FID' xmlns='urn:xmpp:jingle:apps:rtp:ssma:0'>
<ssrc-group semantics='FID' xmlns='urn:xmpp:jingle:apps:rtp:ssma:0'>
<source ssrc='2301230316'/> <source ssrc='2301230316'/>
<source ssrc='386328120'/> <source ssrc='386328120'/>
</ssrc-group>" </ssrc-group>"

View file

@ -186,8 +186,7 @@ mod tests {
#[test] #[test]
fn parse_bundle() { fn parse_bundle() {
let elem: Element = r#" let elem: Element = r#"<bundle xmlns="eu.siacs.conversations.axolotl">
<bundle xmlns="eu.siacs.conversations.axolotl">
<signedPreKeyPublic signedPreKeyId="1">BYAbACA15bPn95p7RGC2XbgQyly8aRKS4BaJ+hD8Ybhe</signedPreKeyPublic> <signedPreKeyPublic signedPreKeyId="1">BYAbACA15bPn95p7RGC2XbgQyly8aRKS4BaJ+hD8Ybhe</signedPreKeyPublic>
<signedPreKeySignature>sIJVNDZi/NgFsry4OBdM+adyGttLEXbUh/h/5dVOZveMgyVoIdgwBUzq8Wgd2xYTQMioNzwYebTX+9p0h9eujA==</signedPreKeySignature> <signedPreKeySignature>sIJVNDZi/NgFsry4OBdM+adyGttLEXbUh/h/5dVOZveMgyVoIdgwBUzq8Wgd2xYTQMioNzwYebTX+9p0h9eujA==</signedPreKeySignature>
<identityKey>BQFd2p/Oq97vAAdLKA09DlcSg0x1xWn260p1jaeyIhAZ</identityKey> <identityKey>BQFd2p/Oq97vAAdLKA09DlcSg0x1xWn260p1jaeyIhAZ</identityKey>
@ -254,8 +253,7 @@ mod tests {
} }
#[test] #[test]
fn parse_device_list() { fn parse_device_list() {
let elem: Element = r#" let elem: Element = r#"<list xmlns="eu.siacs.conversations.axolotl">
<list xmlns="eu.siacs.conversations.axolotl">
<device id="1164059891" /> <device id="1164059891" />
<device id="26052318" /> <device id="26052318" />
<device id="564866972" /> <device id="564866972" />
@ -275,8 +273,7 @@ mod tests {
} }
#[test] #[test]
fn parse_encrypted() { fn parse_encrypted() {
let elem: Element = r#" let elem: Element = r#"<encrypted xmlns="eu.siacs.conversations.axolotl">
<encrypted xmlns="eu.siacs.conversations.axolotl">
<header sid="564866972"> <header sid="564866972">
<key prekey="true" rid="1236">Mwjp9AESIQVylscLPpj/HlowaTiIsaBj73HCVEllXpVTtMG9EYwRexohBQFd2p/Oq97vAAdLKA09DlcSg0x1xWn260p1jaeyIhAZImIzCiEFhaQ4I+DuQgo6vCLCjHu4uewDZmWHuBl8uJw1IkyZxhUQABgAIjCoEVgVThWlaIlnN3V5Bg1hQX7OD1cvstLD5lH3zZMadL3KeONELESlBbeKmNgcYC/e3HZnbgWzBiic36yNAjAW</key> <key prekey="true" rid="1236">Mwjp9AESIQVylscLPpj/HlowaTiIsaBj73HCVEllXpVTtMG9EYwRexohBQFd2p/Oq97vAAdLKA09DlcSg0x1xWn260p1jaeyIhAZImIzCiEFhaQ4I+DuQgo6vCLCjHu4uewDZmWHuBl8uJw1IkyZxhUQABgAIjCoEVgVThWlaIlnN3V5Bg1hQX7OD1cvstLD5lH3zZMadL3KeONELESlBbeKmNgcYC/e3HZnbgWzBiic36yNAjAW</key>
<key rid="26052318">MwohBTV6dpumL1OxA9MdIFmu2E19+cIWDHWYfhdubvo0hmh6EAAYHCIwNc9/59eeYi8pVZQhMJJMVkKUkFP/yrTfG3o1lfpHGseCqb/JTgtDytQPiYrTpHl2V/mdsM6IPig=</key> <key rid="26052318">MwohBTV6dpumL1OxA9MdIFmu2E19+cIWDHWYfhdubvo0hmh6EAAYHCIwNc9/59eeYi8pVZQhMJJMVkKUkFP/yrTfG3o1lfpHGseCqb/JTgtDytQPiYrTpHl2V/mdsM6IPig=</key>

View file

@ -123,8 +123,7 @@ mod tests {
#[test] #[test]
fn test_result() { fn test_result() {
#[cfg(not(feature = "component"))] #[cfg(not(feature = "component"))]
let elem: Element = r#" let elem: Element = r#"<result xmlns='urn:xmpp:mam:2' queryid='f27' id='28482-98726-73623'>
<result xmlns='urn:xmpp:mam:2' queryid='f27' id='28482-98726-73623'>
<forwarded xmlns='urn:xmpp:forward:0'> <forwarded xmlns='urn:xmpp:forward:0'>
<delay xmlns='urn:xmpp:delay' stamp='2010-07-10T23:08:25Z'/> <delay xmlns='urn:xmpp:delay' stamp='2010-07-10T23:08:25Z'/>
<message xmlns='jabber:client' from="witch@shakespeare.lit" to="macbeth@shakespeare.lit"> <message xmlns='jabber:client' from="witch@shakespeare.lit" to="macbeth@shakespeare.lit">
@ -136,8 +135,7 @@ mod tests {
.parse() .parse()
.unwrap(); .unwrap();
#[cfg(feature = "component")] #[cfg(feature = "component")]
let elem: Element = r#" let elem: Element = r#"<result xmlns='urn:xmpp:mam:2' queryid='f27' id='28482-98726-73623'>
<result xmlns='urn:xmpp:mam:2' queryid='f27' id='28482-98726-73623'>
<forwarded xmlns='urn:xmpp:forward:0'> <forwarded xmlns='urn:xmpp:forward:0'>
<delay xmlns='urn:xmpp:delay' stamp='2010-07-10T23:08:25Z'/> <delay xmlns='urn:xmpp:delay' stamp='2010-07-10T23:08:25Z'/>
<message xmlns='jabber:component:accept' from="witch@shakespeare.lit" to="macbeth@shakespeare.lit"> <message xmlns='jabber:component:accept' from="witch@shakespeare.lit" to="macbeth@shakespeare.lit">
@ -151,8 +149,7 @@ mod tests {
#[test] #[test]
fn test_fin() { fn test_fin() {
let elem: Element = r#" let elem: Element = r#"<fin xmlns='urn:xmpp:mam:2'>
<fin xmlns='urn:xmpp:mam:2'>
<set xmlns='http://jabber.org/protocol/rsm'> <set xmlns='http://jabber.org/protocol/rsm'>
<first index='0'>28482-98726-73623</first> <first index='0'>28482-98726-73623</first>
<last>09af3-cc343-b409f</last> <last>09af3-cc343-b409f</last>
@ -166,8 +163,7 @@ mod tests {
#[test] #[test]
fn test_query_x() { fn test_query_x() {
let elem: Element = r#" let elem: Element = r#"<query xmlns='urn:xmpp:mam:2'>
<query xmlns='urn:xmpp:mam:2'>
<x xmlns='jabber:x:data' type='submit'> <x xmlns='jabber:x:data' type='submit'>
<field var='FORM_TYPE' type='hidden'> <field var='FORM_TYPE' type='hidden'>
<value>urn:xmpp:mam:2</value> <value>urn:xmpp:mam:2</value>
@ -185,8 +181,7 @@ mod tests {
#[test] #[test]
fn test_query_x_set() { fn test_query_x_set() {
let elem: Element = r#" let elem: Element = r#"<query xmlns='urn:xmpp:mam:2'>
<query xmlns='urn:xmpp:mam:2'>
<x xmlns='jabber:x:data' type='submit'> <x xmlns='jabber:x:data' type='submit'>
<field var='FORM_TYPE' type='hidden'> <field var='FORM_TYPE' type='hidden'>
<value>urn:xmpp:mam:2</value> <value>urn:xmpp:mam:2</value>

View file

@ -134,8 +134,7 @@ mod tests {
assert!(prefs.always.is_empty()); assert!(prefs.always.is_empty());
assert!(prefs.never.is_empty()); assert!(prefs.never.is_empty());
let elem: Element = r#" let elem: Element = r#"<prefs xmlns='urn:xmpp:mam:2' default='roster'>
<prefs xmlns='urn:xmpp:mam:2' default='roster'>
<always/> <always/>
<never/> <never/>
</prefs> </prefs>
@ -149,8 +148,7 @@ mod tests {
#[test] #[test]
fn test_prefs_result() { fn test_prefs_result() {
let elem: Element = r#" let elem: Element = r#"<prefs xmlns='urn:xmpp:mam:2' default='roster'>
<prefs xmlns='urn:xmpp:mam:2' default='roster'>
<always> <always>
<jid>romeo@montague.lit</jid> <jid>romeo@montague.lit</jid>
</always> </always>

View file

@ -174,8 +174,7 @@ mod tests {
#[test] #[test]
fn test_xep_ex1() { fn test_xep_ex1() {
let elem: Element = r#" let elem: Element = r#"<media xmlns='urn:xmpp:media-element'>
<media xmlns='urn:xmpp:media-element'>
<uri type='audio/x-wav'> <uri type='audio/x-wav'>
http://victim.example.com/challenges/speech.wav?F3A6292C http://victim.example.com/challenges/speech.wav?F3A6292C
</uri> </uri>
@ -211,8 +210,7 @@ mod tests {
#[test] #[test]
fn test_xep_ex2() { fn test_xep_ex2() {
let elem: Element = r#" let elem: Element = r#"<x xmlns='jabber:x:data' type='form'>
<x xmlns='jabber:x:data' type='form'>
[ ... ] [ ... ]
<field var='ocr'> <field var='ocr'>
<media xmlns='urn:xmpp:media-element' <media xmlns='urn:xmpp:media-element'

View file

@ -163,8 +163,7 @@ mod tests {
#[test] #[test]
fn history() { fn history() {
let elem: Element = " let elem: Element = "<x xmlns='http://jabber.org/protocol/muc'>
<x xmlns='http://jabber.org/protocol/muc'>
<history maxstanzas='0'/> <history maxstanzas='0'/>
</x>" </x>"
.parse() .parse()
@ -179,8 +178,7 @@ mod tests {
assert_eq!(history.seconds, None); assert_eq!(history.seconds, None);
assert_eq!(history.since, None); assert_eq!(history.since, None);
let elem: Element = " let elem: Element = "<x xmlns='http://jabber.org/protocol/muc'>
<x xmlns='http://jabber.org/protocol/muc'>
<history since='1970-01-01T00:00:00Z'/> <history since='1970-01-01T00:00:00Z'/>
</x>" </x>"
.parse() .parse()

View file

@ -239,9 +239,7 @@ mod tests {
#[test] #[test]
fn test_simple() { fn test_simple() {
let elem: Element = " let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user'/>"
<x xmlns='http://jabber.org/protocol/muc#user'/>
"
.parse() .parse()
.unwrap(); .unwrap();
MucUser::try_from(elem).unwrap(); MucUser::try_from(elem).unwrap();
@ -249,13 +247,11 @@ mod tests {
#[test] #[test]
fn statuses_and_items() { fn statuses_and_items() {
let elem: Element = " let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user'>
<x xmlns='http://jabber.org/protocol/muc#user'>
<status code='101'/> <status code='101'/>
<status code='102'/> <status code='102'/>
<item affiliation='member' role='moderator'/> <item affiliation='member' role='moderator'/>
</x> </x>"
"
.parse() .parse()
.unwrap(); .unwrap();
let muc_user = MucUser::try_from(elem).unwrap(); let muc_user = MucUser::try_from(elem).unwrap();
@ -269,11 +265,9 @@ mod tests {
#[test] #[test]
fn test_invalid_child() { fn test_invalid_child() {
let elem: Element = " let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user'>
<x xmlns='http://jabber.org/protocol/muc#user'>
<coucou/> <coucou/>
</x> </x>"
"
.parse() .parse()
.unwrap(); .unwrap();
let error = MucUser::try_from(elem).unwrap_err(); let error = MucUser::try_from(elem).unwrap_err();
@ -286,9 +280,7 @@ mod tests {
#[test] #[test]
fn test_serialise() { fn test_serialise() {
let elem: Element = " let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user'/>"
<x xmlns='http://jabber.org/protocol/muc#user'/>
"
.parse() .parse()
.unwrap(); .unwrap();
let muc = MucUser { let muc = MucUser {
@ -302,9 +294,7 @@ mod tests {
#[cfg(not(feature = "disable-validation"))] #[cfg(not(feature = "disable-validation"))]
#[test] #[test]
fn test_invalid_attribute() { fn test_invalid_attribute() {
let elem: Element = " let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user' coucou=''/>"
<x xmlns='http://jabber.org/protocol/muc#user' coucou=''/>
"
.parse() .parse()
.unwrap(); .unwrap();
let error = MucUser::try_from(elem).unwrap_err(); let error = MucUser::try_from(elem).unwrap_err();
@ -317,9 +307,7 @@ mod tests {
#[test] #[test]
fn test_status_simple() { fn test_status_simple() {
let elem: Element = " let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user' code='110'/>"
<status xmlns='http://jabber.org/protocol/muc#user' code='110'/>
"
.parse() .parse()
.unwrap(); .unwrap();
Status::try_from(elem).unwrap(); Status::try_from(elem).unwrap();
@ -327,9 +315,7 @@ mod tests {
#[test] #[test]
fn test_status_invalid() { fn test_status_invalid() {
let elem: Element = " let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user'/>"
<status xmlns='http://jabber.org/protocol/muc#user'/>
"
.parse() .parse()
.unwrap(); .unwrap();
let error = Status::try_from(elem).unwrap_err(); let error = Status::try_from(elem).unwrap_err();
@ -343,11 +329,9 @@ mod tests {
#[cfg(not(feature = "disable-validation"))] #[cfg(not(feature = "disable-validation"))]
#[test] #[test]
fn test_status_invalid_child() { fn test_status_invalid_child() {
let elem: Element = " let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user' code='110'>
<status xmlns='http://jabber.org/protocol/muc#user' code='110'>
<foo/> <foo/>
</status> </status>"
"
.parse() .parse()
.unwrap(); .unwrap();
let error = Status::try_from(elem).unwrap_err(); let error = Status::try_from(elem).unwrap_err();
@ -360,9 +344,7 @@ mod tests {
#[test] #[test]
fn test_status_simple_code() { fn test_status_simple_code() {
let elem: Element = " let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user' code='307'/>"
<status xmlns='http://jabber.org/protocol/muc#user' code='307'/>
"
.parse() .parse()
.unwrap(); .unwrap();
let status = Status::try_from(elem).unwrap(); let status = Status::try_from(elem).unwrap();
@ -371,9 +353,7 @@ mod tests {
#[test] #[test]
fn test_status_invalid_code() { fn test_status_invalid_code() {
let elem: Element = " let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user' code='666'/>"
<status xmlns='http://jabber.org/protocol/muc#user' code='666'/>
"
.parse() .parse()
.unwrap(); .unwrap();
let error = Status::try_from(elem).unwrap_err(); let error = Status::try_from(elem).unwrap_err();
@ -386,9 +366,7 @@ mod tests {
#[test] #[test]
fn test_status_invalid_code2() { fn test_status_invalid_code2() {
let elem: Element = " let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user' code='coucou'/>"
<status xmlns='http://jabber.org/protocol/muc#user' code='coucou'/>
"
.parse() .parse()
.unwrap(); .unwrap();
let error = Status::try_from(elem).unwrap_err(); let error = Status::try_from(elem).unwrap_err();
@ -401,9 +379,7 @@ mod tests {
#[test] #[test]
fn test_actor_required_attributes() { fn test_actor_required_attributes() {
let elem: Element = " let elem: Element = "<actor xmlns='http://jabber.org/protocol/muc#user'/>"
<actor xmlns='http://jabber.org/protocol/muc#user'/>
"
.parse() .parse()
.unwrap(); .unwrap();
let error = Actor::try_from(elem).unwrap_err(); let error = Actor::try_from(elem).unwrap_err();
@ -416,11 +392,9 @@ mod tests {
#[test] #[test]
fn test_actor_required_attributes2() { fn test_actor_required_attributes2() {
let elem: Element = " let elem: Element = "<actor xmlns='http://jabber.org/protocol/muc#user'
<actor xmlns='http://jabber.org/protocol/muc#user'
jid='foo@bar/baz' jid='foo@bar/baz'
nick='baz'/> nick='baz'/>"
"
.parse() .parse()
.unwrap(); .unwrap();
let error = Actor::try_from(elem).unwrap_err(); let error = Actor::try_from(elem).unwrap_err();
@ -433,10 +407,8 @@ mod tests {
#[test] #[test]
fn test_actor_jid() { fn test_actor_jid() {
let elem: Element = " let elem: Element = "<actor xmlns='http://jabber.org/protocol/muc#user'
<actor xmlns='http://jabber.org/protocol/muc#user' jid='foo@bar/baz'/>"
jid='foo@bar/baz'/>
"
.parse() .parse()
.unwrap(); .unwrap();
let actor = Actor::try_from(elem).unwrap(); let actor = Actor::try_from(elem).unwrap();
@ -449,9 +421,7 @@ mod tests {
#[test] #[test]
fn test_actor_nick() { fn test_actor_nick() {
let elem: Element = " let elem: Element = "<actor xmlns='http://jabber.org/protocol/muc#user' nick='baz'/>"
<actor xmlns='http://jabber.org/protocol/muc#user' nick='baz'/>
"
.parse() .parse()
.unwrap(); .unwrap();
let actor = Actor::try_from(elem).unwrap(); let actor = Actor::try_from(elem).unwrap();
@ -464,9 +434,7 @@ mod tests {
#[test] #[test]
fn test_continue_simple() { fn test_continue_simple() {
let elem: Element = " let elem: Element = "<continue xmlns='http://jabber.org/protocol/muc#user'/>"
<continue xmlns='http://jabber.org/protocol/muc#user'/>
"
.parse() .parse()
.unwrap(); .unwrap();
Continue::try_from(elem).unwrap(); Continue::try_from(elem).unwrap();
@ -474,10 +442,8 @@ mod tests {
#[test] #[test]
fn test_continue_thread_attribute() { fn test_continue_thread_attribute() {
let elem: Element = " let elem: Element = "<continue xmlns='http://jabber.org/protocol/muc#user'
<continue xmlns='http://jabber.org/protocol/muc#user' thread='foo'/>"
thread='foo'/>
"
.parse() .parse()
.unwrap(); .unwrap();
let continue_ = Continue::try_from(elem).unwrap(); let continue_ = Continue::try_from(elem).unwrap();
@ -486,11 +452,9 @@ mod tests {
#[test] #[test]
fn test_continue_invalid() { fn test_continue_invalid() {
let elem: Element = " let elem: Element = "<continue xmlns='http://jabber.org/protocol/muc#user'>
<continue xmlns='http://jabber.org/protocol/muc#user'>
<foobar/> <foobar/>
</continue> </continue>"
"
.parse() .parse()
.unwrap(); .unwrap();
let continue_ = Continue::try_from(elem).unwrap_err(); let continue_ = Continue::try_from(elem).unwrap_err();
@ -503,8 +467,7 @@ mod tests {
#[test] #[test]
fn test_reason_simple() { fn test_reason_simple() {
let elem: Element = " let elem: Element = "<reason xmlns='http://jabber.org/protocol/muc#user'>Reason</reason>"
<reason xmlns='http://jabber.org/protocol/muc#user'>Reason</reason>"
.parse() .parse()
.unwrap(); .unwrap();
let elem2 = elem.clone(); let elem2 = elem.clone();
@ -518,9 +481,7 @@ mod tests {
#[cfg(not(feature = "disable-validation"))] #[cfg(not(feature = "disable-validation"))]
#[test] #[test]
fn test_reason_invalid_attribute() { fn test_reason_invalid_attribute() {
let elem: Element = " let elem: Element = "<reason xmlns='http://jabber.org/protocol/muc#user' foo='bar'/>"
<reason xmlns='http://jabber.org/protocol/muc#user' foo='bar'/>
"
.parse() .parse()
.unwrap(); .unwrap();
let error = Reason::try_from(elem).unwrap_err(); let error = Reason::try_from(elem).unwrap_err();
@ -534,11 +495,9 @@ mod tests {
#[cfg(not(feature = "disable-validation"))] #[cfg(not(feature = "disable-validation"))]
#[test] #[test]
fn test_reason_invalid() { fn test_reason_invalid() {
let elem: Element = " let elem: Element = "<reason xmlns='http://jabber.org/protocol/muc#user'>
<reason xmlns='http://jabber.org/protocol/muc#user'>
<foobar/> <foobar/>
</reason> </reason>"
"
.parse() .parse()
.unwrap(); .unwrap();
let error = Reason::try_from(elem).unwrap_err(); let error = Reason::try_from(elem).unwrap_err();
@ -552,10 +511,8 @@ mod tests {
#[cfg(not(feature = "disable-validation"))] #[cfg(not(feature = "disable-validation"))]
#[test] #[test]
fn test_item_invalid_attr() { fn test_item_invalid_attr() {
let elem: Element = " let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
<item xmlns='http://jabber.org/protocol/muc#user' foo='bar'/>"
foo='bar'/>
"
.parse() .parse()
.unwrap(); .unwrap();
let error = Item::try_from(elem).unwrap_err(); let error = Item::try_from(elem).unwrap_err();
@ -568,11 +525,9 @@ mod tests {
#[test] #[test]
fn test_item_affiliation_role_attr() { fn test_item_affiliation_role_attr() {
let elem: Element = " let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
<item xmlns='http://jabber.org/protocol/muc#user'
affiliation='member' affiliation='member'
role='moderator'/> role='moderator'/>"
"
.parse() .parse()
.unwrap(); .unwrap();
Item::try_from(elem).unwrap(); Item::try_from(elem).unwrap();
@ -580,10 +535,8 @@ mod tests {
#[test] #[test]
fn test_item_affiliation_role_invalid_attr() { fn test_item_affiliation_role_invalid_attr() {
let elem: Element = " let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
<item xmlns='http://jabber.org/protocol/muc#user' affiliation='member'/>"
affiliation='member'/>
"
.parse() .parse()
.unwrap(); .unwrap();
let error = Item::try_from(elem).unwrap_err(); let error = Item::try_from(elem).unwrap_err();
@ -596,12 +549,10 @@ mod tests {
#[test] #[test]
fn test_item_nick_attr() { fn test_item_nick_attr() {
let elem: Element = " let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
<item xmlns='http://jabber.org/protocol/muc#user'
affiliation='member' affiliation='member'
role='moderator' role='moderator'
nick='foobar'/> nick='foobar'/>"
"
.parse() .parse()
.unwrap(); .unwrap();
let item = Item::try_from(elem).unwrap(); let item = Item::try_from(elem).unwrap();
@ -612,10 +563,8 @@ mod tests {
#[test] #[test]
fn test_item_affiliation_role_invalid_attr2() { fn test_item_affiliation_role_invalid_attr2() {
let elem: Element = " let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
<item xmlns='http://jabber.org/protocol/muc#user' role='moderator'/>"
role='moderator'/>
"
.parse() .parse()
.unwrap(); .unwrap();
let error = Item::try_from(elem).unwrap_err(); let error = Item::try_from(elem).unwrap_err();
@ -631,13 +580,11 @@ mod tests {
#[test] #[test]
fn test_item_role_actor_child() { fn test_item_role_actor_child() {
let elem: Element = " let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
<item xmlns='http://jabber.org/protocol/muc#user'
affiliation='member' affiliation='member'
role='moderator'> role='moderator'>
<actor nick='foobar'/> <actor nick='foobar'/>
</item> </item>"
"
.parse() .parse()
.unwrap(); .unwrap();
let item = Item::try_from(elem).unwrap(); let item = Item::try_from(elem).unwrap();
@ -648,13 +595,11 @@ mod tests {
#[test] #[test]
fn test_item_role_continue_child() { fn test_item_role_continue_child() {
let elem: Element = " let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
<item xmlns='http://jabber.org/protocol/muc#user'
affiliation='member' affiliation='member'
role='moderator'> role='moderator'>
<continue thread='foobar'/> <continue thread='foobar'/>
</item> </item>"
"
.parse() .parse()
.unwrap(); .unwrap();
let item = Item::try_from(elem).unwrap(); let item = Item::try_from(elem).unwrap();
@ -672,13 +617,11 @@ mod tests {
#[test] #[test]
fn test_item_role_reason_child() { fn test_item_role_reason_child() {
let elem: Element = " let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
<item xmlns='http://jabber.org/protocol/muc#user'
affiliation='member' affiliation='member'
role='moderator'> role='moderator'>
<reason>foobar</reason> <reason>foobar</reason>
</item> </item>"
"
.parse() .parse()
.unwrap(); .unwrap();
let item = Item::try_from(elem).unwrap(); let item = Item::try_from(elem).unwrap();

View file

@ -142,8 +142,7 @@ mod tests {
assert_eq!(roster.ver, Some(String::from("ver9"))); assert_eq!(roster.ver, Some(String::from("ver9")));
assert!(roster.items.is_empty()); assert!(roster.items.is_empty());
let elem: Element = r#" let elem: Element = r#"<query xmlns='jabber:iq:roster' ver='ver11'>
<query xmlns='jabber:iq:roster' ver='ver11'>
<item jid='romeo@example.net' <item jid='romeo@example.net'
name='Romeo' name='Romeo'
subscription='both'> subscription='both'>
@ -215,14 +214,12 @@ mod tests {
assert!(roster.ver.is_none()); assert!(roster.ver.is_none());
assert_eq!(roster.items.len(), 1); assert_eq!(roster.items.len(), 1);
let elem: Element = r#" let elem: Element = r#"<query xmlns='jabber:iq:roster'>
<query xmlns='jabber:iq:roster'>
<item jid='nurse@example.com' <item jid='nurse@example.com'
name='Nurse'> name='Nurse'>
<group>Servants</group> <group>Servants</group>
</item> </item>
</query> </query>"#
"#
.parse() .parse()
.unwrap(); .unwrap();
let roster = Roster::try_from(elem).unwrap(); let roster = Roster::try_from(elem).unwrap();
@ -236,12 +233,10 @@ mod tests {
Group::from_str("Servants").unwrap() Group::from_str("Servants").unwrap()
); );
let elem: Element = r#" let elem: Element = r#"<query xmlns='jabber:iq:roster'>
<query xmlns='jabber:iq:roster'>
<item jid='nurse@example.com' <item jid='nurse@example.com'
subscription='remove'/> subscription='remove'/>
</query> </query>"#
"#
.parse() .parse()
.unwrap(); .unwrap();
let roster = Roster::try_from(elem).unwrap(); let roster = Roster::try_from(elem).unwrap();

View file

@ -180,7 +180,7 @@ mod tests {
#[test] #[test]
fn a() { fn a() {
let elem: Element = "<a xmlns='urn:xmpp:sm:3' h='5'".parse().unwrap(); let elem: Element = "<a xmlns='urn:xmpp:sm:3' h='5'/>".parse().unwrap();
let a = A::try_from(elem).unwrap(); let a = A::try_from(elem).unwrap();
assert_eq!(a.h, 5); assert_eq!(a.h, 5);
} }

View file

@ -46,7 +46,7 @@ impl TryFrom<Element> for TimeResult {
check_no_children!(child, "tzo"); check_no_children!(child, "tzo");
check_no_attributes!(child, "tzo"); check_no_attributes!(child, "tzo");
// TODO: Add a FromStr implementation to FixedOffset to avoid this hack. // TODO: Add a FromStr implementation to FixedOffset to avoid this hack.
let fake_date = String::from("2019-04-22T11:38:00") + &child.text(); let fake_date = format!("{}{}", "2019-04-22T11:38:00", child.text());
let date_time = DateTime::from_str(&fake_date)?; let date_time = DateTime::from_str(&fake_date)?;
tzo = Some(date_time.timezone()); tzo = Some(date_time.timezone());
} else if child.is("utc", ns::TIME) { } else if child.is("utc", ns::TIME) {

View file

@ -25,8 +25,9 @@ tokio-stream = { version = "0.1", features = [] }
tokio-util = { version = "0.6", features = ["codec"] } tokio-util = { version = "0.6", features = ["codec"] }
trust-dns-proto = "0.20" trust-dns-proto = "0.20"
trust-dns-resolver = "0.20" trust-dns-resolver = "0.20"
xml5ever = "0.16"
xmpp-parsers = "0.19" xmpp-parsers = "0.19"
minidom = "0.14"
rxml = "^0.7.1"
webpki-roots = { version = "0.22", optional = true } webpki-roots = { version = "0.22", optional = true }
[build-dependencies] [build-dependencies]

View file

@ -203,8 +203,7 @@ impl Stream for Client {
self.poll_next(cx) self.poll_next(cx)
} }
ClientState::Disconnected => Poll::Ready(None), ClientState::Disconnected => Poll::Ready(None),
ClientState::Connecting(mut connect) => { ClientState::Connecting(mut connect) => match Pin::new(&mut connect).poll(cx) {
match Pin::new(&mut connect).poll(cx) {
Poll::Ready(Ok(Ok(stream))) => { Poll::Ready(Ok(Ok(stream))) => {
let bound_jid = stream.jid.clone(); let bound_jid = stream.jid.clone();
self.state = ClientState::Connected(stream); self.state = ClientState::Connected(stream);
@ -225,8 +224,7 @@ impl Stream for Client {
self.state = ClientState::Connecting(connect); self.state = ClientState::Connecting(connect);
Poll::Pending Poll::Pending
} }
} },
}
ClientState::Connected(mut stream) => { ClientState::Connected(mut stream) => {
// Poll sink // Poll sink
match Pin::new(&mut stream).poll_ready(cx) { match Pin::new(&mut stream).poll_ready(cx) {

View file

@ -5,7 +5,6 @@ use std::borrow::Cow;
use std::error::Error as StdError; use std::error::Error as StdError;
use std::fmt; use std::fmt;
use std::io::Error as IoError; use std::io::Error as IoError;
use std::str::Utf8Error;
#[cfg(feature = "tls-rust")] #[cfg(feature = "tls-rust")]
use tokio_rustls::rustls::client::InvalidDnsNameError; use tokio_rustls::rustls::client::InvalidDnsNameError;
#[cfg(feature = "tls-rust")] #[cfg(feature = "tls-rust")]
@ -106,44 +105,6 @@ impl From<InvalidDnsNameError> for Error {
} }
} }
/// Causes for stream parsing errors
#[derive(Debug)]
pub enum ParserError {
/// Encoding error
Utf8(Utf8Error),
/// XML parse error
Parse(ParseError),
/// Illegal `</>`
ShortTag,
/// Required by `impl Decoder`
Io(IoError),
}
impl fmt::Display for ParserError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
ParserError::Utf8(e) => write!(fmt, "UTF-8 error: {}", e),
ParserError::Parse(e) => write!(fmt, "parse error: {}", e),
ParserError::ShortTag => write!(fmt, "short tag"),
ParserError::Io(e) => write!(fmt, "IO error: {}", e),
}
}
}
impl StdError for ParserError {}
impl From<IoError> for ParserError {
fn from(e: IoError) -> Self {
ParserError::Io(e)
}
}
impl From<ParserError> for Error {
fn from(e: ParserError) -> Self {
ProtocolError::Parser(e).into()
}
}
/// XML parse error wrapper type /// XML parse error wrapper type
#[derive(Debug)] #[derive(Debug)]
pub struct ParseError(pub Cow<'static, str>); pub struct ParseError(pub Cow<'static, str>);
@ -167,7 +128,7 @@ impl fmt::Display for ParseError {
#[derive(Debug)] #[derive(Debug)]
pub enum ProtocolError { pub enum ProtocolError {
/// XML parser error /// XML parser error
Parser(ParserError), Parser(minidom::Error),
/// Error with expected stanza schema /// Error with expected stanza schema
Parsers(ParsersError), Parsers(ParsersError),
/// No TLS available /// No TLS available
@ -205,12 +166,18 @@ impl fmt::Display for ProtocolError {
impl StdError for ProtocolError {} impl StdError for ProtocolError {}
impl From<ParserError> for ProtocolError { impl From<minidom::Error> for ProtocolError {
fn from(e: ParserError) -> Self { fn from(e: minidom::Error) -> Self {
ProtocolError::Parser(e) ProtocolError::Parser(e)
} }
} }
impl From<minidom::Error> for Error {
fn from(e: minidom::Error) -> Self {
ProtocolError::Parser(e).into()
}
}
impl From<ParsersError> for ProtocolError { impl From<ParsersError> for ProtocolError {
fn from(e: ParsersError) -> Self { fn from(e: ParsersError) -> Self {
ProtocolError::Parsers(e) ProtocolError::Parsers(e)

View file

@ -16,5 +16,5 @@ pub use client::{async_client::Client as AsyncClient, simple_client::Client as S
mod component; mod component;
pub use crate::component::Component; pub use crate::component::Component;
mod error; mod error;
pub use crate::error::{AuthError, ConnecterError, Error, ParseError, ParserError, ProtocolError}; pub use crate::error::{AuthError, ConnecterError, Error, ParseError, ProtocolError};
pub use starttls::starttls; pub use starttls::starttls;

View file

@ -1,23 +1,16 @@
//! XML stream parser for XMPP //! XML stream parser for XMPP
use crate::{ParseError, ParserError}; use crate::Error;
use bytes::{BufMut, BytesMut}; use bytes::{BufMut, BytesMut};
use log::{debug, error}; use log::debug;
use minidom::tree_builder::TreeBuilder;
use rxml::{Lexer, PushDriver, RawParser};
use std; use std;
use std::borrow::Cow;
use std::collections::vec_deque::VecDeque;
use std::collections::HashMap; use std::collections::HashMap;
use std::default::Default; use std::default::Default;
use std::fmt::Write; use std::fmt::Write;
use std::io; use std::io;
use std::iter::FromIterator;
use std::str::from_utf8;
use std::sync::Arc;
use std::sync::Mutex;
use tokio_util::codec::{Decoder, Encoder}; use tokio_util::codec::{Decoder, Encoder};
use xml5ever::buffer_queue::BufferQueue;
use xml5ever::interface::Attribute;
use xml5ever::tokenizer::{Tag, TagKind, Token, TokenSink, XmlTokenizer};
use xmpp_parsers::Element; use xmpp_parsers::Element;
/// Anything that can be sent or received on an XMPP/XML stream /// Anything that can be sent or received on an XMPP/XML stream
@ -33,175 +26,24 @@ pub enum Packet {
StreamEnd, StreamEnd,
} }
type QueueItem = Result<Packet, ParserError>;
/// Parser state
struct ParserSink {
// Ready stanzas, shared with XMPPCodec
queue: Arc<Mutex<VecDeque<QueueItem>>>,
// Parsing stack
stack: Vec<Element>,
ns_stack: Vec<HashMap<Option<String>, String>>,
}
impl ParserSink {
pub fn new(queue: Arc<Mutex<VecDeque<QueueItem>>>) -> Self {
ParserSink {
queue,
stack: vec![],
ns_stack: vec![],
}
}
fn push_queue(&self, pkt: Packet) {
self.queue.lock().unwrap().push_back(Ok(pkt));
}
fn push_queue_error(&self, e: ParserError) {
self.queue.lock().unwrap().push_back(Err(e));
}
/// Lookup XML namespace declaration for given prefix (or no prefix)
fn lookup_ns(&self, prefix: &Option<String>) -> Option<&str> {
for nss in self.ns_stack.iter().rev() {
if let Some(ns) = nss.get(prefix) {
return Some(ns);
}
}
None
}
fn handle_start_tag(&mut self, tag: Tag) {
let mut nss = HashMap::new();
let is_prefix_xmlns = |attr: &Attribute| {
attr.name
.prefix
.as_ref()
.map(|prefix| prefix.eq_str_ignore_ascii_case("xmlns"))
.unwrap_or(false)
};
for attr in &tag.attrs {
match attr.name.local.as_ref() {
"xmlns" => {
nss.insert(None, attr.value.as_ref().to_owned());
}
prefix if is_prefix_xmlns(attr) => {
nss.insert(Some(prefix.to_owned()), attr.value.as_ref().to_owned());
}
_ => (),
}
}
self.ns_stack.push(nss);
let el = {
let el_ns = self
.lookup_ns(&tag.name.prefix.map(|prefix| prefix.as_ref().to_owned()))
.unwrap();
let mut el_builder = Element::builder(tag.name.local.as_ref(), el_ns);
for attr in &tag.attrs {
match attr.name.local.as_ref() {
"xmlns" => (),
_ if is_prefix_xmlns(attr) => (),
_ => {
let attr_name = if let Some(ref prefix) = attr.name.prefix {
Cow::Owned(format!("{}:{}", prefix, attr.name.local))
} else {
Cow::Borrowed(attr.name.local.as_ref())
};
el_builder = el_builder.attr(attr_name, attr.value.as_ref());
}
}
}
el_builder.build()
};
if self.stack.is_empty() {
let attrs = HashMap::from_iter(tag.attrs.iter().map(|attr| {
(
attr.name.local.as_ref().to_owned(),
attr.value.as_ref().to_owned(),
)
}));
self.push_queue(Packet::StreamStart(attrs));
}
self.stack.push(el);
}
fn handle_end_tag(&mut self) {
let el = self.stack.pop().unwrap();
self.ns_stack.pop();
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);
}
}
}
}
impl TokenSink for ParserSink {
fn process_token(&mut self, 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_error(ParserError::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) => {
self.push_queue_error(ParserError::Parse(ParseError(s)));
}
_ => (),
}
}
// fn end(&mut self) {
// }
}
/// Stateful encoder/decoder for a bytestream from/to XMPP `Packet` /// Stateful encoder/decoder for a bytestream from/to XMPP `Packet`
pub struct XMPPCodec { pub struct XMPPCodec {
/// Outgoing /// Outgoing
ns: Option<String>, ns: Option<String>,
/// Incoming /// Incoming
parser: XmlTokenizer<ParserSink>, driver: PushDriver<RawParser>,
/// For handling incoming truncated utf8 stanza_builder: TreeBuilder,
// TODO: optimize using tendrils?
buf: Vec<u8>,
/// Shared with ParserSink
queue: Arc<Mutex<VecDeque<QueueItem>>>,
} }
impl XMPPCodec { impl XMPPCodec {
/// Constructor /// Constructor
pub fn new() -> Self { pub fn new() -> Self {
let queue = Arc::new(Mutex::new(VecDeque::new())); let stanza_builder = TreeBuilder::new();
let sink = ParserSink::new(queue.clone()); let driver = PushDriver::wrap(Lexer::new(), RawParser::new());
// TODO: configure parser?
let parser = XmlTokenizer::new(sink, Default::default());
XMPPCodec { XMPPCodec {
ns: None, ns: None,
parser, driver,
queue, stanza_builder,
buf: vec![],
} }
} }
} }
@ -214,57 +56,53 @@ impl Default for XMPPCodec {
impl Decoder for XMPPCodec { impl Decoder for XMPPCodec {
type Item = Packet; type Item = Packet;
type Error = ParserError; 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> {
let buf1: Box<dyn AsRef<[u8]>> = if !self.buf.is_empty() && !buf.is_empty() { loop {
let mut prefix = std::mem::replace(&mut self.buf, vec![]); let token = match self.driver.parse(buf, false) {
prefix.extend_from_slice(&buf.split_to(buf.len())); Ok(Some(token)) => token,
Box::new(prefix) Ok(None) => break,
} else { Err(rxml::Error::IO(e)) if e.kind() == std::io::ErrorKind::WouldBlock => break,
Box::new(buf.split_to(buf.len())) Err(e) => return Err(minidom::Error::from(e).into()),
}; };
let buf1 = buf1.as_ref().as_ref();
match from_utf8(buf1) {
Ok(s) => {
debug!("<< {:?}", s);
if !s.is_empty() {
let mut buffer_queue = BufferQueue::new();
let tendril = FromIterator::from_iter(s.chars());
buffer_queue.push_back(tendril);
self.parser.feed(&mut buffer_queue);
}
}
// Remedies for truncated utf8
Err(e) if e.valid_up_to() >= buf1.len() - 3 => {
// Prepare all the valid data
let mut b = BytesMut::with_capacity(e.valid_up_to());
b.put(&buf1[0..e.valid_up_to()]);
// Retry let had_stream_root = self.stanza_builder.depth() > 0;
let result = self.decode(&mut b); self.stanza_builder.process_event(token)?;
let has_stream_root = self.stanza_builder.depth() > 0;
// Keep the tail back in if !had_stream_root && has_stream_root {
self.buf.extend_from_slice(&buf1[e.valid_up_to()..]); let root = self.stanza_builder.top().unwrap();
let attrs =
root.attrs()
.map(|(name, value)| (name.to_owned(), value.to_owned()))
.chain(root.prefixes.declared_prefixes().iter().map(
|(prefix, namespace)| {
(
prefix
.as_ref()
.map(|prefix| format!("xmlns:{}", prefix))
.unwrap_or_else(|| "xmlns".to_owned()),
namespace.clone(),
)
},
))
.collect();
return Ok(Some(Packet::StreamStart(attrs)));
} else if self.stanza_builder.depth() == 1 {
self.driver.release_temporaries();
return result; if let Some(stanza) = self.stanza_builder.unshift_child() {
return Ok(Some(Packet::Stanza(stanza)));
} }
Err(e) => { } else if let Some(_) = self.stanza_builder.root.take() {
error!( self.driver.release_temporaries();
"error {} at {}/{} in {:?}",
e, return Ok(Some(Packet::StreamEnd));
e.valid_up_to(),
buf1.len(),
buf1
);
return Err(ParserError::Utf8(e));
} }
} }
match self.queue.lock().unwrap().pop_front() { Ok(None)
None => Ok(None),
Some(result) => result.map(|pkt| Some(pkt)),
}
} }
fn decode_eof(&mut self, buf: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> { fn decode_eof(&mut self, buf: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
@ -392,7 +230,6 @@ mod tests {
Ok(Some(Packet::StreamStart(_))) => true, Ok(Some(Packet::StreamStart(_))) => true,
_ => false, _ => false,
}); });
b.clear();
b.put_slice(b"</stream:stream>"); b.put_slice(b"</stream:stream>");
let r = c.decode(&mut b); let r = c.decode(&mut b);
assert!(match r { assert!(match r {
@ -412,7 +249,6 @@ mod tests {
_ => false, _ => false,
}); });
b.clear();
b.put_slice("<test>ß</test".as_bytes()); b.put_slice("<test>ß</test".as_bytes());
let r = c.decode(&mut b); let r = c.decode(&mut b);
assert!(match r { assert!(match r {
@ -420,7 +256,6 @@ mod tests {
_ => false, _ => false,
}); });
b.clear();
b.put_slice(b">"); b.put_slice(b">");
let r = c.decode(&mut b); let r = c.decode(&mut b);
assert!(match r { assert!(match r {
@ -440,7 +275,6 @@ mod tests {
_ => false, _ => false,
}); });
b.clear();
b.put(&b"<test>\xc3"[..]); b.put(&b"<test>\xc3"[..]);
let r = c.decode(&mut b); let r = c.decode(&mut b);
assert!(match r { assert!(match r {
@ -448,7 +282,6 @@ mod tests {
_ => false, _ => false,
}); });
b.clear();
b.put(&b"\x9f</test>"[..]); b.put(&b"\x9f</test>"[..]);
let r = c.decode(&mut b); let r = c.decode(&mut b);
assert!(match r { assert!(match r {
@ -469,7 +302,6 @@ mod tests {
_ => false, _ => false,
}); });
b.clear();
b.put_slice(b"<status xml:lang='en'>Test status</status>"); b.put_slice(b"<status xml:lang='en'>Test status</status>");
let r = c.decode(&mut b); let r = c.decode(&mut b);
assert!(match r { assert!(match r {
@ -503,7 +335,10 @@ mod tests {
block_on(framed.send(Packet::Stanza(stanza))).expect("send"); block_on(framed.send(Packet::Stanza(stanza))).expect("send");
assert_eq!( assert_eq!(
framed.get_ref().get_ref(), framed.get_ref().get_ref(),
&("<message xmlns=\"jabber:client\"><body>".to_owned() + &text + "</body></message>") &format!(
"<message xmlns=\"jabber:client\"><body>{}</body></message>",
text
)
.as_bytes() .as_bytes()
); );
} }
@ -519,7 +354,6 @@ mod tests {
_ => false, _ => false,
}); });
b.clear();
b.put_slice(b"<message "); b.put_slice(b"<message ");
b.put_slice(b"type='chat'><body>Foo</body></message>"); b.put_slice(b"type='chat'><body>Foo</body></message>");
let r = c.decode(&mut b); let r = c.decode(&mut b);

View file

@ -54,7 +54,7 @@ impl<S: AsyncRead + AsyncWrite + Unpin> XMPPStream<S> {
} }
/// Send a `<stream:stream>` start tag /// Send a `<stream:stream>` start tag
pub async fn start<'a>(stream: S, jid: Jid, ns: String) -> Result<Self, Error> { pub async fn start(stream: S, jid: Jid, ns: String) -> Result<Self, Error> {
let xmpp_stream = Framed::new(stream, XMPPCodec::new()); let xmpp_stream = Framed::new(stream, XMPPCodec::new());
stream_start::start(xmpp_stream, jid, ns).await stream_start::start(xmpp_stream, jid, ns).await
} }
@ -65,7 +65,7 @@ impl<S: AsyncRead + AsyncWrite + Unpin> XMPPStream<S> {
} }
/// Re-run `start()` /// Re-run `start()`
pub async fn restart<'a>(self) -> Result<Self, Error> { pub async fn restart(self) -> Result<Self, Error> {
let stream = self.stream.into_inner().unwrap().into_inner(); let stream = self.stream.into_inner().unwrap().into_inner();
Self::start(stream, self.jid, self.ns).await Self::start(stream, self.jid, self.ns).await
} }