diff --git a/parsers/Cargo.toml b/parsers/Cargo.toml index b15a4af5..16f6a6f8 100644 --- a/parsers/Cargo.toml +++ b/parsers/Cargo.toml @@ -24,7 +24,7 @@ chrono = { version = "0.4.5", default-features = false, features = ["std"] } # same repository dependencies jid = { version = "0.10", features = ["minidom"], path = "../jid" } minidom = { version = "0.15", path = "../minidom" } -xso = { version = "0.0.2", features = ["macros", "minidom", "panicking-into-impl", "jid"] } +xso = { version = "0.0.2", features = ["macros", "minidom", "panicking-into-impl", "jid", "base64"] } [features] # Build xmpp-parsers to make components instead of clients. diff --git a/xso/Cargo.toml b/xso/Cargo.toml index b7d7957a..20b73a7b 100644 --- a/xso/Cargo.toml +++ b/xso/Cargo.toml @@ -14,13 +14,15 @@ rxml = { version = "0.11.0", default-features = false } minidom = { version = "^0.15" } xso_proc = { version = "0.0.2", optional = true } -# optional dependencies to provide text conversion to/from types from these crates +# optional dependencies to provide text conversion to/from types from/using +# these crates # NOTE: because we don't have public/private dependencies yet and cargo # defaults to picking the highest matching version by default, the only # sensible thing we can do here is to depend on the least version of the most # recent semver of each crate. jid = { version = "^0.10", optional = true } uuid = { version = "^1", optional = true } +base64 = { version = "^0.22", optional = true } [features] macros = [ "dep:xso_proc" ] diff --git a/xso/src/text.rs b/xso/src/text.rs index 167502a5..d62fe2a9 100644 --- a/xso/src/text.rs +++ b/xso/src/text.rs @@ -6,8 +6,13 @@ //! Module containing implementations for conversions to/from XML text. +#[cfg(feature = "base64")] +use core::marker::PhantomData; + use crate::{error::Error, FromXmlText, IntoXmlText}; +#[cfg(feature = "base64")] +use base64::engine::{general_purpose::STANDARD as StandardBase64Engine, Engine as _}; #[cfg(feature = "jid")] use jid; #[cfg(feature = "uuid")] @@ -160,3 +165,72 @@ impl TextCodec> for EmptyAsNone { }) } } + +/// Trait for preprocessing text data from XML. +/// +/// This may be used by codecs to allow to customize some of their behaviour. +pub trait TextFilter { + /// Process the incoming string and return the result of the processing. + fn preprocess(s: String) -> String; +} + +/// Text preprocessor which returns the input unchanged. +pub struct NoFilter; + +impl TextFilter for NoFilter { + fn preprocess(s: String) -> String { + s + } +} + +/// Text preprocessor to remove all whitespace. +pub struct StripWhitespace; + +impl TextFilter for StripWhitespace { + fn preprocess(s: String) -> String { + let s: String = s + .chars() + .filter(|ch| *ch != ' ' && *ch != '\n' && *ch != '\t') + .collect(); + s + } +} + +/// Text codec transforming text to binary using standard base64. +/// +/// The `Filter` type argument can be used to employ additional preprocessing +/// of incoming text data. Most interestingly, passing [`StripWhitespace`] +/// will make the implementation ignore any whitespace within the text. +#[cfg(feature = "base64")] +#[cfg_attr(docsrs, doc(cfg(feature = "base64")))] +pub struct Base64(PhantomData); + +#[cfg(feature = "base64")] +#[cfg_attr(docsrs, doc(cfg(feature = "base64")))] +impl TextCodec> for Base64 { + fn decode(s: String) -> Result, Error> { + let value = Filter::preprocess(s); + Ok(StandardBase64Engine + .decode(value.as_str().as_bytes()) + .map_err(Error::text_parse_error)?) + } + + fn encode(value: Vec) -> Result, Error> { + Ok(Some(StandardBase64Engine.encode(&value))) + } +} + +#[cfg(feature = "base64")] +#[cfg_attr(docsrs, doc(cfg(feature = "base64")))] +impl TextCodec>> for Base64 { + fn decode(s: String) -> Result>, Error> { + if s.len() == 0 { + return Ok(None); + } + Ok(Some(Self::decode(s)?)) + } + + fn encode(decoded: Option>) -> Result, Error> { + decoded.map(Self::encode).transpose().map(Option::flatten) + } +}