Rustfmt pass, and rustfmt --check in CI"
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
This commit is contained in:
parent
e1b53eae3c
commit
a104ebc3f6
79 changed files with 1344 additions and 957 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
stages:
|
stages:
|
||||||
|
- lint
|
||||||
- build
|
- build
|
||||||
- test
|
- test
|
||||||
|
|
||||||
|
|
@ -30,6 +31,11 @@ variables:
|
||||||
script:
|
script:
|
||||||
- cargo test --verbose
|
- cargo test --verbose
|
||||||
|
|
||||||
|
rustfmt:
|
||||||
|
stage: lint
|
||||||
|
script:
|
||||||
|
- rustfmt --check --edition 2018 **/*.rs
|
||||||
|
|
||||||
rust-latest-build:
|
rust-latest-build:
|
||||||
extends:
|
extends:
|
||||||
- .build
|
- .build
|
||||||
|
|
|
||||||
|
|
@ -40,12 +40,16 @@ impl StdError for JidParseError {}
|
||||||
|
|
||||||
impl fmt::Display for JidParseError {
|
impl fmt::Display for JidParseError {
|
||||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(fmt, "{}", match self {
|
write!(
|
||||||
JidParseError::NoDomain => "no domain found in this JID",
|
fmt,
|
||||||
JidParseError::NoResource => "no resource found in this full JID",
|
"{}",
|
||||||
JidParseError::EmptyNode => "nodepart empty despite the presence of a @",
|
match self {
|
||||||
JidParseError::EmptyResource => "resource empty despite the presence of a /",
|
JidParseError::NoDomain => "no domain found in this JID",
|
||||||
})
|
JidParseError::NoResource => "no resource found in this full JID",
|
||||||
|
JidParseError::EmptyNode => "nodepart empty despite the presence of a @",
|
||||||
|
JidParseError::EmptyResource => "resource empty despite the presence of a /",
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -623,8 +627,8 @@ impl Into<Node> for BareJid {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use std::str::FromStr;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_parse_full_jids() {
|
fn can_parse_full_jids() {
|
||||||
|
|
@ -710,22 +714,13 @@ mod tests {
|
||||||
let full = FullJid::new("a", "b.c", "d");
|
let full = FullJid::new("a", "b.c", "d");
|
||||||
let bare = BareJid::new("a", "b.c");
|
let bare = BareJid::new("a", "b.c");
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(FullJid::try_from(Jid::Full(full.clone())), Ok(full.clone()),);
|
||||||
FullJid::try_from(Jid::Full(full.clone())),
|
|
||||||
Ok(full.clone()),
|
|
||||||
);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
FullJid::try_from(Jid::Bare(bare.clone())),
|
FullJid::try_from(Jid::Bare(bare.clone())),
|
||||||
Err(JidParseError::NoResource),
|
Err(JidParseError::NoResource),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(BareJid::from(Jid::Full(full.clone())), bare.clone(),);
|
||||||
BareJid::from(Jid::Full(full.clone())),
|
assert_eq!(BareJid::from(Jid::Bare(bare.clone())), bare,);
|
||||||
bare.clone(),
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
BareJid::from(Jid::Bare(bare.clone())),
|
|
||||||
bare,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -760,10 +755,19 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn display_jids() {
|
fn display_jids() {
|
||||||
assert_eq!(format!("{}", FullJid::new("a", "b", "c")), String::from("a@b/c"));
|
assert_eq!(
|
||||||
assert_eq!(format!("{}", BareJid::new("a", "b")), String::from("a@b"));
|
format!("{}", FullJid::new("a", "b", "c")),
|
||||||
assert_eq!(format!("{}", Jid::Full(FullJid::new("a", "b", "c"))), String::from("a@b/c"));
|
String::from("a@b/c")
|
||||||
assert_eq!(format!("{}", Jid::Bare(BareJid::new("a", "b"))), String::from("a@b"));
|
);
|
||||||
|
assert_eq!(format!("{}", BareJid::new("a", "b")), String::from("a@b"));
|
||||||
|
assert_eq!(
|
||||||
|
format!("{}", Jid::Full(FullJid::new("a", "b", "c"))),
|
||||||
|
String::from("a@b/c")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
format!("{}", Jid::Bare(BareJid::new("a", "b"))),
|
||||||
|
String::from("a@b")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "minidom")]
|
#[cfg(feature = "minidom")]
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ macro_rules! impl_into_attribute_value {
|
||||||
Some(format!("{}", self))
|
Some(format!("{}", self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! impl_into_attribute_values {
|
macro_rules! impl_into_attribute_values {
|
||||||
|
|
@ -22,7 +22,19 @@ macro_rules! impl_into_attribute_values {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_into_attribute_values!(usize, u64, u32, u16, u8, isize, i64, i32, i16, i8, ::std::net::IpAddr);
|
impl_into_attribute_values!(
|
||||||
|
usize,
|
||||||
|
u64,
|
||||||
|
u32,
|
||||||
|
u16,
|
||||||
|
u8,
|
||||||
|
isize,
|
||||||
|
i64,
|
||||||
|
i32,
|
||||||
|
i16,
|
||||||
|
i8,
|
||||||
|
::std::net::IpAddr
|
||||||
|
);
|
||||||
|
|
||||||
impl IntoAttributeValue for String {
|
impl IntoAttributeValue for String {
|
||||||
fn into_attribute_value(self) -> Option<String> {
|
fn into_attribute_value(self) -> Option<String> {
|
||||||
|
|
@ -56,14 +68,20 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_into_attribute_value_on_ints() {
|
fn test_into_attribute_value_on_ints() {
|
||||||
assert_eq!(16u8.into_attribute_value().unwrap() , "16");
|
assert_eq!(16u8.into_attribute_value().unwrap(), "16");
|
||||||
assert_eq!(17u16.into_attribute_value().unwrap() , "17");
|
assert_eq!(17u16.into_attribute_value().unwrap(), "17");
|
||||||
assert_eq!(18u32.into_attribute_value().unwrap() , "18");
|
assert_eq!(18u32.into_attribute_value().unwrap(), "18");
|
||||||
assert_eq!(19u64.into_attribute_value().unwrap() , "19");
|
assert_eq!(19u64.into_attribute_value().unwrap(), "19");
|
||||||
assert_eq!( 16i8.into_attribute_value().unwrap() , "16");
|
assert_eq!(16i8.into_attribute_value().unwrap(), "16");
|
||||||
assert_eq!((-17i16).into_attribute_value().unwrap(), "-17");
|
assert_eq!((-17i16).into_attribute_value().unwrap(), "-17");
|
||||||
assert_eq!( 18i32.into_attribute_value().unwrap(), "18");
|
assert_eq!(18i32.into_attribute_value().unwrap(), "18");
|
||||||
assert_eq!((-19i64).into_attribute_value().unwrap(), "-19");
|
assert_eq!((-19i64).into_attribute_value().unwrap(), "-19");
|
||||||
assert_eq!(IpAddr::from_str("127.000.0.1").unwrap().into_attribute_value().unwrap(), "127.0.0.1");
|
assert_eq!(
|
||||||
|
IpAddr::from_str("127.000.0.1")
|
||||||
|
.unwrap()
|
||||||
|
.into_attribute_value()
|
||||||
|
.unwrap(),
|
||||||
|
"127.0.0.1"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,16 +5,16 @@ use crate::error::{Error, Result};
|
||||||
use crate::namespace_set::NamespaceSet;
|
use crate::namespace_set::NamespaceSet;
|
||||||
use crate::node::Node;
|
use crate::node::Node;
|
||||||
|
|
||||||
use std::io:: Write;
|
|
||||||
use std::collections::{btree_map, BTreeMap};
|
use std::collections::{btree_map, BTreeMap};
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
use std::str;
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::str;
|
||||||
|
|
||||||
|
use quick_xml::events::{BytesDecl, BytesEnd, BytesStart, Event};
|
||||||
use quick_xml::Reader as EventReader;
|
use quick_xml::Reader as EventReader;
|
||||||
use quick_xml::Writer as EventWriter;
|
use quick_xml::Writer as EventWriter;
|
||||||
use quick_xml::events::{Event, BytesStart, BytesEnd, BytesDecl};
|
|
||||||
|
|
||||||
use std::io::BufRead;
|
use std::io::BufRead;
|
||||||
|
|
||||||
|
|
@ -68,7 +68,6 @@ pub fn escape(raw: &[u8]) -> Cow<[u8]> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
/// A struct representing a DOM Element.
|
/// A struct representing a DOM Element.
|
||||||
pub struct Element {
|
pub struct Element {
|
||||||
|
|
@ -97,9 +96,16 @@ impl FromStr for Element {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Element {
|
impl Element {
|
||||||
fn new<NS: Into<NamespaceSet>>(name: String, prefix: Option<String>, namespaces: NS, attributes: BTreeMap<String, String>, children: Vec<Node>) -> Element {
|
fn new<NS: Into<NamespaceSet>>(
|
||||||
|
name: String,
|
||||||
|
prefix: Option<String>,
|
||||||
|
namespaces: NS,
|
||||||
|
attributes: BTreeMap<String, String>,
|
||||||
|
children: Vec<Node>,
|
||||||
|
) -> Element {
|
||||||
Element {
|
Element {
|
||||||
prefix, name,
|
prefix,
|
||||||
|
name,
|
||||||
namespaces: Rc::new(namespaces.into()),
|
namespaces: Rc::new(namespaces.into()),
|
||||||
attributes,
|
attributes,
|
||||||
children,
|
children,
|
||||||
|
|
@ -186,7 +192,7 @@ impl Element {
|
||||||
/// Returns a reference to the value of the given attribute, if it exists, else `None`.
|
/// Returns a reference to the value of the given attribute, if it exists, else `None`.
|
||||||
pub fn attr(&self, name: &str) -> Option<&str> {
|
pub fn attr(&self, name: &str) -> Option<&str> {
|
||||||
if let Some(value) = self.attributes.get(name) {
|
if let Some(value) = self.attributes.get(name) {
|
||||||
return Some(value)
|
return Some(value);
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
@ -225,7 +231,8 @@ impl Element {
|
||||||
let val = val.into_attribute_value();
|
let val = val.into_attribute_value();
|
||||||
|
|
||||||
if let Some(value) = self.attributes.get_mut(&name) {
|
if let Some(value) = self.attributes.get_mut(&name) {
|
||||||
*value = val.expect("removing existing value via set_attr, this is not yet supported (TODO)"); // TODO
|
*value = val
|
||||||
|
.expect("removing existing value via set_attr, this is not yet supported (TODO)"); // TODO
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -249,8 +256,7 @@ impl Element {
|
||||||
/// assert_eq!(elem.is("wrong", "wrong"), false);
|
/// assert_eq!(elem.is("wrong", "wrong"), false);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn is<N: AsRef<str>, NS: AsRef<str>>(&self, name: N, namespace: NS) -> bool {
|
pub fn is<N: AsRef<str>, NS: AsRef<str>>(&self, name: N, namespace: NS) -> bool {
|
||||||
self.name == name.as_ref() &&
|
self.name == name.as_ref() && self.has_ns(namespace)
|
||||||
self.has_ns(namespace)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether the element has the given namespace.
|
/// Returns whether the element has the given namespace.
|
||||||
|
|
@ -278,22 +284,22 @@ impl Element {
|
||||||
match e {
|
match e {
|
||||||
Event::Empty(ref e) | Event::Start(ref e) => {
|
Event::Empty(ref e) | Event::Start(ref e) => {
|
||||||
break build_element(reader, e)?;
|
break build_element(reader, e)?;
|
||||||
},
|
}
|
||||||
Event::Eof => {
|
Event::Eof => {
|
||||||
return Err(Error::EndOfDocument);
|
return Err(Error::EndOfDocument);
|
||||||
},
|
}
|
||||||
#[cfg(not(feature = "comments"))]
|
#[cfg(not(feature = "comments"))]
|
||||||
Event::Comment { .. } => {
|
Event::Comment { .. } => {
|
||||||
return Err(Error::CommentsDisabled);
|
return Err(Error::CommentsDisabled);
|
||||||
}
|
}
|
||||||
#[cfg(feature = "comments")]
|
#[cfg(feature = "comments")]
|
||||||
Event::Comment { .. } => (),
|
Event::Comment { .. } => (),
|
||||||
Event::Text { .. } |
|
Event::Text { .. }
|
||||||
Event::End { .. } |
|
| Event::End { .. }
|
||||||
Event::CData { .. } |
|
| Event::CData { .. }
|
||||||
Event::Decl { .. } |
|
| Event::Decl { .. }
|
||||||
Event::PI { .. } |
|
| Event::PI { .. }
|
||||||
Event::DocType { .. } => (), // TODO: may need more errors
|
| Event::DocType { .. } => (), // TODO: may need more errors
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -305,11 +311,11 @@ impl Element {
|
||||||
let elem = build_element(reader, e)?;
|
let elem = build_element(reader, e)?;
|
||||||
// Since there is no Event::End after, directly append it to the current node
|
// Since there is no Event::End after, directly append it to the current node
|
||||||
stack.last_mut().unwrap().append_child(elem);
|
stack.last_mut().unwrap().append_child(elem);
|
||||||
},
|
}
|
||||||
Event::Start(ref e) => {
|
Event::Start(ref e) => {
|
||||||
let elem = build_element(reader, e)?;
|
let elem = build_element(reader, e)?;
|
||||||
stack.push(elem);
|
stack.push(elem);
|
||||||
},
|
}
|
||||||
Event::End(ref e) => {
|
Event::End(ref e) => {
|
||||||
if stack.len() <= 1 {
|
if stack.len() <= 1 {
|
||||||
break;
|
break;
|
||||||
|
|
@ -327,15 +333,15 @@ impl Element {
|
||||||
if possible_prefix != prefix.as_bytes() {
|
if possible_prefix != prefix.as_bytes() {
|
||||||
return Err(Error::InvalidElementClosed);
|
return Err(Error::InvalidElementClosed);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
None => {
|
None => {
|
||||||
return Err(Error::InvalidElementClosed);
|
return Err(Error::InvalidElementClosed);
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
if name != elem.name().as_bytes() {
|
if name != elem.name().as_bytes() {
|
||||||
return Err(Error::InvalidElementClosed);
|
return Err(Error::InvalidElementClosed);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
None => {
|
None => {
|
||||||
if elem.prefix().is_some() {
|
if elem.prefix().is_some() {
|
||||||
return Err(Error::InvalidElementClosed);
|
return Err(Error::InvalidElementClosed);
|
||||||
|
|
@ -343,28 +349,28 @@ impl Element {
|
||||||
if possible_prefix != elem.name().as_bytes() {
|
if possible_prefix != elem.name().as_bytes() {
|
||||||
return Err(Error::InvalidElementClosed);
|
return Err(Error::InvalidElementClosed);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
to.append_child(elem);
|
to.append_child(elem);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Event::Text(s) => {
|
Event::Text(s) => {
|
||||||
let text = s.unescape_and_decode(reader)?;
|
let text = s.unescape_and_decode(reader)?;
|
||||||
if text != "" {
|
if text != "" {
|
||||||
let current_elem = stack.last_mut().unwrap();
|
let current_elem = stack.last_mut().unwrap();
|
||||||
current_elem.append_text_node(text);
|
current_elem.append_text_node(text);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Event::CData(s) => {
|
Event::CData(s) => {
|
||||||
let text = reader.decode(&s)?.to_owned();
|
let text = reader.decode(&s)?.to_owned();
|
||||||
if text != "" {
|
if text != "" {
|
||||||
let current_elem = stack.last_mut().unwrap();
|
let current_elem = stack.last_mut().unwrap();
|
||||||
current_elem.append_text_node(text);
|
current_elem.append_text_node(text);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Event::Eof => {
|
Event::Eof => {
|
||||||
break;
|
break;
|
||||||
},
|
}
|
||||||
#[cfg(not(feature = "comments"))]
|
#[cfg(not(feature = "comments"))]
|
||||||
Event::Comment(_) => return Err(Error::CommentsDisabled),
|
Event::Comment(_) => return Err(Error::CommentsDisabled),
|
||||||
#[cfg(feature = "comments")]
|
#[cfg(feature = "comments")]
|
||||||
|
|
@ -374,10 +380,8 @@ impl Element {
|
||||||
let current_elem = stack.last_mut().unwrap();
|
let current_elem = stack.last_mut().unwrap();
|
||||||
current_elem.append_comment_node(comment);
|
current_elem.append_comment_node(comment);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Event::Decl { .. } |
|
Event::Decl { .. } | Event::PI { .. } | Event::DocType { .. } => (),
|
||||||
Event::PI { .. } |
|
|
||||||
Event::DocType { .. } => (),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(stack.pop().unwrap())
|
Ok(stack.pop().unwrap())
|
||||||
|
|
@ -408,7 +412,7 @@ impl Element {
|
||||||
Some(ref prefix) => {
|
Some(ref prefix) => {
|
||||||
let key = format!("xmlns:{}", prefix);
|
let key = format!("xmlns:{}", prefix);
|
||||||
start.push_attribute((key.as_bytes(), ns.as_bytes()))
|
start.push_attribute((key.as_bytes(), ns.as_bytes()))
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (key, value) in &self.attributes {
|
for (key, value) in &self.attributes {
|
||||||
|
|
@ -417,7 +421,7 @@ impl Element {
|
||||||
|
|
||||||
if self.children.is_empty() {
|
if self.children.is_empty() {
|
||||||
writer.write_event(Event::Empty(start))?;
|
writer.write_event(Event::Empty(start))?;
|
||||||
return Ok(())
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.write_event(Event::Start(start))?;
|
writer.write_event(Event::Start(start))?;
|
||||||
|
|
@ -448,12 +452,14 @@ impl Element {
|
||||||
/// assert_eq!(iter.next().unwrap().as_text().unwrap(), "c");
|
/// assert_eq!(iter.next().unwrap().as_text().unwrap(), "c");
|
||||||
/// assert_eq!(iter.next(), None);
|
/// assert_eq!(iter.next(), None);
|
||||||
/// ```
|
/// ```
|
||||||
#[inline] pub fn nodes(&self) -> Nodes {
|
#[inline]
|
||||||
|
pub fn nodes(&self) -> Nodes {
|
||||||
self.children.iter()
|
self.children.iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an iterator over mutable references to every child node of this element.
|
/// Returns an iterator over mutable references to every child node of this element.
|
||||||
#[inline] pub fn nodes_mut(&mut self) -> NodesMut {
|
#[inline]
|
||||||
|
pub fn nodes_mut(&mut self) -> NodesMut {
|
||||||
self.children.iter_mut()
|
self.children.iter_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -472,14 +478,16 @@ impl Element {
|
||||||
/// assert_eq!(iter.next().unwrap().name(), "child3");
|
/// assert_eq!(iter.next().unwrap().name(), "child3");
|
||||||
/// assert_eq!(iter.next(), None);
|
/// assert_eq!(iter.next(), None);
|
||||||
/// ```
|
/// ```
|
||||||
#[inline] pub fn children(&self) -> Children {
|
#[inline]
|
||||||
|
pub fn children(&self) -> Children {
|
||||||
Children {
|
Children {
|
||||||
iter: self.children.iter(),
|
iter: self.children.iter(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an iterator over mutable references to every child element of this element.
|
/// Returns an iterator over mutable references to every child element of this element.
|
||||||
#[inline] pub fn children_mut(&mut self) -> ChildrenMut {
|
#[inline]
|
||||||
|
pub fn children_mut(&mut self) -> ChildrenMut {
|
||||||
ChildrenMut {
|
ChildrenMut {
|
||||||
iter: self.children.iter_mut(),
|
iter: self.children.iter_mut(),
|
||||||
}
|
}
|
||||||
|
|
@ -499,14 +507,16 @@ impl Element {
|
||||||
/// assert_eq!(iter.next().unwrap(), " world!");
|
/// assert_eq!(iter.next().unwrap(), " world!");
|
||||||
/// assert_eq!(iter.next(), None);
|
/// assert_eq!(iter.next(), None);
|
||||||
/// ```
|
/// ```
|
||||||
#[inline] pub fn texts(&self) -> Texts {
|
#[inline]
|
||||||
|
pub fn texts(&self) -> Texts {
|
||||||
Texts {
|
Texts {
|
||||||
iter: self.children.iter(),
|
iter: self.children.iter(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an iterator over mutable references to every text node of this element.
|
/// Returns an iterator over mutable references to every text node of this element.
|
||||||
#[inline] pub fn texts_mut(&mut self) -> TextsMut {
|
#[inline]
|
||||||
|
pub fn texts_mut(&mut self) -> TextsMut {
|
||||||
TextsMut {
|
TextsMut {
|
||||||
iter: self.children.iter_mut(),
|
iter: self.children.iter_mut(),
|
||||||
}
|
}
|
||||||
|
|
@ -630,7 +640,11 @@ impl Element {
|
||||||
/// assert_eq!(elem.get_child("b", "other_ns"), None);
|
/// assert_eq!(elem.get_child("b", "other_ns"), None);
|
||||||
/// assert_eq!(elem.get_child("a", "inexistent_ns"), None);
|
/// assert_eq!(elem.get_child("a", "inexistent_ns"), None);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn get_child<N: AsRef<str>, NS: AsRef<str>>(&self, name: N, namespace: NS) -> Option<&Element> {
|
pub fn get_child<N: AsRef<str>, NS: AsRef<str>>(
|
||||||
|
&self,
|
||||||
|
name: N,
|
||||||
|
namespace: NS,
|
||||||
|
) -> Option<&Element> {
|
||||||
for fork in &self.children {
|
for fork in &self.children {
|
||||||
if let Node::Element(ref e) = *fork {
|
if let Node::Element(ref e) = *fork {
|
||||||
if e.is(name.as_ref(), namespace.as_ref()) {
|
if e.is(name.as_ref(), namespace.as_ref()) {
|
||||||
|
|
@ -643,7 +657,11 @@ impl Element {
|
||||||
|
|
||||||
/// Returns a mutable reference to the first child element with the specific name and namespace,
|
/// Returns a mutable reference to the first child element with the specific name and namespace,
|
||||||
/// if it exists in the direct descendants of this `Element`, else returns `None`.
|
/// if it exists in the direct descendants of this `Element`, else returns `None`.
|
||||||
pub fn get_child_mut<N: AsRef<str>, NS: AsRef<str>>(&mut self, name: N, namespace: NS) -> Option<&mut Element> {
|
pub fn get_child_mut<N: AsRef<str>, NS: AsRef<str>>(
|
||||||
|
&mut self,
|
||||||
|
name: N,
|
||||||
|
namespace: NS,
|
||||||
|
) -> Option<&mut Element> {
|
||||||
for fork in &mut self.children {
|
for fork in &mut self.children {
|
||||||
if let Node::Element(ref mut e) = *fork {
|
if let Node::Element(ref mut e) = *fork {
|
||||||
if e.is(name.as_ref(), namespace.as_ref()) {
|
if e.is(name.as_ref(), namespace.as_ref()) {
|
||||||
|
|
@ -690,7 +708,11 @@ impl Element {
|
||||||
/// assert!(elem.remove_child("a", "ns").is_none());
|
/// assert!(elem.remove_child("a", "ns").is_none());
|
||||||
/// assert!(elem.remove_child("inexistent", "inexistent").is_none());
|
/// assert!(elem.remove_child("inexistent", "inexistent").is_none());
|
||||||
/// ```
|
/// ```
|
||||||
pub fn remove_child<N: AsRef<str>, NS: AsRef<str>>(&mut self, name: N, namespace: NS) -> Option<Element> {
|
pub fn remove_child<N: AsRef<str>, NS: AsRef<str>>(
|
||||||
|
&mut self,
|
||||||
|
name: N,
|
||||||
|
namespace: NS,
|
||||||
|
) -> Option<Element> {
|
||||||
let name = name.as_ref();
|
let name = name.as_ref();
|
||||||
let namespace = namespace.as_ref();
|
let namespace = namespace.as_ref();
|
||||||
let idx = self.children.iter().position(|x| {
|
let idx = self.children.iter().position(|x| {
|
||||||
|
|
@ -715,25 +737,24 @@ fn split_element_name<S: AsRef<str>>(s: S) -> Result<(Option<String>, String)> {
|
||||||
|
|
||||||
fn build_element<R: BufRead>(reader: &EventReader<R>, event: &BytesStart) -> Result<Element> {
|
fn build_element<R: BufRead>(reader: &EventReader<R>, event: &BytesStart) -> Result<Element> {
|
||||||
let mut namespaces = BTreeMap::new();
|
let mut namespaces = BTreeMap::new();
|
||||||
let attributes = event.attributes()
|
let attributes = event
|
||||||
|
.attributes()
|
||||||
.map(|o| {
|
.map(|o| {
|
||||||
let o = o?;
|
let o = o?;
|
||||||
let key = str::from_utf8(o.key)?.to_owned();
|
let key = str::from_utf8(o.key)?.to_owned();
|
||||||
let value = o.unescape_and_decode_value(reader)?;
|
let value = o.unescape_and_decode_value(reader)?;
|
||||||
Ok((key, value))
|
Ok((key, value))
|
||||||
})
|
})
|
||||||
.filter(|o| {
|
.filter(|o| match *o {
|
||||||
match *o {
|
Ok((ref key, ref value)) if key == "xmlns" => {
|
||||||
Ok((ref key, ref value)) if key == "xmlns" => {
|
namespaces.insert(None, value.to_owned());
|
||||||
namespaces.insert(None, value.to_owned());
|
false
|
||||||
false
|
|
||||||
},
|
|
||||||
Ok((ref key, ref value)) if key.starts_with("xmlns:") => {
|
|
||||||
namespaces.insert(Some(key[6..].to_owned()), value.to_owned());
|
|
||||||
false
|
|
||||||
},
|
|
||||||
_ => true,
|
|
||||||
}
|
}
|
||||||
|
Ok((ref key, ref value)) if key.starts_with("xmlns:") => {
|
||||||
|
namespaces.insert(Some(key[6..].to_owned()), value.to_owned());
|
||||||
|
false
|
||||||
|
}
|
||||||
|
_ => true,
|
||||||
})
|
})
|
||||||
.collect::<Result<BTreeMap<String, String>>>()?;
|
.collect::<Result<BTreeMap<String, String>>>()?;
|
||||||
|
|
||||||
|
|
@ -861,7 +882,11 @@ impl ElementBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets an attribute.
|
/// Sets an attribute.
|
||||||
pub fn attr<S: Into<String>, V: IntoAttributeValue>(mut self, name: S, value: V) -> ElementBuilder {
|
pub fn attr<S: Into<String>, V: IntoAttributeValue>(
|
||||||
|
mut self,
|
||||||
|
name: S,
|
||||||
|
value: V,
|
||||||
|
) -> ElementBuilder {
|
||||||
self.root.set_attr(name, value);
|
self.root.set_attr(name, value);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
@ -873,7 +898,10 @@ impl ElementBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Appends an iterator of things implementing `Into<Node>` into the tree.
|
/// Appends an iterator of things implementing `Into<Node>` into the tree.
|
||||||
pub fn append_all<T: Into<Node>, I: IntoIterator<Item = T>>(mut self, iter: I) -> ElementBuilder {
|
pub fn append_all<T: Into<Node>, I: IntoIterator<Item = T>>(
|
||||||
|
mut self,
|
||||||
|
iter: I,
|
||||||
|
) -> ElementBuilder {
|
||||||
for node in iter {
|
for node in iter {
|
||||||
self.root.append_node(node.into());
|
self.root.append_node(node.into());
|
||||||
}
|
}
|
||||||
|
|
@ -903,11 +931,13 @@ mod tests {
|
||||||
fn test_element_new() {
|
fn test_element_new() {
|
||||||
use std::iter::FromIterator;
|
use std::iter::FromIterator;
|
||||||
|
|
||||||
let elem = Element::new( "name".to_owned()
|
let elem = Element::new(
|
||||||
, None
|
"name".to_owned(),
|
||||||
, Some("namespace".to_owned())
|
None,
|
||||||
, BTreeMap::from_iter(vec![ ("name".to_string(), "value".to_string()) ].into_iter() )
|
Some("namespace".to_owned()),
|
||||||
, Vec::new() );
|
BTreeMap::from_iter(vec![("name".to_string(), "value".to_string())].into_iter()),
|
||||||
|
Vec::new(),
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(elem.name(), "name");
|
assert_eq!(elem.name(), "name");
|
||||||
assert_eq!(elem.ns(), Some("namespace".to_owned()));
|
assert_eq!(elem.ns(), Some("namespace".to_owned()));
|
||||||
|
|
@ -932,12 +962,8 @@ mod tests {
|
||||||
let mut reader = EventReader::from_str(xml);
|
let mut reader = EventReader::from_str(xml);
|
||||||
let elem = Element::from_reader(&mut reader);
|
let elem = Element::from_reader(&mut reader);
|
||||||
|
|
||||||
let nested = Element::builder("bar")
|
let nested = Element::builder("bar").attr("baz", "qxx").build();
|
||||||
.attr("baz", "qxx")
|
let elem2 = Element::builder("foo").append(nested).build();
|
||||||
.build();
|
|
||||||
let elem2 = Element::builder("foo")
|
|
||||||
.append(nested)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
assert_eq!(elem.unwrap(), elem2);
|
assert_eq!(elem.unwrap(), elem2);
|
||||||
}
|
}
|
||||||
|
|
@ -948,18 +974,15 @@ mod tests {
|
||||||
let mut reader = EventReader::from_str(xml);
|
let mut reader = EventReader::from_str(xml);
|
||||||
let elem = Element::from_reader(&mut reader);
|
let elem = Element::from_reader(&mut reader);
|
||||||
|
|
||||||
let nested = Element::builder("prefix:bar")
|
let nested = Element::builder("prefix:bar").attr("baz", "qxx").build();
|
||||||
.attr("baz", "qxx")
|
let elem2 = Element::builder("foo").append(nested).build();
|
||||||
.build();
|
|
||||||
let elem2 = Element::builder("foo")
|
|
||||||
.append(nested)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
assert_eq!(elem.unwrap(), elem2);
|
assert_eq!(elem.unwrap(), elem2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parses_spectest_xml() { // From: https://gitlab.com/lumi/minidom-rs/issues/8
|
fn parses_spectest_xml() {
|
||||||
|
// From: https://gitlab.com/lumi/minidom-rs/issues/8
|
||||||
let xml = r#"
|
let xml = r#"
|
||||||
<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>
|
||||||
|
|
|
||||||
|
|
@ -52,11 +52,18 @@ impl std::fmt::Display for Error {
|
||||||
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::Utf8Error(e) => write!(fmt, "UTF-8 error: {}", e),
|
||||||
Error::IoError(e) => write!(fmt, "IO error: {}", e),
|
Error::IoError(e) => write!(fmt, "IO error: {}", e),
|
||||||
Error::EndOfDocument => write!(fmt, "the end of the document has been reached prematurely"),
|
Error::EndOfDocument => {
|
||||||
Error::InvalidElementClosed => write!(fmt, "the XML is invalid, an element was wrongly closed"),
|
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::InvalidElement => write!(fmt, "the XML element is invalid"),
|
||||||
#[cfg(not(comments))]
|
#[cfg(not(comments))]
|
||||||
Error::CommentsDisabled => write!(fmt, "a comment has been found even though comments are disabled by feature"),
|
Error::CommentsDisabled => write!(
|
||||||
|
fmt,
|
||||||
|
"a comment has been found even though comments are disabled by feature"
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,15 +66,16 @@
|
||||||
|
|
||||||
pub use quick_xml;
|
pub use quick_xml;
|
||||||
|
|
||||||
pub mod error;
|
|
||||||
pub mod element;
|
|
||||||
pub mod convert;
|
pub mod convert;
|
||||||
pub mod node;
|
pub mod element;
|
||||||
|
pub mod error;
|
||||||
mod namespace_set;
|
mod namespace_set;
|
||||||
|
pub mod node;
|
||||||
|
|
||||||
#[cfg(test)] mod tests;
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
pub use error::{Error, Result};
|
|
||||||
pub use element::{Element, Children, ChildrenMut, ElementBuilder};
|
|
||||||
pub use node::Node;
|
|
||||||
pub use convert::IntoAttributeValue;
|
pub use convert::IntoAttributeValue;
|
||||||
|
pub use element::{Children, ChildrenMut, Element, ElementBuilder};
|
||||||
|
pub use error::{Error, Result};
|
||||||
|
pub use node::Node;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
use std::collections::BTreeMap;
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq)]
|
#[derive(Clone, PartialEq, Eq)]
|
||||||
pub struct NamespaceSet {
|
pub struct NamespaceSet {
|
||||||
parent: RefCell<Option<Rc<NamespaceSet>>>,
|
parent: RefCell<Option<Rc<NamespaceSet>>>,
|
||||||
|
|
@ -23,10 +22,15 @@ impl fmt::Debug for NamespaceSet {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "NamespaceSet(")?;
|
write!(f, "NamespaceSet(")?;
|
||||||
for (prefix, namespace) in &self.namespaces {
|
for (prefix, namespace) in &self.namespaces {
|
||||||
write!(f, "xmlns{}={:?}, ", match prefix {
|
write!(
|
||||||
None => String::new(),
|
f,
|
||||||
Some(prefix) => format!(":{}", prefix),
|
"xmlns{}={:?}, ",
|
||||||
}, namespace)?;
|
match prefix {
|
||||||
|
None => String::new(),
|
||||||
|
Some(prefix) => format!(":{}", prefix),
|
||||||
|
},
|
||||||
|
namespace
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
write!(f, "parent: {:?})", *self.parent.borrow())
|
write!(f, "parent: {:?})", *self.parent.borrow())
|
||||||
}
|
}
|
||||||
|
|
@ -42,20 +46,17 @@ impl NamespaceSet {
|
||||||
Some(ns) => Some(ns.clone()),
|
Some(ns) => Some(ns.clone()),
|
||||||
None => match *self.parent.borrow() {
|
None => match *self.parent.borrow() {
|
||||||
None => None,
|
None => None,
|
||||||
Some(ref parent) => parent.get(prefix)
|
Some(ref parent) => parent.get(prefix),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has<NS: AsRef<str>>(&self, prefix: &Option<String>, wanted_ns: NS) -> bool {
|
pub fn has<NS: AsRef<str>>(&self, prefix: &Option<String>, wanted_ns: NS) -> bool {
|
||||||
match self.namespaces.get(prefix) {
|
match self.namespaces.get(prefix) {
|
||||||
Some(ns) =>
|
Some(ns) => ns == wanted_ns.as_ref(),
|
||||||
ns == wanted_ns.as_ref(),
|
|
||||||
None => match *self.parent.borrow() {
|
None => match *self.parent.borrow() {
|
||||||
None =>
|
None => false,
|
||||||
false,
|
Some(ref parent) => parent.has(prefix, wanted_ns),
|
||||||
Some(ref parent) =>
|
|
||||||
parent.has(prefix, wanted_ns),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -65,7 +66,6 @@ impl NamespaceSet {
|
||||||
let new_set = parent;
|
let new_set = parent;
|
||||||
*parent_ns = Some(new_set);
|
*parent_ns = Some(new_set);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<BTreeMap<Option<String>, String>> for NamespaceSet {
|
impl From<BTreeMap<Option<String>, String>> for NamespaceSet {
|
||||||
|
|
@ -132,7 +132,10 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn get_has_prefixed() {
|
fn get_has_prefixed() {
|
||||||
let namespaces = NamespaceSet::from(("x".to_owned(), "bar".to_owned()));
|
let namespaces = NamespaceSet::from(("x".to_owned(), "bar".to_owned()));
|
||||||
assert_eq!(namespaces.get(&Some("x".to_owned())), Some("bar".to_owned()));
|
assert_eq!(
|
||||||
|
namespaces.get(&Some("x".to_owned())),
|
||||||
|
Some("bar".to_owned())
|
||||||
|
);
|
||||||
assert!(namespaces.has(&Some("x".to_owned()), "bar"));
|
assert!(namespaces.has(&Some("x".to_owned()), "bar"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -154,7 +157,10 @@ mod tests {
|
||||||
for _ in 0..1000 {
|
for _ in 0..1000 {
|
||||||
let namespaces = NamespaceSet::default();
|
let namespaces = NamespaceSet::default();
|
||||||
namespaces.set_parent(Rc::new(parent));
|
namespaces.set_parent(Rc::new(parent));
|
||||||
assert_eq!(namespaces.get(&Some("x".to_owned())), Some("bar".to_owned()));
|
assert_eq!(
|
||||||
|
namespaces.get(&Some("x".to_owned())),
|
||||||
|
Some("bar".to_owned())
|
||||||
|
);
|
||||||
assert!(namespaces.has(&Some("x".to_owned()), "bar"));
|
assert!(namespaces.has(&Some("x".to_owned()), "bar"));
|
||||||
parent = namespaces;
|
parent = namespaces;
|
||||||
}
|
}
|
||||||
|
|
@ -163,7 +169,10 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn debug_looks_correct() {
|
fn debug_looks_correct() {
|
||||||
let parent = NamespaceSet::from("http://www.w3.org/2000/svg".to_owned());
|
let parent = NamespaceSet::from("http://www.w3.org/2000/svg".to_owned());
|
||||||
let namespaces = NamespaceSet::from(("xhtml".to_owned(), "http://www.w3.org/1999/xhtml".to_owned()));
|
let namespaces = NamespaceSet::from((
|
||||||
|
"xhtml".to_owned(),
|
||||||
|
"http://www.w3.org/1999/xhtml".to_owned(),
|
||||||
|
));
|
||||||
namespaces.set_parent(Rc::new(parent));
|
namespaces.set_parent(Rc::new(parent));
|
||||||
assert_eq!(format!("{:?}", namespaces), "NamespaceSet(xmlns:xhtml=\"http://www.w3.org/1999/xhtml\", parent: Some(NamespaceSet(xmlns=\"http://www.w3.org/2000/svg\", parent: None)))");
|
assert_eq!(format!("{:?}", namespaces), "NamespaceSet(xmlns:xhtml=\"http://www.w3.org/1999/xhtml\", parent: Some(NamespaceSet(xmlns=\"http://www.w3.org/2000/svg\", parent: None)))");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ use crate::error::Result;
|
||||||
|
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
|
use quick_xml::events::{BytesText, Event};
|
||||||
use quick_xml::Writer as EventWriter;
|
use quick_xml::Writer as EventWriter;
|
||||||
use quick_xml::events::{Event, BytesText};
|
|
||||||
|
|
||||||
/// A node in an element tree.
|
/// A node in an element tree.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
|
@ -166,16 +166,16 @@ impl Node {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub(crate) fn write_to_inner<W: Write>(&self, writer: &mut EventWriter<W>) -> Result<()>{
|
pub(crate) fn write_to_inner<W: Write>(&self, writer: &mut EventWriter<W>) -> Result<()> {
|
||||||
match *self {
|
match *self {
|
||||||
Node::Element(ref elmt) => elmt.write_to_inner(writer)?,
|
Node::Element(ref elmt) => elmt.write_to_inner(writer)?,
|
||||||
Node::Text(ref s) => {
|
Node::Text(ref s) => {
|
||||||
writer.write_event(Event::Text(BytesText::from_plain_str(s)))?;
|
writer.write_event(Event::Text(BytesText::from_plain_str(s)))?;
|
||||||
},
|
}
|
||||||
#[cfg(feature = "comments")]
|
#[cfg(feature = "comments")]
|
||||||
Node::Comment(ref s) => {
|
Node::Comment(ref s) => {
|
||||||
writer.write_event(Event::Comment(BytesText::from_plain_str(s)))?;
|
writer.write_event(Event::Comment(BytesText::from_plain_str(s)))?;
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -6,20 +6,18 @@ const TEST_STRING: &'static str = r#"<?xml version="1.0" encoding="utf-8"?><root
|
||||||
|
|
||||||
fn build_test_tree() -> Element {
|
fn build_test_tree() -> Element {
|
||||||
let mut root = Element::builder("root")
|
let mut root = Element::builder("root")
|
||||||
.ns("root_ns")
|
.ns("root_ns")
|
||||||
.attr("xml:lang", "en")
|
.attr("xml:lang", "en")
|
||||||
.attr("a", "b")
|
.attr("a", "b")
|
||||||
.build();
|
.build();
|
||||||
root.append_text_node("meow");
|
root.append_text_node("meow");
|
||||||
let child = Element::builder("child")
|
let child = Element::builder("child").attr("c", "d").build();
|
||||||
.attr("c", "d")
|
|
||||||
.build();
|
|
||||||
root.append_child(child);
|
root.append_child(child);
|
||||||
let other_child = Element::builder("child")
|
let other_child = Element::builder("child")
|
||||||
.ns("child_ns")
|
.ns("child_ns")
|
||||||
.attr("d", "e")
|
.attr("d", "e")
|
||||||
.attr("xml:lang", "fr")
|
.attr("xml:lang", "fr")
|
||||||
.build();
|
.build();
|
||||||
root.append_child(other_child);
|
root.append_child(other_child);
|
||||||
root.append_text_node("nya");
|
root.append_text_node("nya");
|
||||||
root
|
root
|
||||||
|
|
@ -43,7 +41,10 @@ fn build_comment_test_tree() -> Element {
|
||||||
#[test]
|
#[test]
|
||||||
fn reader_works() {
|
fn reader_works() {
|
||||||
let mut reader = Reader::from_str(TEST_STRING);
|
let mut reader = Reader::from_str(TEST_STRING);
|
||||||
assert_eq!(Element::from_reader(&mut reader).unwrap(), build_test_tree());
|
assert_eq!(
|
||||||
|
Element::from_reader(&mut reader).unwrap(),
|
||||||
|
build_test_tree()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -58,40 +59,38 @@ fn writer_works() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn writer_escapes_attributes() {
|
fn writer_escapes_attributes() {
|
||||||
let root = Element::builder("root")
|
let root = Element::builder("root").attr("a", "\"Air\" quotes").build();
|
||||||
.attr("a", "\"Air\" quotes")
|
|
||||||
.build();
|
|
||||||
let mut writer = Vec::new();
|
let mut writer = Vec::new();
|
||||||
{
|
{
|
||||||
root.write_to(&mut writer).unwrap();
|
root.write_to(&mut writer).unwrap();
|
||||||
}
|
}
|
||||||
assert_eq!(String::from_utf8(writer).unwrap(),
|
assert_eq!(
|
||||||
r#"<?xml version="1.0" encoding="utf-8"?><root a=""Air" quotes"/>"#
|
String::from_utf8(writer).unwrap(),
|
||||||
|
r#"<?xml version="1.0" encoding="utf-8"?><root a=""Air" quotes"/>"#
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn writer_escapes_text() {
|
fn writer_escapes_text() {
|
||||||
let root = Element::builder("root")
|
let root = Element::builder("root").append("<3").build();
|
||||||
.append("<3")
|
|
||||||
.build();
|
|
||||||
let mut writer = Vec::new();
|
let mut writer = Vec::new();
|
||||||
{
|
{
|
||||||
root.write_to(&mut writer).unwrap();
|
root.write_to(&mut writer).unwrap();
|
||||||
}
|
}
|
||||||
assert_eq!(String::from_utf8(writer).unwrap(),
|
assert_eq!(
|
||||||
r#"<?xml version="1.0" encoding="utf-8"?><root><3</root>"#
|
String::from_utf8(writer).unwrap(),
|
||||||
|
r#"<?xml version="1.0" encoding="utf-8"?><root><3</root>"#
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn builder_works() {
|
fn builder_works() {
|
||||||
let elem = Element::builder("a")
|
let elem = Element::builder("a")
|
||||||
.ns("b")
|
.ns("b")
|
||||||
.attr("c", "d")
|
.attr("c", "d")
|
||||||
.append(Element::builder("child"))
|
.append(Element::builder("child"))
|
||||||
.append("e")
|
.append("e")
|
||||||
.build();
|
.build();
|
||||||
assert_eq!(elem.name(), "a");
|
assert_eq!(elem.name(), "a");
|
||||||
assert_eq!(elem.ns(), Some("b".to_owned()));
|
assert_eq!(elem.ns(), Some("b".to_owned()));
|
||||||
assert_eq!(elem.attr("c"), Some("d"));
|
assert_eq!(elem.attr("c"), Some("d"));
|
||||||
|
|
@ -115,10 +114,22 @@ fn get_child_works() {
|
||||||
let root = build_test_tree();
|
let root = build_test_tree();
|
||||||
assert_eq!(root.get_child("child", "inexistent_ns"), None);
|
assert_eq!(root.get_child("child", "inexistent_ns"), None);
|
||||||
assert_eq!(root.get_child("not_a_child", "root_ns"), None);
|
assert_eq!(root.get_child("not_a_child", "root_ns"), None);
|
||||||
assert!(root.get_child("child", "root_ns").unwrap().is("child", "root_ns"));
|
assert!(root
|
||||||
assert!(root.get_child("child", "child_ns").unwrap().is("child", "child_ns"));
|
.get_child("child", "root_ns")
|
||||||
assert_eq!(root.get_child("child", "root_ns").unwrap().attr("c"), Some("d"));
|
.unwrap()
|
||||||
assert_eq!(root.get_child("child", "child_ns").unwrap().attr("d"), Some("e"));
|
.is("child", "root_ns"));
|
||||||
|
assert!(root
|
||||||
|
.get_child("child", "child_ns")
|
||||||
|
.unwrap()
|
||||||
|
.is("child", "child_ns"));
|
||||||
|
assert_eq!(
|
||||||
|
root.get_child("child", "root_ns").unwrap().attr("c"),
|
||||||
|
Some("d")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
root.get_child("child", "child_ns").unwrap().attr("d"),
|
||||||
|
Some("e")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -130,9 +141,14 @@ fn namespace_propagation_works() {
|
||||||
root.append_child(child);
|
root.append_child(child);
|
||||||
|
|
||||||
assert_eq!(root.get_child("child", "root_ns").unwrap().ns(), root.ns());
|
assert_eq!(root.get_child("child", "root_ns").unwrap().ns(), root.ns());
|
||||||
assert_eq!(root.get_child("child", "root_ns").unwrap()
|
assert_eq!(
|
||||||
.get_child("grandchild", "root_ns").unwrap()
|
root.get_child("child", "root_ns")
|
||||||
.ns(), root.ns());
|
.unwrap()
|
||||||
|
.get_child("grandchild", "root_ns")
|
||||||
|
.unwrap()
|
||||||
|
.ns(),
|
||||||
|
root.ns()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -151,7 +167,13 @@ fn namespace_attributes_works() {
|
||||||
let mut reader = Reader::from_str(TEST_STRING);
|
let mut reader = Reader::from_str(TEST_STRING);
|
||||||
let root = Element::from_reader(&mut reader).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!("fr", root.get_child("child", "child_ns").unwrap().attr("xml:lang").unwrap());
|
assert_eq!(
|
||||||
|
"fr",
|
||||||
|
root.get_child("child", "child_ns")
|
||||||
|
.unwrap()
|
||||||
|
.attr("xml:lang")
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -174,14 +196,20 @@ fn namespace_simple() {
|
||||||
#[test]
|
#[test]
|
||||||
fn namespace_prefixed() {
|
fn namespace_prefixed() {
|
||||||
let elem: Element = "<stream:features xmlns:stream='http://etherx.jabber.org/streams'/>"
|
let elem: Element = "<stream:features xmlns:stream='http://etherx.jabber.org/streams'/>"
|
||||||
.parse().unwrap();
|
.parse()
|
||||||
|
.unwrap();
|
||||||
assert_eq!(elem.name(), "features");
|
assert_eq!(elem.name(), "features");
|
||||||
assert_eq!(elem.ns(), Some("http://etherx.jabber.org/streams".to_owned()));
|
assert_eq!(
|
||||||
|
elem.ns(),
|
||||||
|
Some("http://etherx.jabber.org/streams".to_owned())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn namespace_inherited_simple() {
|
fn namespace_inherited_simple() {
|
||||||
let elem: Element = "<stream xmlns='jabber:client'><message/></stream>".parse().unwrap();
|
let elem: Element = "<stream xmlns='jabber:client'><message/></stream>"
|
||||||
|
.parse()
|
||||||
|
.unwrap();
|
||||||
assert_eq!(elem.name(), "stream");
|
assert_eq!(elem.name(), "stream");
|
||||||
assert_eq!(elem.ns(), Some("jabber:client".to_owned()));
|
assert_eq!(elem.ns(), Some("jabber:client".to_owned()));
|
||||||
let child = elem.children().next().unwrap();
|
let child = elem.children().next().unwrap();
|
||||||
|
|
@ -194,7 +222,10 @@ fn namespace_inherited_prefixed1() {
|
||||||
let elem: Element = "<stream:features xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:client'><message/></stream:features>"
|
let elem: Element = "<stream:features xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:client'><message/></stream:features>"
|
||||||
.parse().unwrap();
|
.parse().unwrap();
|
||||||
assert_eq!(elem.name(), "features");
|
assert_eq!(elem.name(), "features");
|
||||||
assert_eq!(elem.ns(), Some("http://etherx.jabber.org/streams".to_owned()));
|
assert_eq!(
|
||||||
|
elem.ns(),
|
||||||
|
Some("http://etherx.jabber.org/streams".to_owned())
|
||||||
|
);
|
||||||
let child = elem.children().next().unwrap();
|
let child = elem.children().next().unwrap();
|
||||||
assert_eq!(child.name(), "message");
|
assert_eq!(child.name(), "message");
|
||||||
assert_eq!(child.ns(), Some("jabber:client".to_owned()));
|
assert_eq!(child.ns(), Some("jabber:client".to_owned()));
|
||||||
|
|
@ -205,7 +236,10 @@ fn namespace_inherited_prefixed2() {
|
||||||
let elem: Element = "<stream xmlns='http://etherx.jabber.org/streams' xmlns:jabber='jabber:client'><jabber:message/></stream>"
|
let elem: Element = "<stream xmlns='http://etherx.jabber.org/streams' xmlns:jabber='jabber:client'><jabber:message/></stream>"
|
||||||
.parse().unwrap();
|
.parse().unwrap();
|
||||||
assert_eq!(elem.name(), "stream");
|
assert_eq!(elem.name(), "stream");
|
||||||
assert_eq!(elem.ns(), Some("http://etherx.jabber.org/streams".to_owned()));
|
assert_eq!(
|
||||||
|
elem.ns(),
|
||||||
|
Some("http://etherx.jabber.org/streams".to_owned())
|
||||||
|
);
|
||||||
let child = elem.children().next().unwrap();
|
let child = elem.children().next().unwrap();
|
||||||
assert_eq!(child.name(), "message");
|
assert_eq!(child.name(), "message");
|
||||||
assert_eq!(child.ns(), Some("jabber:client".to_owned()));
|
assert_eq!(child.ns(), Some("jabber:client".to_owned()));
|
||||||
|
|
@ -215,7 +249,10 @@ fn namespace_inherited_prefixed2() {
|
||||||
#[test]
|
#[test]
|
||||||
fn read_comments() {
|
fn read_comments() {
|
||||||
let mut reader = Reader::from_str(COMMENT_TEST_STRING);
|
let mut reader = Reader::from_str(COMMENT_TEST_STRING);
|
||||||
assert_eq!(Element::from_reader(&mut reader).unwrap(), build_comment_test_tree());
|
assert_eq!(
|
||||||
|
Element::from_reader(&mut reader).unwrap(),
|
||||||
|
build_comment_test_tree()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "comments")]
|
#[cfg(feature = "comments")]
|
||||||
|
|
@ -233,12 +270,12 @@ fn write_comments() {
|
||||||
fn xml_error() {
|
fn xml_error() {
|
||||||
match "<a></b>".parse::<Element>() {
|
match "<a></b>".parse::<Element>() {
|
||||||
Err(crate::error::Error::XmlError(_)) => (),
|
Err(crate::error::Error::XmlError(_)) => (),
|
||||||
err => panic!("No or wrong error: {:?}", err)
|
err => panic!("No or wrong error: {:?}", err),
|
||||||
}
|
}
|
||||||
|
|
||||||
match "<a></".parse::<Element>() {
|
match "<a></".parse::<Element>() {
|
||||||
Err(crate::error::Error::XmlError(_)) => (),
|
Err(crate::error::Error::XmlError(_)) => (),
|
||||||
err => panic!("No or wrong error: {:?}", err)
|
err => panic!("No or wrong error: {:?}", err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -246,6 +283,6 @@ fn xml_error() {
|
||||||
fn invalid_element_error() {
|
fn invalid_element_error() {
|
||||||
match "<a:b:c>".parse::<Element>() {
|
match "<a:b:c>".parse::<Element>() {
|
||||||
Err(crate::error::Error::InvalidElement) => (),
|
Err(crate::error::Error::InvalidElement) => (),
|
||||||
err => panic!("No or wrong error: {:?}", err)
|
err => panic!("No or wrong error: {:?}", err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,20 +3,13 @@ use std::convert::TryFrom;
|
||||||
use std::env::args;
|
use std::env::args;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use tokio::runtime::current_thread::Runtime;
|
use tokio::runtime::current_thread::Runtime;
|
||||||
use tokio_xmpp::{Client, xmpp_codec::Packet};
|
use tokio_xmpp::{xmpp_codec::Packet, Client};
|
||||||
use xmpp_parsers::{
|
use xmpp_parsers::{
|
||||||
Element,
|
disco::{DiscoInfoQuery, DiscoInfoResult},
|
||||||
Jid,
|
iq::{Iq, IqType},
|
||||||
ns,
|
ns,
|
||||||
iq::{
|
|
||||||
Iq,
|
|
||||||
IqType,
|
|
||||||
},
|
|
||||||
disco::{
|
|
||||||
DiscoInfoResult,
|
|
||||||
DiscoInfoQuery,
|
|
||||||
},
|
|
||||||
server_info::ServerInfo,
|
server_info::ServerInfo,
|
||||||
|
Element, Jid,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
@ -95,17 +88,19 @@ fn make_disco_iq(target: Jid) -> Element {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_field(field: Vec<String>) -> String {
|
fn convert_field(field: Vec<String>) -> String {
|
||||||
field.iter()
|
field
|
||||||
.fold((field.len(), String::new()), |(l, mut acc), s| {
|
.iter()
|
||||||
acc.push('<');
|
.fold((field.len(), String::new()), |(l, mut acc), s| {
|
||||||
acc.push_str(&s);
|
acc.push('<');
|
||||||
acc.push('>');
|
acc.push_str(&s);
|
||||||
if l > 1 {
|
acc.push('>');
|
||||||
acc.push(',');
|
if l > 1 {
|
||||||
acc.push(' ');
|
acc.push(',');
|
||||||
}
|
acc.push(' ');
|
||||||
(0, acc)
|
}
|
||||||
}).1
|
(0, acc)
|
||||||
|
})
|
||||||
|
.1
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_server_info(server_info: ServerInfo) {
|
fn print_server_info(server_info: ServerInfo) {
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ use xmpp_parsers::{
|
||||||
pubsub::{Items, PubSub},
|
pubsub::{Items, PubSub},
|
||||||
NodeName,
|
NodeName,
|
||||||
},
|
},
|
||||||
stanza_error::{StanzaError, ErrorType, DefinedCondition},
|
stanza_error::{DefinedCondition, ErrorType, StanzaError},
|
||||||
Jid,
|
Jid,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -46,16 +46,14 @@ fn main() {
|
||||||
// Create outgoing pipe
|
// Create outgoing pipe
|
||||||
let (mut tx, rx) = futures::unsync::mpsc::unbounded();
|
let (mut tx, rx) = futures::unsync::mpsc::unbounded();
|
||||||
rt.spawn(
|
rt.spawn(
|
||||||
rx.forward(
|
rx.forward(sink.sink_map_err(|_| panic!("Pipe")))
|
||||||
sink.sink_map_err(|_| panic!("Pipe"))
|
|
||||||
)
|
|
||||||
.map(|(rx, mut sink)| {
|
.map(|(rx, mut sink)| {
|
||||||
drop(rx);
|
drop(rx);
|
||||||
let _ = sink.close();
|
let _ = sink.close();
|
||||||
})
|
})
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
panic!("Send error: {:?}", e);
|
panic!("Send error: {:?}", e);
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
let disco_info = make_disco();
|
let disco_info = make_disco();
|
||||||
|
|
@ -66,8 +64,7 @@ fn main() {
|
||||||
// Helper function to send an iq error.
|
// Helper function to send an iq error.
|
||||||
let mut send_error = |to, id, type_, condition, text: &str| {
|
let mut send_error = |to, id, type_, condition, text: &str| {
|
||||||
let error = StanzaError::new(type_, condition, "en", text);
|
let error = StanzaError::new(type_, condition, "en", text);
|
||||||
let iq = Iq::from_error(id, error)
|
let iq = Iq::from_error(id, error).with_to(to);
|
||||||
.with_to(to);
|
|
||||||
tx.start_send(Packet::Stanza(iq.into())).unwrap();
|
tx.start_send(Packet::Stanza(iq.into())).unwrap();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -89,28 +86,45 @@ fn main() {
|
||||||
Ok(query) => {
|
Ok(query) => {
|
||||||
let mut disco = disco_info.clone();
|
let mut disco = disco_info.clone();
|
||||||
disco.node = query.node;
|
disco.node = query.node;
|
||||||
let iq = Iq::from_result(iq.id, Some(disco))
|
let iq =
|
||||||
.with_to(iq.from.unwrap());
|
Iq::from_result(iq.id, Some(disco)).with_to(iq.from.unwrap());
|
||||||
tx.start_send(Packet::Stanza(iq.into())).unwrap();
|
tx.start_send(Packet::Stanza(iq.into())).unwrap();
|
||||||
},
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
send_error(iq.from.unwrap(), iq.id, ErrorType::Modify, DefinedCondition::BadRequest, &format!("{}", err));
|
send_error(
|
||||||
},
|
iq.from.unwrap(),
|
||||||
|
iq.id,
|
||||||
|
ErrorType::Modify,
|
||||||
|
DefinedCondition::BadRequest,
|
||||||
|
&format!("{}", err),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// We MUST answer unhandled get iqs with a service-unavailable error.
|
// We MUST answer unhandled get iqs with a service-unavailable error.
|
||||||
send_error(iq.from.unwrap(), iq.id, ErrorType::Cancel, DefinedCondition::ServiceUnavailable, "No handler defined for this kind of iq.");
|
send_error(
|
||||||
|
iq.from.unwrap(),
|
||||||
|
iq.id,
|
||||||
|
ErrorType::Cancel,
|
||||||
|
DefinedCondition::ServiceUnavailable,
|
||||||
|
"No handler defined for this kind of iq.",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else if let IqType::Result(Some(payload)) = iq.payload {
|
} else if let IqType::Result(Some(payload)) = iq.payload {
|
||||||
if payload.is("pubsub", ns::PUBSUB) {
|
if payload.is("pubsub", ns::PUBSUB) {
|
||||||
let pubsub = PubSub::try_from(payload).unwrap();
|
let pubsub = PubSub::try_from(payload).unwrap();
|
||||||
let from =
|
let from = iq.from.clone().unwrap_or(Jid::from_str(jid).unwrap());
|
||||||
iq.from.clone().unwrap_or(Jid::from_str(jid).unwrap());
|
|
||||||
handle_iq_result(pubsub, &from);
|
handle_iq_result(pubsub, &from);
|
||||||
}
|
}
|
||||||
} else if let IqType::Set(_) = iq.payload {
|
} else if let IqType::Set(_) = iq.payload {
|
||||||
// We MUST answer unhandled set iqs with a service-unavailable error.
|
// We MUST answer unhandled set iqs with a service-unavailable error.
|
||||||
send_error(iq.from.unwrap(), iq.id, ErrorType::Cancel, DefinedCondition::ServiceUnavailable, "No handler defined for this kind of iq.");
|
send_error(
|
||||||
|
iq.from.unwrap(),
|
||||||
|
iq.id,
|
||||||
|
ErrorType::Cancel,
|
||||||
|
DefinedCondition::ServiceUnavailable,
|
||||||
|
"No handler defined for this kind of iq.",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else if stanza.is("message", "jabber:client") {
|
} else if stanza.is("message", "jabber:client") {
|
||||||
let message = Message::try_from(stanza).unwrap();
|
let message = Message::try_from(stanza).unwrap();
|
||||||
|
|
@ -186,20 +200,22 @@ fn get_disco_caps(disco: &DiscoInfoResult, node: &str) -> Caps {
|
||||||
|
|
||||||
// Construct a <presence/>
|
// Construct a <presence/>
|
||||||
fn make_presence(caps: Caps) -> Presence {
|
fn make_presence(caps: Caps) -> Presence {
|
||||||
let mut presence = Presence::new(PresenceType::None)
|
let mut presence = Presence::new(PresenceType::None).with_priority(-1);
|
||||||
.with_priority(-1);
|
|
||||||
presence.set_status("en", "Downloading avatars.");
|
presence.set_status("en", "Downloading avatars.");
|
||||||
presence.add_payload(caps);
|
presence.add_payload(caps);
|
||||||
presence
|
presence
|
||||||
}
|
}
|
||||||
|
|
||||||
fn download_avatar(from: Jid) -> Iq {
|
fn download_avatar(from: Jid) -> Iq {
|
||||||
Iq::from_get("coucou", PubSub::Items(Items {
|
Iq::from_get(
|
||||||
max_items: None,
|
"coucou",
|
||||||
node: NodeName(String::from(ns::AVATAR_DATA)),
|
PubSub::Items(Items {
|
||||||
subid: None,
|
max_items: None,
|
||||||
items: Vec::new(),
|
node: NodeName(String::from(ns::AVATAR_DATA)),
|
||||||
}))
|
subid: None,
|
||||||
|
items: Vec::new(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
.with_to(from)
|
.with_to(from)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -222,10 +238,7 @@ fn handle_iq_result(pubsub: PubSub, from: &Jid) {
|
||||||
fn save_avatar(from: &Jid, id: String, data: &[u8]) -> io::Result<()> {
|
fn save_avatar(from: &Jid, id: String, data: &[u8]) -> io::Result<()> {
|
||||||
let directory = format!("data/{}", from);
|
let directory = format!("data/{}", from);
|
||||||
let filename = format!("data/{}/{}", from, id);
|
let filename = format!("data/{}/{}", from, id);
|
||||||
println!(
|
println!("Saving avatar from [1m{}[0m to [4m{}[0m.", from, filename);
|
||||||
"Saving avatar from [1m{}[0m to [4m{}[0m.",
|
|
||||||
from, filename
|
|
||||||
);
|
|
||||||
create_dir_all(directory)?;
|
create_dir_all(directory)?;
|
||||||
let mut file = File::create(filename)?;
|
let mut file = File::create(filename)?;
|
||||||
file.write_all(data)
|
file.write_all(data)
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@ use std::env::args;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use tokio::runtime::current_thread::Runtime;
|
use tokio::runtime::current_thread::Runtime;
|
||||||
use tokio_xmpp::{Client, Packet};
|
use tokio_xmpp::{Client, Packet};
|
||||||
use xmpp_parsers::{Jid, Element};
|
|
||||||
use xmpp_parsers::message::{Body, Message, MessageType};
|
use xmpp_parsers::message::{Body, Message, MessageType};
|
||||||
use xmpp_parsers::presence::{Presence, Show as PresenceShow, Type as PresenceType};
|
use xmpp_parsers::presence::{Presence, Show as PresenceShow, Type as PresenceType};
|
||||||
|
use xmpp_parsers::{Element, Jid};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let args: Vec<String> = args().collect();
|
let args: Vec<String> = args().collect();
|
||||||
|
|
@ -29,16 +29,14 @@ fn main() {
|
||||||
// Create outgoing pipe
|
// Create outgoing pipe
|
||||||
let (mut tx, rx) = futures::unsync::mpsc::unbounded();
|
let (mut tx, rx) = futures::unsync::mpsc::unbounded();
|
||||||
rt.spawn(
|
rt.spawn(
|
||||||
rx.forward(
|
rx.forward(sink.sink_map_err(|_| panic!("Pipe")))
|
||||||
sink.sink_map_err(|_| panic!("Pipe"))
|
|
||||||
)
|
|
||||||
.map(|(rx, mut sink)| {
|
.map(|(rx, mut sink)| {
|
||||||
drop(rx);
|
drop(rx);
|
||||||
let _ = sink.close();
|
let _ = sink.close();
|
||||||
})
|
})
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
panic!("Send error: {:?}", e);
|
panic!("Send error: {:?}", e);
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Main loop, processes events
|
// Main loop, processes events
|
||||||
|
|
@ -47,7 +45,8 @@ fn main() {
|
||||||
if wait_for_stream_end {
|
if wait_for_stream_end {
|
||||||
/* Do nothing */
|
/* Do nothing */
|
||||||
} else if event.is_online() {
|
} else if event.is_online() {
|
||||||
let jid = event.get_jid()
|
let jid = event
|
||||||
|
.get_jid()
|
||||||
.map(|jid| format!("{}", jid))
|
.map(|jid| format!("{}", jid))
|
||||||
.unwrap_or("unknown".to_owned());
|
.unwrap_or("unknown".to_owned());
|
||||||
println!("Online at {}", jid);
|
println!("Online at {}", jid);
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@ use std::process::exit;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use tokio::runtime::current_thread::Runtime;
|
use tokio::runtime::current_thread::Runtime;
|
||||||
use tokio_xmpp::Component;
|
use tokio_xmpp::Component;
|
||||||
use xmpp_parsers::{Jid, Element};
|
|
||||||
use xmpp_parsers::message::{Body, Message, MessageType};
|
use xmpp_parsers::message::{Body, Message, MessageType};
|
||||||
use xmpp_parsers::presence::{Presence, Show as PresenceShow, Type as PresenceType};
|
use xmpp_parsers::presence::{Presence, Show as PresenceShow, Type as PresenceType};
|
||||||
|
use xmpp_parsers::{Element, Jid};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let args: Vec<String> = args().collect();
|
let args: Vec<String> = args().collect();
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
use std::str::FromStr;
|
use futures::{
|
||||||
use std::collections::HashSet;
|
future::{err, ok, IntoFuture},
|
||||||
use std::convert::TryFrom;
|
Future, Poll, Stream,
|
||||||
use futures::{Future, Poll, Stream, future::{ok, err, IntoFuture}};
|
};
|
||||||
use sasl::client::mechanisms::{Anonymous, Plain, Scram};
|
use sasl::client::mechanisms::{Anonymous, Plain, Scram};
|
||||||
use sasl::client::Mechanism;
|
use sasl::client::Mechanism;
|
||||||
use sasl::common::scram::{Sha1, Sha256};
|
use sasl::common::scram::{Sha1, Sha256};
|
||||||
use sasl::common::Credentials;
|
use sasl::common::Credentials;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::str::FromStr;
|
||||||
use tokio_io::{AsyncRead, AsyncWrite};
|
use tokio_io::{AsyncRead, AsyncWrite};
|
||||||
use xmpp_parsers::sasl::{Auth, Challenge, Failure, Mechanism as XMPPMechanism, Response, Success};
|
use xmpp_parsers::sasl::{Auth, Challenge, Failure, Mechanism as XMPPMechanism, Response, Success};
|
||||||
|
|
||||||
|
|
@ -41,67 +44,74 @@ impl<S: AsyncRead + AsyncWrite + 'static> ClientAuth<S> {
|
||||||
let mut mechanism = local_mech();
|
let mut mechanism = local_mech();
|
||||||
if remote_mechs.contains(mechanism.name()) {
|
if remote_mechs.contains(mechanism.name()) {
|
||||||
let initial = mechanism.initial().map_err(AuthError::Sasl)?;
|
let initial = mechanism.initial().map_err(AuthError::Sasl)?;
|
||||||
let mechanism_name = XMPPMechanism::from_str(mechanism.name()).map_err(ProtocolError::Parsers)?;
|
let mechanism_name =
|
||||||
|
XMPPMechanism::from_str(mechanism.name()).map_err(ProtocolError::Parsers)?;
|
||||||
|
|
||||||
let send_initial = Box::new(stream.send_stanza(Auth {
|
let send_initial = Box::new(stream.send_stanza(Auth {
|
||||||
mechanism: mechanism_name,
|
mechanism: mechanism_name,
|
||||||
data: initial,
|
data: initial,
|
||||||
}))
|
}))
|
||||||
.map_err(Error::Io);
|
.map_err(Error::Io);
|
||||||
let future = Box::new(send_initial.and_then(
|
let future = Box::new(
|
||||||
|stream| Self::handle_challenge(stream, mechanism)
|
send_initial
|
||||||
).and_then(
|
.and_then(|stream| Self::handle_challenge(stream, mechanism))
|
||||||
|stream| stream.restart()
|
.and_then(|stream| stream.restart()),
|
||||||
));
|
);
|
||||||
return Ok(ClientAuth {
|
return Ok(ClientAuth { future });
|
||||||
future,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(AuthError::NoMechanism)?
|
Err(AuthError::NoMechanism)?
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_challenge(stream: XMPPStream<S>, mut mechanism: Box<dyn Mechanism>) -> Box<dyn Future<Item = XMPPStream<S>, Error = Error>> {
|
fn handle_challenge(
|
||||||
|
stream: XMPPStream<S>,
|
||||||
|
mut mechanism: Box<dyn Mechanism>,
|
||||||
|
) -> Box<dyn Future<Item = XMPPStream<S>, Error = Error>> {
|
||||||
Box::new(
|
Box::new(
|
||||||
stream.into_future()
|
stream
|
||||||
.map_err(|(e, _stream)| e.into())
|
.into_future()
|
||||||
.and_then(|(stanza, stream)| {
|
.map_err(|(e, _stream)| e.into())
|
||||||
match stanza {
|
.and_then(|(stanza, stream)| {
|
||||||
Some(Packet::Stanza(stanza)) => {
|
match stanza {
|
||||||
if let Ok(challenge) = Challenge::try_from(stanza.clone()) {
|
Some(Packet::Stanza(stanza)) => {
|
||||||
let response = mechanism
|
if let Ok(challenge) = Challenge::try_from(stanza.clone()) {
|
||||||
.response(&challenge.data);
|
let response = mechanism.response(&challenge.data);
|
||||||
Box::new(
|
Box::new(
|
||||||
response
|
response
|
||||||
.map_err(|e| AuthError::Sasl(e).into())
|
.map_err(|e| AuthError::Sasl(e).into())
|
||||||
.into_future()
|
.into_future()
|
||||||
.and_then(|response| {
|
.and_then(|response| {
|
||||||
// Send response and loop
|
// Send response and loop
|
||||||
stream.send_stanza(Response { data: response })
|
stream
|
||||||
.map_err(Error::Io)
|
.send_stanza(Response { data: response })
|
||||||
.and_then(|stream| Self::handle_challenge(stream, mechanism))
|
.map_err(Error::Io)
|
||||||
})
|
.and_then(|stream| {
|
||||||
)
|
Self::handle_challenge(stream, mechanism)
|
||||||
} else if let Ok(_) = Success::try_from(stanza.clone()) {
|
})
|
||||||
Box::new(ok(stream))
|
}),
|
||||||
} else if let Ok(failure) = Failure::try_from(stanza.clone()) {
|
)
|
||||||
Box::new(err(Error::Auth(AuthError::Fail(failure.defined_condition))))
|
} else if let Ok(_) = Success::try_from(stanza.clone()) {
|
||||||
} else if stanza.name() == "failure" {
|
Box::new(ok(stream))
|
||||||
// Workaround for https://gitlab.com/xmpp-rs/xmpp-parsers/merge_requests/1
|
} else if let Ok(failure) = Failure::try_from(stanza.clone()) {
|
||||||
Box::new(err(Error::Auth(AuthError::Sasl("failure".to_string()))))
|
Box::new(err(Error::Auth(AuthError::Fail(
|
||||||
} else {
|
failure.defined_condition,
|
||||||
|
))))
|
||||||
|
} else if stanza.name() == "failure" {
|
||||||
|
// Workaround for https://gitlab.com/xmpp-rs/xmpp-parsers/merge_requests/1
|
||||||
|
Box::new(err(Error::Auth(AuthError::Sasl("failure".to_string()))))
|
||||||
|
} else {
|
||||||
|
// ignore and loop
|
||||||
|
Self::handle_challenge(stream, mechanism)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(_) => {
|
||||||
// ignore and loop
|
// ignore and loop
|
||||||
Self::handle_challenge(stream, mechanism)
|
Self::handle_challenge(stream, mechanism)
|
||||||
}
|
}
|
||||||
|
None => Box::new(err(Error::Disconnected)),
|
||||||
}
|
}
|
||||||
Some(_) => {
|
}),
|
||||||
// ignore and loop
|
|
||||||
Self::handle_challenge(stream, mechanism)
|
|
||||||
}
|
|
||||||
None => Box::new(err(Error::Disconnected))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@ use futures::{sink, Async, Future, Poll, Stream};
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::mem::replace;
|
use std::mem::replace;
|
||||||
use tokio_io::{AsyncRead, AsyncWrite};
|
use tokio_io::{AsyncRead, AsyncWrite};
|
||||||
use xmpp_parsers::Jid;
|
|
||||||
use xmpp_parsers::bind::{BindQuery, BindResponse};
|
use xmpp_parsers::bind::{BindQuery, BindResponse};
|
||||||
use xmpp_parsers::iq::{Iq, IqType};
|
use xmpp_parsers::iq::{Iq, IqType};
|
||||||
|
use xmpp_parsers::Jid;
|
||||||
|
|
||||||
use crate::xmpp_codec::Packet;
|
use crate::xmpp_codec::Packet;
|
||||||
use crate::xmpp_stream::XMPPStream;
|
use crate::xmpp_stream::XMPPStream;
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
use futures::{done, Async, AsyncSink, Future, Poll, Sink, StartSend, Stream};
|
use futures::{done, Async, AsyncSink, Future, Poll, Sink, StartSend, Stream};
|
||||||
use idna;
|
use idna;
|
||||||
use xmpp_parsers::{Jid, JidParseError};
|
|
||||||
use sasl::common::{ChannelBinding, Credentials};
|
use sasl::common::{ChannelBinding, Credentials};
|
||||||
use std::mem::replace;
|
use std::mem::replace;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
use tokio_io::{AsyncRead, AsyncWrite};
|
use tokio_io::{AsyncRead, AsyncWrite};
|
||||||
use tokio_tls::TlsStream;
|
use tokio_tls::TlsStream;
|
||||||
|
use xmpp_parsers::{Jid, JidParseError};
|
||||||
|
|
||||||
use super::event::Event;
|
use super::event::Event;
|
||||||
use super::happy_eyeballs::Connecter;
|
use super::happy_eyeballs::Connecter;
|
||||||
|
|
@ -205,10 +205,8 @@ impl Sink for Client {
|
||||||
|
|
||||||
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> {
|
||||||
match self.state {
|
match self.state {
|
||||||
ClientState::Connected(ref mut stream) =>
|
ClientState::Connected(ref mut stream) => Ok(stream.start_send(item)?),
|
||||||
Ok(stream.start_send(item)?),
|
_ => Ok(AsyncSink::NotReady(item)),
|
||||||
_ =>
|
|
||||||
Ok(AsyncSink::NotReady(item)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -226,11 +224,8 @@ impl Sink for Client {
|
||||||
/// incoming stream before closing the connection.
|
/// incoming stream before closing the connection.
|
||||||
fn close(&mut self) -> Poll<(), Self::SinkError> {
|
fn close(&mut self) -> Poll<(), Self::SinkError> {
|
||||||
match self.state {
|
match self.state {
|
||||||
ClientState::Connected(ref mut stream) =>
|
ClientState::Connected(ref mut stream) => stream.close().map_err(|e| e.into()),
|
||||||
stream.close()
|
_ => Ok(Async::Ready(())),
|
||||||
.map_err(|e| e.into()),
|
|
||||||
_ =>
|
|
||||||
Ok(Async::Ready(())),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,11 @@
|
||||||
//! XMPP server under a JID consisting of just a domain name. They are
|
//! XMPP server under a JID consisting of just a domain name. They are
|
||||||
//! allowed to use any user and resource identifiers in their stanzas.
|
//! allowed to use any user and resource identifiers in their stanzas.
|
||||||
use futures::{done, Async, AsyncSink, Future, Poll, Sink, StartSend, Stream};
|
use futures::{done, Async, AsyncSink, Future, Poll, Sink, StartSend, Stream};
|
||||||
use xmpp_parsers::{Jid, JidParseError, Element};
|
|
||||||
use std::mem::replace;
|
use std::mem::replace;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
use tokio_io::{AsyncRead, AsyncWrite};
|
use tokio_io::{AsyncRead, AsyncWrite};
|
||||||
|
use xmpp_parsers::{Element, Jid, JidParseError};
|
||||||
|
|
||||||
use super::event::Event;
|
use super::event::Event;
|
||||||
use super::happy_eyeballs::Connecter;
|
use super::happy_eyeballs::Connecter;
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ use std::str::Utf8Error;
|
||||||
use trust_dns_proto::error::ProtoError;
|
use trust_dns_proto::error::ProtoError;
|
||||||
use trust_dns_resolver::error::ResolveError;
|
use trust_dns_resolver::error::ResolveError;
|
||||||
|
|
||||||
use xmpp_parsers::Error as ParsersError;
|
|
||||||
use xmpp_parsers::sasl::DefinedCondition as SaslDefinedCondition;
|
use xmpp_parsers::sasl::DefinedCondition as SaslDefinedCondition;
|
||||||
|
use xmpp_parsers::Error as ParsersError;
|
||||||
|
|
||||||
/// Top-level error type
|
/// Top-level error type
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -159,8 +159,12 @@ impl fmt::Display for ProtocolError {
|
||||||
ProtocolError::Parser(e) => write!(fmt, "XML parser error: {}", e),
|
ProtocolError::Parser(e) => write!(fmt, "XML parser error: {}", e),
|
||||||
ProtocolError::Parsers(e) => write!(fmt, "error with expected stanza schema: {}", e),
|
ProtocolError::Parsers(e) => write!(fmt, "error with expected stanza schema: {}", e),
|
||||||
ProtocolError::NoTls => write!(fmt, "no TLS available"),
|
ProtocolError::NoTls => write!(fmt, "no TLS available"),
|
||||||
ProtocolError::InvalidBindResponse => write!(fmt, "invalid response to resource binding"),
|
ProtocolError::InvalidBindResponse => {
|
||||||
ProtocolError::NoStreamNamespace => write!(fmt, "no xmlns attribute in <stream:stream>"),
|
write!(fmt, "invalid response to resource binding")
|
||||||
|
}
|
||||||
|
ProtocolError::NoStreamNamespace => {
|
||||||
|
write!(fmt, "no xmlns attribute in <stream:stream>")
|
||||||
|
}
|
||||||
ProtocolError::NoStreamId => write!(fmt, "no id attribute in <stream:stream>"),
|
ProtocolError::NoStreamId => write!(fmt, "no id attribute in <stream:stream>"),
|
||||||
ProtocolError::InvalidToken => write!(fmt, "encountered an unexpected XML token"),
|
ProtocolError::InvalidToken => write!(fmt, "encountered an unexpected XML token"),
|
||||||
ProtocolError::InvalidStreamStart => write!(fmt, "unexpected <stream:stream>"),
|
ProtocolError::InvalidStreamStart => write!(fmt, "unexpected <stream:stream>"),
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,10 @@ use std::mem;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use tokio::net::tcp::ConnectFuture;
|
use tokio::net::tcp::ConnectFuture;
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
use trust_dns_resolver::{AsyncResolver, Name, IntoName, Background, BackgroundLookup};
|
|
||||||
use trust_dns_resolver::config::LookupIpStrategy;
|
use trust_dns_resolver::config::LookupIpStrategy;
|
||||||
use trust_dns_resolver::lookup::SrvLookupFuture;
|
use trust_dns_resolver::lookup::SrvLookupFuture;
|
||||||
use trust_dns_resolver::lookup_ip::LookupIpFuture;
|
use trust_dns_resolver::lookup_ip::LookupIpFuture;
|
||||||
|
use trust_dns_resolver::{AsyncResolver, Background, BackgroundLookup, IntoName, Name};
|
||||||
|
|
||||||
enum State {
|
enum State {
|
||||||
ResolveSrv(AsyncResolver, BackgroundLookup<SrvLookupFuture>),
|
ResolveSrv(AsyncResolver, BackgroundLookup<SrvLookupFuture>),
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
use futures::sink;
|
use futures::sink;
|
||||||
use futures::stream::Stream;
|
use futures::stream::Stream;
|
||||||
use futures::{Async, Future, Poll, Sink};
|
use futures::{Async, Future, Poll, Sink};
|
||||||
use xmpp_parsers::{Jid, Element};
|
|
||||||
use native_tls::TlsConnector as NativeTlsConnector;
|
use native_tls::TlsConnector as NativeTlsConnector;
|
||||||
use std::mem::replace;
|
use std::mem::replace;
|
||||||
use tokio_io::{AsyncRead, AsyncWrite};
|
use tokio_io::{AsyncRead, AsyncWrite};
|
||||||
use tokio_tls::{Connect, TlsConnector, TlsStream};
|
use tokio_tls::{Connect, TlsConnector, TlsStream};
|
||||||
|
use xmpp_parsers::{Element, Jid};
|
||||||
|
|
||||||
use crate::xmpp_codec::Packet;
|
use crate::xmpp_codec::Packet;
|
||||||
use crate::xmpp_stream::XMPPStream;
|
use crate::xmpp_stream::XMPPStream;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use futures::{sink, Async, Future, Poll, Sink, Stream};
|
use futures::{sink, Async, Future, Poll, Sink, Stream};
|
||||||
use xmpp_parsers::{Jid, Element};
|
|
||||||
use std::mem::replace;
|
use std::mem::replace;
|
||||||
use tokio_codec::Framed;
|
use tokio_codec::Framed;
|
||||||
use tokio_io::{AsyncRead, AsyncWrite};
|
use tokio_io::{AsyncRead, AsyncWrite};
|
||||||
|
use xmpp_parsers::{Element, Jid};
|
||||||
|
|
||||||
use crate::xmpp_codec::{Packet, XMPPCodec};
|
use crate::xmpp_codec::{Packet, XMPPCodec};
|
||||||
use crate::xmpp_stream::XMPPStream;
|
use crate::xmpp_stream::XMPPStream;
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
use crate::{ParseError, ParserError};
|
use crate::{ParseError, ParserError};
|
||||||
use bytes::{BufMut, BytesMut};
|
use bytes::{BufMut, BytesMut};
|
||||||
use xmpp_parsers::Element;
|
|
||||||
use quick_xml::Writer as EventWriter;
|
use quick_xml::Writer as EventWriter;
|
||||||
use std;
|
use std;
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::vec_deque::VecDeque;
|
use std::collections::vec_deque::VecDeque;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
@ -14,11 +14,11 @@ use std::io;
|
||||||
use std::iter::FromIterator;
|
use std::iter::FromIterator;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::str::from_utf8;
|
use std::str::from_utf8;
|
||||||
use std::borrow::Cow;
|
|
||||||
use tokio_codec::{Decoder, Encoder};
|
use tokio_codec::{Decoder, Encoder};
|
||||||
|
use xml5ever::buffer_queue::BufferQueue;
|
||||||
use xml5ever::interface::Attribute;
|
use xml5ever::interface::Attribute;
|
||||||
use xml5ever::tokenizer::{Tag, TagKind, Token, TokenSink, XmlTokenizer};
|
use xml5ever::tokenizer::{Tag, TagKind, Token, TokenSink, XmlTokenizer};
|
||||||
use xml5ever::buffer_queue::BufferQueue;
|
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
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
|
@ -288,21 +288,17 @@ impl Encoder for XMPPCodec {
|
||||||
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")
|
write!(buf, "<stream:stream").map_err(to_io_err)?;
|
||||||
.map_err(to_io_err)?;
|
|
||||||
for (name, value) in start_attrs {
|
for (name, value) in start_attrs {
|
||||||
write!(buf, " {}=\"{}\"", escape(&name), escape(&value))
|
write!(buf, " {}=\"{}\"", escape(&name), escape(&value)).map_err(to_io_err)?;
|
||||||
.map_err(to_io_err)?;
|
|
||||||
if name == "xmlns" {
|
if name == "xmlns" {
|
||||||
self.ns = Some(value);
|
self.ns = Some(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
write!(buf, ">\n")
|
write!(buf, ">\n").map_err(to_io_err)?;
|
||||||
.map_err(to_io_err)?;
|
|
||||||
|
|
||||||
// print!(">> {}", buf);
|
// print!(">> {}", buf);
|
||||||
write!(dst, "{}", buf)
|
write!(dst, "{}", buf).map_err(to_io_err)
|
||||||
.map_err(to_io_err)
|
|
||||||
}
|
}
|
||||||
Packet::Stanza(stanza) => {
|
Packet::Stanza(stanza) => {
|
||||||
stanza
|
stanza
|
||||||
|
|
@ -321,10 +317,7 @@ impl Encoder for XMPPCodec {
|
||||||
})
|
})
|
||||||
.map_err(to_io_err)
|
.map_err(to_io_err)
|
||||||
}
|
}
|
||||||
Packet::StreamEnd => {
|
Packet::StreamEnd => write!(dst, "</stream:stream>\n").map_err(to_io_err),
|
||||||
write!(dst, "</stream:stream>\n")
|
|
||||||
.map_err(to_io_err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -483,10 +476,13 @@ mod tests {
|
||||||
b.put(r"<status xml:lang='en'>Test status</status>");
|
b.put(r"<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 {
|
||||||
Ok(Some(Packet::Stanza(ref el))) if el.name() == "status" && el.text() == "Test status" && el.attr("xml:lang").map_or(false, |a| a == "en") => true,
|
Ok(Some(Packet::Stanza(ref el)))
|
||||||
|
if el.name() == "status"
|
||||||
|
&& el.text() == "Test status"
|
||||||
|
&& el.attr("xml:lang").map_or(false, |a| a == "en") =>
|
||||||
|
true,
|
||||||
_ => false,
|
_ => false,
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// By default, encode() only get's a BytesMut that has 8kb space reserved.
|
/// By default, encode() only get's a BytesMut that has 8kb space reserved.
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
use futures::sink::Send;
|
use futures::sink::Send;
|
||||||
use futures::{Poll, Sink, StartSend, Stream};
|
use futures::{Poll, Sink, StartSend, Stream};
|
||||||
use xmpp_parsers::{Jid, Element};
|
|
||||||
use tokio_codec::Framed;
|
use tokio_codec::Framed;
|
||||||
use tokio_io::{AsyncRead, AsyncWrite};
|
use tokio_io::{AsyncRead, AsyncWrite};
|
||||||
|
use xmpp_parsers::{Element, Jid};
|
||||||
|
|
||||||
use crate::stream_start::StreamStart;
|
use crate::stream_start::StreamStart;
|
||||||
use crate::xmpp_codec::{Packet, XMPPCodec};
|
use crate::xmpp_codec::{Packet, XMPPCodec};
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,14 @@
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::io::{self, Read};
|
|
||||||
use std::env;
|
use std::env;
|
||||||
|
use std::io::{self, Read};
|
||||||
use xmpp_parsers::{
|
use xmpp_parsers::{
|
||||||
Element,
|
caps::{compute_disco as compute_disco_caps, hash_caps, Caps},
|
||||||
disco::DiscoInfoResult,
|
disco::DiscoInfoResult,
|
||||||
caps::{Caps, compute_disco as compute_disco_caps, hash_caps},
|
ecaps2::{compute_disco as compute_disco_ecaps2, hash_ecaps2, ECaps2},
|
||||||
ecaps2::{ECaps2, compute_disco as compute_disco_ecaps2, hash_ecaps2},
|
|
||||||
hashes::Algo,
|
hashes::Algo,
|
||||||
|
Element,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn get_caps(disco: &DiscoInfoResult, node: String) -> Result<Caps, String> {
|
fn get_caps(disco: &DiscoInfoResult, node: String) -> Result<Caps, String> {
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,13 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use crate::util::error::Error;
|
|
||||||
use crate::iq::{IqResultPayload, IqSetPayload};
|
use crate::iq::{IqResultPayload, IqSetPayload};
|
||||||
use crate::ns;
|
use crate::ns;
|
||||||
use jid::{FullJid, Jid};
|
use crate::util::error::Error;
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
use std::str::FromStr;
|
use jid::{FullJid, Jid};
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
/// The request for resource binding, which is the process by which a client
|
/// The request for resource binding, which is the process by which a client
|
||||||
/// can obtain a full JID and start exchanging on the XMPP network.
|
/// can obtain a full JID and start exchanging on the XMPP network.
|
||||||
|
|
@ -63,10 +63,10 @@ impl From<BindQuery> for Element {
|
||||||
fn from(bind: BindQuery) -> Element {
|
fn from(bind: BindQuery) -> Element {
|
||||||
Element::builder("bind")
|
Element::builder("bind")
|
||||||
.ns(ns::BIND)
|
.ns(ns::BIND)
|
||||||
.append_all(bind.resource.map(|resource|
|
.append_all(
|
||||||
Element::builder("resource")
|
bind.resource
|
||||||
.ns(ns::BIND)
|
.map(|resource| Element::builder("resource").ns(ns::BIND).append(resource)),
|
||||||
.append(resource)))
|
)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -115,10 +115,16 @@ impl TryFrom<Element> for BindResponse {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(BindResponse { jid: match jid {
|
Ok(BindResponse {
|
||||||
None => return Err(Error::ParseError("Bind response must contain a jid element.")),
|
jid: match jid {
|
||||||
Some(jid) => jid,
|
None => {
|
||||||
} })
|
return Err(Error::ParseError(
|
||||||
|
"Bind response must contain a jid element.",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
Some(jid) => jid,
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -157,9 +163,10 @@ mod tests {
|
||||||
let bind = BindQuery::try_from(elem).unwrap();
|
let bind = BindQuery::try_from(elem).unwrap();
|
||||||
assert_eq!(bind.resource, None);
|
assert_eq!(bind.resource, None);
|
||||||
|
|
||||||
let elem: Element = "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource>Hello™</resource></bind>"
|
let elem: Element =
|
||||||
.parse()
|
"<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource>Hello™</resource></bind>"
|
||||||
.unwrap();
|
.parse()
|
||||||
|
.unwrap();
|
||||||
let bind = BindQuery::try_from(elem).unwrap();
|
let bind = BindQuery::try_from(elem).unwrap();
|
||||||
// FIXME: “™” should be resourceprep’d into “TM” here…
|
// FIXME: “™” should be resourceprep’d into “TM” here…
|
||||||
//assert_eq!(bind.resource.unwrap(), "HelloTM");
|
//assert_eq!(bind.resource.unwrap(), "HelloTM");
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,11 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use crate::util::error::Error;
|
|
||||||
use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload};
|
use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload};
|
||||||
use crate::ns;
|
use crate::ns;
|
||||||
use jid::Jid;
|
use crate::util::error::Error;
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
|
use jid::Jid;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
generate_empty_element!(
|
generate_empty_element!(
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use crate::hashes::{Hash, Algo};
|
use crate::hashes::{Algo, Hash};
|
||||||
use crate::util::helpers::Base64;
|
|
||||||
use crate::util::error::Error;
|
use crate::util::error::Error;
|
||||||
|
use crate::util::helpers::Base64;
|
||||||
use minidom::IntoAttributeValue;
|
use minidom::IntoAttributeValue;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
|
@ -27,11 +27,11 @@ impl FromStr for ContentId {
|
||||||
let temp: Vec<_> = match temp[..] {
|
let temp: Vec<_> = match temp[..] {
|
||||||
[lhs, rhs] => {
|
[lhs, rhs] => {
|
||||||
if rhs != "bob.xmpp.org" {
|
if rhs != "bob.xmpp.org" {
|
||||||
return Err(Error::ParseError("Wrong domain for cid URI."))
|
return Err(Error::ParseError("Wrong domain for cid URI."));
|
||||||
}
|
}
|
||||||
lhs.splitn(2, '+').collect()
|
lhs.splitn(2, '+').collect()
|
||||||
},
|
}
|
||||||
_ => return Err(Error::ParseError("Missing @ in cid URI."))
|
_ => return Err(Error::ParseError("Missing @ in cid URI.")),
|
||||||
};
|
};
|
||||||
let (algo, hex) = match temp[..] {
|
let (algo, hex) = match temp[..] {
|
||||||
[lhs, rhs] => {
|
[lhs, rhs] => {
|
||||||
|
|
@ -41,8 +41,8 @@ impl FromStr for ContentId {
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
};
|
};
|
||||||
(algo, rhs)
|
(algo, rhs)
|
||||||
},
|
}
|
||||||
_ => return Err(Error::ParseError("Missing + in cid URI."))
|
_ => return Err(Error::ParseError("Missing + in cid URI.")),
|
||||||
};
|
};
|
||||||
let hash = Hash::from_hex(algo, hex)?;
|
let hash = Hash::from_hex(algo, hex)?;
|
||||||
Ok(ContentId { hash })
|
Ok(ContentId { hash })
|
||||||
|
|
@ -108,15 +108,26 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_simple() {
|
fn test_simple() {
|
||||||
let cid: ContentId = "sha1+8f35fef110ffc5df08d579a50083ff9308fb6242@bob.xmpp.org".parse().unwrap();
|
let cid: ContentId = "sha1+8f35fef110ffc5df08d579a50083ff9308fb6242@bob.xmpp.org"
|
||||||
|
.parse()
|
||||||
|
.unwrap();
|
||||||
assert_eq!(cid.hash.algo, Algo::Sha_1);
|
assert_eq!(cid.hash.algo, Algo::Sha_1);
|
||||||
assert_eq!(cid.hash.hash, b"\x8f\x35\xfe\xf1\x10\xff\xc5\xdf\x08\xd5\x79\xa5\x00\x83\xff\x93\x08\xfb\x62\x42");
|
assert_eq!(
|
||||||
assert_eq!(cid.into_attribute_value().unwrap(), "sha1+8f35fef110ffc5df08d579a50083ff9308fb6242@bob.xmpp.org");
|
cid.hash.hash,
|
||||||
|
b"\x8f\x35\xfe\xf1\x10\xff\xc5\xdf\x08\xd5\x79\xa5\x00\x83\xff\x93\x08\xfb\x62\x42"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
cid.into_attribute_value().unwrap(),
|
||||||
|
"sha1+8f35fef110ffc5df08d579a50083ff9308fb6242@bob.xmpp.org"
|
||||||
|
);
|
||||||
|
|
||||||
let elem: Element = "<data xmlns='urn:xmpp:bob' cid='sha1+8f35fef110ffc5df08d579a50083ff9308fb6242@bob.xmpp.org'/>".parse().unwrap();
|
let elem: Element = "<data xmlns='urn:xmpp:bob' cid='sha1+8f35fef110ffc5df08d579a50083ff9308fb6242@bob.xmpp.org'/>".parse().unwrap();
|
||||||
let data = Data::try_from(elem).unwrap();
|
let data = Data::try_from(elem).unwrap();
|
||||||
assert_eq!(data.cid.hash.algo, Algo::Sha_1);
|
assert_eq!(data.cid.hash.algo, Algo::Sha_1);
|
||||||
assert_eq!(data.cid.hash.hash, b"\x8f\x35\xfe\xf1\x10\xff\xc5\xdf\x08\xd5\x79\xa5\x00\x83\xff\x93\x08\xfb\x62\x42");
|
assert_eq!(
|
||||||
|
data.cid.hash.hash,
|
||||||
|
b"\x8f\x35\xfe\xf1\x10\xff\xc5\xdf\x08\xd5\x79\xa5\x00\x83\xff\x93\x08\xfb\x62\x42"
|
||||||
|
);
|
||||||
assert!(data.max_age.is_none());
|
assert!(data.max_age.is_none());
|
||||||
assert!(data.type_.is_none());
|
assert!(data.type_.is_none());
|
||||||
assert!(data.data.is_empty());
|
assert!(data.data.is_empty());
|
||||||
|
|
@ -138,14 +149,18 @@ mod tests {
|
||||||
};
|
};
|
||||||
assert_eq!(message, "Missing + in cid URI.");
|
assert_eq!(message, "Missing + in cid URI.");
|
||||||
|
|
||||||
let error = "sha1+1234@coucou.linkmauve.fr".parse::<ContentId>().unwrap_err();
|
let error = "sha1+1234@coucou.linkmauve.fr"
|
||||||
|
.parse::<ContentId>()
|
||||||
|
.unwrap_err();
|
||||||
let message = match error {
|
let message = match error {
|
||||||
Error::ParseError(string) => string,
|
Error::ParseError(string) => string,
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
};
|
};
|
||||||
assert_eq!(message, "Wrong domain for cid URI.");
|
assert_eq!(message, "Wrong domain for cid URI.");
|
||||||
|
|
||||||
let error = "sha1+invalid@bob.xmpp.org".parse::<ContentId>().unwrap_err();
|
let error = "sha1+invalid@bob.xmpp.org"
|
||||||
|
.parse::<ContentId>()
|
||||||
|
.unwrap_err();
|
||||||
let message = match error {
|
let message = match error {
|
||||||
Error::ParseIntError(error) => error,
|
Error::ParseIntError(error) => error,
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
|
|
|
||||||
|
|
@ -45,12 +45,12 @@ impl Conference {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::ns;
|
||||||
|
use crate::pubsub::event::PubSubEvent;
|
||||||
|
use crate::pubsub::pubsub::Item as PubSubItem;
|
||||||
use crate::util::compare_elements::NamespaceAwareCompare;
|
use crate::util::compare_elements::NamespaceAwareCompare;
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use crate::pubsub::pubsub::Item as PubSubItem;
|
|
||||||
use crate::pubsub::event::PubSubEvent;
|
|
||||||
use crate::ns;
|
|
||||||
|
|
||||||
#[cfg(target_pointer_width = "32")]
|
#[cfg(target_pointer_width = "32")]
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -66,7 +66,9 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn simple() {
|
fn simple() {
|
||||||
let elem: Element = "<conference xmlns='urn:xmpp:bookmarks:0'/>".parse().unwrap();
|
let elem: Element = "<conference xmlns='urn:xmpp:bookmarks:0'/>"
|
||||||
|
.parse()
|
||||||
|
.unwrap();
|
||||||
let elem1 = elem.clone();
|
let elem1 = elem.clone();
|
||||||
let conference = Conference::try_from(elem).unwrap();
|
let conference = Conference::try_from(elem).unwrap();
|
||||||
assert_eq!(conference.autojoin, Autojoin::False);
|
assert_eq!(conference.autojoin, Autojoin::False);
|
||||||
|
|
@ -104,7 +106,7 @@ mod tests {
|
||||||
Ok(PubSubEvent::PublishedItems { node, items }) => {
|
Ok(PubSubEvent::PublishedItems { node, items }) => {
|
||||||
assert_eq!(&node.0, ns::BOOKMARKS2);
|
assert_eq!(&node.0, ns::BOOKMARKS2);
|
||||||
items
|
items
|
||||||
},
|
}
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
};
|
};
|
||||||
assert_eq!(items.len(), 1);
|
assert_eq!(items.len(), 1);
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,13 @@
|
||||||
|
|
||||||
use crate::data_forms::DataForm;
|
use crate::data_forms::DataForm;
|
||||||
use crate::disco::{DiscoInfoQuery, DiscoInfoResult, Feature, Identity};
|
use crate::disco::{DiscoInfoQuery, DiscoInfoResult, Feature, Identity};
|
||||||
use crate::util::error::Error;
|
|
||||||
use crate::hashes::{Algo, Hash};
|
use crate::hashes::{Algo, Hash};
|
||||||
use crate::ns;
|
use crate::ns;
|
||||||
use crate::presence::PresencePayload;
|
use crate::presence::PresencePayload;
|
||||||
|
use crate::util::error::Error;
|
||||||
|
use crate::Element;
|
||||||
use blake2::VarBlake2b;
|
use blake2::VarBlake2b;
|
||||||
use digest::{Digest, Input, VariableOutput};
|
use digest::{Digest, Input, VariableOutput};
|
||||||
use crate::Element;
|
|
||||||
use sha1::Sha1;
|
use sha1::Sha1;
|
||||||
use sha2::{Sha256, Sha512};
|
use sha2::{Sha256, Sha512};
|
||||||
use sha3::{Sha3_256, Sha3_512};
|
use sha3::{Sha3_256, Sha3_512};
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,14 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use crate::iq::{IqSetPayload, IqGetPayload, IqResultPayload};
|
use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload};
|
||||||
use crate::util::helpers::Base64;
|
use crate::util::helpers::Base64;
|
||||||
|
|
||||||
generate_elem_id!(
|
generate_elem_id!(
|
||||||
/// The name of a certificate.
|
/// The name of a certificate.
|
||||||
Name, "name", SASL_CERT
|
Name,
|
||||||
|
"name",
|
||||||
|
SASL_CERT
|
||||||
);
|
);
|
||||||
|
|
||||||
generate_element!(
|
generate_element!(
|
||||||
|
|
@ -40,14 +42,18 @@ impl IqSetPayload for Append {}
|
||||||
|
|
||||||
generate_empty_element!(
|
generate_empty_element!(
|
||||||
/// Client requests the current list of X.509 certificates.
|
/// Client requests the current list of X.509 certificates.
|
||||||
ListCertsQuery, "items", SASL_CERT
|
ListCertsQuery,
|
||||||
|
"items",
|
||||||
|
SASL_CERT
|
||||||
);
|
);
|
||||||
|
|
||||||
impl IqGetPayload for ListCertsQuery {}
|
impl IqGetPayload for ListCertsQuery {}
|
||||||
|
|
||||||
generate_elem_id!(
|
generate_elem_id!(
|
||||||
/// One resource currently using a certificate.
|
/// One resource currently using a certificate.
|
||||||
Resource, "resource", SASL_CERT
|
Resource,
|
||||||
|
"resource",
|
||||||
|
SASL_CERT
|
||||||
);
|
);
|
||||||
|
|
||||||
generate_element!(
|
generate_element!(
|
||||||
|
|
@ -113,10 +119,10 @@ impl IqSetPayload for Revoke {}
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::ns;
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use crate::ns;
|
|
||||||
|
|
||||||
#[cfg(target_pointer_width = "32")]
|
#[cfg(target_pointer_width = "32")]
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -153,11 +159,17 @@ mod tests {
|
||||||
assert_eq!(append.name.0, "Mobile Client");
|
assert_eq!(append.name.0, "Mobile Client");
|
||||||
assert_eq!(append.cert.data, b"\0\0\0");
|
assert_eq!(append.cert.data, b"\0\0\0");
|
||||||
|
|
||||||
let elem: Element = "<disable xmlns='urn:xmpp:saslcert:1'><name>Mobile Client</name></disable>".parse().unwrap();
|
let elem: Element =
|
||||||
|
"<disable xmlns='urn:xmpp:saslcert:1'><name>Mobile Client</name></disable>"
|
||||||
|
.parse()
|
||||||
|
.unwrap();
|
||||||
let disable = Disable::try_from(elem).unwrap();
|
let disable = Disable::try_from(elem).unwrap();
|
||||||
assert_eq!(disable.name.0, "Mobile Client");
|
assert_eq!(disable.name.0, "Mobile Client");
|
||||||
|
|
||||||
let elem: Element = "<revoke xmlns='urn:xmpp:saslcert:1'><name>Mobile Client</name></revoke>".parse().unwrap();
|
let elem: Element =
|
||||||
|
"<revoke xmlns='urn:xmpp:saslcert:1'><name>Mobile Client</name></revoke>"
|
||||||
|
.parse()
|
||||||
|
.unwrap();
|
||||||
let revoke = Revoke::try_from(elem).unwrap();
|
let revoke = Revoke::try_from(elem).unwrap();
|
||||||
assert_eq!(revoke.name.0, "Mobile Client");
|
assert_eq!(revoke.name.0, "Mobile Client");
|
||||||
}
|
}
|
||||||
|
|
@ -177,7 +189,9 @@ mod tests {
|
||||||
<name>Laptop</name>
|
<name>Laptop</name>
|
||||||
<x509cert>BBBB</x509cert>
|
<x509cert>BBBB</x509cert>
|
||||||
</item>
|
</item>
|
||||||
</items>"#.parse().unwrap();
|
</items>"#
|
||||||
|
.parse()
|
||||||
|
.unwrap();
|
||||||
let mut list = ListCertsResponse::try_from(elem).unwrap();
|
let mut list = ListCertsResponse::try_from(elem).unwrap();
|
||||||
assert_eq!(list.items.len(), 2);
|
assert_eq!(list.items.len(), 2);
|
||||||
|
|
||||||
|
|
@ -196,7 +210,9 @@ mod tests {
|
||||||
fn test_serialise() {
|
fn test_serialise() {
|
||||||
let append = Append {
|
let append = Append {
|
||||||
name: Name::from_str("Mobile Client").unwrap(),
|
name: Name::from_str("Mobile Client").unwrap(),
|
||||||
cert: Cert { data: b"\0\0\0".to_vec() },
|
cert: Cert {
|
||||||
|
data: b"\0\0\0".to_vec(),
|
||||||
|
},
|
||||||
no_cert_management: false,
|
no_cert_management: false,
|
||||||
};
|
};
|
||||||
let elem: Element = append.into();
|
let elem: Element = append.into();
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,8 @@ impl MessagePayload for ChatState {}
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::util::error::Error;
|
|
||||||
use crate::ns;
|
use crate::ns;
|
||||||
|
use crate::util::error::Error;
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,25 +6,31 @@
|
||||||
|
|
||||||
generate_empty_element!(
|
generate_empty_element!(
|
||||||
/// Stream:feature sent by the server to advertise it supports CSI.
|
/// Stream:feature sent by the server to advertise it supports CSI.
|
||||||
Feature, "csi", CSI
|
Feature,
|
||||||
|
"csi",
|
||||||
|
CSI
|
||||||
);
|
);
|
||||||
|
|
||||||
generate_empty_element!(
|
generate_empty_element!(
|
||||||
/// Client indicates it is inactive.
|
/// Client indicates it is inactive.
|
||||||
Inactive, "inactive", CSI
|
Inactive,
|
||||||
|
"inactive",
|
||||||
|
CSI
|
||||||
);
|
);
|
||||||
|
|
||||||
generate_empty_element!(
|
generate_empty_element!(
|
||||||
/// Client indicates it is active again.
|
/// Client indicates it is active again.
|
||||||
Active, "active", CSI
|
Active,
|
||||||
|
"active",
|
||||||
|
CSI
|
||||||
);
|
);
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::ns;
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use crate::ns;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_size() {
|
fn test_size() {
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use crate::util::error::Error;
|
|
||||||
use crate::media_element::MediaElement;
|
use crate::media_element::MediaElement;
|
||||||
use crate::ns;
|
use crate::ns;
|
||||||
|
use crate::util::error::Error;
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
|
@ -162,11 +162,7 @@ impl From<Field> for Element {
|
||||||
field
|
field
|
||||||
.values
|
.values
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|value| {
|
.map(|value| Element::builder("value").ns(ns::DATA_FORMS).append(value)),
|
||||||
Element::builder("value")
|
|
||||||
.ns(ns::DATA_FORMS)
|
|
||||||
.append(value)
|
|
||||||
})
|
|
||||||
)
|
)
|
||||||
.append_all(field.media.iter().cloned().map(Element::from))
|
.append_all(field.media.iter().cloned().map(Element::from))
|
||||||
.build()
|
.build()
|
||||||
|
|
@ -287,9 +283,11 @@ impl From<DataForm> for Element {
|
||||||
.ns(ns::DATA_FORMS)
|
.ns(ns::DATA_FORMS)
|
||||||
.attr("var", "FORM_TYPE")
|
.attr("var", "FORM_TYPE")
|
||||||
.attr("type", "hidden")
|
.attr("type", "hidden")
|
||||||
.append(Element::builder("value")
|
.append(
|
||||||
.ns(ns::DATA_FORMS)
|
Element::builder("value")
|
||||||
.append(form_type))
|
.ns(ns::DATA_FORMS)
|
||||||
|
.append(form_type),
|
||||||
|
)
|
||||||
}))
|
}))
|
||||||
.append_all(form.fields.iter().cloned().map(Element::from))
|
.append_all(form.fields.iter().cloned().map(Element::from))
|
||||||
.build()
|
.build()
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use crate::date::DateTime;
|
use crate::date::DateTime;
|
||||||
use crate::util::helpers::PlainText;
|
|
||||||
use crate::message::MessagePayload;
|
use crate::message::MessagePayload;
|
||||||
use crate::presence::PresencePayload;
|
use crate::presence::PresencePayload;
|
||||||
|
use crate::util::helpers::PlainText;
|
||||||
use jid::Jid;
|
use jid::Jid;
|
||||||
|
|
||||||
generate_element!(
|
generate_element!(
|
||||||
|
|
@ -34,8 +34,8 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::util::error::Error;
|
use crate::util::error::Error;
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
use std::str::FromStr;
|
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[cfg(target_pointer_width = "32")]
|
#[cfg(target_pointer_width = "32")]
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,11 @@
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use crate::data_forms::{DataForm, DataFormType};
|
use crate::data_forms::{DataForm, DataFormType};
|
||||||
use crate::util::error::Error;
|
|
||||||
use crate::iq::{IqGetPayload, IqResultPayload};
|
use crate::iq::{IqGetPayload, IqResultPayload};
|
||||||
use crate::ns;
|
use crate::ns;
|
||||||
use jid::Jid;
|
use crate::util::error::Error;
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
|
use jid::Jid;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
generate_element!(
|
generate_element!(
|
||||||
|
|
@ -37,9 +37,7 @@ attributes: [
|
||||||
impl Feature {
|
impl Feature {
|
||||||
/// Create a new `<feature/>` with the according `@var`.
|
/// Create a new `<feature/>` with the according `@var`.
|
||||||
pub fn new<S: Into<String>>(var: S) -> Feature {
|
pub fn new<S: Into<String>>(var: S) -> Feature {
|
||||||
Feature {
|
Feature { var: var.into() }
|
||||||
var: var.into(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -66,10 +64,11 @@ generate_element!(
|
||||||
impl Identity {
|
impl Identity {
|
||||||
/// Create a new `<identity/>`.
|
/// Create a new `<identity/>`.
|
||||||
pub fn new<C, T, L, N>(category: C, type_: T, lang: L, name: N) -> Identity
|
pub fn new<C, T, L, N>(category: C, type_: T, lang: L, name: N) -> Identity
|
||||||
where C: Into<String>,
|
where
|
||||||
T: Into<String>,
|
C: Into<String>,
|
||||||
L: Into<String>,
|
T: Into<String>,
|
||||||
N: Into<String>,
|
L: Into<String>,
|
||||||
|
N: Into<String>,
|
||||||
{
|
{
|
||||||
Identity {
|
Identity {
|
||||||
category: category.into(),
|
category: category.into(),
|
||||||
|
|
@ -81,8 +80,9 @@ impl Identity {
|
||||||
|
|
||||||
/// Create a new `<identity/>` without a name.
|
/// Create a new `<identity/>` without a name.
|
||||||
pub fn new_anonymous<C, T, L, N>(category: C, type_: T) -> Identity
|
pub fn new_anonymous<C, T, L, N>(category: C, type_: T) -> Identity
|
||||||
where C: Into<String>,
|
where
|
||||||
T: Into<String>,
|
C: Into<String>,
|
||||||
|
T: Into<String>,
|
||||||
{
|
{
|
||||||
Identity {
|
Identity {
|
||||||
category: category.into(),
|
category: category.into(),
|
||||||
|
|
@ -340,10 +340,7 @@ mod tests {
|
||||||
Error::ParseError(string) => string,
|
Error::ParseError(string) => string,
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(message, "Required attribute 'category' must not be empty.");
|
||||||
message,
|
|
||||||
"Required attribute 'category' must not be empty."
|
|
||||||
);
|
|
||||||
|
|
||||||
let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity category='coucou'/></query>".parse().unwrap();
|
let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity category='coucou'/></query>".parse().unwrap();
|
||||||
let error = DiscoInfoResult::try_from(elem).unwrap_err();
|
let error = DiscoInfoResult::try_from(elem).unwrap_err();
|
||||||
|
|
|
||||||
|
|
@ -30,9 +30,7 @@ impl PresencePayload for ECaps2 {}
|
||||||
impl ECaps2 {
|
impl ECaps2 {
|
||||||
/// Create an ECaps2 element from a list of hashes.
|
/// Create an ECaps2 element from a list of hashes.
|
||||||
pub fn new(hashes: Vec<Hash>) -> ECaps2 {
|
pub fn new(hashes: Vec<Hash>) -> ECaps2 {
|
||||||
ECaps2 {
|
ECaps2 { hashes }
|
||||||
hashes,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,7 +83,13 @@ fn compute_extensions(extensions: &[DataForm]) -> Result<Vec<u8>, ()> {
|
||||||
}
|
}
|
||||||
Ok(compute_items(extensions, 0x1c, |extension| {
|
Ok(compute_items(extensions, 0x1c, |extension| {
|
||||||
let mut bytes = compute_item("FORM_TYPE");
|
let mut bytes = compute_item("FORM_TYPE");
|
||||||
bytes.append(&mut compute_item(if let Some(ref form_type) = extension.form_type { form_type } else { unreachable!() }));
|
bytes.append(&mut compute_item(
|
||||||
|
if let Some(ref form_type) = extension.form_type {
|
||||||
|
form_type
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
},
|
||||||
|
));
|
||||||
bytes.push(0x1e);
|
bytes.push(0x1e);
|
||||||
bytes.append(&mut compute_items(&extension.fields, 0x1d, |field| {
|
bytes.append(&mut compute_items(&extension.fields, 0x1d, |field| {
|
||||||
let mut bytes = compute_item(&field.var);
|
let mut bytes = compute_item(&field.var);
|
||||||
|
|
|
||||||
|
|
@ -236,8 +236,14 @@ mod tests {
|
||||||
fn value_serialisation() {
|
fn value_serialisation() {
|
||||||
let elem: Element = "<hash xmlns='urn:xmpp:hashes:2' algo='sha-256'>2XarmwTlNxDAMkvymloX3S5+VbylNrJt/l5QyPa+YoU=</hash>".parse().unwrap();
|
let elem: Element = "<hash xmlns='urn:xmpp:hashes:2' algo='sha-256'>2XarmwTlNxDAMkvymloX3S5+VbylNrJt/l5QyPa+YoU=</hash>".parse().unwrap();
|
||||||
let hash = Hash::try_from(elem).unwrap();
|
let hash = Hash::try_from(elem).unwrap();
|
||||||
assert_eq!(hash.to_base64(), "2XarmwTlNxDAMkvymloX3S5+VbylNrJt/l5QyPa+YoU=");
|
assert_eq!(
|
||||||
assert_eq!(hash.to_hex(), "d976ab9b04e53710c0324bf29a5a17dd2e7e55bca536b26dfe5e50c8f6be6285");
|
hash.to_base64(),
|
||||||
|
"2XarmwTlNxDAMkvymloX3S5+VbylNrJt/l5QyPa+YoU="
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
hash.to_hex(),
|
||||||
|
"d976ab9b04e53710c0324bf29a5a17dd2e7e55bca536b26dfe5e50c8f6be6285"
|
||||||
|
);
|
||||||
assert_eq!(hash.to_colon_separated_hex(), "d9:76:ab:9b:04:e5:37:10:c0:32:4b:f2:9a:5a:17:dd:2e:7e:55:bc:a5:36:b2:6d:fe:5e:50:c8:f6:be:62:85");
|
assert_eq!(hash.to_colon_separated_hex(), "d9:76:ab:9b:04:e5:37:10:c0:32:4b:f2:9a:5a:17:dd:2e:7e:55:bc:a5:36:b2:6d:fe:5e:50:c8:f6:be:62:85");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use crate::util::helpers::Base64;
|
|
||||||
use crate::iq::IqSetPayload;
|
use crate::iq::IqSetPayload;
|
||||||
|
use crate::util::helpers::Base64;
|
||||||
|
|
||||||
generate_id!(
|
generate_id!(
|
||||||
/// An identifier matching a stream.
|
/// An identifier matching a stream.
|
||||||
|
|
@ -74,8 +74,8 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::util::error::Error;
|
use crate::util::error::Error;
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
use std::error::Error as StdError;
|
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
use std::error::Error as StdError;
|
||||||
|
|
||||||
#[cfg(target_pointer_width = "32")]
|
#[cfg(target_pointer_width = "32")]
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use crate::data_forms::DataForm;
|
use crate::data_forms::DataForm;
|
||||||
use crate::util::error::Error;
|
|
||||||
use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload};
|
use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload};
|
||||||
use crate::ns;
|
use crate::ns;
|
||||||
|
use crate::util::error::Error;
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
@ -102,7 +102,7 @@ impl From<Query> for Element {
|
||||||
query
|
query
|
||||||
.fields
|
.fields
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(name, value)| Element::builder(name).ns(ns::REGISTER).append(value))
|
.map(|(name, value)| Element::builder(name).ns(ns::REGISTER).append(value)),
|
||||||
)
|
)
|
||||||
.append_all(if query.remove {
|
.append_all(if query.remove {
|
||||||
Some(Element::builder("remove").ns(ns::REGISTER))
|
Some(Element::builder("remove").ns(ns::REGISTER))
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,9 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::util::error::Error;
|
use crate::util::error::Error;
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
|
use std::convert::TryFrom;
|
||||||
use std::error::Error as StdError;
|
use std::error::Error as StdError;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::convert::TryFrom;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_size() {
|
fn test_size() {
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,11 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use crate::util::error::Error;
|
|
||||||
use crate::ns;
|
use crate::ns;
|
||||||
use crate::stanza_error::StanzaError;
|
use crate::stanza_error::StanzaError;
|
||||||
use jid::Jid;
|
use crate::util::error::Error;
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
|
use jid::Jid;
|
||||||
use minidom::IntoAttributeValue;
|
use minidom::IntoAttributeValue;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
|
@ -218,9 +218,9 @@ impl From<Iq> for Element {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::util::compare_elements::NamespaceAwareCompare;
|
|
||||||
use crate::disco::DiscoInfoQuery;
|
use crate::disco::DiscoInfoQuery;
|
||||||
use crate::stanza_error::{DefinedCondition, ErrorType};
|
use crate::stanza_error::{DefinedCondition, ErrorType};
|
||||||
|
use crate::util::compare_elements::NamespaceAwareCompare;
|
||||||
|
|
||||||
#[cfg(target_pointer_width = "32")]
|
#[cfg(target_pointer_width = "32")]
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -252,7 +252,9 @@ mod tests {
|
||||||
#[cfg(not(feature = "component"))]
|
#[cfg(not(feature = "component"))]
|
||||||
let elem: Element = "<iq xmlns='jabber:client' id='coucou'/>".parse().unwrap();
|
let elem: Element = "<iq xmlns='jabber:client' id='coucou'/>".parse().unwrap();
|
||||||
#[cfg(feature = "component")]
|
#[cfg(feature = "component")]
|
||||||
let elem: Element = "<iq xmlns='jabber:component:accept' id='coucou'/>".parse().unwrap();
|
let elem: Element = "<iq xmlns='jabber:component:accept' id='coucou'/>"
|
||||||
|
.parse()
|
||||||
|
.unwrap();
|
||||||
let error = Iq::try_from(elem).unwrap_err();
|
let error = Iq::try_from(elem).unwrap_err();
|
||||||
let message = match error {
|
let message = match error {
|
||||||
Error::ParseError(string) => string,
|
Error::ParseError(string) => string,
|
||||||
|
|
@ -314,7 +316,9 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_result_empty() {
|
fn test_result_empty() {
|
||||||
#[cfg(not(feature = "component"))]
|
#[cfg(not(feature = "component"))]
|
||||||
let elem: Element = "<iq xmlns='jabber:client' type='result' id='res'/>".parse().unwrap();
|
let elem: Element = "<iq xmlns='jabber:client' type='result' id='res'/>"
|
||||||
|
.parse()
|
||||||
|
.unwrap();
|
||||||
#[cfg(feature = "component")]
|
#[cfg(feature = "component")]
|
||||||
let elem: Element = "<iq xmlns='jabber:component:accept' type='result' id='res'/>"
|
let elem: Element = "<iq xmlns='jabber:component:accept' type='result' id='res'/>"
|
||||||
.parse()
|
.parse()
|
||||||
|
|
@ -416,7 +420,9 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_serialise() {
|
fn test_serialise() {
|
||||||
#[cfg(not(feature = "component"))]
|
#[cfg(not(feature = "component"))]
|
||||||
let elem: Element = "<iq xmlns='jabber:client' type='result' id='res'/>".parse().unwrap();
|
let elem: Element = "<iq xmlns='jabber:client' type='result' id='res'/>"
|
||||||
|
.parse()
|
||||||
|
.unwrap();
|
||||||
#[cfg(feature = "component")]
|
#[cfg(feature = "component")]
|
||||||
let elem: Element = "<iq xmlns='jabber:component:accept' type='result' id='res'/>"
|
let elem: Element = "<iq xmlns='jabber:component:accept' type='result' id='res'/>"
|
||||||
.parse()
|
.parse()
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use crate::iq::{IqGetPayload, IqResultPayload};
|
use crate::iq::{IqGetPayload, IqResultPayload};
|
||||||
use crate::util::helpers::{Text, JidCodec};
|
use crate::util::helpers::{JidCodec, Text};
|
||||||
use jid::Jid;
|
use jid::Jid;
|
||||||
|
|
||||||
generate_element!(
|
generate_element!(
|
||||||
|
|
@ -22,9 +22,7 @@ impl IqGetPayload for JidPrepQuery {}
|
||||||
impl JidPrepQuery {
|
impl JidPrepQuery {
|
||||||
/// Create a new JID Prep query.
|
/// Create a new JID Prep query.
|
||||||
pub fn new<J: Into<String>>(jid: J) -> JidPrepQuery {
|
pub fn new<J: Into<String>>(jid: J) -> JidPrepQuery {
|
||||||
JidPrepQuery {
|
JidPrepQuery { data: jid.into() }
|
||||||
data: jid.into(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,12 +60,19 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn simple() {
|
fn simple() {
|
||||||
let elem: Element = "<jid xmlns='urn:xmpp:jidprep:0'>ROMeo@montague.lit/orchard</jid>".parse().unwrap();
|
let elem: Element = "<jid xmlns='urn:xmpp:jidprep:0'>ROMeo@montague.lit/orchard</jid>"
|
||||||
|
.parse()
|
||||||
|
.unwrap();
|
||||||
let query = JidPrepQuery::try_from(elem).unwrap();
|
let query = JidPrepQuery::try_from(elem).unwrap();
|
||||||
assert_eq!(query.data, "ROMeo@montague.lit/orchard");
|
assert_eq!(query.data, "ROMeo@montague.lit/orchard");
|
||||||
|
|
||||||
let elem: Element = "<jid xmlns='urn:xmpp:jidprep:0'>romeo@montague.lit/orchard</jid>".parse().unwrap();
|
let elem: Element = "<jid xmlns='urn:xmpp:jidprep:0'>romeo@montague.lit/orchard</jid>"
|
||||||
|
.parse()
|
||||||
|
.unwrap();
|
||||||
let response = JidPrepResponse::try_from(elem).unwrap();
|
let response = JidPrepResponse::try_from(elem).unwrap();
|
||||||
assert_eq!(response.jid, Jid::from_str("romeo@montague.lit/orchard").unwrap());
|
assert_eq!(
|
||||||
|
response.jid,
|
||||||
|
Jid::from_str("romeo@montague.lit/orchard").unwrap()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,18 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use crate::util::error::Error;
|
|
||||||
use crate::iq::IqSetPayload;
|
use crate::iq::IqSetPayload;
|
||||||
use crate::jingle_rtp::Description as RtpDescription;
|
|
||||||
use crate::jingle_ice_udp::Transport as IceUdpTransport;
|
|
||||||
use crate::jingle_ibb::Transport as IbbTransport;
|
use crate::jingle_ibb::Transport as IbbTransport;
|
||||||
|
use crate::jingle_ice_udp::Transport as IceUdpTransport;
|
||||||
|
use crate::jingle_rtp::Description as RtpDescription;
|
||||||
use crate::jingle_s5b::Transport as Socks5Transport;
|
use crate::jingle_s5b::Transport as Socks5Transport;
|
||||||
use crate::ns;
|
use crate::ns;
|
||||||
use jid::Jid;
|
use crate::util::error::Error;
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
|
use jid::Jid;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::str::FromStr;
|
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
generate_attribute!(
|
generate_attribute!(
|
||||||
/// The action attribute.
|
/// The action attribute.
|
||||||
|
|
@ -473,8 +473,8 @@ impl From<Reason> for Element {
|
||||||
Reason::UnsupportedApplications => "unsupported-applications",
|
Reason::UnsupportedApplications => "unsupported-applications",
|
||||||
Reason::UnsupportedTransports => "unsupported-transports",
|
Reason::UnsupportedTransports => "unsupported-transports",
|
||||||
})
|
})
|
||||||
.ns(ns::JINGLE)
|
.ns(ns::JINGLE)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -521,13 +521,8 @@ impl TryFrom<Element> for ReasonElement {
|
||||||
return Err(Error::ParseError("Reason contains a foreign element."));
|
return Err(Error::ParseError("Reason contains a foreign element."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let reason = reason.ok_or(Error::ParseError(
|
let reason = reason.ok_or(Error::ParseError("Reason doesn’t contain a valid reason."))?;
|
||||||
"Reason doesn’t contain a valid reason.",
|
Ok(ReasonElement { reason, texts })
|
||||||
))?;
|
|
||||||
Ok(ReasonElement {
|
|
||||||
reason,
|
|
||||||
texts,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -536,13 +531,12 @@ impl From<ReasonElement> for Element {
|
||||||
Element::builder("reason")
|
Element::builder("reason")
|
||||||
.ns(ns::JINGLE)
|
.ns(ns::JINGLE)
|
||||||
.append(Element::from(reason.reason))
|
.append(Element::from(reason.reason))
|
||||||
.append_all(
|
.append_all(reason.texts.into_iter().map(|(lang, text)| {
|
||||||
reason.texts.into_iter().map(|(lang, text)| {
|
Element::builder("text")
|
||||||
Element::builder("text")
|
.ns(ns::JINGLE)
|
||||||
.ns(ns::JINGLE)
|
.attr("xml:lang", lang)
|
||||||
.attr("xml:lang", lang)
|
.append(text)
|
||||||
.append(text)
|
}))
|
||||||
}))
|
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use crate::util::helpers::ColonSeparatedHex;
|
use crate::hashes::{Algo, Hash};
|
||||||
use crate::util::error::Error;
|
use crate::util::error::Error;
|
||||||
use crate::hashes::{Hash, Algo};
|
use crate::util::helpers::ColonSeparatedHex;
|
||||||
|
|
||||||
generate_attribute!(
|
generate_attribute!(
|
||||||
/// Indicates which of the end points should initiate the TCP connection establishment.
|
/// Indicates which of the end points should initiate the TCP connection establishment.
|
||||||
|
|
@ -58,7 +58,11 @@ impl Fingerprint {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new Fingerprint from a Setup and parsing the hash.
|
/// Create a new Fingerprint from a Setup and parsing the hash.
|
||||||
pub fn from_colon_separated_hex(setup: Setup, algo: &str, hash: &str) -> Result<Fingerprint, Error> {
|
pub fn from_colon_separated_hex(
|
||||||
|
setup: Setup,
|
||||||
|
algo: &str,
|
||||||
|
hash: &str,
|
||||||
|
) -> Result<Fingerprint, Error> {
|
||||||
let algo = algo.parse()?;
|
let algo = algo.parse()?;
|
||||||
let hash = Hash::from_colon_separated_hex(algo, hash)?;
|
let hash = Hash::from_colon_separated_hex(algo, hash)?;
|
||||||
Ok(Fingerprint::from_hash(setup, hash))
|
Ok(Fingerprint::from_hash(setup, hash))
|
||||||
|
|
@ -93,6 +97,12 @@ mod tests {
|
||||||
let fingerprint = Fingerprint::try_from(elem).unwrap();
|
let fingerprint = Fingerprint::try_from(elem).unwrap();
|
||||||
assert_eq!(fingerprint.setup, Setup::Actpass);
|
assert_eq!(fingerprint.setup, Setup::Actpass);
|
||||||
assert_eq!(fingerprint.hash, Algo::Sha_256);
|
assert_eq!(fingerprint.hash, Algo::Sha_256);
|
||||||
assert_eq!(fingerprint.value, [2, 26, 204, 84, 39, 171, 235, 156, 83, 63, 62, 75, 101, 46, 125, 70, 63, 84, 66, 205, 84, 241, 122, 3, 162, 125, 249, 176, 127, 70, 25, 178]);
|
assert_eq!(
|
||||||
|
fingerprint.value,
|
||||||
|
[
|
||||||
|
2, 26, 204, 84, 39, 171, 235, 156, 83, 63, 62, 75, 101, 46, 125, 70, 63, 84, 66,
|
||||||
|
205, 84, 241, 122, 3, 162, 125, 249, 176, 127, 70, 25, 178
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,14 @@
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use crate::date::DateTime;
|
use crate::date::DateTime;
|
||||||
use crate::util::error::Error;
|
|
||||||
use crate::hashes::Hash;
|
use crate::hashes::Hash;
|
||||||
use crate::jingle::{ContentId, Creator};
|
use crate::jingle::{ContentId, Creator};
|
||||||
use crate::ns;
|
use crate::ns;
|
||||||
|
use crate::util::error::Error;
|
||||||
use minidom::{Element, Node};
|
use minidom::{Element, Node};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::str::FromStr;
|
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
generate_element!(
|
generate_element!(
|
||||||
/// Represents a range in a file.
|
/// Represents a range in a file.
|
||||||
|
|
@ -195,22 +195,21 @@ impl From<File> for Element {
|
||||||
fn from(file: File) -> Element {
|
fn from(file: File) -> Element {
|
||||||
Element::builder("file")
|
Element::builder("file")
|
||||||
.ns(ns::JINGLE_FT)
|
.ns(ns::JINGLE_FT)
|
||||||
.append_all(file.date.map(|date|
|
.append_all(file.date.map(|date| Element::builder("date").append(date)))
|
||||||
Element::builder("date")
|
.append_all(
|
||||||
.append(date)))
|
file.media_type
|
||||||
.append_all(file.media_type.map(|media_type|
|
.map(|media_type| Element::builder("media-type").append(media_type)),
|
||||||
Element::builder("media-type")
|
)
|
||||||
.append(media_type)))
|
.append_all(file.name.map(|name| Element::builder("name").append(name)))
|
||||||
.append_all(file.name.map(|name|
|
.append_all(file.descs.into_iter().map(|(lang, desc)| {
|
||||||
Element::builder("name")
|
|
||||||
.append(name)))
|
|
||||||
.append_all(file.descs.into_iter().map(|(lang, desc)|
|
|
||||||
Element::builder("desc")
|
Element::builder("desc")
|
||||||
.attr("xml:lang", lang)
|
.attr("xml:lang", lang)
|
||||||
.append(desc.0)))
|
.append(desc.0)
|
||||||
.append_all(file.size.map(|size|
|
}))
|
||||||
Element::builder("size")
|
.append_all(
|
||||||
.append(format!("{}", size))))
|
file.size
|
||||||
|
.map(|size| Element::builder("size").append(format!("{}", size))),
|
||||||
|
)
|
||||||
.append_all(file.range)
|
.append_all(file.range)
|
||||||
.append_all(file.hashes)
|
.append_all(file.hashes)
|
||||||
.build()
|
.build()
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,8 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::util::error::Error;
|
use crate::util::error::Error;
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
use std::error::Error as StdError;
|
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
use std::error::Error as StdError;
|
||||||
|
|
||||||
#[cfg(target_pointer_width = "32")]
|
#[cfg(target_pointer_width = "32")]
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -115,10 +115,10 @@ generate_element!(
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::Element;
|
|
||||||
use std::convert::TryFrom;
|
|
||||||
use crate::hashes::Algo;
|
use crate::hashes::Algo;
|
||||||
use crate::jingle_dtls_srtp::Setup;
|
use crate::jingle_dtls_srtp::Setup;
|
||||||
|
use crate::Element;
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
#[cfg(target_pointer_width = "32")]
|
#[cfg(target_pointer_width = "32")]
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -184,6 +184,12 @@ mod tests {
|
||||||
let fingerprint = transport.fingerprint.unwrap();
|
let fingerprint = transport.fingerprint.unwrap();
|
||||||
assert_eq!(fingerprint.hash, Algo::Sha_1);
|
assert_eq!(fingerprint.hash, Algo::Sha_1);
|
||||||
assert_eq!(fingerprint.setup, Setup::Actpass);
|
assert_eq!(fingerprint.setup, Setup::Actpass);
|
||||||
assert_eq!(fingerprint.value, [151, 242, 181, 190, 219, 166, 0, 177, 62, 64, 178, 65, 60, 13, 252, 224, 189, 178, 160, 232]);
|
assert_eq!(
|
||||||
|
fingerprint.value,
|
||||||
|
[
|
||||||
|
151, 242, 181, 190, 219, 166, 0, 177, 62, 64, 178, 65, 60, 13, 252, 224, 189, 178,
|
||||||
|
160, 232
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use crate::util::error::Error;
|
|
||||||
use crate::jingle::SessionId;
|
use crate::jingle::SessionId;
|
||||||
use crate::ns;
|
use crate::ns;
|
||||||
|
use crate::util::error::Error;
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,8 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_simple() {
|
fn parse_simple() {
|
||||||
let elem: Element = "<rtcp-fb xmlns='urn:xmpp:jingle:apps:rtp:rtcp-fb:0' type='nack' subtype='sli'/>"
|
let elem: Element =
|
||||||
|
"<rtcp-fb xmlns='urn:xmpp:jingle:apps:rtp:rtcp-fb:0' type='nack' subtype='sli'/>"
|
||||||
.parse()
|
.parse()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let rtcp_fb = RtcpFb::try_from(elem).unwrap();
|
let rtcp_fb = RtcpFb::try_from(elem).unwrap();
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use crate::jingle_ssma::{Source, Group};
|
|
||||||
use crate::jingle_rtcp_fb::RtcpFb;
|
use crate::jingle_rtcp_fb::RtcpFb;
|
||||||
|
use crate::jingle_ssma::{Group, Source};
|
||||||
|
|
||||||
generate_element!(
|
generate_element!(
|
||||||
/// Wrapper element describing an RTP session.
|
/// Wrapper element describing an RTP session.
|
||||||
|
|
@ -48,7 +48,10 @@ impl Description {
|
||||||
|
|
||||||
generate_attribute!(
|
generate_attribute!(
|
||||||
/// The number of channels.
|
/// The number of channels.
|
||||||
Channels, "channels", u8, Default = 1
|
Channels,
|
||||||
|
"channels",
|
||||||
|
u8,
|
||||||
|
Default = 1
|
||||||
);
|
);
|
||||||
|
|
||||||
generate_element!(
|
generate_element!(
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,12 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use crate::util::error::Error;
|
|
||||||
use crate::ns;
|
use crate::ns;
|
||||||
use jid::Jid;
|
use crate::util::error::Error;
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
use std::net::IpAddr;
|
use jid::Jid;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
use std::net::IpAddr;
|
||||||
|
|
||||||
generate_attribute!(
|
generate_attribute!(
|
||||||
/// The type of the connection being proposed by this candidate.
|
/// The type of the connection being proposed by this candidate.
|
||||||
|
|
@ -263,9 +263,9 @@ impl From<Transport> for Element {
|
||||||
.ns(ns::JINGLE_S5B)
|
.ns(ns::JINGLE_S5B)
|
||||||
.attr("cid", cid)
|
.attr("cid", cid)
|
||||||
.build()],
|
.build()],
|
||||||
TransportPayload::ProxyError => vec![Element::builder("proxy-error")
|
TransportPayload::ProxyError => {
|
||||||
.ns(ns::JINGLE_S5B)
|
vec![Element::builder("proxy-error").ns(ns::JINGLE_S5B).build()]
|
||||||
.build()],
|
}
|
||||||
TransportPayload::None => vec![],
|
TransportPayload::None => vec![],
|
||||||
})
|
})
|
||||||
.build()
|
.build()
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,10 @@ mod tests {
|
||||||
assert_eq!(ssrc.parameters.len(), 2);
|
assert_eq!(ssrc.parameters.len(), 2);
|
||||||
let parameter = ssrc.parameters.pop().unwrap();
|
let parameter = ssrc.parameters.pop().unwrap();
|
||||||
assert_eq!(parameter.name, "msid");
|
assert_eq!(parameter.name, "msid");
|
||||||
assert_eq!(parameter.value.unwrap(), "MLTJKIHilGn71fNQoszkQ4jlPTuS5vJyKVIv MLTJKIHilGn71fNQoszkQ4jlPTuS5vJyKVIva0");
|
assert_eq!(
|
||||||
|
parameter.value.unwrap(),
|
||||||
|
"MLTJKIHilGn71fNQoszkQ4jlPTuS5vJyKVIv MLTJKIHilGn71fNQoszkQ4jlPTuS5vJyKVIva0"
|
||||||
|
);
|
||||||
let parameter = ssrc.parameters.pop().unwrap();
|
let parameter = ssrc.parameters.pop().unwrap();
|
||||||
assert_eq!(parameter.name, "cname");
|
assert_eq!(parameter.name, "cname");
|
||||||
assert_eq!(parameter.value.unwrap(), "Yv/wvbCdsDW2Prgd");
|
assert_eq!(parameter.value.unwrap(), "Yv/wvbCdsDW2Prgd");
|
||||||
|
|
@ -101,8 +104,8 @@ mod tests {
|
||||||
<source ssrc='2301230316'/>
|
<source ssrc='2301230316'/>
|
||||||
<source ssrc='386328120'/>
|
<source ssrc='386328120'/>
|
||||||
</ssrc-group>"
|
</ssrc-group>"
|
||||||
.parse()
|
.parse()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut group = Group::try_from(elem).unwrap();
|
let mut group = Group::try_from(elem).unwrap();
|
||||||
assert_eq!(group.semantics, "FID");
|
assert_eq!(group.semantics, "FID");
|
||||||
assert_eq!(group.sources.len(), 2);
|
assert_eq!(group.sources.len(), 2);
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,9 @@
|
||||||
|
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
pub use minidom::Element;
|
|
||||||
pub use jid::{BareJid, FullJid, Jid, JidParseError};
|
|
||||||
pub use crate::util::error::Error;
|
pub use crate::util::error::Error;
|
||||||
|
pub use jid::{BareJid, FullJid, Jid, JidParseError};
|
||||||
|
pub use minidom::Element;
|
||||||
|
|
||||||
/// XML namespace definitions used through XMPP.
|
/// XML namespace definitions used through XMPP.
|
||||||
pub mod ns;
|
pub mod ns;
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,13 @@
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use crate::data_forms::DataForm;
|
use crate::data_forms::DataForm;
|
||||||
use crate::util::error::Error;
|
|
||||||
use crate::forwarding::Forwarded;
|
use crate::forwarding::Forwarded;
|
||||||
use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload};
|
use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload};
|
||||||
use crate::message::MessagePayload;
|
use crate::message::MessagePayload;
|
||||||
use crate::ns;
|
use crate::ns;
|
||||||
use crate::pubsub::NodeName;
|
use crate::pubsub::NodeName;
|
||||||
use crate::rsm::{SetQuery, SetResult};
|
use crate::rsm::{SetQuery, SetResult};
|
||||||
|
use crate::util::error::Error;
|
||||||
use jid::Jid;
|
use jid::Jid;
|
||||||
use minidom::{Element, Node};
|
use minidom::{Element, Node};
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
@ -168,14 +168,14 @@ fn serialise_jid_list(name: &str, jids: Vec<Jid>) -> ::std::option::IntoIter<Nod
|
||||||
Some(
|
Some(
|
||||||
Element::builder(name)
|
Element::builder(name)
|
||||||
.ns(ns::MAM)
|
.ns(ns::MAM)
|
||||||
.append_all(
|
.append_all(jids.into_iter().map(|jid| {
|
||||||
jids.into_iter()
|
Element::builder("jid")
|
||||||
.map(|jid|
|
.ns(ns::MAM)
|
||||||
Element::builder("jid")
|
.append(String::from(jid))
|
||||||
.ns(ns::MAM)
|
}))
|
||||||
.append(String::from(jid))))
|
|
||||||
.into(),
|
.into(),
|
||||||
).into_iter()
|
)
|
||||||
|
.into_iter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,8 +48,8 @@ mod tests {
|
||||||
use crate::data_forms::DataForm;
|
use crate::data_forms::DataForm;
|
||||||
use crate::util::error::Error;
|
use crate::util::error::Error;
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
use std::error::Error as StdError;
|
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
use std::error::Error as StdError;
|
||||||
|
|
||||||
#[cfg(target_pointer_width = "32")]
|
#[cfg(target_pointer_width = "32")]
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,10 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use crate::util::error::Error;
|
|
||||||
use crate::ns;
|
use crate::ns;
|
||||||
use jid::Jid;
|
use crate::util::error::Error;
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
|
use jid::Jid;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
|
@ -212,38 +212,28 @@ impl From<Message> for Element {
|
||||||
.attr("to", message.to)
|
.attr("to", message.to)
|
||||||
.attr("id", message.id)
|
.attr("id", message.id)
|
||||||
.attr("type", message.type_)
|
.attr("type", message.type_)
|
||||||
.append_all(
|
.append_all(message.subjects.into_iter().map(|(lang, subject)| {
|
||||||
message
|
let mut subject = Element::from(subject);
|
||||||
.subjects
|
subject.set_attr(
|
||||||
.into_iter()
|
"xml:lang",
|
||||||
.map(|(lang, subject)| {
|
match lang.as_ref() {
|
||||||
let mut subject = Element::from(subject);
|
"" => None,
|
||||||
subject.set_attr(
|
lang => Some(lang),
|
||||||
"xml:lang",
|
},
|
||||||
match lang.as_ref() {
|
);
|
||||||
"" => None,
|
subject
|
||||||
lang => Some(lang),
|
}))
|
||||||
},
|
.append_all(message.bodies.into_iter().map(|(lang, body)| {
|
||||||
);
|
let mut body = Element::from(body);
|
||||||
subject
|
body.set_attr(
|
||||||
})
|
"xml:lang",
|
||||||
)
|
match lang.as_ref() {
|
||||||
.append_all(
|
"" => None,
|
||||||
message
|
lang => Some(lang),
|
||||||
.bodies
|
},
|
||||||
.into_iter()
|
);
|
||||||
.map(|(lang, body)| {
|
body
|
||||||
let mut body = Element::from(body);
|
}))
|
||||||
body.set_attr(
|
|
||||||
"xml:lang",
|
|
||||||
match lang.as_ref() {
|
|
||||||
"" => None,
|
|
||||||
lang => Some(lang),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
body
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.append_all(message.payloads.into_iter())
|
.append_all(message.payloads.into_iter())
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -97,8 +97,8 @@ mod tests {
|
||||||
use crate::util::compare_elements::NamespaceAwareCompare;
|
use crate::util::compare_elements::NamespaceAwareCompare;
|
||||||
use crate::util::error::Error;
|
use crate::util::error::Error;
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
use std::str::FromStr;
|
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_muc_simple() {
|
fn test_muc_simple() {
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,10 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use crate::util::error::Error;
|
|
||||||
use crate::ns;
|
use crate::ns;
|
||||||
use jid::FullJid;
|
use crate::util::error::Error;
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
|
use jid::FullJid;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
generate_attribute_enum!(
|
generate_attribute_enum!(
|
||||||
|
|
@ -99,11 +99,9 @@ impl TryFrom<Element> for Actor {
|
||||||
let nick = get_attr!(elem, "nick", Option);
|
let nick = get_attr!(elem, "nick", Option);
|
||||||
|
|
||||||
match (jid, nick) {
|
match (jid, nick) {
|
||||||
(Some(_), Some(_)) | (None, None) => {
|
(Some(_), Some(_)) | (None, None) => Err(Error::ParseError(
|
||||||
Err(Error::ParseError(
|
"Either 'jid' or 'nick' attribute is required.",
|
||||||
"Either 'jid' or 'nick' attribute is required.",
|
)),
|
||||||
))
|
|
||||||
}
|
|
||||||
(Some(jid), _) => Ok(Actor::Jid(jid)),
|
(Some(jid), _) => Ok(Actor::Jid(jid)),
|
||||||
(_, Some(nick)) => Ok(Actor::Nick(nick)),
|
(_, Some(nick)) => Ok(Actor::Nick(nick)),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,9 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_invalid_id() {
|
fn test_invalid_id() {
|
||||||
let elem: Element = "<occupant-id xmlns='urn:xmpp:occupant-id:0'/>".parse().unwrap();
|
let elem: Element = "<occupant-id xmlns='urn:xmpp:occupant-id:0'/>"
|
||||||
|
.parse()
|
||||||
|
.unwrap();
|
||||||
let error = OccupantId::try_from(elem).unwrap_err();
|
let error = OccupantId::try_from(elem).unwrap_err();
|
||||||
let message = match error {
|
let message = match error {
|
||||||
Error::ParseError(string) => string,
|
Error::ParseError(string) => string,
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use crate::date::DateTime;
|
use crate::date::DateTime;
|
||||||
use crate::util::helpers::Base64;
|
|
||||||
use crate::pubsub::PubSubPayload;
|
use crate::pubsub::PubSubPayload;
|
||||||
|
use crate::util::helpers::Base64;
|
||||||
|
|
||||||
// TODO: Merge this container with the PubKey struct
|
// TODO: Merge this container with the PubKey struct
|
||||||
generate_element!(
|
generate_element!(
|
||||||
|
|
@ -59,8 +59,11 @@ impl PubSubPayload for PubKeysMeta {}
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::ns;
|
use crate::ns;
|
||||||
|
use crate::pubsub::{
|
||||||
|
pubsub::{Item as PubSubItem, Publish},
|
||||||
|
Item, NodeName,
|
||||||
|
};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use crate::pubsub::{NodeName, Item, pubsub::{Item as PubSubItem, Publish}};
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pubsub_publish_pubkey_data() {
|
fn pubsub_publish_pubkey_data() {
|
||||||
|
|
@ -68,15 +71,13 @@ mod tests {
|
||||||
date: None,
|
date: None,
|
||||||
data: PubKeyData {
|
data: PubKeyData {
|
||||||
data: (&"Foo").as_bytes().to_vec(),
|
data: (&"Foo").as_bytes().to_vec(),
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
println!("Foo1: {:?}", pubkey);
|
println!("Foo1: {:?}", pubkey);
|
||||||
|
|
||||||
let pubsub = Publish {
|
let pubsub = Publish {
|
||||||
node: NodeName(format!("{}:{}", ns::OX_PUBKEYS, "some-fingerprint")),
|
node: NodeName(format!("{}:{}", ns::OX_PUBKEYS, "some-fingerprint")),
|
||||||
items: vec![
|
items: vec![PubSubItem(Item::new(None, None, Some(pubkey)))],
|
||||||
PubSubItem(Item::new(None, None, Some(pubkey))),
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
println!("Foo2: {:?}", pubsub);
|
println!("Foo2: {:?}", pubsub);
|
||||||
}
|
}
|
||||||
|
|
@ -84,20 +85,16 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn pubsub_publish_pubkey_meta() {
|
fn pubsub_publish_pubkey_meta() {
|
||||||
let pubkeymeta = PubKeysMeta {
|
let pubkeymeta = PubKeysMeta {
|
||||||
pubkeys: vec![
|
pubkeys: vec![PubKeyMeta {
|
||||||
PubKeyMeta {
|
v4fingerprint: "some-fingerprint".to_owned(),
|
||||||
v4fingerprint: "some-fingerprint".to_owned(),
|
date: DateTime::from_str("2019-03-30T18:30:25Z").unwrap(),
|
||||||
date: DateTime::from_str("2019-03-30T18:30:25Z").unwrap(),
|
}],
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
println!("Foo1: {:?}", pubkeymeta);
|
println!("Foo1: {:?}", pubkeymeta);
|
||||||
|
|
||||||
let pubsub = Publish {
|
let pubsub = Publish {
|
||||||
node: NodeName("foo".to_owned()),
|
node: NodeName("foo".to_owned()),
|
||||||
items: vec![
|
items: vec![PubSubItem(Item::new(None, None, Some(pubkeymeta)))],
|
||||||
PubSubItem(Item::new(None, None, Some(pubkeymeta))),
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
println!("Foo2: {:?}", pubsub);
|
println!("Foo2: {:?}", pubsub);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,13 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use crate::util::error::Error;
|
|
||||||
use crate::ns;
|
use crate::ns;
|
||||||
|
use crate::util::error::Error;
|
||||||
use jid::Jid;
|
use jid::Jid;
|
||||||
use minidom::{Element, IntoAttributeValue, Node};
|
use minidom::{Element, IntoAttributeValue, Node};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::str::FromStr;
|
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
/// Should be implemented on every known payload of a `<presence/>`.
|
/// Should be implemented on every known payload of a `<presence/>`.
|
||||||
pub trait PresencePayload: TryFrom<Element> + Into<Element> {}
|
pub trait PresencePayload: TryFrom<Element> + Into<Element> {}
|
||||||
|
|
@ -232,8 +232,9 @@ impl Presence {
|
||||||
|
|
||||||
/// Set the availability information of this presence.
|
/// Set the availability information of this presence.
|
||||||
pub fn set_status<L, S>(&mut self, lang: L, status: S)
|
pub fn set_status<L, S>(&mut self, lang: L, status: S)
|
||||||
where L: Into<Lang>,
|
where
|
||||||
S: Into<Status>,
|
L: Into<Lang>,
|
||||||
|
S: Into<Status>,
|
||||||
{
|
{
|
||||||
self.statuses.insert(lang.into(), status.into());
|
self.statuses.insert(lang.into(), status.into());
|
||||||
}
|
}
|
||||||
|
|
@ -310,27 +311,21 @@ impl From<Presence> for Element {
|
||||||
.attr("id", presence.id)
|
.attr("id", presence.id)
|
||||||
.attr("type", presence.type_)
|
.attr("type", presence.type_)
|
||||||
.append_all(presence.show.into_iter())
|
.append_all(presence.show.into_iter())
|
||||||
.append_all(
|
.append_all(presence.statuses.into_iter().map(|(lang, status)| {
|
||||||
presence
|
Element::builder("status")
|
||||||
.statuses
|
.attr(
|
||||||
.into_iter()
|
"xml:lang",
|
||||||
.map(|(lang, status)| {
|
match lang.as_ref() {
|
||||||
Element::builder("status")
|
"" => None,
|
||||||
.attr(
|
lang => Some(lang),
|
||||||
"xml:lang",
|
},
|
||||||
match lang.as_ref() {
|
)
|
||||||
"" => None,
|
.append(status)
|
||||||
lang => Some(lang),
|
}))
|
||||||
},
|
|
||||||
)
|
|
||||||
.append(status)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.append_all(if presence.priority == 0 {
|
.append_all(if presence.priority == 0 {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(Element::builder("priority")
|
Some(Element::builder("priority").append(format!("{}", presence.priority)))
|
||||||
.append(format!("{}", presence.priority)))
|
|
||||||
})
|
})
|
||||||
.append_all(presence.payloads.into_iter())
|
.append_all(presence.payloads.into_iter())
|
||||||
.build()
|
.build()
|
||||||
|
|
@ -409,9 +404,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_empty_show_value() {
|
fn test_empty_show_value() {
|
||||||
#[cfg(not(feature = "component"))]
|
#[cfg(not(feature = "component"))]
|
||||||
let elem: Element = "<presence xmlns='jabber:client'/>"
|
let elem: Element = "<presence xmlns='jabber:client'/>".parse().unwrap();
|
||||||
.parse()
|
|
||||||
.unwrap();
|
|
||||||
#[cfg(feature = "component")]
|
#[cfg(feature = "component")]
|
||||||
let elem: Element = "<presence xmlns='jabber:component:accept'/>"
|
let elem: Element = "<presence xmlns='jabber:component:accept'/>"
|
||||||
.parse()
|
.parse()
|
||||||
|
|
@ -623,8 +616,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_serialise_priority() {
|
fn test_serialise_priority() {
|
||||||
let presence = Presence::new(Type::None)
|
let presence = Presence::new(Type::None).with_priority(42);
|
||||||
.with_priority(42);
|
|
||||||
let elem: Element = presence.into();
|
let elem: Element = presence.into();
|
||||||
assert!(elem.is("presence", ns::DEFAULT_NS));
|
assert!(elem.is("presence", ns::DEFAULT_NS));
|
||||||
let priority = elem.children().next().unwrap();
|
let priority = elem.children().next().unwrap();
|
||||||
|
|
@ -638,23 +630,24 @@ mod tests {
|
||||||
let elem: Element = presence.into();
|
let elem: Element = presence.into();
|
||||||
assert_eq!(elem.attr("to"), None);
|
assert_eq!(elem.attr("to"), None);
|
||||||
|
|
||||||
let presence = Presence::new(Type::None)
|
let presence = Presence::new(Type::None).with_to(Jid::Bare(BareJid::domain("localhost")));
|
||||||
.with_to(Jid::Bare(BareJid::domain("localhost")));
|
|
||||||
let elem: Element = presence.into();
|
let elem: Element = presence.into();
|
||||||
assert_eq!(elem.attr("to"), Some("localhost"));
|
assert_eq!(elem.attr("to"), Some("localhost"));
|
||||||
|
|
||||||
let presence = Presence::new(Type::None)
|
let presence = Presence::new(Type::None).with_to(BareJid::domain("localhost"));
|
||||||
.with_to(BareJid::domain("localhost"));
|
|
||||||
let elem: Element = presence.into();
|
let elem: Element = presence.into();
|
||||||
assert_eq!(elem.attr("to"), Some("localhost"));
|
assert_eq!(elem.attr("to"), Some("localhost"));
|
||||||
|
|
||||||
let presence = Presence::new(Type::None)
|
let presence = Presence::new(Type::None).with_to(Jid::Full(FullJid::new(
|
||||||
.with_to(Jid::Full(FullJid::new("test", "localhost", "coucou")));
|
"test",
|
||||||
|
"localhost",
|
||||||
|
"coucou",
|
||||||
|
)));
|
||||||
let elem: Element = presence.into();
|
let elem: Element = presence.into();
|
||||||
assert_eq!(elem.attr("to"), Some("test@localhost/coucou"));
|
assert_eq!(elem.attr("to"), Some("test@localhost/coucou"));
|
||||||
|
|
||||||
let presence = Presence::new(Type::None)
|
let presence =
|
||||||
.with_to(FullJid::new("test", "localhost", "coucou"));
|
Presence::new(Type::None).with_to(FullJid::new("test", "localhost", "coucou"));
|
||||||
let elem: Element = presence.into();
|
let elem: Element = presence.into();
|
||||||
assert_eq!(elem.attr("to"), Some("test@localhost/coucou"));
|
assert_eq!(elem.attr("to"), Some("test@localhost/coucou"));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,11 @@
|
||||||
|
|
||||||
use crate::data_forms::DataForm;
|
use crate::data_forms::DataForm;
|
||||||
use crate::date::DateTime;
|
use crate::date::DateTime;
|
||||||
use crate::util::error::Error;
|
|
||||||
use crate::ns;
|
use crate::ns;
|
||||||
use crate::pubsub::{ItemId, NodeName, Subscription, SubscriptionId, Item as PubSubItem};
|
use crate::pubsub::{Item as PubSubItem, ItemId, NodeName, Subscription, SubscriptionId};
|
||||||
use jid::Jid;
|
use crate::util::error::Error;
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
|
use jid::Jid;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
/// Event wrapper for a PubSub `<item/>`.
|
/// Event wrapper for a PubSub `<item/>`.
|
||||||
|
|
@ -214,15 +214,11 @@ impl From<PubSubEvent> for Element {
|
||||||
PubSubEvent::RetractedItems { node, items } => Element::builder("items")
|
PubSubEvent::RetractedItems { node, items } => Element::builder("items")
|
||||||
.ns(ns::PUBSUB_EVENT)
|
.ns(ns::PUBSUB_EVENT)
|
||||||
.attr("node", node)
|
.attr("node", node)
|
||||||
.append_all(
|
.append_all(items.into_iter().map(|id| {
|
||||||
items
|
Element::builder("retract")
|
||||||
.into_iter()
|
.ns(ns::PUBSUB_EVENT)
|
||||||
.map(|id| {
|
.attr("id", id)
|
||||||
Element::builder("retract")
|
})),
|
||||||
.ns(ns::PUBSUB_EVENT)
|
|
||||||
.attr("id", id)
|
|
||||||
})
|
|
||||||
),
|
|
||||||
PubSubEvent::Purge { node } => Element::builder("purge")
|
PubSubEvent::Purge { node } => Element::builder("purge")
|
||||||
.ns(ns::PUBSUB_EVENT)
|
.ns(ns::PUBSUB_EVENT)
|
||||||
.attr("node", node),
|
.attr("node", node),
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ pub mod pubsub;
|
||||||
pub use self::event::PubSubEvent;
|
pub use self::event::PubSubEvent;
|
||||||
pub use self::pubsub::PubSub;
|
pub use self::pubsub::PubSub;
|
||||||
|
|
||||||
use crate::{Jid, Element};
|
use crate::{Element, Jid};
|
||||||
|
|
||||||
generate_id!(
|
generate_id!(
|
||||||
/// The name of a PubSub node, used to identify it on a JID.
|
/// The name of a PubSub node, used to identify it on a JID.
|
||||||
|
|
@ -63,7 +63,11 @@ pub struct Item {
|
||||||
|
|
||||||
impl Item {
|
impl Item {
|
||||||
/// Create a new item, accepting only payloads implementing `PubSubPayload`.
|
/// Create a new item, accepting only payloads implementing `PubSubPayload`.
|
||||||
pub fn new<P: PubSubPayload>(id: Option<ItemId>, publisher: Option<Jid>, payload: Option<P>) -> Item {
|
pub fn new<P: PubSubPayload>(
|
||||||
|
id: Option<ItemId>,
|
||||||
|
publisher: Option<Jid>,
|
||||||
|
payload: Option<P>,
|
||||||
|
) -> Item {
|
||||||
Item {
|
Item {
|
||||||
id,
|
id,
|
||||||
publisher,
|
publisher,
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,12 @@
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use crate::data_forms::DataForm;
|
use crate::data_forms::DataForm;
|
||||||
use crate::util::error::Error;
|
|
||||||
use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload};
|
use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload};
|
||||||
use crate::ns;
|
use crate::ns;
|
||||||
use crate::pubsub::{NodeName, Subscription, SubscriptionId, Item as PubSubItem};
|
use crate::pubsub::{Item as PubSubItem, NodeName, Subscription, SubscriptionId};
|
||||||
use jid::Jid;
|
use crate::util::error::Error;
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
|
use jid::Jid;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
// TODO: a better solution would be to split this into a query and a result elements, like for
|
// TODO: a better solution would be to split this into a query and a result elements, like for
|
||||||
|
|
|
||||||
|
|
@ -32,9 +32,9 @@ impl MessagePayload for Received {}
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::ns;
|
use crate::ns;
|
||||||
|
use crate::util::error::Error;
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use crate::util::error::Error;
|
|
||||||
|
|
||||||
#[cfg(target_pointer_width = "32")]
|
#[cfg(target_pointer_width = "32")]
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -95,8 +95,8 @@ mod tests {
|
||||||
use crate::util::compare_elements::NamespaceAwareCompare;
|
use crate::util::compare_elements::NamespaceAwareCompare;
|
||||||
use crate::util::error::Error;
|
use crate::util::error::Error;
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
use std::str::FromStr;
|
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[cfg(target_pointer_width = "32")]
|
#[cfg(target_pointer_width = "32")]
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use crate::util::error::Error;
|
|
||||||
use crate::ns;
|
use crate::ns;
|
||||||
|
use crate::util::error::Error;
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
|
@ -79,13 +79,12 @@ impl From<SetQuery> for Element {
|
||||||
}))
|
}))
|
||||||
.append_all(
|
.append_all(
|
||||||
set.after
|
set.after
|
||||||
.map(|after| Element::builder("after").ns(ns::RSM).append(after))
|
.map(|after| Element::builder("after").ns(ns::RSM).append(after)),
|
||||||
|
)
|
||||||
|
.append_all(
|
||||||
|
set.before
|
||||||
|
.map(|before| Element::builder("before").ns(ns::RSM).append(before)),
|
||||||
)
|
)
|
||||||
.append_all(set.before.map(|before| {
|
|
||||||
Element::builder("before")
|
|
||||||
.ns(ns::RSM)
|
|
||||||
.append(before)
|
|
||||||
}))
|
|
||||||
.append_all(set.index.map(|index| {
|
.append_all(set.index.map(|index| {
|
||||||
Element::builder("index")
|
Element::builder("index")
|
||||||
.ns(ns::RSM)
|
.ns(ns::RSM)
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
use crate::ns;
|
||||||
use crate::util::error::Error;
|
use crate::util::error::Error;
|
||||||
use crate::util::helpers::Base64;
|
use crate::util::helpers::Base64;
|
||||||
use crate::ns;
|
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
@ -203,17 +203,12 @@ impl From<Failure> for Element {
|
||||||
Element::builder("failure")
|
Element::builder("failure")
|
||||||
.ns(ns::SASL)
|
.ns(ns::SASL)
|
||||||
.append(failure.defined_condition)
|
.append(failure.defined_condition)
|
||||||
.append_all(
|
.append_all(failure.texts.into_iter().map(|(lang, text)| {
|
||||||
failure
|
Element::builder("text")
|
||||||
.texts
|
.ns(ns::SASL)
|
||||||
.into_iter()
|
.attr("xml:lang", lang)
|
||||||
.map(|(lang, text)| {
|
.append(text)
|
||||||
Element::builder("text")
|
}))
|
||||||
.ns(ns::SASL)
|
|
||||||
.attr("xml:lang", lang)
|
|
||||||
.append(text)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,12 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use crate::util::error::Error;
|
|
||||||
use crate::message::MessagePayload;
|
use crate::message::MessagePayload;
|
||||||
use crate::ns;
|
use crate::ns;
|
||||||
use crate::presence::PresencePayload;
|
use crate::presence::PresencePayload;
|
||||||
use jid::Jid;
|
use crate::util::error::Error;
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
|
use jid::Jid;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
|
@ -217,9 +217,15 @@ impl PresencePayload for StanzaError {}
|
||||||
|
|
||||||
impl StanzaError {
|
impl StanzaError {
|
||||||
/// Create a new `<error/>` with the according content.
|
/// Create a new `<error/>` with the according content.
|
||||||
pub fn new<L, T>(type_: ErrorType, defined_condition: DefinedCondition, lang: L, text: T) -> StanzaError
|
pub fn new<L, T>(
|
||||||
where L: Into<Lang>,
|
type_: ErrorType,
|
||||||
T: Into<String>,
|
defined_condition: DefinedCondition,
|
||||||
|
lang: L,
|
||||||
|
text: T,
|
||||||
|
) -> StanzaError
|
||||||
|
where
|
||||||
|
L: Into<Lang>,
|
||||||
|
T: Into<String>,
|
||||||
{
|
{
|
||||||
StanzaError {
|
StanzaError {
|
||||||
type_,
|
type_,
|
||||||
|
|
@ -294,14 +300,12 @@ impl From<StanzaError> for Element {
|
||||||
.attr("type", err.type_)
|
.attr("type", err.type_)
|
||||||
.attr("by", err.by)
|
.attr("by", err.by)
|
||||||
.append(err.defined_condition)
|
.append(err.defined_condition)
|
||||||
.append_all(
|
.append_all(err.texts.into_iter().map(|(lang, text)| {
|
||||||
err.texts.into_iter().map(|(lang, text)| {
|
Element::builder("text")
|
||||||
Element::builder("text")
|
.ns(ns::XMPP_STANZAS)
|
||||||
.ns(ns::XMPP_STANZAS)
|
.attr("xml:lang", lang)
|
||||||
.attr("xml:lang", lang)
|
.append(text)
|
||||||
.append(text)
|
}))
|
||||||
})
|
|
||||||
)
|
|
||||||
.append_all(err.other)
|
.append_all(err.other)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,8 +39,8 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::util::error::Error;
|
use crate::util::error::Error;
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
use std::str::FromStr;
|
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[cfg(target_pointer_width = "32")]
|
#[cfg(target_pointer_width = "32")]
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,20 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use chrono::FixedOffset;
|
|
||||||
use crate::date::DateTime;
|
use crate::date::DateTime;
|
||||||
use crate::iq::{IqGetPayload, IqResultPayload};
|
use crate::iq::{IqGetPayload, IqResultPayload};
|
||||||
use crate::ns;
|
use crate::ns;
|
||||||
use crate::util::error::Error;
|
use crate::util::error::Error;
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
|
use chrono::FixedOffset;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
generate_empty_element!(
|
generate_empty_element!(
|
||||||
/// An entity time query.
|
/// An entity time query.
|
||||||
TimeQuery, "time", TIME
|
TimeQuery,
|
||||||
|
"time",
|
||||||
|
TIME
|
||||||
);
|
);
|
||||||
|
|
||||||
impl IqGetPayload for TimeQuery {}
|
impl IqGetPayload for TimeQuery {}
|
||||||
|
|
@ -59,9 +61,7 @@ impl TryFrom<Element> for TimeResult {
|
||||||
}
|
}
|
||||||
utc = Some(date_time);
|
utc = Some(date_time);
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::ParseError(
|
return Err(Error::ParseError("Unknown child in time element."));
|
||||||
"Unknown child in time element.",
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -77,10 +77,11 @@ impl From<TimeResult> for Element {
|
||||||
fn from(time: TimeResult) -> Element {
|
fn from(time: TimeResult) -> Element {
|
||||||
Element::builder("time")
|
Element::builder("time")
|
||||||
.ns(ns::TIME)
|
.ns(ns::TIME)
|
||||||
.append(Element::builder("tzo")
|
.append(Element::builder("tzo").append(format!("{}", time.0.timezone())))
|
||||||
.append(format!("{}", time.0.timezone())))
|
.append(
|
||||||
.append(Element::builder("utc")
|
Element::builder("utc")
|
||||||
.append(time.0.with_timezone(FixedOffset::east(0)).format("%FT%TZ")))
|
.append(time.0.with_timezone(FixedOffset::east(0)).format("%FT%TZ")),
|
||||||
|
)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -105,7 +106,10 @@ mod tests {
|
||||||
let elem1 = elem.clone();
|
let elem1 = elem.clone();
|
||||||
let time = TimeResult::try_from(elem).unwrap();
|
let time = TimeResult::try_from(elem).unwrap();
|
||||||
assert_eq!(time.0.timezone(), FixedOffset::west(6 * 3600));
|
assert_eq!(time.0.timezone(), FixedOffset::west(6 * 3600));
|
||||||
assert_eq!(time.0, DateTime::from_str("2006-12-19T12:58:35-05:00").unwrap());
|
assert_eq!(
|
||||||
|
time.0,
|
||||||
|
DateTime::from_str("2006-12-19T12:58:35-05:00").unwrap()
|
||||||
|
);
|
||||||
let elem2 = Element::from(time);
|
let elem2 = Element::from(time);
|
||||||
assert_eq!(elem1, elem2);
|
assert_eq!(elem1, elem2);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,49 +4,63 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use crate::util::error::Error;
|
|
||||||
use crate::pubsub::PubSubPayload;
|
|
||||||
use crate::ns;
|
use crate::ns;
|
||||||
|
use crate::pubsub::PubSubPayload;
|
||||||
|
use crate::util::error::Error;
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
generate_elem_id!(
|
generate_elem_id!(
|
||||||
/// The artist or performer of the song or piece.
|
/// The artist or performer of the song or piece.
|
||||||
Artist, "artist", TUNE
|
Artist,
|
||||||
|
"artist",
|
||||||
|
TUNE
|
||||||
);
|
);
|
||||||
|
|
||||||
generate_elem_id!(
|
generate_elem_id!(
|
||||||
/// The duration of the song or piece in seconds.
|
/// The duration of the song or piece in seconds.
|
||||||
Length, "length", TUNE,
|
Length,
|
||||||
|
"length",
|
||||||
|
TUNE,
|
||||||
u16
|
u16
|
||||||
);
|
);
|
||||||
|
|
||||||
generate_elem_id!(
|
generate_elem_id!(
|
||||||
/// The user's rating of the song or piece, from 1 (lowest) to 10 (highest).
|
/// The user's rating of the song or piece, from 1 (lowest) to 10 (highest).
|
||||||
Rating, "rating", TUNE,
|
Rating,
|
||||||
|
"rating",
|
||||||
|
TUNE,
|
||||||
u8
|
u8
|
||||||
);
|
);
|
||||||
|
|
||||||
generate_elem_id!(
|
generate_elem_id!(
|
||||||
/// The collection (e.g., album) or other source (e.g., a band website that hosts streams or
|
/// The collection (e.g., album) or other source (e.g., a band website that hosts streams or
|
||||||
/// audio files).
|
/// audio files).
|
||||||
Source, "source", TUNE
|
Source,
|
||||||
|
"source",
|
||||||
|
TUNE
|
||||||
);
|
);
|
||||||
|
|
||||||
generate_elem_id!(
|
generate_elem_id!(
|
||||||
/// The title of the song or piece.
|
/// The title of the song or piece.
|
||||||
Title, "title", TUNE
|
Title,
|
||||||
|
"title",
|
||||||
|
TUNE
|
||||||
);
|
);
|
||||||
|
|
||||||
generate_elem_id!(
|
generate_elem_id!(
|
||||||
/// A unique identifier for the tune; e.g., the track number within a collection or the
|
/// A unique identifier for the tune; e.g., the track number within a collection or the
|
||||||
/// specific URI for the object (e.g., a stream or audio file).
|
/// specific URI for the object (e.g., a stream or audio file).
|
||||||
Track, "track", TUNE
|
Track,
|
||||||
|
"track",
|
||||||
|
TUNE
|
||||||
);
|
);
|
||||||
|
|
||||||
generate_elem_id!(
|
generate_elem_id!(
|
||||||
/// A URI or URL pointing to information about the song, collection, or artist.
|
/// A URI or URL pointing to information about the song, collection, or artist.
|
||||||
Uri, "uri", TUNE
|
Uri,
|
||||||
|
"uri",
|
||||||
|
TUNE
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Container for formatted text.
|
/// Container for formatted text.
|
||||||
|
|
@ -221,8 +235,14 @@ mod tests {
|
||||||
assert_eq!(tune.length, Some(Length(686)));
|
assert_eq!(tune.length, Some(Length(686)));
|
||||||
assert_eq!(tune.rating, Some(Rating(8)));
|
assert_eq!(tune.rating, Some(Rating(8)));
|
||||||
assert_eq!(tune.source, Some(Source::from_str("Yessongs").unwrap()));
|
assert_eq!(tune.source, Some(Source::from_str("Yessongs").unwrap()));
|
||||||
assert_eq!(tune.title, Some(Title::from_str("Heart of the Sunrise").unwrap()));
|
assert_eq!(
|
||||||
|
tune.title,
|
||||||
|
Some(Title::from_str("Heart of the Sunrise").unwrap())
|
||||||
|
);
|
||||||
assert_eq!(tune.track, Some(Track::from_str("3").unwrap()));
|
assert_eq!(tune.track, Some(Track::from_str("3").unwrap()));
|
||||||
assert_eq!(tune.uri, Some(Uri::from_str("http://www.yesworld.com/lyrics/Fragile.html#9").unwrap()));
|
assert_eq!(
|
||||||
|
tune.uri,
|
||||||
|
Some(Uri::from_str("http://www.yesworld.com/lyrics/Fragile.html#9").unwrap())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,10 @@ pub struct WhitespaceAwareBase64;
|
||||||
|
|
||||||
impl WhitespaceAwareBase64 {
|
impl WhitespaceAwareBase64 {
|
||||||
pub fn decode(s: &str) -> Result<Vec<u8>, Error> {
|
pub fn decode(s: &str) -> Result<Vec<u8>, Error> {
|
||||||
let s: String = s.chars().filter(|ch| *ch != ' ' && *ch != '\n' && *ch != '\t').collect();
|
let s: String = s
|
||||||
|
.chars()
|
||||||
|
.filter(|ch| *ch != ' ' && *ch != '\n' && *ch != '\t')
|
||||||
|
.collect();
|
||||||
Ok(base64::decode(&s)?)
|
Ok(base64::decode(&s)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ macro_rules! get_attr {
|
||||||
$attr,
|
$attr,
|
||||||
"' must not be empty."
|
"' must not be empty."
|
||||||
)));
|
)));
|
||||||
},
|
}
|
||||||
Some($value) => $func,
|
Some($value) => $func,
|
||||||
None => {
|
None => {
|
||||||
return Err(crate::util::error::Error::ParseError(concat!(
|
return Err(crate::util::error::Error::ParseError(concat!(
|
||||||
|
|
@ -601,41 +601,42 @@ macro_rules! generate_serialiser {
|
||||||
$builder.append(
|
$builder.append(
|
||||||
crate::Element::builder($name)
|
crate::Element::builder($name)
|
||||||
.ns(crate::ns::$ns)
|
.ns(crate::ns::$ns)
|
||||||
.append(::minidom::Node::Text($parent.$elem))
|
.append(::minidom::Node::Text($parent.$elem)),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
($builder:ident, $parent:ident, $elem:ident, Option, String, ($name:tt, $ns:ident)) => {
|
($builder:ident, $parent:ident, $elem:ident, Option, String, ($name:tt, $ns:ident)) => {
|
||||||
$builder.append_all($parent.$elem.map(|elem| {
|
$builder.append_all($parent.$elem.map(|elem| {
|
||||||
crate::Element::builder($name)
|
crate::Element::builder($name)
|
||||||
.ns(crate::ns::$ns)
|
.ns(crate::ns::$ns)
|
||||||
.append(::minidom::Node::Text(elem))
|
.append(::minidom::Node::Text(elem))
|
||||||
})
|
}))
|
||||||
)
|
|
||||||
};
|
};
|
||||||
($builder:ident, $parent:ident, $elem:ident, Option, $constructor:ident, ($name:tt, *)) => {
|
($builder:ident, $parent:ident, $elem:ident, Option, $constructor:ident, ($name:tt, *)) => {
|
||||||
$builder.append_all($parent.$elem.map(|elem| {
|
$builder.append_all($parent.$elem.map(|elem| {
|
||||||
crate::Element::builder($name)
|
crate::Element::builder($name)
|
||||||
.ns(elem.get_ns())
|
.ns(elem.get_ns())
|
||||||
.append(::minidom::Node::Element(crate::Element::from(elem)))
|
.append(::minidom::Node::Element(crate::Element::from(elem)))
|
||||||
})
|
}))
|
||||||
)
|
|
||||||
};
|
};
|
||||||
($builder:ident, $parent:ident, $elem:ident, Option, $constructor:ident, ($name:tt, $ns:ident)) => {
|
($builder:ident, $parent:ident, $elem:ident, Option, $constructor:ident, ($name:tt, $ns:ident)) => {
|
||||||
$builder.append_all($parent.$elem.map(|elem| {
|
$builder.append_all($parent.$elem.map(|elem| {
|
||||||
crate::Element::builder($name)
|
crate::Element::builder($name)
|
||||||
.ns(crate::ns::$ns)
|
.ns(crate::ns::$ns)
|
||||||
.append(::minidom::Node::Element(crate::Element::from(elem)))
|
.append(::minidom::Node::Element(crate::Element::from(elem)))
|
||||||
})
|
}))
|
||||||
)
|
|
||||||
};
|
};
|
||||||
($builder:ident, $parent:ident, $elem:ident, Vec, $constructor:ident, ($name:tt, $ns:ident)) => {
|
($builder:ident, $parent:ident, $elem:ident, Vec, $constructor:ident, ($name:tt, $ns:ident)) => {
|
||||||
$builder.append_all($parent.$elem.into_iter())
|
$builder.append_all($parent.$elem.into_iter())
|
||||||
};
|
};
|
||||||
($builder:ident, $parent:ident, $elem:ident, Present, $constructor:ident, ($name:tt, $ns:ident)) => {
|
($builder:ident, $parent:ident, $elem:ident, Present, $constructor:ident, ($name:tt, $ns:ident)) => {
|
||||||
$builder.append(::minidom::Node::Element(crate::Element::builder($name).ns(crate::ns::$ns).build()))
|
$builder.append(::minidom::Node::Element(
|
||||||
|
crate::Element::builder($name).ns(crate::ns::$ns).build(),
|
||||||
|
))
|
||||||
};
|
};
|
||||||
($builder:ident, $parent:ident, $elem:ident, $_:ident, $constructor:ident, ($name:tt, $ns:ident)) => {
|
($builder:ident, $parent:ident, $elem:ident, $_:ident, $constructor:ident, ($name:tt, $ns:ident)) => {
|
||||||
$builder.append(::minidom::Node::Element(crate::Element::from($parent.$elem)))
|
$builder.append(::minidom::Node::Element(crate::Element::from(
|
||||||
|
$parent.$elem,
|
||||||
|
)))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -804,5 +805,5 @@ macro_rules! impl_pubsub_item {
|
||||||
&mut self.0
|
&mut self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,12 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use crate::util::error::Error;
|
|
||||||
use crate::message::MessagePayload;
|
use crate::message::MessagePayload;
|
||||||
use crate::ns;
|
use crate::ns;
|
||||||
|
use crate::util::error::Error;
|
||||||
use minidom::{Element, Node};
|
use minidom::{Element, Node};
|
||||||
use std::convert::TryFrom;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
// TODO: Use a proper lang type.
|
// TODO: Use a proper lang type.
|
||||||
type Lang = String;
|
type Lang = String;
|
||||||
|
|
@ -51,15 +51,10 @@ impl XhtmlIm {
|
||||||
}
|
}
|
||||||
acc
|
acc
|
||||||
});
|
});
|
||||||
let body = Body {
|
let body = Body { children, ..body };
|
||||||
children,
|
|
||||||
..body
|
|
||||||
};
|
|
||||||
bodies.insert(lang, body);
|
bodies.insert(lang, body);
|
||||||
}
|
}
|
||||||
XhtmlIm {
|
XhtmlIm { bodies }
|
||||||
bodies,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,11 +74,16 @@ impl TryFrom<Element> for XhtmlIm {
|
||||||
let lang = match child.attr("xml:lang") {
|
let lang = match child.attr("xml:lang") {
|
||||||
Some(lang) => lang,
|
Some(lang) => lang,
|
||||||
None => "",
|
None => "",
|
||||||
}.to_string();
|
}
|
||||||
|
.to_string();
|
||||||
let body = Body::try_from(child)?;
|
let body = Body::try_from(child)?;
|
||||||
match bodies.insert(lang, body) {
|
match bodies.insert(lang, body) {
|
||||||
None => (),
|
None => (),
|
||||||
Some(_) => return Err(Error::ParseError("Two identical language bodies found in XHTML-IM."))
|
Some(_) => {
|
||||||
|
return Err(Error::ParseError(
|
||||||
|
"Two identical language bodies found in XHTML-IM.",
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::ParseError("Unknown element in XHTML-IM."));
|
return Err(Error::ParseError("Unknown element in XHTML-IM."));
|
||||||
|
|
@ -160,11 +160,15 @@ impl TryFrom<Element> for Body {
|
||||||
match child {
|
match child {
|
||||||
Node::Element(child) => children.push(Child::Tag(Tag::try_from(child.clone())?)),
|
Node::Element(child) => children.push(Child::Tag(Tag::try_from(child.clone())?)),
|
||||||
Node::Text(text) => children.push(Child::Text(text.clone())),
|
Node::Text(text) => children.push(Child::Text(text.clone())),
|
||||||
Node::Comment(_) => unimplemented!() // XXX: remove!
|
Node::Comment(_) => unimplemented!(), // XXX: remove!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Body { style: parse_css(elem.attr("style")), xml_lang: elem.attr("xml:lang").map(|xml_lang| xml_lang.to_string()), children })
|
Ok(Body {
|
||||||
|
style: parse_css(elem.attr("style")),
|
||||||
|
xml_lang: elem.attr("xml:lang").map(|xml_lang| xml_lang.to_string()),
|
||||||
|
children,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -181,39 +185,87 @@ impl From<Body> for Element {
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
enum Tag {
|
enum Tag {
|
||||||
A { href: Option<String>, style: Css, type_: Option<String>, children: Vec<Child> },
|
A {
|
||||||
Blockquote { style: Css, children: Vec<Child> },
|
href: Option<String>,
|
||||||
|
style: Css,
|
||||||
|
type_: Option<String>,
|
||||||
|
children: Vec<Child>,
|
||||||
|
},
|
||||||
|
Blockquote {
|
||||||
|
style: Css,
|
||||||
|
children: Vec<Child>,
|
||||||
|
},
|
||||||
Br,
|
Br,
|
||||||
Cite { style: Css, children: Vec<Child> },
|
Cite {
|
||||||
Em { children: Vec<Child> },
|
style: Css,
|
||||||
Img { src: Option<String>, alt: Option<String> }, // TODO: height, width, style
|
children: Vec<Child>,
|
||||||
Li { style: Css, children: Vec<Child> },
|
},
|
||||||
Ol { style: Css, children: Vec<Child> },
|
Em {
|
||||||
P { style: Css, children: Vec<Child> },
|
children: Vec<Child>,
|
||||||
Span { style: Css, children: Vec<Child> },
|
},
|
||||||
Strong { children: Vec<Child> },
|
Img {
|
||||||
Ul { style: Css, children: Vec<Child> },
|
src: Option<String>,
|
||||||
|
alt: Option<String>,
|
||||||
|
}, // TODO: height, width, style
|
||||||
|
Li {
|
||||||
|
style: Css,
|
||||||
|
children: Vec<Child>,
|
||||||
|
},
|
||||||
|
Ol {
|
||||||
|
style: Css,
|
||||||
|
children: Vec<Child>,
|
||||||
|
},
|
||||||
|
P {
|
||||||
|
style: Css,
|
||||||
|
children: Vec<Child>,
|
||||||
|
},
|
||||||
|
Span {
|
||||||
|
style: Css,
|
||||||
|
children: Vec<Child>,
|
||||||
|
},
|
||||||
|
Strong {
|
||||||
|
children: Vec<Child>,
|
||||||
|
},
|
||||||
|
Ul {
|
||||||
|
style: Css,
|
||||||
|
children: Vec<Child>,
|
||||||
|
},
|
||||||
Unknown(Vec<Child>),
|
Unknown(Vec<Child>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tag {
|
impl Tag {
|
||||||
fn to_html(self) -> String {
|
fn to_html(self) -> String {
|
||||||
match self {
|
match self {
|
||||||
Tag::A { href, style, type_, children } => {
|
Tag::A {
|
||||||
|
href,
|
||||||
|
style,
|
||||||
|
type_,
|
||||||
|
children,
|
||||||
|
} => {
|
||||||
let href = write_attr(href, "href");
|
let href = write_attr(href, "href");
|
||||||
let style = write_attr(get_style_string(style), "style");
|
let style = write_attr(get_style_string(style), "style");
|
||||||
let type_ = write_attr(type_, "type");
|
let type_ = write_attr(type_, "type");
|
||||||
format!("<a{}{}{}>{}</a>", href, style, type_, children_to_html(children))
|
format!(
|
||||||
},
|
"<a{}{}{}>{}</a>",
|
||||||
|
href,
|
||||||
|
style,
|
||||||
|
type_,
|
||||||
|
children_to_html(children)
|
||||||
|
)
|
||||||
|
}
|
||||||
Tag::Blockquote { style, children } => {
|
Tag::Blockquote { style, children } => {
|
||||||
let style = write_attr(get_style_string(style), "style");
|
let style = write_attr(get_style_string(style), "style");
|
||||||
format!("<blockquote{}>{}</blockquote>", style, children_to_html(children))
|
format!(
|
||||||
},
|
"<blockquote{}>{}</blockquote>",
|
||||||
|
style,
|
||||||
|
children_to_html(children)
|
||||||
|
)
|
||||||
|
}
|
||||||
Tag::Br => String::from("<br>"),
|
Tag::Br => String::from("<br>"),
|
||||||
Tag::Cite { style, children } => {
|
Tag::Cite { style, children } => {
|
||||||
let style = write_attr(get_style_string(style), "style");
|
let style = write_attr(get_style_string(style), "style");
|
||||||
format!("<cite{}>{}</cite>", style, children_to_html(children))
|
format!("<cite{}>{}</cite>", style, children_to_html(children))
|
||||||
},
|
}
|
||||||
Tag::Em { children } => format!("<em>{}</em>", children_to_html(children)),
|
Tag::Em { children } => format!("<em>{}</em>", children_to_html(children)),
|
||||||
Tag::Img { src, alt } => {
|
Tag::Img { src, alt } => {
|
||||||
let src = write_attr(src, "src");
|
let src = write_attr(src, "src");
|
||||||
|
|
@ -241,7 +293,9 @@ impl Tag {
|
||||||
let style = write_attr(get_style_string(style), "style");
|
let style = write_attr(get_style_string(style), "style");
|
||||||
format!("<ul{}>{}</ul>", style, children_to_html(children))
|
format!("<ul{}>{}</ul>", style, children_to_html(children))
|
||||||
}
|
}
|
||||||
Tag::Unknown(_) => panic!("No unknown element should be present in XHTML-IM after parsing."),
|
Tag::Unknown(_) => {
|
||||||
|
panic!("No unknown element should be present in XHTML-IM after parsing.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -255,23 +309,52 @@ impl TryFrom<Element> for Tag {
|
||||||
match child {
|
match child {
|
||||||
Node::Element(child) => children.push(Child::Tag(Tag::try_from(child.clone())?)),
|
Node::Element(child) => children.push(Child::Tag(Tag::try_from(child.clone())?)),
|
||||||
Node::Text(text) => children.push(Child::Text(text.clone())),
|
Node::Text(text) => children.push(Child::Text(text.clone())),
|
||||||
Node::Comment(_) => unimplemented!() // XXX: remove!
|
Node::Comment(_) => unimplemented!(), // XXX: remove!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(match elem.name() {
|
Ok(match elem.name() {
|
||||||
"a" => Tag::A { href: elem.attr("href").map(|href| href.to_string()), style: parse_css(elem.attr("style")), type_: elem.attr("type").map(|type_| type_.to_string()), children },
|
"a" => Tag::A {
|
||||||
"blockquote" => Tag::Blockquote { style: parse_css(elem.attr("style")), children },
|
href: elem.attr("href").map(|href| href.to_string()),
|
||||||
|
style: parse_css(elem.attr("style")),
|
||||||
|
type_: elem.attr("type").map(|type_| type_.to_string()),
|
||||||
|
children,
|
||||||
|
},
|
||||||
|
"blockquote" => Tag::Blockquote {
|
||||||
|
style: parse_css(elem.attr("style")),
|
||||||
|
children,
|
||||||
|
},
|
||||||
"br" => Tag::Br,
|
"br" => Tag::Br,
|
||||||
"cite" => Tag::Cite { style: parse_css(elem.attr("style")), children },
|
"cite" => Tag::Cite {
|
||||||
|
style: parse_css(elem.attr("style")),
|
||||||
|
children,
|
||||||
|
},
|
||||||
"em" => Tag::Em { children },
|
"em" => Tag::Em { children },
|
||||||
"img" => Tag::Img { src: elem.attr("src").map(|src| src.to_string()), alt: elem.attr("alt").map(|alt| alt.to_string()) },
|
"img" => Tag::Img {
|
||||||
"li" => Tag::Li { style: parse_css(elem.attr("style")), children },
|
src: elem.attr("src").map(|src| src.to_string()),
|
||||||
"ol" => Tag::Ol { style: parse_css(elem.attr("style")), children },
|
alt: elem.attr("alt").map(|alt| alt.to_string()),
|
||||||
"p" => Tag::P { style: parse_css(elem.attr("style")), children },
|
},
|
||||||
"span" => Tag::Span { style: parse_css(elem.attr("style")), children },
|
"li" => Tag::Li {
|
||||||
|
style: parse_css(elem.attr("style")),
|
||||||
|
children,
|
||||||
|
},
|
||||||
|
"ol" => Tag::Ol {
|
||||||
|
style: parse_css(elem.attr("style")),
|
||||||
|
children,
|
||||||
|
},
|
||||||
|
"p" => Tag::P {
|
||||||
|
style: parse_css(elem.attr("style")),
|
||||||
|
children,
|
||||||
|
},
|
||||||
|
"span" => Tag::Span {
|
||||||
|
style: parse_css(elem.attr("style")),
|
||||||
|
children,
|
||||||
|
},
|
||||||
"strong" => Tag::Strong { children },
|
"strong" => Tag::Strong { children },
|
||||||
"ul" => Tag::Ul { style: parse_css(elem.attr("style")), children },
|
"ul" => Tag::Ul {
|
||||||
|
style: parse_css(elem.attr("style")),
|
||||||
|
children,
|
||||||
|
},
|
||||||
_ => Tag::Unknown(children),
|
_ => Tag::Unknown(children),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -280,28 +363,45 @@ impl TryFrom<Element> for Tag {
|
||||||
impl From<Tag> for Element {
|
impl From<Tag> for Element {
|
||||||
fn from(tag: Tag) -> Element {
|
fn from(tag: Tag) -> Element {
|
||||||
let (name, attrs, children) = match tag {
|
let (name, attrs, children) = match tag {
|
||||||
Tag::A { href, style, type_, children } => ("a", {
|
Tag::A {
|
||||||
let mut attrs = vec![];
|
href,
|
||||||
if let Some(href) = href {
|
style,
|
||||||
attrs.push(("href", href));
|
type_,
|
||||||
}
|
children,
|
||||||
if let Some(style) = get_style_string(style) {
|
} => (
|
||||||
attrs.push(("style", style));
|
"a",
|
||||||
}
|
{
|
||||||
if let Some(type_) = type_ {
|
let mut attrs = vec![];
|
||||||
attrs.push(("type", type_));
|
if let Some(href) = href {
|
||||||
}
|
attrs.push(("href", href));
|
||||||
attrs
|
}
|
||||||
}, children),
|
if let Some(style) = get_style_string(style) {
|
||||||
Tag::Blockquote { style, children } => ("blockquote", match get_style_string(style) {
|
attrs.push(("style", style));
|
||||||
Some(style) => vec![("style", style)],
|
}
|
||||||
None => vec![],
|
if let Some(type_) = type_ {
|
||||||
}, children),
|
attrs.push(("type", type_));
|
||||||
|
}
|
||||||
|
attrs
|
||||||
|
},
|
||||||
|
children,
|
||||||
|
),
|
||||||
|
Tag::Blockquote { style, children } => (
|
||||||
|
"blockquote",
|
||||||
|
match get_style_string(style) {
|
||||||
|
Some(style) => vec![("style", style)],
|
||||||
|
None => vec![],
|
||||||
|
},
|
||||||
|
children,
|
||||||
|
),
|
||||||
Tag::Br => ("br", vec![], vec![]),
|
Tag::Br => ("br", vec![], vec![]),
|
||||||
Tag::Cite { style, children } => ("cite", match get_style_string(style) {
|
Tag::Cite { style, children } => (
|
||||||
Some(style) => vec![("style", style)],
|
"cite",
|
||||||
None => vec![],
|
match get_style_string(style) {
|
||||||
}, children),
|
Some(style) => vec![("style", style)],
|
||||||
|
None => vec![],
|
||||||
|
},
|
||||||
|
children,
|
||||||
|
),
|
||||||
Tag::Em { children } => ("em", vec![], children),
|
Tag::Em { children } => ("em", vec![], children),
|
||||||
Tag::Img { src, alt } => {
|
Tag::Img { src, alt } => {
|
||||||
let mut attrs = vec![];
|
let mut attrs = vec![];
|
||||||
|
|
@ -312,29 +412,51 @@ impl From<Tag> for Element {
|
||||||
attrs.push(("alt", alt));
|
attrs.push(("alt", alt));
|
||||||
}
|
}
|
||||||
("img", attrs, vec![])
|
("img", attrs, vec![])
|
||||||
},
|
}
|
||||||
Tag::Li { style, children } => ("li", match get_style_string(style) {
|
Tag::Li { style, children } => (
|
||||||
Some(style) => vec![("style", style)],
|
"li",
|
||||||
None => vec![],
|
match get_style_string(style) {
|
||||||
}, children),
|
Some(style) => vec![("style", style)],
|
||||||
Tag::Ol { style, children } => ("ol", match get_style_string(style) {
|
None => vec![],
|
||||||
Some(style) => vec![("style", style)],
|
},
|
||||||
None => vec![],
|
children,
|
||||||
}, children),
|
),
|
||||||
Tag::P { style, children } => ("p", match get_style_string(style) {
|
Tag::Ol { style, children } => (
|
||||||
Some(style) => vec![("style", style)],
|
"ol",
|
||||||
None => vec![],
|
match get_style_string(style) {
|
||||||
}, children),
|
Some(style) => vec![("style", style)],
|
||||||
Tag::Span { style, children } => ("span", match get_style_string(style) {
|
None => vec![],
|
||||||
Some(style) => vec![("style", style)],
|
},
|
||||||
None => vec![],
|
children,
|
||||||
}, children),
|
),
|
||||||
|
Tag::P { style, children } => (
|
||||||
|
"p",
|
||||||
|
match get_style_string(style) {
|
||||||
|
Some(style) => vec![("style", style)],
|
||||||
|
None => vec![],
|
||||||
|
},
|
||||||
|
children,
|
||||||
|
),
|
||||||
|
Tag::Span { style, children } => (
|
||||||
|
"span",
|
||||||
|
match get_style_string(style) {
|
||||||
|
Some(style) => vec![("style", style)],
|
||||||
|
None => vec![],
|
||||||
|
},
|
||||||
|
children,
|
||||||
|
),
|
||||||
Tag::Strong { children } => ("strong", vec![], children),
|
Tag::Strong { children } => ("strong", vec![], children),
|
||||||
Tag::Ul { style, children } => ("ul", match get_style_string(style) {
|
Tag::Ul { style, children } => (
|
||||||
Some(style) => vec![("style", style)],
|
"ul",
|
||||||
None => vec![],
|
match get_style_string(style) {
|
||||||
}, children),
|
Some(style) => vec![("style", style)],
|
||||||
Tag::Unknown(_) => panic!("No unknown element should be present in XHTML-IM after parsing."),
|
None => vec![],
|
||||||
|
},
|
||||||
|
children,
|
||||||
|
),
|
||||||
|
Tag::Unknown(_) => {
|
||||||
|
panic!("No unknown element should be present in XHTML-IM after parsing.")
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let mut builder = Element::builder(name)
|
let mut builder = Element::builder(name)
|
||||||
.ns(ns::XHTML)
|
.ns(ns::XHTML)
|
||||||
|
|
@ -354,7 +476,11 @@ fn children_to_nodes(children: Vec<Child>) -> impl IntoIterator<Item = Node> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn children_to_html(children: Vec<Child>) -> String {
|
fn children_to_html(children: Vec<Child>) -> String {
|
||||||
children.into_iter().map(|child| child.to_html()).collect::<Vec<_>>().concat()
|
children
|
||||||
|
.into_iter()
|
||||||
|
.map(|child| child.to_html())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.concat()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_attr(attr: Option<String>, name: &str) -> String {
|
fn write_attr(attr: Option<String>, name: &str) -> String {
|
||||||
|
|
@ -369,7 +495,10 @@ fn parse_css(style: Option<&str>) -> Css {
|
||||||
if let Some(style) = style {
|
if let Some(style) = style {
|
||||||
// TODO: make that parser a bit more resilient to things.
|
// TODO: make that parser a bit more resilient to things.
|
||||||
for part in style.split(";") {
|
for part in style.split(";") {
|
||||||
let mut part = part.splitn(2, ":").map(|a| a.to_string()).collect::<Vec<_>>();
|
let mut part = part
|
||||||
|
.splitn(2, ":")
|
||||||
|
.map(|a| a.to_string())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
let key = part.pop().unwrap();
|
let key = part.pop().unwrap();
|
||||||
let value = part.pop().unwrap();
|
let value = part.pop().unwrap();
|
||||||
properties.push(Property { key, value });
|
properties.push(Property { key, value });
|
||||||
|
|
@ -457,7 +586,7 @@ mod tests {
|
||||||
assert_eq!(style.len(), 0);
|
assert_eq!(style.len(), 0);
|
||||||
assert_eq!(children.len(), 1);
|
assert_eq!(children.len(), 1);
|
||||||
children
|
children
|
||||||
},
|
}
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
};
|
};
|
||||||
let text = match children.pop() {
|
let text = match children.pop() {
|
||||||
|
|
@ -502,14 +631,19 @@ mod tests {
|
||||||
fn generate_tree() {
|
fn generate_tree() {
|
||||||
let world = "world".to_string();
|
let world = "world".to_string();
|
||||||
|
|
||||||
Body { style: vec![], xml_lang: Some("en".to_string()), children: vec![
|
Body {
|
||||||
Child::Tag(Tag::P { style: vec![], children: vec![
|
style: vec![],
|
||||||
Child::Text("Hello ".to_string()),
|
xml_lang: Some("en".to_string()),
|
||||||
Child::Tag(Tag::Strong { children: vec![
|
children: vec![Child::Tag(Tag::P {
|
||||||
Child::Text(world),
|
style: vec![],
|
||||||
] }),
|
children: vec![
|
||||||
Child::Text("!".to_string()),
|
Child::Text("Hello ".to_string()),
|
||||||
] }),
|
Child::Tag(Tag::Strong {
|
||||||
] };
|
children: vec![Child::Text(world)],
|
||||||
|
}),
|
||||||
|
Child::Text("!".to_string()),
|
||||||
|
],
|
||||||
|
})],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@ use futures::prelude::*;
|
||||||
use std::env::args;
|
use std::env::args;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use tokio::runtime::current_thread::Runtime;
|
use tokio::runtime::current_thread::Runtime;
|
||||||
|
use xmpp::{ClientBuilder, ClientFeature, ClientType, Event};
|
||||||
use xmpp_parsers::{message::MessageType, Jid};
|
use xmpp_parsers::{message::MessageType, Jid};
|
||||||
use xmpp::{ClientBuilder, ClientType, ClientFeature, Event};
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let args: Vec<String> = args().collect();
|
let args: Vec<String> = args().collect();
|
||||||
|
|
@ -40,40 +40,46 @@ fn main() {
|
||||||
match evt {
|
match evt {
|
||||||
Event::Online => {
|
Event::Online => {
|
||||||
println!("Online.");
|
println!("Online.");
|
||||||
},
|
}
|
||||||
Event::Disconnected => {
|
Event::Disconnected => {
|
||||||
println!("Disconnected.");
|
println!("Disconnected.");
|
||||||
return Err(None);
|
return Err(None);
|
||||||
},
|
}
|
||||||
Event::ContactAdded(contact) => {
|
Event::ContactAdded(contact) => {
|
||||||
println!("Contact {} added.", contact.jid);
|
println!("Contact {} added.", contact.jid);
|
||||||
},
|
}
|
||||||
Event::ContactRemoved(contact) => {
|
Event::ContactRemoved(contact) => {
|
||||||
println!("Contact {} removed.", contact.jid);
|
println!("Contact {} removed.", contact.jid);
|
||||||
},
|
}
|
||||||
Event::ContactChanged(contact) => {
|
Event::ContactChanged(contact) => {
|
||||||
println!("Contact {} changed.", contact.jid);
|
println!("Contact {} changed.", contact.jid);
|
||||||
},
|
}
|
||||||
Event::JoinRoom(jid, conference) => {
|
Event::JoinRoom(jid, conference) => {
|
||||||
println!("Joining room {} ({:?})…", jid, conference.name);
|
println!("Joining room {} ({:?})…", jid, conference.name);
|
||||||
agent.join_room(jid, conference.nick, conference.password, "en", "Yet another bot!");
|
agent.join_room(
|
||||||
},
|
jid,
|
||||||
|
conference.nick,
|
||||||
|
conference.password,
|
||||||
|
"en",
|
||||||
|
"Yet another bot!",
|
||||||
|
);
|
||||||
|
}
|
||||||
Event::LeaveRoom(jid) => {
|
Event::LeaveRoom(jid) => {
|
||||||
println!("Leaving room {}…", jid);
|
println!("Leaving room {}…", jid);
|
||||||
},
|
}
|
||||||
Event::LeaveAllRooms => {
|
Event::LeaveAllRooms => {
|
||||||
println!("Leaving all rooms…");
|
println!("Leaving all rooms…");
|
||||||
},
|
}
|
||||||
Event::RoomJoined(jid) => {
|
Event::RoomJoined(jid) => {
|
||||||
println!("Joined room {}.", jid);
|
println!("Joined room {}.", jid);
|
||||||
agent.send_message(Jid::Bare(jid), MessageType::Groupchat, "en", "Hello world!");
|
agent.send_message(Jid::Bare(jid), MessageType::Groupchat, "en", "Hello world!");
|
||||||
},
|
}
|
||||||
Event::RoomLeft(jid) => {
|
Event::RoomLeft(jid) => {
|
||||||
println!("Left room {}.", jid);
|
println!("Left room {}.", jid);
|
||||||
},
|
}
|
||||||
Event::AvatarRetrieved(jid, path) => {
|
Event::AvatarRetrieved(jid, path) => {
|
||||||
println!("Received avatar for {} in {}.", jid, path);
|
println!("Received avatar for {} in {}.", jid, path);
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -6,33 +6,29 @@
|
||||||
|
|
||||||
#![deny(bare_trait_objects)]
|
#![deny(bare_trait_objects)]
|
||||||
|
|
||||||
use std::str::FromStr;
|
use futures::{sync::mpsc, Future, Sink, Stream};
|
||||||
use std::rc::Rc;
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use futures::{Future,Stream, Sink, sync::mpsc};
|
use std::rc::Rc;
|
||||||
use tokio_xmpp::{
|
use std::str::FromStr;
|
||||||
Client as TokioXmppClient,
|
use tokio_xmpp::{Client as TokioXmppClient, Event as TokioXmppEvent, Packet};
|
||||||
Event as TokioXmppEvent,
|
|
||||||
Packet,
|
|
||||||
};
|
|
||||||
use xmpp_parsers::{
|
use xmpp_parsers::{
|
||||||
bookmarks2::Conference,
|
bookmarks2::Conference,
|
||||||
caps::{compute_disco, hash_caps, Caps},
|
caps::{compute_disco, hash_caps, Caps},
|
||||||
disco::{DiscoInfoQuery, DiscoInfoResult, Feature, Identity},
|
disco::{DiscoInfoQuery, DiscoInfoResult, Feature, Identity},
|
||||||
hashes::Algo,
|
hashes::Algo,
|
||||||
iq::{Iq, IqType},
|
iq::{Iq, IqType},
|
||||||
message::{Message, MessageType, Body},
|
message::{Body, Message, MessageType},
|
||||||
muc::{
|
muc::{
|
||||||
Muc,
|
|
||||||
user::{MucUser, Status},
|
user::{MucUser, Status},
|
||||||
|
Muc,
|
||||||
},
|
},
|
||||||
ns,
|
ns,
|
||||||
presence::{Presence, Type as PresenceType},
|
presence::{Presence, Type as PresenceType},
|
||||||
pubsub::pubsub::{PubSub, Items},
|
pubsub::pubsub::{Items, PubSub},
|
||||||
roster::{Roster, Item as RosterItem},
|
roster::{Item as RosterItem, Roster},
|
||||||
stanza_error::{StanzaError, ErrorType, DefinedCondition},
|
stanza_error::{DefinedCondition, ErrorType, StanzaError},
|
||||||
Jid, BareJid, FullJid, JidParseError,
|
BareJid, FullJid, Jid, JidParseError,
|
||||||
};
|
};
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
|
|
@ -55,12 +51,10 @@ impl Default for ClientType {
|
||||||
|
|
||||||
impl ToString for ClientType {
|
impl ToString for ClientType {
|
||||||
fn to_string(&self) -> String {
|
fn to_string(&self) -> String {
|
||||||
String::from(
|
String::from(match self {
|
||||||
match self {
|
ClientType::Bot => "bot",
|
||||||
ClientType::Bot => "bot",
|
ClientType::Pc => "pc",
|
||||||
ClientType::Pc => "pc",
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -131,11 +125,13 @@ impl ClientBuilder<'_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_disco(&self) -> DiscoInfoResult {
|
fn make_disco(&self) -> DiscoInfoResult {
|
||||||
let identities = vec![Identity::new("client", self.disco.0.to_string(),
|
let identities = vec![Identity::new(
|
||||||
"en", self.disco.1.to_string())];
|
"client",
|
||||||
let mut features = vec![
|
self.disco.0.to_string(),
|
||||||
Feature::new(ns::DISCO_INFO),
|
"en",
|
||||||
];
|
self.disco.1.to_string(),
|
||||||
|
)];
|
||||||
|
let mut features = vec![Feature::new(ns::DISCO_INFO)];
|
||||||
#[cfg(feature = "avatars")]
|
#[cfg(feature = "avatars")]
|
||||||
{
|
{
|
||||||
if self.features.contains(&ClientFeature::Avatars) {
|
if self.features.contains(&ClientFeature::Avatars) {
|
||||||
|
|
@ -180,7 +176,7 @@ impl ClientBuilder<'_> {
|
||||||
) -> Result<(Agent, impl Stream<Item = Event, Error = tokio_xmpp::Error>), JidParseError>
|
) -> Result<(Agent, impl Stream<Item = Event, Error = tokio_xmpp::Error>), JidParseError>
|
||||||
where
|
where
|
||||||
S: Stream<Item = tokio_xmpp::Event, Error = tokio_xmpp::Error>
|
S: Stream<Item = tokio_xmpp::Event, Error = tokio_xmpp::Error>
|
||||||
+ Sink<SinkItem = tokio_xmpp::Packet, SinkError = tokio_xmpp::Error>,
|
+ Sink<SinkItem = tokio_xmpp::Packet, SinkError = tokio_xmpp::Error>,
|
||||||
{
|
{
|
||||||
let disco = self.make_disco();
|
let disco = self.make_disco();
|
||||||
let node = self.website;
|
let node = self.website;
|
||||||
|
|
@ -191,115 +187,151 @@ impl ClientBuilder<'_> {
|
||||||
let reader = {
|
let reader = {
|
||||||
let mut sender_tx = sender_tx.clone();
|
let mut sender_tx = sender_tx.clone();
|
||||||
let jid = self.jid.to_owned();
|
let jid = self.jid.to_owned();
|
||||||
stream.map(move |event| {
|
stream
|
||||||
// Helper function to send an iq error.
|
.map(move |event| {
|
||||||
let mut events = Vec::new();
|
// Helper function to send an iq error.
|
||||||
let send_error = |to, id, type_, condition, text: &str| {
|
let mut events = Vec::new();
|
||||||
let error = StanzaError::new(type_, condition, "en", text);
|
let send_error = |to, id, type_, condition, text: &str| {
|
||||||
let iq = Iq::from_error(id, error)
|
let error = StanzaError::new(type_, condition, "en", text);
|
||||||
.with_to(to)
|
let iq = Iq::from_error(id, error).with_to(to).into();
|
||||||
.into();
|
sender_tx.unbounded_send(Packet::Stanza(iq)).unwrap();
|
||||||
sender_tx.unbounded_send(Packet::Stanza(iq)).unwrap();
|
};
|
||||||
};
|
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
TokioXmppEvent::Online(_) => {
|
TokioXmppEvent::Online(_) => {
|
||||||
let presence = ClientBuilder::make_initial_presence(&disco, &node).into();
|
let presence =
|
||||||
let packet = Packet::Stanza(presence);
|
ClientBuilder::make_initial_presence(&disco, &node).into();
|
||||||
sender_tx.unbounded_send(packet)
|
let packet = Packet::Stanza(presence);
|
||||||
.unwrap();
|
sender_tx.unbounded_send(packet).unwrap();
|
||||||
events.push(Event::Online);
|
events.push(Event::Online);
|
||||||
// TODO: only send this when the ContactList feature is enabled.
|
// TODO: only send this when the ContactList feature is enabled.
|
||||||
let iq = Iq::from_get("roster", Roster { ver: None, items: vec![] })
|
let iq = Iq::from_get(
|
||||||
|
"roster",
|
||||||
|
Roster {
|
||||||
|
ver: None,
|
||||||
|
items: vec![],
|
||||||
|
},
|
||||||
|
)
|
||||||
.into();
|
.into();
|
||||||
sender_tx.unbounded_send(Packet::Stanza(iq)).unwrap();
|
sender_tx.unbounded_send(Packet::Stanza(iq)).unwrap();
|
||||||
// TODO: only send this when the JoinRooms feature is enabled.
|
// TODO: only send this when the JoinRooms feature is enabled.
|
||||||
let iq = Iq::from_get("bookmarks", PubSub::Items(Items::new(ns::BOOKMARKS2)))
|
let iq = Iq::from_get(
|
||||||
|
"bookmarks",
|
||||||
|
PubSub::Items(Items::new(ns::BOOKMARKS2)),
|
||||||
|
)
|
||||||
.into();
|
.into();
|
||||||
sender_tx.unbounded_send(Packet::Stanza(iq)).unwrap();
|
sender_tx.unbounded_send(Packet::Stanza(iq)).unwrap();
|
||||||
}
|
}
|
||||||
TokioXmppEvent::Disconnected => {
|
TokioXmppEvent::Disconnected => {
|
||||||
events.push(Event::Disconnected);
|
events.push(Event::Disconnected);
|
||||||
}
|
}
|
||||||
TokioXmppEvent::Stanza(stanza) => {
|
TokioXmppEvent::Stanza(stanza) => {
|
||||||
if stanza.is("iq", "jabber:client") {
|
if stanza.is("iq", "jabber:client") {
|
||||||
let iq = Iq::try_from(stanza).unwrap();
|
let iq = Iq::try_from(stanza).unwrap();
|
||||||
let from =
|
let from = iq
|
||||||
iq.from.clone().unwrap_or_else(|| Jid::from_str(&jid).unwrap());
|
.from
|
||||||
if let IqType::Get(payload) = iq.payload {
|
.clone()
|
||||||
if payload.is("query", ns::DISCO_INFO) {
|
.unwrap_or_else(|| Jid::from_str(&jid).unwrap());
|
||||||
let query = DiscoInfoQuery::try_from(payload);
|
if let IqType::Get(payload) = iq.payload {
|
||||||
match query {
|
if payload.is("query", ns::DISCO_INFO) {
|
||||||
Ok(query) => {
|
let query = DiscoInfoQuery::try_from(payload);
|
||||||
let mut disco_info = disco.clone();
|
match query {
|
||||||
disco_info.node = query.node;
|
Ok(query) => {
|
||||||
let iq = Iq::from_result(iq.id, Some(disco_info))
|
let mut disco_info = disco.clone();
|
||||||
.with_to(iq.from.unwrap())
|
disco_info.node = query.node;
|
||||||
.into();
|
let iq = Iq::from_result(iq.id, Some(disco_info))
|
||||||
sender_tx.unbounded_send(Packet::Stanza(iq)).unwrap();
|
.with_to(iq.from.unwrap())
|
||||||
},
|
.into();
|
||||||
Err(err) => {
|
sender_tx
|
||||||
send_error(iq.from.unwrap(), iq.id, ErrorType::Modify, DefinedCondition::BadRequest, &format!("{}", err));
|
.unbounded_send(Packet::Stanza(iq))
|
||||||
},
|
.unwrap();
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
send_error(
|
||||||
|
iq.from.unwrap(),
|
||||||
|
iq.id,
|
||||||
|
ErrorType::Modify,
|
||||||
|
DefinedCondition::BadRequest,
|
||||||
|
&format!("{}", err),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We MUST answer unhandled get iqs with a service-unavailable error.
|
||||||
|
send_error(
|
||||||
|
iq.from.unwrap(),
|
||||||
|
iq.id,
|
||||||
|
ErrorType::Cancel,
|
||||||
|
DefinedCondition::ServiceUnavailable,
|
||||||
|
"No handler defined for this kind of iq.",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else if let IqType::Result(Some(payload)) = iq.payload {
|
||||||
// We MUST answer unhandled get iqs with a service-unavailable error.
|
// TODO: move private iqs like this one somewhere else, for
|
||||||
send_error(iq.from.unwrap(), iq.id, ErrorType::Cancel, DefinedCondition::ServiceUnavailable, "No handler defined for this kind of iq.");
|
// security reasons.
|
||||||
}
|
if payload.is("query", ns::ROSTER) && iq.from.is_none() {
|
||||||
} else if let IqType::Result(Some(payload)) = iq.payload {
|
let roster = Roster::try_from(payload).unwrap();
|
||||||
// TODO: move private iqs like this one somewhere else, for
|
for item in roster.items.into_iter() {
|
||||||
// security reasons.
|
events.push(Event::ContactAdded(item));
|
||||||
if payload.is("query", ns::ROSTER) && iq.from.is_none() {
|
}
|
||||||
let roster = Roster::try_from(payload).unwrap();
|
} else if payload.is("pubsub", ns::PUBSUB) {
|
||||||
for item in roster.items.into_iter() {
|
let new_events = pubsub::handle_iq_result(&from, payload);
|
||||||
events.push(Event::ContactAdded(item));
|
events.extend(new_events);
|
||||||
}
|
}
|
||||||
} else if payload.is("pubsub", ns::PUBSUB) {
|
} else if let IqType::Set(_) = iq.payload {
|
||||||
let new_events = pubsub::handle_iq_result(&from, payload);
|
// We MUST answer unhandled set iqs with a service-unavailable error.
|
||||||
events.extend(new_events);
|
send_error(
|
||||||
|
iq.from.unwrap(),
|
||||||
|
iq.id,
|
||||||
|
ErrorType::Cancel,
|
||||||
|
DefinedCondition::ServiceUnavailable,
|
||||||
|
"No handler defined for this kind of iq.",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else if let IqType::Set(_) = iq.payload {
|
} else if stanza.is("message", "jabber:client") {
|
||||||
// We MUST answer unhandled set iqs with a service-unavailable error.
|
let message = Message::try_from(stanza).unwrap();
|
||||||
send_error(iq.from.unwrap(), iq.id, ErrorType::Cancel, DefinedCondition::ServiceUnavailable, "No handler defined for this kind of iq.");
|
let from = message.from.clone().unwrap();
|
||||||
}
|
for child in message.payloads {
|
||||||
} else if stanza.is("message", "jabber:client") {
|
if child.is("event", ns::PUBSUB_EVENT) {
|
||||||
let message = Message::try_from(stanza).unwrap();
|
let new_events =
|
||||||
let from = message.from.clone().unwrap();
|
pubsub::handle_event(&from, child, &mut sender_tx);
|
||||||
for child in message.payloads {
|
events.extend(new_events);
|
||||||
if child.is("event", ns::PUBSUB_EVENT) {
|
}
|
||||||
let new_events = pubsub::handle_event(&from, child, &mut sender_tx);
|
|
||||||
events.extend(new_events);
|
|
||||||
}
|
}
|
||||||
}
|
} else if stanza.is("presence", "jabber:client") {
|
||||||
} else if stanza.is("presence", "jabber:client") {
|
let presence = Presence::try_from(stanza).unwrap();
|
||||||
let presence = Presence::try_from(stanza).unwrap();
|
let from: BareJid = match presence.from.clone().unwrap() {
|
||||||
let from: BareJid = match presence.from.clone().unwrap() {
|
Jid::Full(FullJid { node, domain, .. }) => {
|
||||||
Jid::Full(FullJid { node, domain, .. }) => BareJid { node, domain },
|
BareJid { node, domain }
|
||||||
Jid::Bare(bare) => bare,
|
}
|
||||||
};
|
Jid::Bare(bare) => bare,
|
||||||
for payload in presence.payloads.into_iter() {
|
|
||||||
let muc_user = match MucUser::try_from(payload) {
|
|
||||||
Ok(muc_user) => muc_user,
|
|
||||||
_ => continue
|
|
||||||
};
|
};
|
||||||
for status in muc_user.status.into_iter() {
|
for payload in presence.payloads.into_iter() {
|
||||||
if status == Status::SelfPresence {
|
let muc_user = match MucUser::try_from(payload) {
|
||||||
events.push(Event::RoomJoined(from.clone()));
|
Ok(muc_user) => muc_user,
|
||||||
break;
|
_ => continue,
|
||||||
|
};
|
||||||
|
for status in muc_user.status.into_iter() {
|
||||||
|
if status == Status::SelfPresence {
|
||||||
|
events.push(Event::RoomJoined(from.clone()));
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if stanza.is("error", "http://etherx.jabber.org/streams") {
|
||||||
|
println!(
|
||||||
|
"Received a fatal stream error: {}",
|
||||||
|
String::from(&stanza)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
panic!("Unknown stanza: {}", String::from(&stanza));
|
||||||
}
|
}
|
||||||
} else if stanza.is("error", "http://etherx.jabber.org/streams") {
|
|
||||||
println!("Received a fatal stream error: {}", String::from(&stanza));
|
|
||||||
} else {
|
|
||||||
panic!("Unknown stanza: {}", String::from(&stanza));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
futures::stream::iter_ok(events)
|
futures::stream::iter_ok(events)
|
||||||
})
|
})
|
||||||
.flatten()
|
.flatten()
|
||||||
};
|
};
|
||||||
|
|
||||||
let sender = sender_rx
|
let sender = sender_rx
|
||||||
|
|
@ -335,8 +367,14 @@ pub struct Agent {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Agent {
|
impl Agent {
|
||||||
pub fn join_room(&mut self, room: BareJid, nick: Option<String>, password: Option<String>,
|
pub fn join_room(
|
||||||
lang: &str, status: &str) {
|
&mut self,
|
||||||
|
room: BareJid,
|
||||||
|
nick: Option<String>,
|
||||||
|
password: Option<String>,
|
||||||
|
lang: &str,
|
||||||
|
status: &str,
|
||||||
|
) {
|
||||||
let mut muc = Muc::new();
|
let mut muc = Muc::new();
|
||||||
if let Some(password) = password {
|
if let Some(password) = password {
|
||||||
muc = muc.with_password(password);
|
muc = muc.with_password(password);
|
||||||
|
|
@ -344,32 +382,35 @@ impl Agent {
|
||||||
|
|
||||||
let nick = nick.unwrap_or_else(|| self.default_nick.borrow().clone());
|
let nick = nick.unwrap_or_else(|| self.default_nick.borrow().clone());
|
||||||
let room_jid = room.with_resource(nick);
|
let room_jid = room.with_resource(nick);
|
||||||
let mut presence = Presence::new(PresenceType::None)
|
let mut presence = Presence::new(PresenceType::None).with_to(Jid::Full(room_jid));
|
||||||
.with_to(Jid::Full(room_jid));
|
|
||||||
presence.add_payload(muc);
|
presence.add_payload(muc);
|
||||||
presence.set_status(String::from(lang), String::from(status));
|
presence.set_status(String::from(lang), String::from(status));
|
||||||
let presence = presence.into();
|
let presence = presence.into();
|
||||||
self.sender_tx.unbounded_send(Packet::Stanza(presence))
|
self.sender_tx
|
||||||
|
.unbounded_send(Packet::Stanza(presence))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_message(&mut self, recipient: Jid, type_: MessageType, lang: &str, text: &str) {
|
pub fn send_message(&mut self, recipient: Jid, type_: MessageType, lang: &str, text: &str) {
|
||||||
let mut message = Message::new(Some(recipient));
|
let mut message = Message::new(Some(recipient));
|
||||||
message.type_ = type_;
|
message.type_ = type_;
|
||||||
message.bodies.insert(String::from(lang), Body(String::from(text)));
|
message
|
||||||
|
.bodies
|
||||||
|
.insert(String::from(lang), Body(String::from(text)));
|
||||||
let message = message.into();
|
let message = message.into();
|
||||||
self.sender_tx.unbounded_send(Packet::Stanza(message))
|
self.sender_tx
|
||||||
|
.unbounded_send(Packet::Stanza(message))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use super::{Agent, ClientBuilder, ClientFeature, ClientType, Event};
|
||||||
use futures::prelude::*;
|
use futures::prelude::*;
|
||||||
use tokio_xmpp::Client as TokioXmppClient;
|
|
||||||
use tokio::runtime::current_thread::Runtime;
|
|
||||||
use super::{Agent, ClientBuilder, ClientType, ClientFeature, Event};
|
|
||||||
use futures::sync::mpsc;
|
use futures::sync::mpsc;
|
||||||
|
use tokio::runtime::current_thread::Runtime;
|
||||||
|
use tokio_xmpp::Client as TokioXmppClient;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_simple() {
|
fn test_simple() {
|
||||||
|
|
@ -386,8 +427,7 @@ mod tests {
|
||||||
.enable_feature(ClientFeature::Avatars)
|
.enable_feature(ClientFeature::Avatars)
|
||||||
.enable_feature(ClientFeature::ContactList);
|
.enable_feature(ClientFeature::ContactList);
|
||||||
|
|
||||||
let (_agent, stream): (Agent, _) =
|
let (_agent, stream): (Agent, _) = client_builder
|
||||||
client_builder
|
|
||||||
.build_impl(client, sender_tx.clone(), sender_rx)
|
.build_impl(client, sender_tx.clone(), sender_rx)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ use std::io::{self, Write};
|
||||||
use tokio_xmpp::Packet;
|
use tokio_xmpp::Packet;
|
||||||
use xmpp_parsers::{
|
use xmpp_parsers::{
|
||||||
avatar::{Data, Metadata},
|
avatar::{Data, Metadata},
|
||||||
|
hashes::Hash,
|
||||||
iq::Iq,
|
iq::Iq,
|
||||||
ns,
|
ns,
|
||||||
pubsub::{
|
pubsub::{
|
||||||
|
|
@ -19,7 +20,6 @@ use xmpp_parsers::{
|
||||||
pubsub::{Items, PubSub},
|
pubsub::{Items, PubSub},
|
||||||
NodeName,
|
NodeName,
|
||||||
},
|
},
|
||||||
hashes::Hash,
|
|
||||||
Jid,
|
Jid,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -32,7 +32,11 @@ fn hash_to_hex(hash: &Hash) -> String {
|
||||||
bytes.join("")
|
bytes.join("")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn handle_metadata_pubsub_event(from: &Jid, tx: &mut mpsc::UnboundedSender<Packet>, items: Vec<Item>) -> impl IntoIterator<Item = Event> {
|
pub(crate) fn handle_metadata_pubsub_event(
|
||||||
|
from: &Jid,
|
||||||
|
tx: &mut mpsc::UnboundedSender<Packet>,
|
||||||
|
items: Vec<Item>,
|
||||||
|
) -> impl IntoIterator<Item = Event> {
|
||||||
let mut events = Vec::new();
|
let mut events = Vec::new();
|
||||||
for item in items {
|
for item in items {
|
||||||
let payload = item.payload.clone().unwrap();
|
let payload = item.payload.clone().unwrap();
|
||||||
|
|
@ -58,12 +62,15 @@ pub(crate) fn handle_metadata_pubsub_event(from: &Jid, tx: &mut mpsc::UnboundedS
|
||||||
}
|
}
|
||||||
|
|
||||||
fn download_avatar(from: &Jid) -> Iq {
|
fn download_avatar(from: &Jid) -> Iq {
|
||||||
Iq::from_get("coucou", PubSub::Items(Items {
|
Iq::from_get(
|
||||||
max_items: None,
|
"coucou",
|
||||||
node: NodeName(String::from(ns::AVATAR_DATA)),
|
PubSub::Items(Items {
|
||||||
subid: None,
|
max_items: None,
|
||||||
items: Vec::new(),
|
node: NodeName(String::from(ns::AVATAR_DATA)),
|
||||||
}))
|
subid: None,
|
||||||
|
items: Vec::new(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
.with_to(from.clone())
|
.with_to(from.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,21 +10,21 @@ use std::convert::TryFrom;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use tokio_xmpp::Packet;
|
use tokio_xmpp::Packet;
|
||||||
use xmpp_parsers::{
|
use xmpp_parsers::{
|
||||||
Jid, BareJid,
|
bookmarks2::{Autojoin, Conference},
|
||||||
Element,
|
|
||||||
ns,
|
ns,
|
||||||
bookmarks2::{
|
|
||||||
Autojoin,
|
|
||||||
Conference,
|
|
||||||
},
|
|
||||||
pubsub::event::PubSubEvent,
|
pubsub::event::PubSubEvent,
|
||||||
pubsub::pubsub::PubSub,
|
pubsub::pubsub::PubSub,
|
||||||
|
BareJid, Element, Jid,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "avatars")]
|
#[cfg(feature = "avatars")]
|
||||||
pub(crate) mod avatar;
|
pub(crate) mod avatar;
|
||||||
|
|
||||||
pub(crate) fn handle_event(from: &Jid, elem: Element, mut tx: &mut mpsc::UnboundedSender<Packet>) -> impl IntoIterator<Item = Event> {
|
pub(crate) fn handle_event(
|
||||||
|
from: &Jid,
|
||||||
|
elem: Element,
|
||||||
|
mut tx: &mut mpsc::UnboundedSender<Packet>,
|
||||||
|
) -> impl IntoIterator<Item = Event> {
|
||||||
let mut events = Vec::new();
|
let mut events = Vec::new();
|
||||||
let event = PubSubEvent::try_from(elem);
|
let event = PubSubEvent::try_from(elem);
|
||||||
trace!("PubSub event: {:#?}", event);
|
trace!("PubSub event: {:#?}", event);
|
||||||
|
|
@ -35,7 +35,7 @@ pub(crate) fn handle_event(from: &Jid, elem: Element, mut tx: &mut mpsc::Unbound
|
||||||
ref node if node == ns::AVATAR_METADATA => {
|
ref node if node == ns::AVATAR_METADATA => {
|
||||||
let new_events = avatar::handle_metadata_pubsub_event(&from, &mut tx, items);
|
let new_events = avatar::handle_metadata_pubsub_event(&from, &mut tx, items);
|
||||||
events.extend(new_events);
|
events.extend(new_events);
|
||||||
},
|
}
|
||||||
ref node if node == ns::BOOKMARKS2 => {
|
ref node if node == ns::BOOKMARKS2 => {
|
||||||
// TODO: Check that our bare JID is the sender.
|
// TODO: Check that our bare JID is the sender.
|
||||||
assert_eq!(items.len(), 1);
|
assert_eq!(items.len(), 1);
|
||||||
|
|
@ -49,11 +49,11 @@ pub(crate) fn handle_event(from: &Jid, elem: Element, mut tx: &mut mpsc::Unbound
|
||||||
} else {
|
} else {
|
||||||
events.push(Event::LeaveRoom(jid));
|
events.push(Event::LeaveRoom(jid));
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Err(err) => println!("not bookmark: {}", err)
|
Err(err) => println!("not bookmark: {}", err),
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
ref node => unimplemented!("node {}", node)
|
ref node => unimplemented!("node {}", node),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(PubSubEvent::RetractedItems { node, items }) => {
|
Ok(PubSubEvent::RetractedItems { node, items }) => {
|
||||||
|
|
@ -64,8 +64,8 @@ pub(crate) fn handle_event(from: &Jid, elem: Element, mut tx: &mut mpsc::Unbound
|
||||||
let item = items.clone().pop().unwrap();
|
let item = items.clone().pop().unwrap();
|
||||||
let jid = BareJid::from_str(&item.0).unwrap();
|
let jid = BareJid::from_str(&item.0).unwrap();
|
||||||
events.push(Event::LeaveRoom(jid));
|
events.push(Event::LeaveRoom(jid));
|
||||||
},
|
}
|
||||||
ref node => unimplemented!("node {}", node)
|
ref node => unimplemented!("node {}", node),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(PubSubEvent::Purge { node }) => {
|
Ok(PubSubEvent::Purge { node }) => {
|
||||||
|
|
@ -73,11 +73,11 @@ pub(crate) fn handle_event(from: &Jid, elem: Element, mut tx: &mut mpsc::Unbound
|
||||||
ref node if node == ns::BOOKMARKS2 => {
|
ref node if node == ns::BOOKMARKS2 => {
|
||||||
// TODO: Check that our bare JID is the sender.
|
// TODO: Check that our bare JID is the sender.
|
||||||
events.push(Event::LeaveAllRooms);
|
events.push(Event::LeaveAllRooms);
|
||||||
},
|
}
|
||||||
ref node => unimplemented!("node {}", node)
|
ref node => unimplemented!("node {}", node),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => unimplemented!()
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
events
|
events
|
||||||
}
|
}
|
||||||
|
|
@ -92,7 +92,7 @@ pub(crate) fn handle_iq_result(from: &Jid, elem: Element) -> impl IntoIterator<I
|
||||||
ref node if node == ns::AVATAR_DATA => {
|
ref node if node == ns::AVATAR_DATA => {
|
||||||
let new_events = avatar::handle_data_pubsub_iq(&from, &items);
|
let new_events = avatar::handle_data_pubsub_iq(&from, &items);
|
||||||
events.extend(new_events);
|
events.extend(new_events);
|
||||||
},
|
}
|
||||||
ref node if node == ns::BOOKMARKS2 => {
|
ref node if node == ns::BOOKMARKS2 => {
|
||||||
events.push(Event::LeaveAllRooms);
|
events.push(Event::LeaveAllRooms);
|
||||||
for item in items.items {
|
for item in items.items {
|
||||||
|
|
@ -104,12 +104,12 @@ pub(crate) fn handle_iq_result(from: &Jid, elem: Element) -> impl IntoIterator<I
|
||||||
if let Autojoin::True = conference.autojoin {
|
if let Autojoin::True = conference.autojoin {
|
||||||
events.push(Event::JoinRoom(jid, conference));
|
events.push(Event::JoinRoom(jid, conference));
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Err(err) => panic!("Wrong payload type in bookmarks 2 item: {}", err),
|
Err(err) => panic!("Wrong payload type in bookmarks 2 item: {}", err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
_ => unimplemented!()
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
events
|
events
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue