diff options
author | cel 🌸 <cel@blos.sm> | 2023-10-20 04:51:56 +0100 |
---|---|---|
committer | cel 🌸 <cel@blos.sm> | 2023-10-20 04:51:56 +0100 |
commit | ba94ee66fafbabd63d6d1ed5edf435d4c46c6796 (patch) | |
tree | fe1bebc35914941b5c4fbd6f0286f4c9f8916154 | |
parent | 2536fa4937f0283b4187142cc6cede8e1dbfafa8 (diff) | |
download | luz-ba94ee66fafbabd63d6d1ed5edf435d4c46c6796.tar.gz luz-ba94ee66fafbabd63d6d1ed5edf435d4c46c6796.tar.bz2 luz-ba94ee66fafbabd63d6d1ed5edf435d4c46c6796.zip |
WIP: refactor to parse incoming stream as state machine
-rw-r--r-- | Cargo.toml | 5 | ||||
-rw-r--r-- | src/client/encrypted.rs | 16 | ||||
-rw-r--r-- | src/client/mod.rs | 10 | ||||
-rw-r--r-- | src/client/unencrypted.rs | 199 | ||||
-rw-r--r-- | src/jabber.rs | 18 | ||||
-rw-r--r-- | src/jid.rs (renamed from src/jid/mod.rs) | 19 | ||||
-rw-r--r-- | src/lib.rs | 25 | ||||
-rw-r--r-- | src/stanza/bind.rs | 47 | ||||
-rw-r--r-- | src/stanza/iq.rs | 169 | ||||
-rw-r--r-- | src/stanza/message.rs | 1 | ||||
-rw-r--r-- | src/stanza/mod.rs | 656 | ||||
-rw-r--r-- | src/stanza/presence.rs | 1 | ||||
-rw-r--r-- | src/stanza/sasl.rs | 144 | ||||
-rw-r--r-- | src/stanza/starttls.rs | 1 | ||||
-rw-r--r-- | src/stanza/stream.rs | 226 |
15 files changed, 241 insertions, 1296 deletions
@@ -10,9 +10,12 @@ edition = "2021" async-recursion = "1.0.4" async-trait = "0.1.68" nanoid = "0.4.0" -quick-xml = { git = "https://github.com/tafia/quick-xml.git", features = ["async-tokio"] } +quick-xml = { git = "https://github.com/tafia/quick-xml.git", features = ["async-tokio", "serialize"] } # TODO: remove unneeded features rsasl = { version = "2", default_features = true, features = ["provider_base64", "plain", "config_builder"] } +serde = "1.0.180" tokio = { version = "1.28", features = ["full"] } tokio-native-tls = "0.3.1" +tracing = "0.1.40" trust-dns-resolver = "0.22.0" +try_map = "0.3.1" diff --git a/src/client/encrypted.rs b/src/client/encrypted.rs index 47b2b2c..263d5ff 100644 --- a/src/client/encrypted.rs +++ b/src/client/encrypted.rs @@ -2,36 +2,26 @@ use std::{collections::BTreeMap, str}; use quick_xml::{ events::{BytesDecl, Event}, - Reader, Writer, + NsReader, Writer, }; use rsasl::prelude::{Mechname, SASLClient}; use tokio::io::{BufReader, ReadHalf, WriteHalf}; use tokio::net::TcpStream; use tokio_native_tls::TlsStream; -use crate::stanza::{ - bind::Bind, - iq::IQ, - sasl::{Challenge, Success}, - Element, -}; -use crate::stanza::{ - sasl::{Auth, Response}, - stream::{Stream, StreamFeature}, -}; use crate::Jabber; use crate::JabberError; use crate::Result; pub struct JabberClient<'j> { - pub reader: Reader<BufReader<ReadHalf<TlsStream<TcpStream>>>>, + pub reader: NsReader<BufReader<ReadHalf<TlsStream<TcpStream>>>>, pub writer: Writer<WriteHalf<TlsStream<TcpStream>>>, jabber: &'j mut Jabber<'j>, } impl<'j> JabberClient<'j> { pub fn new( - reader: Reader<BufReader<ReadHalf<TlsStream<TcpStream>>>>, + reader: NsReader<BufReader<ReadHalf<TlsStream<TcpStream>>>>, writer: Writer<WriteHalf<TlsStream<TcpStream>>>, jabber: &'j mut Jabber<'j>, ) -> Self { diff --git a/src/client/mod.rs b/src/client/mod.rs index 280e0a1..01df4a4 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -1,22 +1,24 @@ -pub mod encrypted; +// pub mod encrypted; pub mod unencrypted; // use async_trait::async_trait; -use crate::stanza::stream::StreamFeature; +// use crate::stanza::stream::StreamFeature; use crate::JabberError; use crate::Result; pub enum JabberClientType<'j> { - Encrypted(encrypted::JabberClient<'j>), + // Encrypted(encrypted::JabberClient<'j>), Unencrypted(unencrypted::JabberClient<'j>), } impl<'j> JabberClientType<'j> { + /// ensures an encrypted jabber client pub async fn ensure_tls(self) -> Result<encrypted::JabberClient<'j>> { match self { Self::Encrypted(c) => Ok(c), Self::Unencrypted(mut c) => { + c.start_stream().await?; let features = c.get_features().await?; if features.contains(&StreamFeature::StartTls) { Ok(c.starttls().await?) @@ -28,7 +30,7 @@ impl<'j> JabberClientType<'j> { } } -// TODO: jabber client trait over both client types +// TODO: jabber client trait over both client types using macro // #[async_trait] // pub trait JabberTrait { // async fn start_stream(&mut self) -> Result<()>; diff --git a/src/client/unencrypted.rs b/src/client/unencrypted.rs index 27b0a5f..4aa9c63 100644 --- a/src/client/unencrypted.rs +++ b/src/client/unencrypted.rs @@ -1,27 +1,30 @@ +use std::str; + use quick_xml::{ - events::{BytesDecl, BytesStart, Event}, + events::{BytesStart, Event}, name::QName, - Reader, Writer, + se, NsReader, Writer, }; use tokio::io::{BufReader, ReadHalf, WriteHalf}; use tokio::net::TcpStream; use tokio_native_tls::native_tls::TlsConnector; +use try_map::FallibleMapExt; -use crate::stanza::stream::StreamFeature; -use crate::stanza::Element; +use crate::error::JabberError; +use crate::stanza::stream::Stream; +use crate::stanza::DECLARATION; use crate::Jabber; use crate::Result; -use crate::{error::JabberError, stanza::stream::Stream}; pub struct JabberClient<'j> { - reader: Reader<BufReader<ReadHalf<TcpStream>>>, + reader: NsReader<BufReader<ReadHalf<TcpStream>>>, writer: Writer<WriteHalf<TcpStream>>, jabber: &'j mut Jabber<'j>, } impl<'j> JabberClient<'j> { pub fn new( - reader: Reader<BufReader<ReadHalf<TcpStream>>>, + reader: NsReader<BufReader<ReadHalf<TcpStream>>>, writer: Writer<WriteHalf<TcpStream>>, jabber: &'j mut Jabber<'j>, ) -> Self { @@ -34,60 +37,144 @@ impl<'j> JabberClient<'j> { pub async fn start_stream(&mut self) -> Result<()> { // client to server - let declaration = BytesDecl::new("1.0", None, None); + + // declaration + self.writer.write_event_async(DECLARATION).await?; + + // opening stream element let server = &self.jabber.server.to_owned().try_into()?; - let stream_element = - Stream::new_client(&self.jabber.jid, server, None, Some("en".to_string())); - self.writer - .write_event_async(Event::Decl(declaration)) - .await?; - let stream_element: Element<'_> = stream_element.into(); - stream_element.write_start(&mut self.writer).await?; - // server to client - let mut buf = Vec::new(); - self.reader.read_event_into_async(&mut buf).await?; - let _stream_response = Element::read_start(&mut self.reader).await?; - Ok(()) - } + let stream_element = Stream::new_client(None, server, None, "en"); + se::to_writer_with_root(&mut self.writer, "stream:stream", &stream_element); - pub async fn get_features(&mut self) -> Result<Vec<StreamFeature>> { - Element::read(&mut self.reader).await?.try_into() - } + // server to client - pub async fn starttls(mut self) -> Result<super::encrypted::JabberClient<'j>> { - let mut starttls_element = BytesStart::new("starttls"); - starttls_element.push_attribute(("xmlns", "urn:ietf:params:xml:ns:xmpp-tls")); - self.writer - .write_event_async(Event::Empty(starttls_element)) - .await - .unwrap(); - let mut buf = Vec::new(); - match self.reader.read_event_into_async(&mut buf).await.unwrap() { - Event::Empty(e) => match e.name() { - QName(b"proceed") => { - let connector = TlsConnector::new().unwrap(); - let stream = self - .reader - .into_inner() - .into_inner() - .unsplit(self.writer.into_inner()); - if let Ok(tlsstream) = tokio_native_tls::TlsConnector::from(connector) - .connect(&self.jabber.server, stream) - .await - { - let (read, write) = tokio::io::split(tlsstream); - let reader = Reader::from_reader(BufReader::new(read)); - let writer = Writer::new(write); - let mut client = - super::encrypted::JabberClient::new(reader, writer, self.jabber); - client.start_stream().await?; - return Ok(client); + // may or may not send a declaration + let buf = Vec::new(); + let mut first_event = self.reader.read_resolved_event_into_async(&mut buf).await?; + match first_event { + (quick_xml::name::ResolveResult::Unbound, Event::Decl(e)) => { + if let Ok(version) = e.version() { + if version.as_ref() == b"1.0" { + first_event = self.reader.read_resolved_event_into_async(&mut buf).await? + } else { + // todo: error + todo!() } + } else { + first_event = self.reader.read_resolved_event_into_async(&mut buf).await? + } + } + _ => (), + } + + // receive stream element and validate + let stream_response: Stream; + match first_event { + (quick_xml::name::ResolveResult::Bound(ns), Event::Start(e)) => { + if ns.0 == crate::stanza::stream::XMLNS.as_bytes() { + // stream_response = Stream::new( + // e.try_get_attribute("from")?.try_map(|attribute| { + // str::from_utf8(attribute.value.as_ref())? + // .try_into()? + // .as_ref() + // })?, + // e.try_get_attribute("to")?.try_map(|attribute| { + // str::from_utf8(attribute.value.as_ref())? + // .try_into()? + // .as_ref() + // })?, + // e.try_get_attribute("id")?.try_map(|attribute| { + // str::from_utf8(attribute.value.as_ref())? + // .try_into()? + // .as_ref() + // })?, + // e.try_get_attribute("version")?.try_map(|attribute| { + // str::from_utf8(attribute.value.as_ref())? + // .try_into()? + // .as_ref() + // })?, + // e.try_get_attribute("lang")?.try_map(|attribute| { + // str::from_utf8(attribute.value.as_ref())? + // .try_into()? + // .as_ref() + // })?, + // ); + return Ok(()); + } else { + return Err(JabberError::BadStream); } - QName(_) => return Err(JabberError::TlsNegotiation), - }, - _ => return Err(JabberError::TlsNegotiation), + } + // TODO: errors for incorrect namespace + (quick_xml::name::ResolveResult::Unbound, Event::Decl(_)) => todo!(), + (quick_xml::name::ResolveResult::Unknown(_), Event::Start(_)) => todo!(), + (quick_xml::name::ResolveResult::Unknown(_), Event::End(_)) => todo!(), + (quick_xml::name::ResolveResult::Unknown(_), Event::Empty(_)) => todo!(), + (quick_xml::name::ResolveResult::Unknown(_), Event::Text(_)) => todo!(), + (quick_xml::name::ResolveResult::Unknown(_), Event::CData(_)) => todo!(), + (quick_xml::name::ResolveResult::Unknown(_), Event::Comment(_)) => todo!(), + (quick_xml::name::ResolveResult::Unknown(_), Event::Decl(_)) => todo!(), + (quick_xml::name::ResolveResult::Unknown(_), Event::PI(_)) => todo!(), + (quick_xml::name::ResolveResult::Unknown(_), Event::DocType(_)) => todo!(), + (quick_xml::name::ResolveResult::Unknown(_), Event::Eof) => todo!(), + (quick_xml::name::ResolveResult::Unbound, Event::Start(_)) => todo!(), + (quick_xml::name::ResolveResult::Unbound, Event::End(_)) => todo!(), + (quick_xml::name::ResolveResult::Unbound, Event::Empty(_)) => todo!(), + (quick_xml::name::ResolveResult::Unbound, Event::Text(_)) => todo!(), + (quick_xml::name::ResolveResult::Unbound, Event::CData(_)) => todo!(), + (quick_xml::name::ResolveResult::Unbound, Event::Comment(_)) => todo!(), + (quick_xml::name::ResolveResult::Unbound, Event::PI(_)) => todo!(), + (quick_xml::name::ResolveResult::Unbound, Event::DocType(_)) => todo!(), + (quick_xml::name::ResolveResult::Unbound, Event::Eof) => todo!(), + (quick_xml::name::ResolveResult::Bound(_), Event::End(_)) => todo!(), + (quick_xml::name::ResolveResult::Bound(_), Event::Empty(_)) => todo!(), + (quick_xml::name::ResolveResult::Bound(_), Event::Text(_)) => todo!(), + (quick_xml::name::ResolveResult::Bound(_), Event::CData(_)) => todo!(), + (quick_xml::name::ResolveResult::Bound(_), Event::Comment(_)) => todo!(), + (quick_xml::name::ResolveResult::Bound(_), Event::Decl(_)) => todo!(), + (quick_xml::name::ResolveResult::Bound(_), Event::PI(_)) => todo!(), + (quick_xml::name::ResolveResult::Bound(_), Event::DocType(_)) => todo!(), + (quick_xml::name::ResolveResult::Bound(_), Event::Eof) => todo!(), } - Err(JabberError::TlsNegotiation) } + + // pub async fn get_features(&mut self) -> Result<Vec<StreamFeature>> { + // Element::read(&mut self.reader).await?.try_into() + // } + + // pub async fn starttls(mut self) -> Result<super::encrypted::JabberClient<'j>> { + // let mut starttls_element = BytesStart::new("starttls"); + // starttls_element.push_attribute(("xmlns", "urn:ietf:params:xml:ns:xmpp-tls")); + // self.writer + // .write_event_async(Event::Empty(starttls_element)) + // .await + // .unwrap(); + // let mut buf = Vec::new(); + // match self.reader.read_event_into_async(&mut buf).await.unwrap() { + // Event::Empty(e) => match e.name() { + // QName(b"proceed") => { + // let connector = TlsConnector::new().unwrap(); + // let stream = self + // .reader + // .into_inner() + // .into_inner() + // .unsplit(self.writer.into_inner()); + // if let Ok(tlsstream) = tokio_native_tls::TlsConnector::from(connector) + // .connect(&self.jabber.server, stream) + // .await + // { + // let (read, write) = tokio::io::split(tlsstream); + // let reader = Reader::from_reader(BufReader::new(read)); + // let writer = Writer::new(write); + // let mut client = + // super::encrypted::JabberClient::new(reader, writer, self.jabber); + // client.start_stream().await?; + // return Ok(client); + // } + // } + // QName(_) => return Err(JabberError::TlsNegotiation), + // }, + // _ => return Err(JabberError::TlsNegotiation), + // } + // Err(JabberError::TlsNegotiation) + // } } diff --git a/src/jabber.rs b/src/jabber.rs index 1a7eddb..d48eb9c 100644 --- a/src/jabber.rs +++ b/src/jabber.rs @@ -3,7 +3,7 @@ use std::net::{IpAddr, SocketAddr}; use std::str::FromStr; use std::sync::Arc; -use quick_xml::{Reader, Writer}; +use quick_xml::{NsReader, Writer}; use rsasl::prelude::SASLConfig; use tokio::io::BufReader; use tokio::net::TcpStream; @@ -22,7 +22,7 @@ pub struct Jabber<'j> { } impl<'j> Jabber<'j> { - pub fn new(jid: JID, password: String) -> Result<Self> { + pub fn user(jid: JID, password: String) -> Result<Self> { let server = jid.domainpart.clone(); let auth = SASLConfig::with_credentials(None, jid.localpart.clone().unwrap(), password)?; println!("auth: {:?}", auth); @@ -36,7 +36,7 @@ impl<'j> Jabber<'j> { pub async fn login(&'j mut self) -> Result<JabberClient<'j>> { let mut client = self.connect().await?.ensure_tls().await?; - println!("negotiation"); + client.start_stream().await?; client.negotiate().await?; Ok(client) } @@ -106,6 +106,7 @@ impl<'j> Jabber<'j> { socket_addrs } + /// establishes a connection to the server pub async fn connect(&'j mut self) -> Result<JabberClientType> { for (socket_addr, is_tls) in self.get_sockets().await { println!("trying {}", socket_addr); @@ -118,21 +119,18 @@ impl<'j> Jabber<'j> { .await { let (read, write) = tokio::io::split(stream); - let reader = Reader::from_reader(BufReader::new(read)); + let reader = NsReader::from_reader(BufReader::new(read)); let writer = Writer::new(write); - let mut client = client::encrypted::JabberClient::new(reader, writer, self); - client.start_stream().await?; + let client = client::encrypted::JabberClient::new(reader, writer, self); return Ok(JabberClientType::Encrypted(client)); } } false => { if let Ok(stream) = TcpStream::connect(socket_addr).await { let (read, write) = tokio::io::split(stream); - let reader = Reader::from_reader(BufReader::new(read)); + let reader = NsReader::from_reader(BufReader::new(read)); let writer = Writer::new(write); - let mut client = - client::unencrypted::JabberClient::new(reader, writer, self); - client.start_stream().await?; + let client = client::unencrypted::JabberClient::new(reader, writer, self); return Ok(JabberClientType::Unencrypted(client)); } } diff --git a/src/jid/mod.rs b/src/jid.rs index e13fed7..65738dc 100644 --- a/src/jid/mod.rs +++ b/src/jid.rs @@ -1,5 +1,7 @@ use std::str::FromStr; +use serde::Serialize; + #[derive(PartialEq, Debug, Clone)] pub struct JID { // TODO: validate localpart (length, char] @@ -8,6 +10,15 @@ pub struct JID { pub resourcepart: Option<String>, } +impl Serialize for JID { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + pub enum JIDError { NoResourcePart, ParseError(ParseError), @@ -97,6 +108,14 @@ impl TryFrom<String> for JID { } } +impl TryFrom<&str> for JID { + type Error = ParseError; + + fn try_from(value: &str) -> Result<Self, Self::Error> { + value.parse() + } +} + impl std::fmt::Display for JID { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( @@ -8,7 +8,7 @@ pub mod jabber; pub mod jid; pub mod stanza; -pub use client::encrypted::JabberClient; +// pub use client::encrypted::JabberClient; pub use error::JabberError; pub use jabber::Jabber; pub use jid::JID; @@ -22,30 +22,9 @@ mod tests { use crate::Jabber; use crate::JID; - // #[tokio::test] - // async fn get_sockets() { - // let jabber = Jabber::new(JID::from_str("cel@blos.sm").unwrap(), "password".to_owned()); - // println!("{:?}", jabber.get_sockets().await) - // } - - // #[tokio::test] - // async fn connect() { - // Jabber::new(JID::from_str("cel@blos.sm").unwrap(), "password".to_owned()) - // .unwrap() - // .connect() - // .await - // .unwrap() - // .ensure_tls() - // .await - // .unwrap() - // .start_stream() - // .await - // .unwrap(); - // } - #[tokio::test] async fn login() { - Jabber::new( + Jabber::user( JID::from_str("test@blos.sm/clown").unwrap(), "slayed".to_owned(), ) diff --git a/src/stanza/bind.rs b/src/stanza/bind.rs index 939a716..8b13789 100644 --- a/src/stanza/bind.rs +++ b/src/stanza/bind.rs @@ -1,48 +1 @@ -use super::{Element, ElementParseError}; -use crate::{JabberError, JID}; -const XMLNS: &str = "urn:ietf:params:xml:ns:xmpp-bind"; - -pub struct Bind { - pub resource: Option<String>, - pub jid: Option<JID>, -} - -impl From<Bind> for Element { - fn from(bind: Bind) -> Self { - let bind_element = Element::new("bind", None, XMLNS); - bind_element.push_namespace_declaration((None, XMLNS)); - if let Some(resource) = bind.resource { - let resource_element = Element::new("resource", None, XMLNS); - resource_element.push_child(resource); - bind_element.push_child(resource_element) - } - if let Some(jid) = bind.jid { - let jid_element = Element::new("jid", None, XMLNS); - jid_element.push_child(jid); - bind_element.push_child(jid_element) - } - bind_element - } -} - -impl TryFrom<Element> for Bind { - type Error = JabberError; - - fn try_from(element: Element) -> Result<Self, Self::Error> { - if element.namespace() == XMLNS && element.localname() == "bind" { - let (resource, jid); - let child: &Element = element.child()?; - if child.namespace() == XMLNS { - match child.localname() { - "resource" => Bind::new(Some( - child - .text_content()? - .first() - .ok_or(ElementParseError::NoContent)?, - )), - } - } - } - } -} diff --git a/src/stanza/iq.rs b/src/stanza/iq.rs index 6c7dee3..8b13789 100644 --- a/src/stanza/iq.rs +++ b/src/stanza/iq.rs @@ -1,170 +1 @@ -use nanoid::nanoid; -use quick_xml::{ - events::{BytesStart, Event}, - name::QName, - Reader, Writer, -}; -use crate::{JabberClient, JabberError, JID}; - -use crate::Result; - -#[derive(Debug)] -pub struct IQ { - to: Option<JID>, - from: Option<JID>, - id: String, - r#type: IQType, - lang: Option<String>, - child: Element<'static>, -} - -#[derive(Debug)] -enum IQType { - Get, - Set, - Result, - Error, -} - -impl IQ { - pub async fn set<'j, R: IntoElement<'static>>( - client: &mut JabberClient<'j>, - to: Option<JID>, - from: Option<JID>, - element: R, - ) -> Result<Element<'static>> { - let id = nanoid!(); - let iq = IQ { - to, - from, - id: id.clone(), - r#type: IQType::Set, - lang: None, - child: Element::from(element), - }; - println!("{:?}", iq); - let iq = Element::from(iq); - println!("{:?}", iq); - iq.write(&mut client.writer).await?; - let result = Element::read(&mut client.reader).await?; - let iq = IQ::try_from(result)?; - if iq.id == id { - return Ok(iq.child); - } - Err(JabberError::IDMismatch) - } -} - -impl<'e> IntoElement<'e> for IQ { - fn event(&self) -> quick_xml::events::Event<'e> { - let mut start = BytesStart::new("iq"); - if let Some(to) = &self.to { - start.push_attribute(("to", to.to_string().as_str())); - } - if let Some(from) = &self.from { - start.push_attribute(("from", from.to_string().as_str())); - } - start.push_attribute(("id", self.id.as_str())); - match self.r#type { - IQType::Get => start.push_attribute(("type", "get")), - IQType::Set => start.push_attribute(("type", "set")), - IQType::Result => start.push_attribute(("type", "result")), - IQType::Error => start.push_attribute(("type", "error")), - } - if let Some(lang) = &self.lang { - start.push_attribute(("from", lang.to_string().as_str())); - } - - quick_xml::events::Event::Start(start) - } - - fn children(&self) -> Option<Vec<Element<'e>>> { - Some(vec![self.child.clone()]) - } -} - -impl TryFrom<Element<'static>> for IQ { - type Error = JabberError; - - fn try_from(element: Element<'static>) -> std::result::Result<Self, Self::Error> { - if let Event::Start(start) = &element.event { - if start.name() == QName(b"iq") { - let mut to: Option<JID> = None; - let mut from: Option<JID> = None; - let mut id = None; - let mut r#type = None; - let mut lang = None; - start - .attributes() - .into_iter() - .try_for_each(|attribute| -> Result<()> { - if let Ok(attribute) = attribute { - let buf: Vec<u8> = Vec::new(); - let reader = Reader::from_reader(buf); - match attribute.key { - QName(b"to") => { - to = Some( - attribute - .decode_and_unescape_value(&reader) - .or(Err(JabberError::Utf8Decode))? - .into_owned() - .try_into()?, - ) - } - QName(b"from") => { - from = Some( - attribute - .decode_and_unescape_value(&reader) - .or(Err(JabberError::Utf8Decode))? - .into_owned() - .try_into()?, - ) - } - QName(b"id") => { - id = Some( - attribute - .decode_and_unescape_value(&reader) - .or(Err(JabberError::Utf8Decode))? - .into_owned(), - ) - } - QName(b"type") => { - let value = attribute - .decode_and_unescape_value(&reader) - .or(Err(JabberError::Utf8Decode))?; - match value.as_ref() { - "get" => r#type = Some(IQType::Get), - "set" => r#type = Some(IQType::Set), - "result" => r#type = Some(IQType::Result), - "error" => r#type = Some(IQType::Error), - _ => return Err(JabberError::ParseError), - } - } - QName(b"lang") => { - lang = Some( - attribute - .decode_and_unescape_value(&reader) - .or(Err(JabberError::Utf8Decode))? - .into_owned(), - ) - } - _ => return Err(JabberError::UnknownAttribute), - } - } - Ok(()) - })?; - let iq = IQ { - to, - from, - id: id.ok_or(JabberError::NoID)?, - r#type: r#type.ok_or(JabberError::NoType)?, - lang, - child: element.child()?.to_owned(), - }; - return Ok(iq); - } - } - Err(JabberError::ParseError) - } -} diff --git a/src/stanza/message.rs b/src/stanza/message.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/stanza/message.rs @@ -0,0 +1 @@ + diff --git a/src/stanza/mod.rs b/src/stanza/mod.rs index 13fc31e..c5a6da3 100644 --- a/src/stanza/mod.rs +++ b/src/stanza/mod.rs @@ -1,657 +1,11 @@ -// use quick_xml::events::BytesDecl; - pub mod bind; pub mod iq; +pub mod message; +pub mod presence; pub mod sasl; +pub mod starttls; pub mod stream; -use std::collections::BTreeMap; -use std::str; - -// const DECLARATION: BytesDecl<'_> = BytesDecl::new("1.0", None, None); -use async_recursion::async_recursion; -use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event}; -use quick_xml::name::PrefixDeclaration; -use quick_xml::{Reader, Writer}; -use tokio::io::{AsyncBufRead, AsyncWrite}; - -use crate::{JabberError, Result}; - -#[derive(Clone, Debug)] -/// represents an xml element as a tree of nodes -pub struct Element { - /// element prefix - /// e.g. `foo` in `<foo:bar />`. - prefix: Option<String>, - /// element name - /// e.g. `bar` in `<foo:bar />`. - localname: String, - /// qualifying namespace - /// an element must be qualified by a namespace - /// e.g. for `<stream:features>` in - /// ``` - /// <stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'> - /// <stream:features> - /// <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/> - /// <compression xmlns='http://jabber.org/features/compress'> - /// <method>zlib</method> - /// <method>lzw</method> - /// </compression> - /// </stream:features> - /// </stream:stream> - /// ``` - /// would be `"http://etherx.jabber.org/streams"` but for - /// ``` - /// <stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'> - /// <features> - /// <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/> - /// <compression xmlns='http://jabber.org/features/compress'> - /// <method>zlib</method> - /// <method>lzw</method> - /// </compression> - /// </features> - /// </stream:stream> - /// ``` - /// would be `"jabber:client"` - namespace: String, - /// all namespaces applied to element - /// e.g. for `<bind>` in - /// ``` - /// <stream:features xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'> - /// <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/> - /// <compression xmlns='http://jabber.org/features/compress'> - /// <method>zlib</method> - /// <method>lzw</method> - /// </compression> - /// </stream:features> - /// ``` - /// would be `[(None, "urn:ietf:params:xml:ns:xmpp-bind")]` despite - /// `(Some("stream"), "http://etherx.jabber.org/streams")` also being available - // TODO: maybe not even needed, as can calculate when writing which namespaces need to be declared - // but then can't have unused namespace on element, confusing. - namespace_declarations: Box<BTreeMap<Option<String>, String>>, - /// element attributes - attributes: Box<BTreeMap<String, String>>, - // children elements namespaces contain their parents' namespaces - children: Box<Vec<Node>>, -} - -#[derive(Clone, Debug)] -pub enum Node { - Element(Element), - Text(String), - Unknown, -} - -impl From<Element> for Node { - fn from(element: Element) -> Self { - Self::Element(element) - } -} - -impl<S: ToString> From<S> for Node { - fn from(text: S) -> Self { - Self::Text(text.to_string()) - } -} - -impl<'s> From<&Node> for Vec<Event<'s>> { - fn from(node: &Node) -> Self { - match node { - Node::Element(e) => e.into(), - Node::Text(t) => vec![Event::Text(BytesText::new(t))], - Unknown => vec![], - } - } -} - -impl Element { - /// returns the fully qualified name - /// e.g. `foo:bar` in - /// `<foo:bar>`. - pub fn name(&self) -> &str { - if let Some(prefix) = self.prefix { - format!("{}:{}", prefix, self.localname).as_str() - } else { - &self.localname - } - } - - /// returns the localname. - /// e.g. `bar` in `<foo:bar>` - pub fn localname(&self) -> &str { - &self.localname - } - - /// returns the prefix. - /// e.g. `foo` in `<foo:bar>`. returns None if there is - /// no prefix. - pub fn prefix(&self) -> Option<&str> { - self.prefix - } - - /// returns the namespace which applies to the current element, e.g. for - /// `<element xmlns='foo' xmlns:bar='bar'>` - /// it will be `foo` but for - /// `<bar:element xmlns='foo' xmlns:bar='bar'>` - /// it will be `bar`. - pub fn namespace(&self) -> &str { - &self.namespace - } -} - -impl<'s> From<&Element> for Vec<Event<'s>> { - fn from(element: &Element) -> Self { - let name = element.name(); - - let event = BytesStart::new(name); - - // namespace declarations - let namespace_declarations = element.namespace_declarations.iter().map(|declaration| { - let (prefix, namespace) = declaration; - match prefix { - Some(prefix) => return (format!("xmlns:{}", prefix).as_str(), *namespace), - None => return ("xmlns", *namespace), - } - }); - let event = event.with_attributes(namespace_declarations); - - // attributes - let event = event.with_attributes(element.attributes.into_iter()); - - match element.children.is_empty() { - true => return vec![Event::Empty(event)], - false => { - return { - let start: Vec<Event<'s>> = vec![Event::Start(event)]; - let start_and_content: Vec<Event<'s>> = start - .into_iter() - .chain({ - let u = element.children.iter().fold( - Vec::new(), - |acc: Vec<Event<'s>>, child: &Node<'s>| { - acc.into_iter() - .chain(Into::<Vec<Event<'s>>>::into(child).into_iter()) - .collect() - }, - ); - u - }) - .collect(); - let full: Vec<Event<'s>> = start_and_content - .into_iter() - .chain(vec![Event::End(BytesEnd::new(name))]) - .collect(); - full - } - } - } - } -} - -impl Element { - /// if there is only one child in the vec of children, will return that element - pub fn child(&self) -> Result<&Node> { - if self.children.len() == 1 { - Ok(&self.children[0]) - } else if self.children.len() > 1 { - Err(ElementError::MultipleChildren.into()) - } else { - Err(ElementError::NoChildren.into()) - } - } - - /// returns reference to children - pub fn children(&self) -> Result<&Vec<Node>> { - if !self.children.is_empty() { - Ok(&self.children) - } else { - Err(ElementError::NoChildren.into()) - } - } - - /// returns text content, error if there is none - pub fn text_content(&self) -> Result<Vec<&str>> { - let mut text = Vec::new(); - for node in *self.children { - match node { - Node::Text(t) => text.push(t), - _ => {} - } - } - if text.is_empty() { - return Err(ElementError::NotText.into()); - } - Ok(text) - } - - /// returns whether or not the element is qualified by a namespace, either declared - /// by a parent, or itself. - fn namespace_qualified<S: AsRef<str>>( - &self, - namespace_context: &BTreeMap<Option<S>, S>, - ) -> bool { - // create a new local_namespaces combining that in the context and those declared within the element - let mut local_namespaces = *namespace_context.clone(); - self.namespace_declarations.iter().for_each(|prefix, declaration| local_namespaces.insert(prefix, declaration)); - - if let Some(namespace) = local_namespaces.get(self.prefix) { - if namespace != self.namespace { - return false; - } - } else { - return false; - }; - - for child in *self.children { - if child.namespace_qualified(&local_namespaces) == false { - return false; - } - } - - true - } - - - /// writes an element to a writer. the element's namespace must be qualified by the - /// context given in `local_namespaces` or the element's internal namespace declarations - pub async fn write<S: AsRef<str>, W: AsyncWrite + Unpin + Send>( - &self, - writer: &mut Writer<W>, - local_namespaces: &BTreeMap<Option<S>, S>, - ) -> Result<()> { - // TODO: instead of having a check if namespace qualified function, have the namespace declarations be added if needed given the context when converting from `Element` to `Event`s - if self.namespace_qualified(local_namespaces) { - let events: Vec<Event> = self.into(); - for event in events { - writer.write_event_async(event).await? - } - Ok(()) - } else { - Err(ElementError::NamespaceNotQualified.into()) - } - } - - pub async fn write_start<S: AsRef<str>, W: AsyncWrite + Unpin + Send>( - &self, - writer: &mut Writer<W>, - local_namespaces: &BTreeMap<Option<S>, S>, - ) -> Result<()> { - if self.namespace_qualified(local_namespaces) { - let mut event = BytesStart::new(self.name()); - - // namespace declarations - self.namespace_declarations.iter().for_each(|declaration| { - let (prefix, namespace) = declaration; - match prefix { - Some(prefix) => { - event.push_attribute((format!("xmlns:{}", prefix).as_str(), *namespace)) - } - None => event.push_attribute(("xmlns", *namespace)), - } - }); - - // attributes - let event = - event.with_attributes(self.attributes.iter().map(|(attr, value)| (*attr, *value))); - - writer.write_event_async(Event::Start(event)).await?; - - Ok(()) - } else { - Err(ElementError::NamespaceNotQualified.into()) - } - } - - pub async fn write_end<W: AsyncWrite + Unpin + Send>( - &self, - writer: &mut Writer<W>, - ) -> Result<()> { - let event = BytesEnd::new(self.name()); - writer.write_event_async(Event::End(event)).await?; - Ok(()) - } - - #[async_recursion] - pub async fn read<S: AsRef<str>, R: AsyncBufRead + Unpin + Send>( - reader: &mut Reader<R>, - local_namespaces: &BTreeMap<Option<S>, S>, - ) -> Result<Self> { - let node = Node::read_recursive(reader, local_namespaces) - .await? - .ok_or(JabberError::UnexpectedEnd)?; - match node { - Node::Element(e) => Ok(e), - Node::Text(_) => Err(JabberError::UnexpectedText), - Node::Unknown => Err(JabberError::UnexpectedElement), - } - } - - pub async fn read_start<S: AsRef<str>, R: AsyncBufRead + Unpin + Send>( - reader: &mut Reader<R>, - local_namespaces: &BTreeMap<Option<S>, S>, - ) -> Result<Element> { - let buf = Vec::new(); - let event = reader.read_event_into_async(&mut buf).await?; - match event { - Event::Start(e) => { - let prefix = e.name().prefix().map(|prefix| prefix.into_inner()); - let converted_prefix; - if let Some(raw_prefix) = prefix { - converted_prefix = Some(str::from_utf8(raw_prefix)?) - } - let prefix = converted_prefix; - - let localname = str::from_utf8(e.local_name().into_inner())?.to_owned(); - - let mut local_namespaces = local_namespaces.clone(); - let mut namespace_declarations = BTreeMap::new(); - let attributes = BTreeMap::new(); - - for attribute in e.attributes() { - let attribute = attribute?; - if let Some(prefix_declaration) = attribute.key.as_namespace_binding() { - match prefix_declaration { - PrefixDeclaration::Default => { - let value = str::from_utf8(attribute.value.as_ref())?; - if let Some(_) = namespace_declarations.insert(None, value) { - return Err(ElementParseError::DuplicateAttribute.into()); - }; - local_namespaces.insert(None, value); - } - PrefixDeclaration::Named(prefix) => { - let key = str::from_utf8(prefix)?; - let value = str::from_utf8(attribute.value.as_ref())?; - if let Some(_) = namespace_declarations.insert(Some(key), value) { - return Err(ElementParseError::DuplicateAttribute.into()); - }; - local_namespaces.insert(Some(key), value); - } - } - } else { - if let Some(_) = attributes.insert( - str::from_utf8(attribute.key.into_inner())?, - str::from_utf8(attribute.value.as_ref())?, - ) { - return Err(ElementParseError::DuplicateAttribute.into()); - }; - } - } - - let namespace = *local_namespaces - .get(&prefix) - .ok_or(ElementParseError::NoNamespace)?; - - let mut children = Vec::new(); - - Ok(Self { - prefix, - localname, - namespace, - namespace_declarations: Box::new(namespace_declarations), - attributes: Box::new(attributes), - children: Box::new(children), - }) - } - e => Err(ElementError::NotAStart(e).into()), - } - } -} - -impl Node { - #[async_recursion] - async fn read_recursive<S: AsRef<str>, R: AsyncBufRead + Unpin + Send>( - reader: &mut Reader<R>, - local_namespaces: &BTreeMap<Option<S>, S>, - ) -> Result<Option<Node>> { - let mut buf = Vec::new(); - let event = reader.read_event_into_async(&mut buf).await?; - match event { - Event::Empty(e) => { - let prefix = e.name().prefix().map(|prefix| prefix.into_inner()); - let converted_prefix; - if let Some(raw_prefix) = prefix { - converted_prefix = Some(str::from_utf8(raw_prefix)?) - } - let prefix = converted_prefix; - - let localname = str::from_utf8(e.local_name().into_inner())?.to_owned(); - - let mut local_namespaces = local_namespaces.clone(); - let mut namespace_declarations = BTreeMap::new(); - let attributes = BTreeMap::new(); - - for attribute in e.attributes() { - let attribute = attribute?; - if let Some(prefix_declaration) = attribute.key.as_namespace_binding() { - match prefix_declaration { - PrefixDeclaration::Default => { - let value = str::from_utf8(attribute.value.as_ref())?; - if let Some(_) = namespace_declarations.insert(None, value) { - return Err(ElementParseError::DuplicateAttribute.into()); - }; - local_namespaces.insert(None, value); - } - PrefixDeclaration::Named(prefix) => { - let key = str::from_utf8(prefix)?; - let value = str::from_utf8(attribute.value.as_ref())?; - if let Some(_) = namespace_declarations.insert(Some(key), value) { - return Err(ElementParseError::DuplicateAttribute.into()); - }; - local_namespaces.insert(Some(key), value); - } - } - } else { - if let Some(_) = attributes.insert( - str::from_utf8(attribute.key.into_inner())?, - str::from_utf8(attribute.value.as_ref())?, - ) { - return Err(ElementParseError::DuplicateAttribute.into()); - }; - } - } - - let namespace = *local_namespaces - .get(&prefix) - .ok_or(ElementParseError::NoNamespace)?; - - let mut children = Vec::new(); - - Ok(Some(Self::Element(Element { - prefix, - localname, - namespace, - namespace_declarations: Box::new(namespace_declarations), - attributes: Box::new(attributes), - children: Box::new(children), - }))) - } - Event::Start(e) => { - let prefix = e.name().prefix().map(|prefix| prefix.into_inner()); - let converted_prefix; - if let Some(raw_prefix) = prefix { - converted_prefix = Some(str::from_utf8(raw_prefix)?) - } - let prefix = converted_prefix; - - let localname = str::from_utf8(e.local_name().into_inner())?.to_owned(); - - let mut local_namespaces = local_namespaces.clone(); - let mut namespace_declarations = BTreeMap::new(); - let attributes = BTreeMap::new(); - - for attribute in e.attributes() { - let attribute = attribute?; - if let Some(prefix_declaration) = attribute.key.as_namespace_binding() { - match prefix_declaration { - PrefixDeclaration::Default => { - let value = str::from_utf8(attribute.value.as_ref())?; - if let Some(_) = namespace_declarations.insert(None, value) { - return Err(ElementParseError::DuplicateAttribute.into()); - }; - local_namespaces.insert(None, value); - } - PrefixDeclaration::Named(prefix) => { - let key = str::from_utf8(prefix)?; - let value = str::from_utf8(attribute.value.as_ref())?; - if let Some(_) = namespace_declarations.insert(Some(key), value) { - return Err(ElementParseError::DuplicateAttribute.into()); - }; - local_namespaces.insert(Some(key), value); - } - } - } else { - if let Some(_) = attributes.insert( - str::from_utf8(attribute.key.into_inner())?, - str::from_utf8(attribute.value.as_ref())?, - ) { - return Err(ElementParseError::DuplicateAttribute.into()); - }; - } - } - - let namespace = *local_namespaces - .get(&prefix) - .ok_or(ElementParseError::NoNamespace)?; - - let mut children = Vec::new(); - while let Some(child_node) = Node::read_recursive(reader, &local_namespaces).await? - { - children.push(child_node) - } - - let mut children = Vec::new(); - - Ok(Some(Self::Element(Element { - prefix, - localname, - namespace, - namespace_declarations: Box::new(namespace_declarations), - attributes: Box::new(attributes), - children: Box::new(children), - }))) - } - Event::End(_) => Ok(None), - Event::Text(e) => Ok(Some(Self::Text(e.unescape()?.as_ref().to_string()))), - e => Ok(Some(Self::Unknown)), - } - } - - fn namespace_qualified<S: AsRef<str>>( - &self, - namespace_context: &BTreeMap<Option<S>, S>, - ) -> bool { - match self { - Self::Element(e) => e.namespace_qualified(namespace_context), - _ => true, - } - } -} - -pub enum NodeBuilder { - Text(String), - Element(ElementBuilder), -} - -pub struct ElementBuilder { - localname: String, - prefix: Option<String>, - namespace: String, - namespace_declarations: BTreeMap<Option<String>, String>, - attributes: BTreeMap<String, String>, - children: Vec<NodeBuilder>, -} - -impl ElementBuilder { - pub fn new<S: ToString>(localname: S, prefix: Option<S>, namespace: S) -> Self { - Self { - prefix, - localname, - namespace, - namespace_declarations: Box::new(BTreeMap::new()), - attributes: Box::new(BTreeMap::new()), - children: Box::new(Vec::new()), - } - } - - pub fn push_namespace_declaration<S: ToString>( - &mut self, - (prefix, namespace): (Option<S>, S), - ) -> Option<S> { - self.namespace_declarations.insert(prefix, namespace) - } - - pub fn push_attribute<S: ToString>(&mut self, (key, value): (S, S)) -> Option<S> { - self.attributes.insert(key, value) - } - - pub fn push_child(&mut self, child: Node) { - self.children.push(child) - } - - /// checks if there is a namespace conflict within the element being built - pub fn namespace_conflict<S: AsRef<str>>( - &self - ) -> bool { - self.namespace_conflict_recursive(&BTreeMap::new()) - } - - fn namespace_conflict_recursive<S: AsRef<str>>( - &self, - parent_namespaces: &BTreeMap<Option<S>, S>, - ) -> bool { - // create a new local_namespaces combining that in the context and those declared within the element - let mut local_namespaces = *parent_namespaces.clone(); - self.namespace_declarations.iter().for_each(|prefix, declaration| local_namespaces.insert(prefix, declaration)); - - if let Some(namespace) = local_namespaces.get(self.prefix) { - if namespace != self.namespace { - return false; - } - } else { - return false; - }; - - for child in *self.children { - if child.namespace_conflict(&local_namespaces) == false { - return false; - } - } - - true - } - - // check for possible conflicts in namespace - pub fn build(self) -> Result<Element> { - for child in self.children { - match child { - Node::Element(e) => { - if !e.namespace_conflict() - } - } - } - Element { - prefix: self.prefix, - localname: self.localname, - namespace: self.namespace, - namespace_declarations: self.namespace_declarations, - attributes: self.attributes, - children: self.children, - } - } -} - -#[derive(Debug)] -pub enum ElementError<'e> { - NotAStart(Event<'e>), - NotText, - NoChildren, - NamespaceNotQualified, - MultipleChildren, -} +use quick_xml::events::{BytesDecl, Event}; -#[derive(Debug)] -pub enum ElementParseError { - DuplicateAttribute, - NoNamespace, -} +pub static DECLARATION: Event = Event::Decl(BytesDecl::new("1.0", None, None)); diff --git a/src/stanza/presence.rs b/src/stanza/presence.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/stanza/presence.rs @@ -0,0 +1 @@ + diff --git a/src/stanza/sasl.rs b/src/stanza/sasl.rs index 20cd063..8b13789 100644 --- a/src/stanza/sasl.rs +++ b/src/stanza/sasl.rs @@ -1,145 +1 @@ -use quick_xml::{ - events::{BytesStart, BytesText, Event}, - name::QName, -}; -use crate::error::SASLError; -use crate::JabberError; - -use super::Element; - -const XMLNS: &str = "urn:ietf:params:xml:ns:xmpp-sasl"; - -#[derive(Debug)] -pub struct Auth<'e> { - pub mechanism: &'e str, - pub sasl_data: &'e str, -} - -impl<'e> IntoElement<'e> for Auth<'e> { - fn event(&self) -> Event<'e> { - let mut start = BytesStart::new("auth"); - start.push_attribute(("xmlns", XMLNS)); - start.push_attribute(("mechanism", self.mechanism)); - Event::Start(start) - } - - fn children(&self) -> Option<Vec<Element<'e>>> { - let sasl = BytesText::from_escaped(self.sasl_data); - let sasl = Element { - event: Event::Text(sasl), - children: None, - }; - Some(vec![sasl]) - } -} - -#[derive(Debug)] -pub struct Challenge { - pub sasl_data: Vec<u8>, -} - -impl<'e> TryFrom<&Element<'e>> for Challenge { - type Error = JabberError; - - fn try_from(element: &Element<'e>) -> Result<Challenge, Self::Error> { - if let Event::Start(start) = &element.event { - if start.name() == QName(b"challenge") { - let sasl_data: &Element<'_> = element.child()?; - if let Event::Text(sasl_data) = &sasl_data.event { - let s = sasl_data.clone(); - let s = s.into_inner(); - let s = s.to_vec(); - return Ok(Challenge { sasl_data: s }); - } - } - } - Err(SASLError::NoChallenge.into()) - } -} - -// impl<'e> TryFrom<Element<'e>> for Challenge { -// type Error = JabberError; - -// fn try_from(element: Element<'e>) -> Result<Challenge, Self::Error> { -// if let Event::Start(start) = &element.event { -// if start.name() == QName(b"challenge") { -// println!("one"); -// if let Some(children) = element.children.as_deref() { -// if children.len() == 1 { -// let sasl_data = children.first().unwrap(); -// if let Event::Text(sasl_data) = &sasl_data.event { -// return Ok(Challenge { -// sasl_data: sasl_data.clone().into_inner().to_vec(), -// }); -// } else { -// return Err(SASLError::NoChallenge.into()); -// } -// } else { -// return Err(SASLError::NoChallenge.into()); -// } -// } else { -// return Err(SASLError::NoChallenge.into()); -// } -// } -// } -// Err(SASLError::NoChallenge.into()) -// } -// } - -#[derive(Debug)] -pub struct Response<'e> { - pub sasl_data: &'e str, -} - -impl<'e> IntoElement<'e> for Response<'e> { - fn event(&self) -> Event<'e> { - let mut start = BytesStart::new("response"); - start.push_attribute(("xmlns", XMLNS)); - Event::Start(start) - } - - fn children(&self) -> Option<Vec<Element<'e>>> { - let sasl = BytesText::from_escaped(self.sasl_data); - let sasl = Element { - event: Event::Text(sasl), - children: None, - }; - Some(vec![sasl]) - } -} - -#[derive(Debug)] -pub struct Success { - pub sasl_data: Option<Vec<u8>>, -} - -impl<'e> TryFrom<&Element<'e>> for Success { - type Error = JabberError; - - fn try_from(element: &Element<'e>) -> Result<Success, Self::Error> { - match &element.event { - Event::Start(start) => { - if start.name() == QName(b"success") { - match element.child() { - Ok(sasl_data) => { - if let Event::Text(sasl_data) = &sasl_data.event { - return Ok(Success { - sasl_data: Some(sasl_data.clone().into_inner().to_vec()), - }); - } - } - Err(_) => return Ok(Success { sasl_data: None }), - }; - } - } - Event::Empty(empty) => { - if empty.name() == QName(b"success") { - return Ok(Success { sasl_data: None }); - } - } - _ => {} - } - Err(SASLError::NoSuccess.into()) - } -} diff --git a/src/stanza/starttls.rs b/src/stanza/starttls.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/stanza/starttls.rs @@ -0,0 +1 @@ + diff --git a/src/stanza/stream.rs b/src/stanza/stream.rs index f85166f..07f7e6e 100644 --- a/src/stanza/stream.rs +++ b/src/stanza/stream.rs @@ -1,190 +1,60 @@ -use std::str; - -use quick_xml::{ - events::{BytesStart, Event}, - name::QName, -}; - -use super::Element; -use crate::{JabberError, Result, JID}; - -const XMLNS_STREAM: &str = "http://etherx.jabber.org/streams"; -const VERSION: &str = "1.0"; - -enum XMLNS { - Client, - Server, -} - -impl From<XMLNS> for &str { - fn from(xmlns: XMLNS) -> Self { - match xmlns { - XMLNS::Client => return "jabber:client", - XMLNS::Server => return "jabber:server", - } - } -} - -impl TryInto<XMLNS> for &str { - type Error = JabberError; - - fn try_into(self) -> Result<XMLNS> { - match self { - "jabber:client" => Ok(XMLNS::Client), - "jabber:server" => Ok(XMLNS::Server), - _ => Err(JabberError::UnknownNamespace), - } - } -} - -pub struct Stream { - from: Option<JID>, - id: Option<String>, - to: Option<JID>, - version: Option<String>, - lang: Option<String>, - ns: XMLNS, +use serde::Serialize; + +use crate::JID; + +pub static XMLNS: &str = "http://etherx.jabber.org/streams"; +pub static XMLNS_CLIENT: &str = "jabber:client"; + +// MUST be qualified by stream namespace +#[derive(Serialize)] +pub struct Stream<'s> { + #[serde(rename = "@from")] + from: Option<&'s JID>, + #[serde(rename = "@to")] + to: Option<&'s JID>, + #[serde(rename = "@id")] + id: Option<&'s str>, + #[serde(rename = "@version")] + version: Option<&'s str>, + // TODO: lang enum + #[serde(rename = "@lang")] + lang: Option<&'s str>, + #[serde(rename = "@xmlns")] + xmlns: &'s str, + #[serde(rename = "@xmlns:stream")] + xmlns_stream: &'s str, } impl Stream { - pub fn new_client(from: &JID, to: &JID, id: Option<String>, lang: Option<String>) -> Self { + pub fn new( + from: Option<&JID>, + to: Option<&JID>, + id: Option<&str>, + version: Option<&str>, + lang: Option<&str>, + ) -> Self { Self { - from: Some(from.clone()), + from, + to, id, - to: Some(to.clone()), - version: Some(VERSION.to_owned()), + version, lang, - ns: XMLNS::Client, - } - } - - fn event(&self) -> Event<'static> { - let mut start = BytesStart::new("stream:stream"); - if let Some(from) = &self.from { - start.push_attribute(("from", from.to_string().as_str())); - } - if let Some(id) = &self.id { - start.push_attribute(("id", id.as_str())); - } - if let Some(to) = &self.to { - start.push_attribute(("to", to.to_string().as_str())); - } - if let Some(version) = &self.version { - start.push_attribute(("version", version.to_string().as_str())); - } - if let Some(lang) = &self.lang { - start.push_attribute(("xml:lang", lang.as_str())); - } - match &self.ns { - XMLNS::Client => start.push_attribute(("xmlns", XMLNS::Client.into())), - XMLNS::Server => start.push_attribute(("xmlns", XMLNS::Server.into())), - } - start.push_attribute(("xmlns:stream", XMLNS_STREAM)); - Event::Start(start) - } -} - -impl<'e> Into<Element<'e>> for Stream { - fn into(self) -> Element<'e> { - Element { - event: self.event(), - children: None, + xmlns: XMLNS_CLIENT, + xmlns_stream: XMLNS, } } -} - -impl<'e> TryFrom<Element<'e>> for Stream { - type Error = JabberError; - fn try_from(value: Element<'e>) -> Result<Stream> { - let (mut from, mut id, mut to, mut version, mut lang, mut ns) = - (None, None, None, None, None, XMLNS::Client); - if let Event::Start(e) = value.event.as_ref() { - for attribute in e.attributes() { - let attribute = attribute?; - match attribute.key { - QName(b"from") => { - from = Some(str::from_utf8(&attribute.value)?.to_string().try_into()?); - } - QName(b"id") => { - id = Some(str::from_utf8(&attribute.value)?.to_owned()); - } - QName(b"to") => { - to = Some(str::from_utf8(&attribute.value)?.to_string().try_into()?); - } - QName(b"version") => { - version = Some(str::from_utf8(&attribute.value)?.to_owned()); - } - QName(b"lang") => { - lang = Some(str::from_utf8(&attribute.value)?.to_owned()); - } - QName(b"xmlns") => { - ns = str::from_utf8(&attribute.value)?.try_into()?; - } - _ => { - println!("unknown attribute: {:?}", attribute) - } - } - } - Ok(Stream { - from, - id, - to, - version, - lang, - ns, - }) - } else { - Err(JabberError::ParseError) - } - } -} - -#[derive(PartialEq, Debug)] -pub enum StreamFeature { - StartTls, - Sasl(Vec<String>), - Bind, - Unknown, -} - -impl<'e> TryFrom<Element<'e>> for Vec<StreamFeature> { - type Error = JabberError; - - fn try_from(features_element: Element) -> Result<Self> { - let mut features = Vec::new(); - if let Some(children) = features_element.children { - for feature_element in children { - match feature_element.event { - Event::Start(e) => match e.name() { - QName(b"starttls") => features.push(StreamFeature::StartTls), - QName(b"mechanisms") => { - let mut mechanisms = Vec::new(); - if let Some(children) = feature_element.children { - for mechanism_element in children { - if let Some(children) = mechanism_element.children { - for mechanism_text in children { - match mechanism_text.event { - Event::Text(e) => mechanisms - .push(str::from_utf8(e.as_ref())?.to_owned()), - _ => {} - } - } - } - } - } - features.push(StreamFeature::Sasl(mechanisms)) - } - _ => features.push(StreamFeature::Unknown), - }, - Event::Empty(e) => match e.name() { - QName(b"bind") => features.push(StreamFeature::Bind), - _ => features.push(StreamFeature::Unknown), - }, - _ => features.push(StreamFeature::Unknown), - } - } + /// For initial stream headers, the initiating entity SHOULD include the 'xml:lang' attribute. + /// For privacy, it is better to not set `from` when sending a client stanza over an unencrypted connection. + pub fn new_client(from: Option<&JID>, to: &JID, id: Option<&str>, lang: &str) -> Self { + Self { + from, + to: Some(to), + id, + version: Some("1.0"), + lang: Some(lang), + xmlns: XMLNS_CLIENT, + xmlns_stream: XMLNS, } - Ok(features) } } |