xmpp-rs/parsers/src/bob.rs
Jonas Schäfer b8af0d8fa2 xmpp_parsers: --features disable-validation
It was broken in multiple ways:

- xso did not honour it: unknown children and attributes would cause a
  parse error even with `--features disable-validation` set on parsers.
  For this, we introduce a new feature flag on xso, `non-pedantic`,
  which defaults unknown children and attributes to discard instead of
  fail.

  Note that individual XSOs can still choose to be always pedantic or
  always lenient by explicitly declaring the intent via the
  `on_unknown_child` and `on_unknown_attribute` metas.

- Many tests in `xmpp_parsers` were broken with `--features
  disable-validation`. They now all pass while *still* being rn with
  `disable-validation` set: In that case, they test that parsing in fact
  succeeds.
2024-12-20 14:50:14 +01:00

209 lines
6.5 KiB
Rust

// Copyright (c) 2019 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use alloc::borrow::Cow;
use core::str::FromStr;
use xso::{error::Error, text::Base64, AsXml, AsXmlText, FromXml, FromXmlText};
use crate::hashes::{Algo, Hash};
use crate::ns;
use minidom::IntoAttributeValue;
/// A Content-ID, as defined in RFC2111.
///
/// The text value SHOULD be of the form algo+hash@bob.xmpp.org, this struct
/// enforces that format.
#[derive(Clone, Debug, PartialEq)]
pub struct ContentId {
hash: Hash,
}
impl FromStr for ContentId {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
let temp: Vec<_> = s.splitn(2, '@').collect();
let temp: Vec<_> = match temp[..] {
[lhs, rhs] => {
if rhs != "bob.xmpp.org" {
return Err(Error::Other("Wrong domain for cid URI."));
}
lhs.splitn(2, '+').collect()
}
_ => return Err(Error::Other("Missing @ in cid URI.")),
};
let (algo, hex) = match temp[..] {
[lhs, rhs] => {
let algo = match lhs {
"sha1" => Algo::Sha_1,
"sha256" => Algo::Sha_256,
_ => unimplemented!(),
};
(algo, rhs)
}
_ => return Err(Error::Other("Missing + in cid URI.")),
};
let hash = Hash::from_hex(algo, hex).map_err(Error::text_parse_error)?;
Ok(ContentId { hash })
}
}
impl FromXmlText for ContentId {
fn from_xml_text(value: String) -> Result<Self, Error> {
value.parse().map_err(Error::text_parse_error)
}
}
impl AsXmlText for ContentId {
fn as_xml_text(&self) -> Result<Cow<'_, str>, Error> {
let algo = match self.hash.algo {
Algo::Sha_1 => "sha1",
Algo::Sha_256 => "sha256",
_ => unimplemented!(),
};
Ok(Cow::Owned(format!(
"{}+{}@bob.xmpp.org",
algo,
self.hash.to_hex()
)))
}
}
impl IntoAttributeValue for ContentId {
fn into_attribute_value(self) -> Option<String> {
let algo = match self.hash.algo {
Algo::Sha_1 => "sha1",
Algo::Sha_256 => "sha256",
_ => unimplemented!(),
};
Some(format!("{}+{}@bob.xmpp.org", algo, self.hash.to_hex()))
}
}
/// Request for an uncached cid file.
#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
#[xml(namespace = ns::BOB, name = "data")]
pub struct Data {
/// The cid in question.
#[xml(attribute)]
pub cid: ContentId,
/// How long to cache it (in seconds).
#[xml(attribute(default, name = "max-age"))]
pub max_age: Option<usize>,
/// The MIME type of the data being transmitted.
///
/// See the [IANA MIME Media Types Registry][1] for a list of
/// registered types, but unregistered or yet-to-be-registered are
/// accepted too.
///
/// [1]: <https://www.iana.org/assignments/media-types/media-types.xhtml>
#[xml(attribute(default, name = "type"))]
pub type_: Option<String>,
/// The actual data.
#[xml(text = Base64)]
pub data: Vec<u8>,
}
#[cfg(test)]
mod tests {
use super::*;
use minidom::Element;
use xso::error::FromElementError;
#[cfg(target_pointer_width = "32")]
#[test]
fn test_size() {
assert_size!(ContentId, 24);
assert_size!(Data, 56);
}
#[cfg(target_pointer_width = "64")]
#[test]
fn test_size() {
assert_size!(ContentId, 48);
assert_size!(Data, 112);
}
#[test]
fn test_simple() {
let cid: ContentId = "sha1+8f35fef110ffc5df08d579a50083ff9308fb6242@bob.xmpp.org"
.parse()
.unwrap();
assert_eq!(cid.hash.algo, Algo::Sha_1);
assert_eq!(
cid.hash.hash,
b"\x8f\x35\xfe\xf1\x10\xff\xc5\xdf\x08\xd5\x79\xa5\x00\x83\xff\x93\x08\xfb\x62\x42"
);
assert_eq!(
cid.into_attribute_value().unwrap(),
"sha1+8f35fef110ffc5df08d579a50083ff9308fb6242@bob.xmpp.org"
);
let elem: Element = "<data xmlns='urn:xmpp:bob' cid='sha1+8f35fef110ffc5df08d579a50083ff9308fb6242@bob.xmpp.org'/>".parse().unwrap();
let data = Data::try_from(elem).unwrap();
assert_eq!(data.cid.hash.algo, Algo::Sha_1);
assert_eq!(
data.cid.hash.hash,
b"\x8f\x35\xfe\xf1\x10\xff\xc5\xdf\x08\xd5\x79\xa5\x00\x83\xff\x93\x08\xfb\x62\x42"
);
assert!(data.max_age.is_none());
assert!(data.type_.is_none());
assert!(data.data.is_empty());
}
#[test]
fn invalid_cid() {
let error = "Hello world!".parse::<ContentId>().unwrap_err();
let message = match error {
Error::Other(string) => string,
_ => panic!(),
};
assert_eq!(message, "Missing @ in cid URI.");
let error = "Hello world@bob.xmpp.org".parse::<ContentId>().unwrap_err();
let message = match error {
Error::Other(string) => string,
_ => panic!(),
};
assert_eq!(message, "Missing + in cid URI.");
let error = "sha1+1234@coucou.linkmauve.fr"
.parse::<ContentId>()
.unwrap_err();
let message = match error {
Error::Other(string) => string,
_ => panic!(),
};
assert_eq!(message, "Wrong domain for cid URI.");
let error = "sha1+invalid@bob.xmpp.org"
.parse::<ContentId>()
.unwrap_err();
let message = match error {
Error::TextParseError(error) if error.is::<core::num::ParseIntError>() => error,
_ => panic!(),
};
assert_eq!(message.to_string(), "invalid digit found in string");
}
#[test]
#[cfg_attr(feature = "disable-validation", should_panic = "Result::unwrap_err")]
fn unknown_child() {
let elem: Element = "<data xmlns='urn:xmpp:bob' cid='sha1+8f35fef110ffc5df08d579a50083ff9308fb6242@bob.xmpp.org'><coucou/></data>"
.parse()
.unwrap();
let error = Data::try_from(elem).unwrap_err();
let message = match error {
FromElementError::Invalid(Error::Other(string)) => string,
_ => panic!(),
};
assert_eq!(message, "Unknown child in Data element.");
}
}