diff options
author | 2024-12-04 18:18:37 +0000 | |
---|---|---|
committer | 2024-12-04 18:18:37 +0000 | |
commit | 1b91ff690488b65b552c90bd5392b9a300c8c981 (patch) | |
tree | 9c290f69b26eba0393d7bbc05ba29c28ea74a26e /src | |
parent | 03764f8cedb3f0a55a61be0f0a59faaa6357a83a (diff) | |
download | luz-1b91ff690488b65b552c90bd5392b9a300c8c981.tar.gz luz-1b91ff690488b65b552c90bd5392b9a300c8c981.tar.bz2 luz-1b91ff690488b65b552c90bd5392b9a300c8c981.zip |
use cargo workspace
Diffstat (limited to 'src')
-rw-r--r-- | src/client.rs | 246 | ||||
-rw-r--r-- | src/connection.rs | 184 | ||||
-rw-r--r-- | src/error.rs | 78 | ||||
-rw-r--r-- | src/jabber_stream.rs | 394 | ||||
-rw-r--r-- | src/jid.rs | 179 | ||||
-rw-r--r-- | src/lib.rs | 36 | ||||
-rw-r--r-- | src/stanza/bind.rs | 99 | ||||
-rw-r--r-- | src/stanza/client/error.rs | 83 | ||||
-rw-r--r-- | src/stanza/client/iq.rs | 124 | ||||
-rw-r--r-- | src/stanza/client/message.rs | 186 | ||||
-rw-r--r-- | src/stanza/client/mod.rs | 61 | ||||
-rw-r--r-- | src/stanza/client/presence.rs | 226 | ||||
-rw-r--r-- | src/stanza/mod.rs | 11 | ||||
-rw-r--r-- | src/stanza/sasl.rs | 246 | ||||
-rw-r--r-- | src/stanza/stanza_error.rs | 126 | ||||
-rw-r--r-- | src/stanza/starttls.rs | 94 | ||||
-rw-r--r-- | src/stanza/stream.rs | 191 | ||||
-rw-r--r-- | src/stanza/stream_error.rs | 137 |
18 files changed, 0 insertions, 2701 deletions
diff --git a/src/client.rs b/src/client.rs deleted file mode 100644 index e94008d..0000000 --- a/src/client.rs +++ /dev/null @@ -1,246 +0,0 @@ -use std::{pin::pin, sync::Arc, task::Poll}; - -use futures::{Sink, Stream, StreamExt}; -use rsasl::config::SASLConfig; - -use crate::{ - connection::{Tls, Unencrypted}, - jid::ParseError, - stanza::{ - client::Stanza, - sasl::Mechanisms, - stream::{Feature, Features}, - }, - Connection, Error, JabberStream, Result, JID, -}; - -// feed it client stanzas, receive client stanzas -pub struct JabberClient { - connection: ConnectionState, - jid: JID, - password: Arc<SASLConfig>, - server: String, -} - -impl JabberClient { - pub fn new( - jid: impl TryInto<JID, Error = ParseError>, - password: impl ToString, - ) -> Result<JabberClient> { - let jid = jid.try_into()?; - let sasl_config = SASLConfig::with_credentials( - None, - jid.localpart.clone().ok_or(Error::NoLocalpart)?, - password.to_string(), - )?; - Ok(JabberClient { - connection: ConnectionState::Disconnected, - jid: jid.clone(), - password: sasl_config, - server: jid.domainpart, - }) - } - - pub async fn connect(&mut self) -> Result<()> { - match &self.connection { - ConnectionState::Disconnected => { - // TODO: actually set the self.connection as it is connecting, make more asynchronous (mutex while connecting?) - // perhaps use take_mut? - self.connection = ConnectionState::Disconnected - .connect(&mut self.jid, self.password.clone(), &mut self.server) - .await?; - Ok(()) - } - ConnectionState::Connecting(_connecting) => Err(Error::AlreadyConnecting), - ConnectionState::Connected(_jabber_stream) => Ok(()), - } - } - - pub async fn send_stanza(&mut self, stanza: &Stanza) -> Result<()> { - match &mut self.connection { - ConnectionState::Disconnected => return Err(Error::Disconnected), - ConnectionState::Connecting(_connecting) => return Err(Error::Connecting), - ConnectionState::Connected(jabber_stream) => { - Ok(jabber_stream.send_stanza(stanza).await?) - } - } - } -} - -impl Stream for JabberClient { - type Item = Result<Stanza>; - - fn poll_next( - self: std::pin::Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> std::task::Poll<Option<Self::Item>> { - let mut client = pin!(self); - match &mut client.connection { - ConnectionState::Disconnected => Poll::Pending, - ConnectionState::Connecting(_connecting) => Poll::Pending, - ConnectionState::Connected(jabber_stream) => jabber_stream.poll_next_unpin(cx), - } - } -} - -pub enum ConnectionState { - Disconnected, - Connecting(Connecting), - Connected(JabberStream<Tls>), -} - -impl ConnectionState { - pub async fn connect( - mut self, - jid: &mut JID, - auth: Arc<SASLConfig>, - server: &mut String, - ) -> Result<Self> { - loop { - match self { - ConnectionState::Disconnected => { - self = ConnectionState::Connecting(Connecting::start(&server).await?); - } - ConnectionState::Connecting(connecting) => match connecting { - Connecting::InsecureConnectionEstablised(tcp_stream) => { - self = ConnectionState::Connecting(Connecting::InsecureStreamStarted( - JabberStream::start_stream(tcp_stream, server).await?, - )) - } - Connecting::InsecureStreamStarted(jabber_stream) => { - self = ConnectionState::Connecting(Connecting::InsecureGotFeatures( - jabber_stream.get_features().await?, - )) - } - Connecting::InsecureGotFeatures((features, jabber_stream)) => { - match features.negotiate()? { - Feature::StartTls(_start_tls) => { - self = - ConnectionState::Connecting(Connecting::StartTls(jabber_stream)) - } - // TODO: better error - _ => return Err(Error::TlsRequired), - } - } - Connecting::StartTls(jabber_stream) => { - self = ConnectionState::Connecting(Connecting::ConnectionEstablished( - jabber_stream.starttls(&server).await?, - )) - } - Connecting::ConnectionEstablished(tls_stream) => { - self = ConnectionState::Connecting(Connecting::StreamStarted( - JabberStream::start_stream(tls_stream, server).await?, - )) - } - Connecting::StreamStarted(jabber_stream) => { - self = ConnectionState::Connecting(Connecting::GotFeatures( - jabber_stream.get_features().await?, - )) - } - Connecting::GotFeatures((features, jabber_stream)) => { - match features.negotiate()? { - Feature::StartTls(_start_tls) => return Err(Error::AlreadyTls), - Feature::Sasl(mechanisms) => { - self = ConnectionState::Connecting(Connecting::Sasl( - mechanisms, - jabber_stream, - )) - } - Feature::Bind => { - self = ConnectionState::Connecting(Connecting::Bind(jabber_stream)) - } - Feature::Unknown => return Err(Error::Unsupported), - } - } - Connecting::Sasl(mechanisms, jabber_stream) => { - self = ConnectionState::Connecting(Connecting::ConnectionEstablished( - jabber_stream.sasl(mechanisms, auth.clone()).await?, - )) - } - Connecting::Bind(jabber_stream) => { - self = ConnectionState::Connected(jabber_stream.bind(jid).await?) - } - }, - connected => return Ok(connected), - } - } - } -} - -pub enum Connecting { - InsecureConnectionEstablised(Unencrypted), - InsecureStreamStarted(JabberStream<Unencrypted>), - InsecureGotFeatures((Features, JabberStream<Unencrypted>)), - StartTls(JabberStream<Unencrypted>), - ConnectionEstablished(Tls), - StreamStarted(JabberStream<Tls>), - GotFeatures((Features, JabberStream<Tls>)), - Sasl(Mechanisms, JabberStream<Tls>), - Bind(JabberStream<Tls>), -} - -impl Connecting { - pub async fn start(server: &str) -> Result<Self> { - match Connection::connect(server).await? { - Connection::Encrypted(tls_stream) => Ok(Connecting::ConnectionEstablished(tls_stream)), - Connection::Unencrypted(tcp_stream) => { - Ok(Connecting::InsecureConnectionEstablised(tcp_stream)) - } - } - } -} - -impl Features { - pub fn negotiate(self) -> Result<Feature> { - if let Some(Feature::StartTls(s)) = self - .features - .iter() - .find(|feature| matches!(feature, Feature::StartTls(_s))) - { - // TODO: avoid clone - return Ok(Feature::StartTls(s.clone())); - } else if let Some(Feature::Sasl(mechanisms)) = self - .features - .iter() - .find(|feature| matches!(feature, Feature::Sasl(_))) - { - // TODO: avoid clone - return Ok(Feature::Sasl(mechanisms.clone())); - } else if let Some(Feature::Bind) = self - .features - .into_iter() - .find(|feature| matches!(feature, Feature::Bind)) - { - Ok(Feature::Bind) - } else { - // TODO: better error - return Err(Error::Negotiation); - } - } -} - -pub enum InsecureConnecting { - Disconnected, - ConnectionEstablished(Connection), - PreStarttls(JabberStream<Unencrypted>), - PreAuthenticated(JabberStream<Tls>), - Authenticated(Tls), - PreBound(JabberStream<Tls>), - Bound(JabberStream<Tls>), -} - -#[cfg(test)] -mod tests { - use std::time::Duration; - - use super::JabberClient; - use test_log::test; - use tokio::time::sleep; - - #[test(tokio::test)] - async fn login() { - let mut client = JabberClient::new("test@blos.sm", "slayed").unwrap(); - client.connect().await.unwrap(); - sleep(Duration::from_secs(5)).await - } -} diff --git a/src/connection.rs b/src/connection.rs deleted file mode 100644 index bc5a282..0000000 --- a/src/connection.rs +++ /dev/null @@ -1,184 +0,0 @@ -use std::net::{IpAddr, SocketAddr}; -use std::str; -use std::str::FromStr; -use std::sync::Arc; - -use rsasl::config::SASLConfig; -use tokio::net::TcpStream; -use tokio_native_tls::native_tls::TlsConnector; -// TODO: use rustls -use tokio_native_tls::TlsStream; -use tracing::{debug, info, instrument, trace}; - -use crate::Result; -use crate::{Error, JID}; - -pub type Tls = TlsStream<TcpStream>; -pub type Unencrypted = TcpStream; - -#[derive(Debug)] -pub enum Connection { - Encrypted(Tls), - Unencrypted(Unencrypted), -} - -impl Connection { - // #[instrument] - /// stream not started - // pub async fn ensure_tls(self) -> Result<Jabber<Tls>> { - // match self { - // Connection::Encrypted(j) => Ok(j), - // Connection::Unencrypted(mut j) => { - // j.start_stream().await?; - // info!("upgrading connection to tls"); - // j.get_features().await?; - // let j = j.starttls().await?; - // Ok(j) - // } - // } - // } - - pub async fn connect_user(jid: impl AsRef<str>) -> Result<Self> { - let jid: JID = JID::from_str(jid.as_ref())?; - let server = jid.domainpart.clone(); - Self::connect(&server).await - } - - #[instrument] - pub async fn connect(server: impl AsRef<str> + std::fmt::Debug) -> Result<Self> { - info!("connecting to {}", server.as_ref()); - let sockets = Self::get_sockets(server.as_ref()).await; - debug!("discovered sockets: {:?}", sockets); - for (socket_addr, tls) in sockets { - match tls { - true => { - if let Ok(connection) = Self::connect_tls(socket_addr, server.as_ref()).await { - info!("connected via encrypted stream to {}", socket_addr); - // let (readhalf, writehalf) = tokio::io::split(connection); - return Ok(Self::Encrypted(connection)); - } - } - false => { - if let Ok(connection) = Self::connect_unencrypted(socket_addr).await { - info!("connected via unencrypted stream to {}", socket_addr); - // let (readhalf, writehalf) = tokio::io::split(connection); - return Ok(Self::Unencrypted(connection)); - } - } - } - } - Err(Error::Connection) - } - - #[instrument] - async fn get_sockets(address: &str) -> Vec<(SocketAddr, bool)> { - let mut socket_addrs = Vec::new(); - - // if it's a socket/ip then just return that - - // socket - trace!("checking if address is a socket address"); - if let Ok(socket_addr) = SocketAddr::from_str(address) { - debug!("{} is a socket address", address); - match socket_addr.port() { - 5223 => socket_addrs.push((socket_addr, true)), - _ => socket_addrs.push((socket_addr, false)), - } - - return socket_addrs; - } - // ip - trace!("checking if address is an ip"); - if let Ok(ip) = IpAddr::from_str(address) { - debug!("{} is an ip", address); - socket_addrs.push((SocketAddr::new(ip, 5222), false)); - socket_addrs.push((SocketAddr::new(ip, 5223), true)); - return socket_addrs; - } - - // otherwise resolve - debug!("resolving {}", address); - if let Ok(resolver) = trust_dns_resolver::AsyncResolver::tokio_from_system_conf() { - if let Ok(lookup) = resolver - .srv_lookup(format!("_xmpp-client._tcp.{}", address)) - .await - { - for srv in lookup { - resolver - .lookup_ip(srv.target().to_owned()) - .await - .map(|ips| { - for ip in ips { - socket_addrs.push((SocketAddr::new(ip, srv.port()), false)) - } - }); - } - } - if let Ok(lookup) = resolver - .srv_lookup(format!("_xmpps-client._tcp.{}", address)) - .await - { - for srv in lookup { - resolver - .lookup_ip(srv.target().to_owned()) - .await - .map(|ips| { - for ip in ips { - socket_addrs.push((SocketAddr::new(ip, srv.port()), true)) - } - }); - } - } - - // in case cannot connect through SRV records - resolver.lookup_ip(address).await.map(|ips| { - for ip in ips { - socket_addrs.push((SocketAddr::new(ip, 5222), false)); - socket_addrs.push((SocketAddr::new(ip, 5223), true)); - } - }); - } - socket_addrs - } - - /// establishes a connection to the server - #[instrument] - pub async fn connect_tls(socket_addr: SocketAddr, domain_name: &str) -> Result<Tls> { - let socket = TcpStream::connect(socket_addr) - .await - .map_err(|_| Error::Connection)?; - let connector = TlsConnector::new().map_err(|_| Error::Connection)?; - tokio_native_tls::TlsConnector::from(connector) - .connect(domain_name, socket) - .await - .map_err(|_| Error::Connection) - } - - #[instrument] - pub async fn connect_unencrypted(socket_addr: SocketAddr) -> Result<Unencrypted> { - TcpStream::connect(socket_addr) - .await - .map_err(|_| Error::Connection) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use test_log::test; - - #[test(tokio::test)] - async fn connect() { - Connection::connect("blos.sm").await.unwrap(); - } - - // #[test(tokio::test)] - // async fn test_tls() { - // Connection::connect("blos.sm", None, None) - // .await - // .unwrap() - // .ensure_tls() - // .await - // .unwrap(); - // } -} diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index 8875ebb..0000000 --- a/src/error.rs +++ /dev/null @@ -1,78 +0,0 @@ -use std::str::Utf8Error; - -use rsasl::mechname::MechanismNameError; - -use crate::stanza::client::error::Error as ClientError; -use crate::stanza::stream::Error as StreamError; -use crate::{jid::ParseError, stanza::sasl::Failure}; - -#[derive(Debug)] -pub enum Error { - Connection, - Utf8Decode, - Negotiation, - TlsRequired, - AlreadyTls, - Unsupported, - NoLocalpart, - AlreadyConnecting, - UnexpectedElement(peanuts::Element), - XML(peanuts::Error), - Deserialization(peanuts::DeserializeError), - SASL(SASLError), - JID(ParseError), - Authentication(Failure), - ClientError(ClientError), - StreamError(StreamError), - MissingError, - Disconnected, - Connecting, -} - -#[derive(Debug)] -pub enum SASLError { - SASL(rsasl::prelude::SASLError), - MechanismName(MechanismNameError), -} - -impl From<rsasl::prelude::SASLError> for Error { - fn from(e: rsasl::prelude::SASLError) -> Self { - Self::SASL(SASLError::SASL(e)) - } -} - -impl From<peanuts::DeserializeError> for Error { - fn from(e: peanuts::DeserializeError) -> Self { - Error::Deserialization(e) - } -} - -impl From<MechanismNameError> for Error { - fn from(e: MechanismNameError) -> Self { - Self::SASL(SASLError::MechanismName(e)) - } -} - -impl From<SASLError> for Error { - fn from(e: SASLError) -> Self { - Self::SASL(e) - } -} - -impl From<Utf8Error> for Error { - fn from(_e: Utf8Error) -> Self { - Self::Utf8Decode - } -} - -impl From<peanuts::Error> for Error { - fn from(e: peanuts::Error) -> Self { - Self::XML(e) - } -} - -impl From<ParseError> for Error { - fn from(e: ParseError) -> Self { - Self::JID(e) - } -} diff --git a/src/jabber_stream.rs b/src/jabber_stream.rs deleted file mode 100644 index 8ee45b5..0000000 --- a/src/jabber_stream.rs +++ /dev/null @@ -1,394 +0,0 @@ -use std::pin::pin; -use std::str::{self, FromStr}; -use std::sync::Arc; - -use async_recursion::async_recursion; -use futures::StreamExt; -use peanuts::element::{FromContent, IntoElement}; -use peanuts::{Reader, Writer}; -use rsasl::prelude::{Mechname, SASLClient, SASLConfig}; -use tokio::io::{AsyncRead, AsyncWrite, ReadHalf, WriteHalf}; -use tokio_native_tls::native_tls::TlsConnector; -use tracing::{debug, instrument}; - -use crate::connection::{Tls, Unencrypted}; -use crate::error::Error; -use crate::stanza::bind::{Bind, BindType, FullJidType, ResourceType}; -use crate::stanza::client::iq::{Iq, IqType, Query}; -use crate::stanza::client::Stanza; -use crate::stanza::sasl::{Auth, Challenge, Mechanisms, Response, ServerResponse}; -use crate::stanza::starttls::{Proceed, StartTls}; -use crate::stanza::stream::{Feature, Features, Stream}; -use crate::stanza::XML_VERSION; -use crate::JID; -use crate::{Connection, Result}; - -// open stream (streams started) -pub struct JabberStream<S> { - reader: Reader<ReadHalf<S>>, - writer: Writer<WriteHalf<S>>, -} - -impl<S: AsyncRead> futures::Stream for JabberStream<S> { - type Item = Result<Stanza>; - - fn poll_next( - self: std::pin::Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> std::task::Poll<Option<Self::Item>> { - pin!(self).reader.poll_next_unpin(cx).map(|content| { - content.map(|content| -> Result<Stanza> { - let stanza = content.map(|content| Stanza::from_content(content))?; - Ok(stanza?) - }) - }) - } -} - -impl<S> JabberStream<S> -where - S: AsyncRead + AsyncWrite + Unpin + Send + std::fmt::Debug, - JabberStream<S>: std::fmt::Debug, -{ - #[instrument] - pub async fn sasl(mut self, mechanisms: Mechanisms, sasl_config: Arc<SASLConfig>) -> Result<S> { - let sasl = SASLClient::new(sasl_config); - let mut offered_mechs: Vec<&Mechname> = Vec::new(); - for mechanism in &mechanisms.mechanisms { - offered_mechs.push(Mechname::parse(mechanism.as_bytes())?) - } - debug!("{:?}", offered_mechs); - let mut session = sasl.start_suggested(&offered_mechs)?; - let selected_mechanism = session.get_mechname().as_str().to_owned(); - debug!("selected mech: {:?}", selected_mechanism); - let mut data: Option<Vec<u8>> = None; - - if !session.are_we_first() { - // if not first mention the mechanism then get challenge data - // mention mechanism - let auth = Auth { - mechanism: selected_mechanism, - sasl_data: "=".to_string(), - }; - self.writer.write_full(&auth).await?; - // get challenge data - let challenge: Challenge = self.reader.read().await?; - debug!("challenge: {:?}", challenge); - data = Some((*challenge).as_bytes().to_vec()); - debug!("we didn't go first"); - } else { - // if first, mention mechanism and send data - let mut sasl_data = Vec::new(); - session.step64(None, &mut sasl_data).unwrap(); - let auth = Auth { - mechanism: selected_mechanism, - sasl_data: str::from_utf8(&sasl_data)?.to_string(), - }; - debug!("{:?}", auth); - self.writer.write_full(&auth).await?; - - let server_response: ServerResponse = self.reader.read().await?; - debug!("server_response: {:#?}", server_response); - match server_response { - ServerResponse::Challenge(challenge) => { - data = Some((*challenge).as_bytes().to_vec()) - } - ServerResponse::Success(success) => { - data = success.clone().map(|success| success.as_bytes().to_vec()) - } - ServerResponse::Failure(failure) => return Err(Error::Authentication(failure)), - } - debug!("we went first"); - } - - // stepping the authentication exchange to completion - if data != None { - debug!("data: {:?}", data); - let mut sasl_data = Vec::new(); - while { - // decide if need to send more data over - let state = session - .step64(data.as_deref(), &mut sasl_data) - .expect("step errored!"); - state.is_running() - } { - // While we aren't finished, receive more data from the other party - let response = Response::new(str::from_utf8(&sasl_data)?.to_string()); - debug!("response: {:?}", response); - let stdout = tokio::io::stdout(); - let mut writer = Writer::new(stdout); - writer.write_full(&response).await?; - self.writer.write_full(&response).await?; - debug!("response written"); - - let server_response: ServerResponse = self.reader.read().await?; - debug!("server_response: {:#?}", server_response); - match server_response { - ServerResponse::Challenge(challenge) => { - data = Some((*challenge).as_bytes().to_vec()) - } - ServerResponse::Success(success) => { - data = success.clone().map(|success| success.as_bytes().to_vec()) - } - ServerResponse::Failure(failure) => return Err(Error::Authentication(failure)), - } - } - } - let writer = self.writer.into_inner(); - let reader = self.reader.into_inner(); - let stream = reader.unsplit(writer); - Ok(stream) - } - - #[instrument] - pub async fn bind(mut self, jid: &mut JID) -> Result<Self> { - let iq_id = nanoid::nanoid!(); - if let Some(resource) = &jid.resourcepart { - let iq = Iq { - from: None, - id: iq_id.clone(), - to: None, - r#type: IqType::Set, - lang: None, - query: Some(Query::Bind(Bind { - r#type: Some(BindType::Resource(ResourceType(resource.to_string()))), - })), - errors: Vec::new(), - }; - self.writer.write_full(&iq).await?; - let result: Iq = self.reader.read().await?; - match result { - Iq { - from: _, - id, - to: _, - r#type: IqType::Result, - lang: _, - query: - Some(Query::Bind(Bind { - r#type: Some(BindType::Jid(FullJidType(new_jid))), - })), - errors: _, - } if id == iq_id => { - *jid = new_jid; - return Ok(self); - } - Iq { - from: _, - id, - to: _, - r#type: IqType::Error, - lang: _, - query: None, - errors, - } if id == iq_id => { - return Err(Error::ClientError( - errors.first().ok_or(Error::MissingError)?.clone(), - )) - } - _ => return Err(Error::UnexpectedElement(result.into_element())), - } - } else { - let iq = Iq { - from: None, - id: iq_id.clone(), - to: None, - r#type: IqType::Set, - lang: None, - query: Some(Query::Bind(Bind { r#type: None })), - errors: Vec::new(), - }; - self.writer.write_full(&iq).await?; - let result: Iq = self.reader.read().await?; - match result { - Iq { - from: _, - id, - to: _, - r#type: IqType::Result, - lang: _, - query: - Some(Query::Bind(Bind { - r#type: Some(BindType::Jid(FullJidType(new_jid))), - })), - errors: _, - } if id == iq_id => { - *jid = new_jid; - return Ok(self); - } - Iq { - from: _, - id, - to: _, - r#type: IqType::Error, - lang: _, - query: None, - errors, - } if id == iq_id => { - return Err(Error::ClientError( - errors.first().ok_or(Error::MissingError)?.clone(), - )) - } - _ => return Err(Error::UnexpectedElement(result.into_element())), - } - } - } - - #[instrument] - pub async fn start_stream(connection: S, server: &mut String) -> Result<Self> { - // client to server - let (reader, writer) = tokio::io::split(connection); - let mut reader = Reader::new(reader); - let mut writer = Writer::new(writer); - - // declaration - writer.write_declaration(XML_VERSION).await?; - - // opening stream element - let stream = Stream::new_client( - None, - JID::from_str(server.as_ref())?, - None, - "en".to_string(), - ); - writer.write_start(&stream).await?; - - // server to client - - // may or may not send a declaration - let _decl = reader.read_prolog().await?; - - // receive stream element and validate - let stream: Stream = reader.read_start().await?; - debug!("got stream: {:?}", stream); - if let Some(from) = stream.from { - *server = from.to_string(); - } - - Ok(Self { reader, writer }) - } - - #[instrument] - pub async fn get_features(mut self) -> Result<(Features, Self)> { - debug!("getting features"); - let features: Features = self.reader.read().await?; - debug!("got features: {:?}", features); - Ok((features, self)) - } - - pub fn into_inner(self) -> S { - self.reader.into_inner().unsplit(self.writer.into_inner()) - } - - pub async fn send_stanza(&mut self, stanza: &Stanza) -> Result<()> { - self.writer.write(stanza).await?; - Ok(()) - } -} - -impl JabberStream<Unencrypted> { - #[instrument] - pub async fn starttls(mut self, domain: impl AsRef<str> + std::fmt::Debug) -> Result<Tls> { - self.writer - .write_full(&StartTls { required: false }) - .await?; - let proceed: Proceed = self.reader.read().await?; - debug!("got proceed: {:?}", proceed); - let connector = TlsConnector::new().unwrap(); - let stream = self.reader.into_inner().unsplit(self.writer.into_inner()); - if let Ok(tls_stream) = tokio_native_tls::TlsConnector::from(connector) - .connect(domain.as_ref(), stream) - .await - { - return Ok(tls_stream); - } else { - return Err(Error::Connection); - } - } -} - -impl std::fmt::Debug for JabberStream<Tls> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Jabber") - .field("connection", &"tls") - .finish() - } -} - -impl std::fmt::Debug for JabberStream<Unencrypted> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Jabber") - .field("connection", &"unencrypted") - .finish() - } -} - -#[cfg(test)] -mod tests { - use std::time::Duration; - - use super::*; - use crate::connection::Connection; - use test_log::test; - use tokio::time::sleep; - - #[test(tokio::test)] - async fn start_stream() { - // let connection = Connection::connect("blos.sm", None, None).await.unwrap(); - // match connection { - // Connection::Encrypted(mut c) => c.start_stream().await.unwrap(), - // Connection::Unencrypted(mut c) => c.start_stream().await.unwrap(), - // } - } - - #[test(tokio::test)] - async fn sasl() { - // let mut jabber = Connection::connect_user("test@blos.sm", "slayed".to_string()) - // .await - // .unwrap() - // .ensure_tls() - // .await - // .unwrap(); - // let text = str::from_utf8(jabber.reader.buffer.data()).unwrap(); - // println!("data: {}", text); - // jabber.start_stream().await.unwrap(); - - // let text = str::from_utf8(jabber.reader.buffer.data()).unwrap(); - // println!("data: {}", text); - // jabber.reader.read_buf().await.unwrap(); - // let text = str::from_utf8(jabber.reader.buffer.data()).unwrap(); - // println!("data: {}", text); - - // let features = jabber.get_features().await.unwrap(); - // let (sasl_config, feature) = ( - // jabber.auth.clone().unwrap(), - // features - // .features - // .iter() - // .find(|feature| matches!(feature, Feature::Sasl(_))) - // .unwrap(), - // ); - // match feature { - // Feature::StartTls(_start_tls) => todo!(), - // Feature::Sasl(mechanisms) => { - // jabber.sasl(mechanisms.clone(), sasl_config).await.unwrap(); - // } - // Feature::Bind => todo!(), - // Feature::Unknown => todo!(), - // } - } - - #[tokio::test] - async fn negotiate() { - // let _jabber = Connection::connect_user("test@blos.sm", "slayed".to_string()) - // .await - // .unwrap() - // .ensure_tls() - // .await - // .unwrap() - // .negotiate() - // .await - // .unwrap(); - // sleep(Duration::from_secs(5)).await - } -} diff --git a/src/jid.rs b/src/jid.rs deleted file mode 100644 index 233227a..0000000 --- a/src/jid.rs +++ /dev/null @@ -1,179 +0,0 @@ -use std::str::FromStr; - -#[derive(PartialEq, Debug, Clone)] -pub struct JID { - // TODO: validate localpart (length, char] - pub localpart: Option<String>, - pub domainpart: String, - pub resourcepart: Option<String>, -} - -pub enum JIDError { - NoResourcePart, - ParseError(ParseError), -} - -#[derive(Debug)] -pub enum ParseError { - Empty, - Malformed(String), -} - -impl From<ParseError> for peanuts::Error { - fn from(e: ParseError) -> Self { - match e { - ParseError::Empty => peanuts::Error::DeserializeError("".to_string()), - ParseError::Malformed(e) => peanuts::Error::DeserializeError(e), - } - } -} - -impl JID { - pub fn new( - localpart: Option<String>, - domainpart: String, - resourcepart: Option<String>, - ) -> Self { - Self { - localpart, - domainpart: domainpart.parse().unwrap(), - resourcepart, - } - } - - pub fn as_bare(&self) -> Self { - Self { - localpart: self.localpart.clone(), - domainpart: self.domainpart.clone(), - resourcepart: None, - } - } - - pub fn as_full(&self) -> Result<&Self, JIDError> { - if let Some(_) = self.resourcepart { - Ok(&self) - } else { - Err(JIDError::NoResourcePart) - } - } -} - -impl FromStr for JID { - type Err = ParseError; - - fn from_str(s: &str) -> Result<Self, Self::Err> { - let split: Vec<&str> = s.split('@').collect(); - match split.len() { - 0 => Err(ParseError::Empty), - 1 => { - let split: Vec<&str> = split[0].split('/').collect(); - match split.len() { - 1 => Ok(JID::new(None, split[0].to_string(), None)), - 2 => Ok(JID::new( - None, - split[0].to_string(), - Some(split[1].to_string()), - )), - _ => Err(ParseError::Malformed(s.to_string())), - } - } - 2 => { - let split2: Vec<&str> = split[1].split('/').collect(); - match split2.len() { - 1 => Ok(JID::new( - Some(split[0].to_string()), - split2[0].to_string(), - None, - )), - 2 => Ok(JID::new( - Some(split[0].to_string()), - split2[0].to_string(), - Some(split2[1].to_string()), - )), - _ => Err(ParseError::Malformed(s.to_string())), - } - } - _ => Err(ParseError::Malformed(s.to_string())), - } - } -} - -impl TryFrom<String> for JID { - type Error = ParseError; - - fn try_from(value: String) -> Result<Self, Self::Error> { - value.parse() - } -} - -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!( - f, - "{}{}{}", - self.localpart.clone().map(|l| l + "@").unwrap_or_default(), - self.domainpart, - self.resourcepart - .clone() - .map(|r| "/".to_owned() + &r) - .unwrap_or_default() - ) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn jid_to_string() { - assert_eq!( - JID::new(Some("cel".into()), "blos.sm".into(), None).to_string(), - "cel@blos.sm".to_owned() - ); - } - - #[test] - fn parse_full_jid() { - assert_eq!( - "cel@blos.sm/greenhouse".parse::<JID>().unwrap(), - JID::new( - Some("cel".into()), - "blos.sm".into(), - Some("greenhouse".into()) - ) - ) - } - - #[test] - fn parse_bare_jid() { - assert_eq!( - "cel@blos.sm".parse::<JID>().unwrap(), - JID::new(Some("cel".into()), "blos.sm".into(), None) - ) - } - - #[test] - fn parse_domain_jid() { - assert_eq!( - "component.blos.sm".parse::<JID>().unwrap(), - JID::new(None, "component.blos.sm".into(), None) - ) - } - - #[test] - fn parse_full_domain_jid() { - assert_eq!( - "component.blos.sm/bot".parse::<JID>().unwrap(), - JID::new(None, "component.blos.sm".into(), Some("bot".into())) - ) - } -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 43aa581..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,36 +0,0 @@ -#![allow(unused_must_use)] -// #![feature(let_chains)] - -// TODO: logging (dropped errors) -pub mod client; -pub mod connection; -pub mod error; -pub mod jabber_stream; -pub mod jid; -pub mod stanza; - -pub use connection::Connection; -use connection::Tls; -pub use error::Error; -pub use jabber_stream::JabberStream; -pub use jid::JID; - -pub type Result<T> = std::result::Result<T, Error>; - -pub async fn login<J: AsRef<str>, P: AsRef<str>>(jid: J, password: P) -> Result<JabberStream<Tls>> { - todo!() - // Ok(Connection::connect_user(jid, password.as_ref().to_string()) - // .await? - // .ensure_tls() - // .await? - // .negotiate() - // .await?) -} - -#[cfg(test)] -mod tests { - // #[tokio::test] - // async fn test_login() { - // crate::login("test@blos.sm/clown", "slayed").await.unwrap(); - // } -} diff --git a/src/stanza/bind.rs b/src/stanza/bind.rs deleted file mode 100644 index 0e67a83..0000000 --- a/src/stanza/bind.rs +++ /dev/null @@ -1,99 +0,0 @@ -use peanuts::{ - element::{FromElement, IntoElement}, - Element, -}; - -use crate::JID; - -pub const XMLNS: &str = "urn:ietf:params:xml:ns:xmpp-bind"; - -#[derive(Clone)] -pub struct Bind { - pub r#type: Option<BindType>, -} - -impl FromElement for Bind { - fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> { - element.check_name("bind"); - element.check_name(XMLNS); - - let r#type = element.pop_child_opt()?; - - Ok(Bind { r#type }) - } -} - -impl IntoElement for Bind { - fn builder(&self) -> peanuts::element::ElementBuilder { - Element::builder("bind", Some(XMLNS)).push_child_opt(self.r#type.clone()) - } -} - -#[derive(Clone)] -pub enum BindType { - Resource(ResourceType), - Jid(FullJidType), -} - -impl FromElement for BindType { - fn from_element(element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> { - match element.identify() { - (Some(XMLNS), "resource") => { - Ok(BindType::Resource(ResourceType::from_element(element)?)) - } - (Some(XMLNS), "jid") => Ok(BindType::Jid(FullJidType::from_element(element)?)), - _ => Err(peanuts::DeserializeError::UnexpectedElement(element)), - } - } -} - -impl IntoElement for BindType { - fn builder(&self) -> peanuts::element::ElementBuilder { - match self { - BindType::Resource(resource_type) => resource_type.builder(), - BindType::Jid(full_jid_type) => full_jid_type.builder(), - } - } -} - -// minLength 8 maxLength 3071 -#[derive(Clone)] -pub struct FullJidType(pub JID); - -impl FromElement for FullJidType { - fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> { - element.check_name("jid"); - element.check_namespace(XMLNS); - - let jid = element.pop_value()?; - - Ok(FullJidType(jid)) - } -} - -impl IntoElement for FullJidType { - fn builder(&self) -> peanuts::element::ElementBuilder { - Element::builder("jid", Some(XMLNS)).push_text(self.0.clone()) - } -} - -// minLength 1 maxLength 1023 -#[derive(Clone)] -pub struct ResourceType(pub String); - -impl FromElement for ResourceType { - fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> { - element.check_name("resource")?; - element.check_namespace(XMLNS)?; - - let resource = element.pop_value()?; - - Ok(ResourceType(resource)) - } -} - -impl IntoElement for ResourceType { - fn builder(&self) -> peanuts::element::ElementBuilder { - Element::builder("resource", Some(XMLNS)).push_text(self.0.clone()) - } -} diff --git a/src/stanza/client/error.rs b/src/stanza/client/error.rs deleted file mode 100644 index 545b9a7..0000000 --- a/src/stanza/client/error.rs +++ /dev/null @@ -1,83 +0,0 @@ -use std::str::FromStr; - -use peanuts::element::{FromElement, IntoElement}; -use peanuts::{DeserializeError, Element}; - -use crate::stanza::stanza_error::Error as StanzaError; -use crate::stanza::stanza_error::Text; - -use super::XMLNS; - -#[derive(Clone, Debug)] -pub struct Error { - by: Option<String>, - r#type: ErrorType, - // children (sequence) - error: StanzaError, - text: Option<Text>, -} - -impl FromElement for Error { - fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> { - element.check_name("error")?; - element.check_name(XMLNS)?; - - let by = element.attribute_opt("by")?; - let r#type = element.attribute("type")?; - let error = element.pop_child_one()?; - let text = element.pop_child_opt()?; - - Ok(Error { - by, - r#type, - error, - text, - }) - } -} - -impl IntoElement for Error { - fn builder(&self) -> peanuts::element::ElementBuilder { - Element::builder("error", Some(XMLNS)) - .push_attribute_opt("by", self.by.clone()) - .push_attribute("type", self.r#type) - .push_child(self.error.clone()) - .push_child_opt(self.text.clone()) - } -} - -#[derive(Copy, Clone, Debug)] -pub enum ErrorType { - Auth, - Cancel, - Continue, - Modify, - Wait, -} - -impl FromStr for ErrorType { - type Err = DeserializeError; - - fn from_str(s: &str) -> Result<Self, Self::Err> { - match s { - "auth" => Ok(ErrorType::Auth), - "cancel" => Ok(ErrorType::Cancel), - "continue" => Ok(ErrorType::Continue), - "modify" => Ok(ErrorType::Modify), - "wait" => Ok(ErrorType::Wait), - _ => Err(DeserializeError::FromStr(s.to_string())), - } - } -} - -impl ToString for ErrorType { - fn to_string(&self) -> String { - match self { - ErrorType::Auth => "auth".to_string(), - ErrorType::Cancel => "cancel".to_string(), - ErrorType::Continue => "continue".to_string(), - ErrorType::Modify => "modify".to_string(), - ErrorType::Wait => "wait".to_string(), - } - } -} diff --git a/src/stanza/client/iq.rs b/src/stanza/client/iq.rs deleted file mode 100644 index b23f8b7..0000000 --- a/src/stanza/client/iq.rs +++ /dev/null @@ -1,124 +0,0 @@ -use std::str::FromStr; - -use peanuts::{ - element::{FromElement, IntoElement}, - DeserializeError, Element, XML_NS, -}; - -use crate::{ - stanza::{ - bind::{self, Bind}, - client::error::Error, - }, - JID, -}; - -use super::XMLNS; - -pub struct Iq { - pub from: Option<JID>, - pub id: String, - pub to: Option<JID>, - pub r#type: IqType, - pub lang: Option<String>, - // children - // ##other - pub query: Option<Query>, - pub errors: Vec<Error>, -} - -#[derive(Clone)] -pub enum Query { - Bind(Bind), - Unsupported, -} - -impl FromElement for Query { - fn from_element(element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> { - match element.identify() { - (Some(bind::XMLNS), "bind") => Ok(Query::Bind(Bind::from_element(element)?)), - _ => Ok(Query::Unsupported), - } - } -} - -impl IntoElement for Query { - fn builder(&self) -> peanuts::element::ElementBuilder { - match self { - Query::Bind(bind) => bind.builder(), - // TODO: consider what to do if attempt to serialize unsupported - Query::Unsupported => todo!(), - } - } -} - -impl FromElement for Iq { - fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> { - element.check_name("iq")?; - element.check_namespace(XMLNS)?; - - let from = element.attribute_opt("from")?; - let id = element.attribute("id")?; - let to = element.attribute_opt("to")?; - let r#type = element.attribute("type")?; - let lang = element.attribute_opt_namespaced("lang", XML_NS)?; - let query = element.pop_child_opt()?; - let errors = element.pop_children()?; - - Ok(Iq { - from, - id, - to, - r#type, - lang, - query, - errors, - }) - } -} - -impl IntoElement for Iq { - fn builder(&self) -> peanuts::element::ElementBuilder { - Element::builder("iq", Some(XMLNS)) - .push_attribute_opt("from", self.from.clone()) - .push_attribute("id", self.id.clone()) - .push_attribute_opt("to", self.to.clone()) - .push_attribute("type", self.r#type) - .push_attribute_opt_namespaced(XML_NS, "lang", self.lang.clone()) - .push_child_opt(self.query.clone()) - .push_children(self.errors.clone()) - } -} - -#[derive(Copy, Clone, PartialEq, Eq)] -pub enum IqType { - Error, - Get, - Result, - Set, -} - -impl FromStr for IqType { - type Err = DeserializeError; - - fn from_str(s: &str) -> Result<Self, Self::Err> { - match s { - "error" => Ok(IqType::Error), - "get" => Ok(IqType::Get), - "result" => Ok(IqType::Result), - "set" => Ok(IqType::Set), - _ => Err(DeserializeError::FromStr(s.to_string())), - } - } -} - -impl ToString for IqType { - fn to_string(&self) -> String { - match self { - IqType::Error => "error".to_string(), - IqType::Get => "get".to_string(), - IqType::Result => "result".to_string(), - IqType::Set => "set".to_string(), - } - } -} diff --git a/src/stanza/client/message.rs b/src/stanza/client/message.rs deleted file mode 100644 index 626d781..0000000 --- a/src/stanza/client/message.rs +++ /dev/null @@ -1,186 +0,0 @@ -use std::str::FromStr; - -use peanuts::{ - element::{FromElement, IntoElement}, - DeserializeError, Element, XML_NS, -}; - -use crate::JID; - -use super::XMLNS; - -pub struct Message { - from: Option<JID>, - id: Option<String>, - to: Option<JID>, - // can be omitted, if so default to normal - r#type: MessageType, - lang: Option<String>, - // children - subject: Option<Subject>, - body: Option<Body>, - thread: Option<Thread>, -} - -impl FromElement for Message { - fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> { - element.check_name("message")?; - element.check_namespace(XMLNS)?; - - let from = element.attribute_opt("from")?; - let id = element.attribute_opt("id")?; - let to = element.attribute_opt("to")?; - let r#type = element.attribute_opt("type")?.unwrap_or_default(); - let lang = element.attribute_opt_namespaced("lang", XML_NS)?; - - let subject = element.child_opt()?; - let body = element.child_opt()?; - let thread = element.child_opt()?; - - Ok(Message { - from, - id, - to, - r#type, - lang, - subject, - body, - thread, - }) - } -} - -impl IntoElement for Message { - fn builder(&self) -> peanuts::element::ElementBuilder { - Element::builder("message", Some(XMLNS)) - .push_attribute_opt("from", self.from.clone()) - .push_attribute_opt("id", self.id.clone()) - .push_attribute_opt("to", self.to.clone()) - .push_attribute_opt("type", { - if self.r#type == MessageType::Normal { - None - } else { - Some(self.r#type) - } - }) - .push_attribute_opt_namespaced(XML_NS, "lang", self.lang.clone()) - .push_child_opt(self.subject.clone()) - .push_child_opt(self.body.clone()) - .push_child_opt(self.thread.clone()) - } -} - -#[derive(Default, PartialEq, Eq, Copy, Clone)] -pub enum MessageType { - Chat, - Error, - Groupchat, - Headline, - #[default] - Normal, -} - -impl FromStr for MessageType { - type Err = DeserializeError; - - fn from_str(s: &str) -> Result<Self, Self::Err> { - match s { - "chat" => Ok(MessageType::Chat), - "error" => Ok(MessageType::Error), - "groupchat" => Ok(MessageType::Groupchat), - "headline" => Ok(MessageType::Headline), - "normal" => Ok(MessageType::Normal), - _ => Err(DeserializeError::FromStr(s.to_string())), - } - } -} - -impl ToString for MessageType { - fn to_string(&self) -> String { - match self { - MessageType::Chat => "chat".to_string(), - MessageType::Error => "error".to_string(), - MessageType::Groupchat => "groupchat".to_string(), - MessageType::Headline => "headline".to_string(), - MessageType::Normal => "normal".to_string(), - } - } -} - -#[derive(Clone)] -pub struct Body { - lang: Option<String>, - body: Option<String>, -} - -impl FromElement for Body { - fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> { - element.check_name("body")?; - element.check_namespace(XMLNS)?; - - let lang = element.attribute_opt_namespaced("lang", XML_NS)?; - let body = element.pop_value_opt()?; - - Ok(Body { lang, body }) - } -} - -impl IntoElement for Body { - fn builder(&self) -> peanuts::element::ElementBuilder { - Element::builder("body", Some(XMLNS)) - .push_attribute_opt_namespaced(XML_NS, "lang", self.lang.clone()) - .push_text_opt(self.body.clone()) - } -} - -#[derive(Clone)] -pub struct Subject { - lang: Option<String>, - subject: Option<String>, -} - -impl FromElement for Subject { - fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> { - element.check_name("subject")?; - element.check_namespace(XMLNS)?; - - let lang = element.attribute_opt_namespaced("lang", XML_NS)?; - let subject = element.pop_value_opt()?; - - Ok(Subject { lang, subject }) - } -} - -impl IntoElement for Subject { - fn builder(&self) -> peanuts::element::ElementBuilder { - Element::builder("subject", Some(XMLNS)) - .push_attribute_opt_namespaced(XML_NS, "lang", self.lang.clone()) - .push_text_opt(self.subject.clone()) - } -} - -#[derive(Clone)] -pub struct Thread { - parent: Option<String>, - thread: Option<String>, -} - -impl FromElement for Thread { - fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> { - element.check_name("thread")?; - element.check_namespace(XMLNS)?; - - let parent = element.attribute_opt("parent")?; - let thread = element.pop_value_opt()?; - - Ok(Thread { parent, thread }) - } -} - -impl IntoElement for Thread { - fn builder(&self) -> peanuts::element::ElementBuilder { - Element::builder("thread", Some(XMLNS)) - .push_attribute_opt("parent", self.parent.clone()) - .push_text_opt(self.thread.clone()) - } -} diff --git a/src/stanza/client/mod.rs b/src/stanza/client/mod.rs deleted file mode 100644 index 2b063d6..0000000 --- a/src/stanza/client/mod.rs +++ /dev/null @@ -1,61 +0,0 @@ -use iq::Iq; -use message::Message; -use peanuts::{ - element::{Content, ContentBuilder, FromContent, FromElement, IntoContent, IntoElement}, - DeserializeError, -}; -use presence::Presence; - -use super::stream::{self, Error as StreamError}; - -pub mod error; -pub mod iq; -pub mod message; -pub mod presence; - -pub const XMLNS: &str = "jabber:client"; - -pub enum Stanza { - Message(Message), - Presence(Presence), - Iq(Iq), - Error(StreamError), - OtherContent(Content), -} - -impl FromContent for Stanza { - fn from_content(content: Content) -> peanuts::element::DeserializeResult<Self> { - match content { - Content::Element(element) => Ok(Stanza::from_element(element)?), - Content::Text(_) => Ok(Stanza::OtherContent(content)), - Content::PI => Ok(Stanza::OtherContent(content)), - Content::Comment(_) => Ok(Stanza::OtherContent(content)), - } - } -} - -impl FromElement for Stanza { - fn from_element(element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> { - match element.identify() { - (Some(XMLNS), "message") => Ok(Stanza::Message(Message::from_element(element)?)), - (Some(XMLNS), "presence") => Ok(Stanza::Presence(Presence::from_element(element)?)), - (Some(XMLNS), "iq") => Ok(Stanza::Iq(Iq::from_element(element)?)), - (Some(stream::XMLNS), "error") => { - Ok(Stanza::Error(StreamError::from_element(element)?)) - } - _ => Err(DeserializeError::UnexpectedElement(element)), - } - } -} - -impl IntoContent for Stanza { - fn builder(&self) -> peanuts::element::ContentBuilder { - match self { - Stanza::Message(message) => <Message as IntoContent>::builder(message), - Stanza::Presence(presence) => <Presence as IntoContent>::builder(presence), - Stanza::Iq(iq) => <Iq as IntoContent>::builder(iq), - Stanza::Error(error) => <StreamError as IntoContent>::builder(error), - Stanza::OtherContent(_content) => ContentBuilder::Comment("other-content".to_string()), - } - } -} diff --git a/src/stanza/client/presence.rs b/src/stanza/client/presence.rs deleted file mode 100644 index bcb04d4..0000000 --- a/src/stanza/client/presence.rs +++ /dev/null @@ -1,226 +0,0 @@ -use std::str::FromStr; - -use peanuts::{ - element::{FromElement, IntoElement}, - DeserializeError, Element, XML_NS, -}; - -use crate::JID; - -use super::{error::Error, XMLNS}; - -pub struct Presence { - from: Option<JID>, - id: Option<String>, - to: Option<JID>, - r#type: Option<PresenceType>, - lang: Option<String>, - // children - show: Option<Show>, - status: Option<Status>, - priority: Option<Priority>, - // TODO: ##other - // other: Vec<Other>, - errors: Vec<Error>, -} - -impl FromElement for Presence { - fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> { - element.check_name("presence")?; - element.check_namespace(XMLNS)?; - - let from = element.attribute_opt("from")?; - let id = element.attribute_opt("id")?; - let to = element.attribute_opt("to")?; - let r#type = element.attribute_opt("type")?; - let lang = element.attribute_opt_namespaced("lang", XML_NS)?; - - let show = element.child_opt()?; - let status = element.child_opt()?; - let priority = element.child_opt()?; - let errors = element.children()?; - - Ok(Presence { - from, - id, - to, - r#type, - lang, - show, - status, - priority, - errors, - }) - } -} - -impl IntoElement for Presence { - fn builder(&self) -> peanuts::element::ElementBuilder { - Element::builder("presence", Some(XMLNS)) - .push_attribute_opt("from", self.from.clone()) - .push_attribute_opt("id", self.id.clone()) - .push_attribute_opt("to", self.to.clone()) - .push_attribute_opt("type", self.r#type) - .push_attribute_opt_namespaced(XML_NS, "lang", self.lang.clone()) - .push_child_opt(self.show) - .push_child_opt(self.status.clone()) - .push_child_opt(self.priority) - .push_children(self.errors.clone()) - } -} - -pub enum Other {} - -#[derive(Copy, Clone)] -pub enum PresenceType { - Error, - Probe, - Subscribe, - Subscribed, - Unavailable, - Unsubscribe, - Unsubscribed, -} - -impl FromStr for PresenceType { - type Err = DeserializeError; - - fn from_str(s: &str) -> Result<Self, Self::Err> { - match s { - "error" => Ok(PresenceType::Error), - "probe" => Ok(PresenceType::Probe), - "subscribe" => Ok(PresenceType::Subscribe), - "subscribed" => Ok(PresenceType::Subscribed), - "unavailable" => Ok(PresenceType::Unavailable), - "unsubscribe" => Ok(PresenceType::Unsubscribe), - "unsubscribed" => Ok(PresenceType::Unsubscribed), - s => Err(DeserializeError::FromStr(s.to_string())), - } - } -} - -impl ToString for PresenceType { - fn to_string(&self) -> String { - match self { - PresenceType::Error => "error".to_string(), - PresenceType::Probe => "probe".to_string(), - PresenceType::Subscribe => "subscribe".to_string(), - PresenceType::Subscribed => "subscribed".to_string(), - PresenceType::Unavailable => "unavailable".to_string(), - PresenceType::Unsubscribe => "unsubscribe".to_string(), - PresenceType::Unsubscribed => "unsubscribed".to_string(), - } - } -} - -#[derive(Copy, Clone)] -pub enum Show { - Away, - Chat, - Dnd, - Xa, -} - -impl FromElement for Show { - fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> { - element.check_name("show")?; - element.check_namespace(XMLNS)?; - - Ok(element.pop_value()?) - } -} - -impl FromStr for Show { - type Err = DeserializeError; - - fn from_str(s: &str) -> Result<Self, Self::Err> { - match s { - "away" => Ok(Show::Away), - "chat" => Ok(Show::Chat), - "dnd" => Ok(Show::Dnd), - "xa" => Ok(Show::Xa), - s => Err(DeserializeError::FromStr(s.to_string())), - } - } -} - -impl IntoElement for Show { - fn builder(&self) -> peanuts::element::ElementBuilder { - Element::builder("show", Some(XMLNS)).push_text(*self) - } -} - -impl ToString for Show { - fn to_string(&self) -> String { - match self { - Show::Away => "away".to_string(), - Show::Chat => "chat".to_string(), - Show::Dnd => "dnd".to_string(), - Show::Xa => "xa".to_string(), - } - } -} - -#[derive(Clone)] -pub struct Status { - lang: Option<String>, - status: String1024, -} - -impl FromElement for Status { - fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> { - element.check_name("status")?; - element.check_namespace(XMLNS)?; - - let lang = element.attribute_opt_namespaced("lang", XML_NS)?; - let status = element.pop_value()?; - - Ok(Status { lang, status }) - } -} - -impl IntoElement for Status { - fn builder(&self) -> peanuts::element::ElementBuilder { - Element::builder("status", Some(XMLNS)) - .push_attribute_opt_namespaced(XML_NS, "lang", self.lang.clone()) - .push_text(self.status.clone()) - } -} - -// TODO: enforce? -/// minLength 1 maxLength 1024 -#[derive(Clone)] -pub struct String1024(pub String); - -impl FromStr for String1024 { - type Err = DeserializeError; - - fn from_str(s: &str) -> Result<Self, Self::Err> { - Ok(String1024(s.to_string())) - } -} - -impl ToString for String1024 { - fn to_string(&self) -> String { - self.0.clone() - } -} - -// xs:byte -#[derive(Clone, Copy)] -pub struct Priority(pub i8); - -impl FromElement for Priority { - fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> { - element.check_name("priority")?; - element.check_namespace(XMLNS)?; - - Ok(Priority(element.pop_value()?)) - } -} - -impl IntoElement for Priority { - fn builder(&self) -> peanuts::element::ElementBuilder { - Element::builder("priority", Some(XMLNS)).push_text(self.0) - } -} diff --git a/src/stanza/mod.rs b/src/stanza/mod.rs deleted file mode 100644 index 32716d3..0000000 --- a/src/stanza/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -use peanuts::declaration::VersionInfo; - -pub mod bind; -pub mod client; -pub mod sasl; -pub mod stanza_error; -pub mod starttls; -pub mod stream; -pub mod stream_error; - -pub static XML_VERSION: VersionInfo = VersionInfo::One; diff --git a/src/stanza/sasl.rs b/src/stanza/sasl.rs deleted file mode 100644 index ec6f63c..0000000 --- a/src/stanza/sasl.rs +++ /dev/null @@ -1,246 +0,0 @@ -use std::ops::Deref; - -use peanuts::{ - element::{FromElement, IntoElement}, - DeserializeError, Element, -}; -use tracing::debug; - -pub const XMLNS: &str = "urn:ietf:params:xml:ns:xmpp-sasl"; - -#[derive(Debug, Clone)] -pub struct Mechanisms { - pub mechanisms: Vec<String>, -} - -impl FromElement for Mechanisms { - fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> { - element.check_name("mechanisms")?; - element.check_namespace(XMLNS)?; - debug!("getting mechanisms"); - let mechanisms: Vec<Mechanism> = element.pop_children()?; - debug!("gottting mechanisms"); - let mechanisms = mechanisms - .into_iter() - .map(|Mechanism(mechanism)| mechanism) - .collect(); - debug!("gottting mechanisms"); - - Ok(Mechanisms { mechanisms }) - } -} - -impl IntoElement for Mechanisms { - fn builder(&self) -> peanuts::element::ElementBuilder { - Element::builder("mechanisms", Some(XMLNS)).push_children( - self.mechanisms - .iter() - .map(|mechanism| Mechanism(mechanism.to_string())) - .collect(), - ) - } -} - -pub struct Mechanism(String); - -impl FromElement for Mechanism { - fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> { - element.check_name("mechanism")?; - element.check_namespace(XMLNS)?; - - let mechanism = element.pop_value()?; - - Ok(Mechanism(mechanism)) - } -} - -impl IntoElement for Mechanism { - fn builder(&self) -> peanuts::element::ElementBuilder { - Element::builder("mechanism", Some(XMLNS)).push_text(self.0.clone()) - } -} - -impl Deref for Mechanism { - type Target = str; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -#[derive(Debug)] -pub struct Auth { - pub mechanism: String, - pub sasl_data: String, -} - -impl IntoElement for Auth { - fn builder(&self) -> peanuts::element::ElementBuilder { - Element::builder("auth", Some(XMLNS)) - .push_attribute("mechanism", self.mechanism.clone()) - .push_text(self.sasl_data.clone()) - } -} - -#[derive(Debug)] -pub struct Challenge(String); - -impl Deref for Challenge { - type Target = str; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl FromElement for Challenge { - fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> { - element.check_name("challenge")?; - element.check_namespace(XMLNS)?; - - let sasl_data = element.value()?; - - Ok(Challenge(sasl_data)) - } -} - -#[derive(Debug)] -pub struct Success(Option<String>); - -impl Deref for Success { - type Target = Option<String>; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl FromElement for Success { - fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> { - element.check_name("success")?; - element.check_namespace(XMLNS)?; - - let sasl_data = element.value_opt()?; - - Ok(Success(sasl_data)) - } -} - -#[derive(Debug)] -pub enum ServerResponse { - Challenge(Challenge), - Success(Success), - Failure(Failure), -} - -impl FromElement for ServerResponse { - fn from_element(element: Element) -> peanuts::element::DeserializeResult<Self> { - debug!("identification: {:?}", element.identify()); - match element.identify() { - (Some(XMLNS), "challenge") => { - Ok(ServerResponse::Challenge(Challenge::from_element(element)?)) - } - (Some(XMLNS), "success") => { - Ok(ServerResponse::Success(Success::from_element(element)?)) - } - (Some(XMLNS), "failure") => { - Ok(ServerResponse::Failure(Failure::from_element(element)?)) - } - _ => Err(DeserializeError::UnexpectedElement(element)), - } - } -} - -#[derive(Debug)] -pub struct Response(String); - -impl Response { - pub fn new(response: String) -> Self { - Self(response) - } -} - -impl Deref for Response { - type Target = str; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl IntoElement for Response { - fn builder(&self) -> peanuts::element::ElementBuilder { - Element::builder("response", Some(XMLNS)).push_text(self.0.clone()) - } -} - -#[derive(Debug)] -pub struct Failure { - r#type: Option<FailureType>, - text: Option<Text>, -} - -impl FromElement for Failure { - fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> { - element.check_name("failure")?; - element.check_namespace(XMLNS)?; - - let r#type = element.pop_child_opt()?; - let text = element.pop_child_opt()?; - - Ok(Failure { r#type, text }) - } -} - -#[derive(Debug)] -pub enum FailureType { - Aborted, - AccountDisabled, - CredentialsExpired, - EncryptionRequired, - IncorrectEncoding, - InvalidAuthzid, - InvalidMechanism, - MalformedRequest, - MechanismTooWeak, - NotAuthorized, - TemporaryAuthFailure, -} - -impl FromElement for FailureType { - fn from_element(element: Element) -> peanuts::element::DeserializeResult<Self> { - match element.identify() { - (Some(XMLNS), "aborted") => Ok(FailureType::Aborted), - (Some(XMLNS), "account-disabled") => Ok(FailureType::AccountDisabled), - (Some(XMLNS), "credentials-expired") => Ok(FailureType::CredentialsExpired), - (Some(XMLNS), "encryption-required") => Ok(FailureType::EncryptionRequired), - (Some(XMLNS), "incorrect-encoding") => Ok(FailureType::IncorrectEncoding), - (Some(XMLNS), "invalid-authzid") => Ok(FailureType::InvalidAuthzid), - (Some(XMLNS), "invalid-mechanism") => Ok(FailureType::InvalidMechanism), - (Some(XMLNS), "malformed-request") => Ok(FailureType::MalformedRequest), - (Some(XMLNS), "mechanism-too-weak") => Ok(FailureType::MechanismTooWeak), - (Some(XMLNS), "not-authorized") => Ok(FailureType::NotAuthorized), - (Some(XMLNS), "temporary-auth-failure") => Ok(FailureType::TemporaryAuthFailure), - _ => Err(DeserializeError::UnexpectedElement(element)), - } - } -} - -#[derive(Debug)] -pub struct Text { - lang: Option<String>, - text: Option<String>, -} - -impl FromElement for Text { - fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> { - element.check_name("text")?; - element.check_namespace(XMLNS)?; - - let lang = element.attribute_opt_namespaced("lang", peanuts::XML_NS)?; - - let text = element.pop_value_opt()?; - - Ok(Text { lang, text }) - } -} diff --git a/src/stanza/stanza_error.rs b/src/stanza/stanza_error.rs deleted file mode 100644 index 99c1f15..0000000 --- a/src/stanza/stanza_error.rs +++ /dev/null @@ -1,126 +0,0 @@ -// https://datatracker.ietf.org/doc/html/rfc6120#appendix-A.8 - -use peanuts::{ - element::{FromElement, IntoElement}, - Element, XML_NS, -}; - -pub const XMLNS: &str = "urn:ietf:params:xml:ns:xmpp-stanzas"; - -#[derive(Clone, Debug)] -pub enum Error { - BadRequest, - Conflict, - FeatureNotImplemented, - Forbidden, - Gone(Option<String>), - InternalServerError, - ItemNotFound, - JidMalformed, - NotAcceptable, - NotAllowed, - NotAuthorized, - PolicyViolation, - RecipientUnavailable, - Redirect(Option<String>), - RegistrationRequired, - RemoteServerNotFound, - RemoteServerTimeout, - ResourceConstraint, - ServiceUnavailable, - SubscriptionRequired, - UndefinedCondition, - UnexpectedRequest, -} - -impl FromElement for Error { - fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> { - let error; - match element.identify() { - (Some(XMLNS), "bad-request") => error = Error::BadRequest, - (Some(XMLNS), "conflict") => error = Error::Conflict, - (Some(XMLNS), "feature-not-implemented") => error = Error::FeatureNotImplemented, - (Some(XMLNS), "forbidden") => error = Error::Forbidden, - (Some(XMLNS), "gone") => return Ok(Error::Gone(element.pop_value_opt()?)), - (Some(XMLNS), "internal-server-error") => error = Error::InternalServerError, - (Some(XMLNS), "item-not-found") => error = Error::ItemNotFound, - (Some(XMLNS), "jid-malformed") => error = Error::JidMalformed, - (Some(XMLNS), "not-acceptable") => error = Error::NotAcceptable, - (Some(XMLNS), "not-allowed") => error = Error::NotAllowed, - (Some(XMLNS), "not-authorized") => error = Error::NotAuthorized, - (Some(XMLNS), "policy-violation") => error = Error::PolicyViolation, - (Some(XMLNS), "recipient-unavailable") => error = Error::RecipientUnavailable, - (Some(XMLNS), "redirect") => return Ok(Error::Redirect(element.pop_value_opt()?)), - (Some(XMLNS), "registration-required") => error = Error::RegistrationRequired, - (Some(XMLNS), "remote-server-not-found") => error = Error::RemoteServerNotFound, - (Some(XMLNS), "remote-server-timeout") => error = Error::RemoteServerTimeout, - (Some(XMLNS), "resource-constraint") => error = Error::ResourceConstraint, - (Some(XMLNS), "service-unavailable") => error = Error::ServiceUnavailable, - (Some(XMLNS), "subscription-required") => error = Error::SubscriptionRequired, - (Some(XMLNS), "undefined-condition") => error = Error::UndefinedCondition, - (Some(XMLNS), "unexpected-request") => error = Error::UnexpectedRequest, - _ => return Err(peanuts::DeserializeError::UnexpectedElement(element)), - } - element.no_more_content()?; - return Ok(error); - } -} - -impl IntoElement for Error { - fn builder(&self) -> peanuts::element::ElementBuilder { - match self { - Error::BadRequest => Element::builder("bad-request", Some(XMLNS)), - Error::Conflict => Element::builder("conflict", Some(XMLNS)), - Error::FeatureNotImplemented => { - Element::builder("feature-not-implemented", Some(XMLNS)) - } - Error::Forbidden => Element::builder("forbidden", Some(XMLNS)), - Error::Gone(r) => Element::builder("gone", Some(XMLNS)).push_text_opt(r.clone()), - Error::InternalServerError => Element::builder("internal-server-error", Some(XMLNS)), - Error::ItemNotFound => Element::builder("item-not-found", Some(XMLNS)), - Error::JidMalformed => Element::builder("jid-malformed", Some(XMLNS)), - Error::NotAcceptable => Element::builder("not-acceptable", Some(XMLNS)), - Error::NotAllowed => Element::builder("not-allowed", Some(XMLNS)), - Error::NotAuthorized => Element::builder("not-authorized", Some(XMLNS)), - Error::PolicyViolation => Element::builder("policy-violation", Some(XMLNS)), - Error::RecipientUnavailable => Element::builder("recipient-unavailable", Some(XMLNS)), - Error::Redirect(r) => { - Element::builder("redirect", Some(XMLNS)).push_text_opt(r.clone()) - } - Error::RegistrationRequired => Element::builder("registration-required", Some(XMLNS)), - Error::RemoteServerNotFound => Element::builder("remote-server-not-found", Some(XMLNS)), - Error::RemoteServerTimeout => Element::builder("remote-server-timeout", Some(XMLNS)), - Error::ResourceConstraint => Element::builder("resource-constraint", Some(XMLNS)), - Error::ServiceUnavailable => Element::builder("service-unavailable", Some(XMLNS)), - Error::SubscriptionRequired => Element::builder("subscription-required", Some(XMLNS)), - Error::UndefinedCondition => Element::builder("undefined-condition", Some(XMLNS)), - Error::UnexpectedRequest => Element::builder("unexpected-request", Some(XMLNS)), - } - } -} - -#[derive(Clone, Debug)] -pub struct Text { - lang: Option<String>, - text: Option<String>, -} - -impl FromElement for Text { - fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> { - element.check_name("text")?; - element.check_name(XMLNS)?; - - let lang = element.attribute_opt_namespaced("lang", XML_NS)?; - let text = element.pop_value_opt()?; - - Ok(Text { lang, text }) - } -} - -impl IntoElement for Text { - fn builder(&self) -> peanuts::element::ElementBuilder { - Element::builder("text", Some(XMLNS)) - .push_attribute_opt_namespaced(XML_NS, "lang", self.lang.clone()) - .push_text_opt(self.text.clone()) - } -} diff --git a/src/stanza/starttls.rs b/src/stanza/starttls.rs deleted file mode 100644 index fb66711..0000000 --- a/src/stanza/starttls.rs +++ /dev/null @@ -1,94 +0,0 @@ -use std::collections::{HashMap, HashSet}; - -use peanuts::{ - element::{Content, FromElement, IntoElement, Name, NamespaceDeclaration}, - Element, -}; - -pub const XMLNS: &str = "urn:ietf:params:xml:ns:xmpp-tls"; - -#[derive(Debug, Clone)] -pub struct StartTls { - pub required: bool, -} - -impl IntoElement for StartTls { - fn builder(&self) -> peanuts::element::ElementBuilder { - let mut builder = Element::builder("starttls", Some(XMLNS)); - - if self.required { - builder = builder.push_child(Required) - } - - builder - } -} - -impl FromElement for StartTls { - fn from_element( - mut element: peanuts::Element, - ) -> std::result::Result<StartTls, peanuts::DeserializeError> { - element.check_name("starttls")?; - element.check_namespace(XMLNS)?; - - let mut required = false; - if let Some(_) = element.child_opt::<Required>()? { - required = true; - } - - Ok(StartTls { required }) - } -} - -#[derive(Debug)] -pub struct Required; - -impl FromElement for Required { - fn from_element(element: Element) -> peanuts::element::DeserializeResult<Self> { - element.check_name("required")?; - element.check_namespace(XMLNS)?; - - Ok(Required) - } -} - -impl IntoElement for Required { - fn builder(&self) -> peanuts::element::ElementBuilder { - Element::builder("required", Some(XMLNS)) - } -} - -#[derive(Debug)] -pub struct Proceed; - -impl IntoElement for Proceed { - fn builder(&self) -> peanuts::element::ElementBuilder { - Element::builder("proceed", Some(XMLNS)) - } -} - -impl FromElement for Proceed { - fn from_element(element: Element) -> peanuts::element::DeserializeResult<Self> { - element.check_name("proceed")?; - element.check_namespace(XMLNS)?; - - Ok(Proceed) - } -} - -pub struct Failure; - -impl IntoElement for Failure { - fn builder(&self) -> peanuts::element::ElementBuilder { - Element::builder("failure", Some(XMLNS)) - } -} - -impl FromElement for Failure { - fn from_element(element: Element) -> peanuts::element::DeserializeResult<Self> { - element.check_name("failure")?; - element.check_namespace(XMLNS)?; - - Ok(Failure) - } -} diff --git a/src/stanza/stream.rs b/src/stanza/stream.rs deleted file mode 100644 index 84d62d9..0000000 --- a/src/stanza/stream.rs +++ /dev/null @@ -1,191 +0,0 @@ -use std::collections::{HashMap, HashSet}; - -use peanuts::element::{Content, ElementBuilder, FromElement, IntoElement, NamespaceDeclaration}; -use peanuts::XML_NS; -use peanuts::{element::Name, Element}; -use tracing::debug; - -use crate::stanza::bind; -use crate::JID; - -use super::sasl::{self, Mechanisms}; -use super::starttls::{self, StartTls}; -use super::stream_error::{Error as StreamError, Text}; -use super::{client, stream_error}; - -pub const XMLNS: &str = "http://etherx.jabber.org/streams"; - -// MUST be qualified by stream namespace -// #[derive(XmlSerialize, XmlDeserialize)] -// #[peanuts(xmlns = XMLNS)] -#[derive(Debug)] -pub struct Stream { - pub from: Option<JID>, - to: Option<JID>, - id: Option<String>, - version: Option<String>, - // TODO: lang enum - lang: Option<String>, - // #[peanuts(content)] - // content: Message, -} - -impl FromElement for Stream { - fn from_element(mut element: Element) -> std::result::Result<Self, peanuts::DeserializeError> { - element.check_namespace(XMLNS)?; - element.check_name("stream")?; - - let from = element.attribute_opt("from")?; - let to = element.attribute_opt("to")?; - let id = element.attribute_opt("id")?; - let version = element.attribute_opt("version")?; - let lang = element.attribute_opt_namespaced("lang", peanuts::XML_NS)?; - - Ok(Stream { - from, - to, - id, - version, - lang, - }) - } -} - -impl IntoElement for Stream { - fn builder(&self) -> ElementBuilder { - Element::builder("stream", Some(XMLNS.to_string())) - .push_namespace_declaration_override(Some("stream"), XMLNS) - .push_namespace_declaration_override(None::<&str>, client::XMLNS) - .push_attribute_opt("to", self.to.clone()) - .push_attribute_opt("from", self.from.clone()) - .push_attribute_opt("id", self.id.clone()) - .push_attribute_opt("version", self.version.clone()) - .push_attribute_opt_namespaced(peanuts::XML_NS, "to", self.lang.clone()) - } -} - -impl<'s> Stream { - pub fn new( - from: Option<JID>, - to: Option<JID>, - id: Option<String>, - version: Option<String>, - lang: Option<String>, - ) -> Self { - Self { - from, - to, - id, - version, - lang, - } - } - - /// 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<String>, lang: String) -> Self { - Self { - from, - to: Some(to), - id, - version: Some("1.0".to_string()), - lang: Some(lang), - } - } -} - -#[derive(Debug)] -pub struct Features { - pub features: Vec<Feature>, -} - -impl IntoElement for Features { - fn builder(&self) -> ElementBuilder { - Element::builder("features", Some(XMLNS)).push_children(self.features.clone()) - } -} - -impl FromElement for Features { - fn from_element( - mut element: Element, - ) -> std::result::Result<Features, peanuts::DeserializeError> { - element.check_namespace(XMLNS)?; - element.check_name("features")?; - - debug!("got features stanza"); - let features = element.children()?; - debug!("got features period"); - - Ok(Features { features }) - } -} - -#[derive(Debug, Clone)] -pub enum Feature { - StartTls(StartTls), - Sasl(Mechanisms), - Bind, - Unknown, -} - -impl IntoElement for Feature { - fn builder(&self) -> ElementBuilder { - match self { - Feature::StartTls(start_tls) => start_tls.builder(), - Feature::Sasl(mechanisms) => mechanisms.builder(), - Feature::Bind => todo!(), - Feature::Unknown => todo!(), - } - } -} - -impl FromElement for Feature { - fn from_element(element: Element) -> peanuts::element::DeserializeResult<Self> { - let identity = element.identify(); - debug!("identity: {:?}", identity); - match element.identify() { - (Some(starttls::XMLNS), "starttls") => { - debug!("identified starttls"); - Ok(Feature::StartTls(StartTls::from_element(element)?)) - } - (Some(sasl::XMLNS), "mechanisms") => { - debug!("identified mechanisms"); - Ok(Feature::Sasl(Mechanisms::from_element(element)?)) - } - (Some(bind::XMLNS), "bind") => { - debug!("identified bind"); - Ok(Feature::Bind) - } - _ => { - debug!("identified unknown feature"); - Ok(Feature::Unknown) - } - } - } -} - -#[derive(Debug)] -pub struct Error { - error: StreamError, - text: Option<Text>, -} - -impl FromElement for Error { - fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> { - element.check_name("error")?; - element.check_namespace(XMLNS)?; - - let error = element.pop_child_one()?; - let text = element.pop_child_opt()?; - - Ok(Error { error, text }) - } -} - -impl IntoElement for Error { - fn builder(&self) -> ElementBuilder { - Element::builder("error", Some(XMLNS)) - .push_child(self.error.clone()) - .push_child_opt(self.text.clone()) - } -} diff --git a/src/stanza/stream_error.rs b/src/stanza/stream_error.rs deleted file mode 100644 index 5ae04a6..0000000 --- a/src/stanza/stream_error.rs +++ /dev/null @@ -1,137 +0,0 @@ -use peanuts::{ - element::{FromElement, IntoElement}, - DeserializeError, Element, XML_NS, -}; - -pub const XMLNS: &str = "urn:ietf:params:xml:ns:xmpp-streams"; - -#[derive(Clone, Debug)] -pub enum Error { - BadFormat, - BadNamespacePrefix, - Conflict, - ConnectionTimeout, - HostGone, - HostUnknown, - ImproperAddressing, - InternalServerError, - InvalidFrom, - InvalidId, - InvalidNamespace, - InvalidXml, - NotAuthorized, - NotWellFormed, - PolicyViolation, - RemoteConnectionFailed, - Reset, - ResourceConstraint, - RestrictedXml, - SeeOtherHost(Option<String>), - SystemShutdown, - UndefinedCondition, - UnsupportedEncoding, - UnsupportedStanzaType, - UnsupportedVersion, -} - -impl FromElement for Error { - fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> { - let error; - match element.identify() { - (Some(XMLNS), "bad-format") => error = Error::BadFormat, - (Some(XMLNS), "bad-namespace-prefix") => error = Error::BadNamespacePrefix, - (Some(XMLNS), "conflict") => error = Error::Conflict, - (Some(XMLNS), "connection-timeout") => error = Error::ConnectionTimeout, - (Some(XMLNS), "host-gone") => error = Error::HostGone, - (Some(XMLNS), "host-unknown") => error = Error::HostUnknown, - (Some(XMLNS), "improper-addressing") => error = Error::ImproperAddressing, - (Some(XMLNS), "internal-server-error") => error = Error::InternalServerError, - (Some(XMLNS), "invalid-from") => error = Error::InvalidFrom, - (Some(XMLNS), "invalid-id") => error = Error::InvalidId, - (Some(XMLNS), "invalid-namespace") => error = Error::InvalidNamespace, - (Some(XMLNS), "invalid-xml") => error = Error::InvalidXml, - (Some(XMLNS), "not-authorized") => error = Error::NotAuthorized, - (Some(XMLNS), "not-well-formed") => error = Error::NotWellFormed, - (Some(XMLNS), "policy-violation") => error = Error::PolicyViolation, - (Some(XMLNS), "remote-connection-failed") => error = Error::RemoteConnectionFailed, - (Some(XMLNS), "reset") => error = Error::Reset, - (Some(XMLNS), "resource-constraint") => error = Error::ResourceConstraint, - (Some(XMLNS), "restricted-xml") => error = Error::RestrictedXml, - (Some(XMLNS), "see-other-host") => { - return Ok(Error::SeeOtherHost(element.pop_value_opt()?)) - } - (Some(XMLNS), "system-shutdown") => error = Error::SystemShutdown, - (Some(XMLNS), "undefined-condition") => error = Error::UndefinedCondition, - (Some(XMLNS), "unsupported-encoding") => error = Error::UnsupportedEncoding, - (Some(XMLNS), "unsupported-stanza-type") => error = Error::UnsupportedStanzaType, - (Some(XMLNS), "unsupported-version") => error = Error::UnsupportedVersion, - _ => return Err(DeserializeError::UnexpectedElement(element)), - } - element.no_more_content()?; - return Ok(error); - } -} - -impl IntoElement for Error { - fn builder(&self) -> peanuts::element::ElementBuilder { - match self { - Error::BadFormat => Element::builder("bad-format", Some(XMLNS)), - Error::BadNamespacePrefix => Element::builder("bad-namespace-prefix", Some(XMLNS)), - Error::Conflict => Element::builder("conflict", Some(XMLNS)), - Error::ConnectionTimeout => Element::builder("connection-timeout", Some(XMLNS)), - Error::HostGone => Element::builder("host-gone", Some(XMLNS)), - Error::HostUnknown => Element::builder("host-unknown", Some(XMLNS)), - Error::ImproperAddressing => Element::builder("improper-addressing", Some(XMLNS)), - Error::InternalServerError => Element::builder("internal-server-error", Some(XMLNS)), - Error::InvalidFrom => Element::builder("invalid-from", Some(XMLNS)), - Error::InvalidId => Element::builder("invalid-id", Some(XMLNS)), - Error::InvalidNamespace => Element::builder("invalid-namespace", Some(XMLNS)), - Error::InvalidXml => Element::builder("invalid-xml", Some(XMLNS)), - Error::NotAuthorized => Element::builder("not-authorized", Some(XMLNS)), - Error::NotWellFormed => Element::builder("not-well-formed", Some(XMLNS)), - Error::PolicyViolation => Element::builder("policy-violation", Some(XMLNS)), - Error::RemoteConnectionFailed => { - Element::builder("remote-connection-failed", Some(XMLNS)) - } - Error::Reset => Element::builder("reset", Some(XMLNS)), - Error::ResourceConstraint => Element::builder("resource-constraint", Some(XMLNS)), - Error::RestrictedXml => Element::builder("restricted-xml", Some(XMLNS)), - Error::SeeOtherHost(h) => { - Element::builder("see-other-host", Some(XMLNS)).push_text_opt(h.clone()) - } - Error::SystemShutdown => Element::builder("system-shutdown", Some(XMLNS)), - Error::UndefinedCondition => Element::builder("undefined-condition", Some(XMLNS)), - Error::UnsupportedEncoding => Element::builder("unsupported-encoding", Some(XMLNS)), - Error::UnsupportedStanzaType => { - Element::builder("unsupported-stanza-type", Some(XMLNS)) - } - Error::UnsupportedVersion => Element::builder("unsupported-version", Some(XMLNS)), - } - } -} - -#[derive(Clone, Debug)] -pub struct Text { - text: Option<String>, - lang: Option<String>, -} - -impl FromElement for Text { - fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> { - element.check_name("text")?; - element.check_name(XMLNS)?; - - let lang = element.attribute_opt_namespaced("lang", XML_NS)?; - let text = element.pop_value_opt()?; - - Ok(Text { lang, text }) - } -} - -impl IntoElement for Text { - fn builder(&self) -> peanuts::element::ElementBuilder { - Element::builder("text", Some(XMLNS)) - .push_attribute_opt_namespaced(XML_NS, "lang", self.lang.clone()) - .push_text_opt(self.text.clone()) - } -} |