2019-07-31 11:51:18 +00:00
|
|
|
// 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/.
|
|
|
|
|
2024-07-09 15:01:42 +00:00
|
|
|
use std::borrow::Cow;
|
|
|
|
use std::str::FromStr;
|
|
|
|
|
|
|
|
use xso::{error::Error, text::Base64, AsXml, AsXmlText, FromXml, FromXmlText};
|
2024-06-26 17:35:56 +00:00
|
|
|
|
2019-10-22 23:32:41 +00:00
|
|
|
use crate::hashes::{Algo, Hash};
|
2024-06-26 17:35:56 +00:00
|
|
|
use crate::ns;
|
2019-07-31 11:51:18 +00:00
|
|
|
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.
|
2020-11-29 20:17:51 +00:00
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
2019-07-31 11:51:18 +00:00
|
|
|
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" {
|
2024-06-21 14:27:43 +00:00
|
|
|
return Err(Error::Other("Wrong domain for cid URI."));
|
2019-07-31 11:51:18 +00:00
|
|
|
}
|
|
|
|
lhs.splitn(2, '+').collect()
|
2019-10-22 23:32:41 +00:00
|
|
|
}
|
2024-06-21 14:27:43 +00:00
|
|
|
_ => return Err(Error::Other("Missing @ in cid URI.")),
|
2019-07-31 11:51:18 +00:00
|
|
|
};
|
|
|
|
let (algo, hex) = match temp[..] {
|
|
|
|
[lhs, rhs] => {
|
|
|
|
let algo = match lhs {
|
|
|
|
"sha1" => Algo::Sha_1,
|
|
|
|
"sha256" => Algo::Sha_256,
|
|
|
|
_ => unimplemented!(),
|
|
|
|
};
|
|
|
|
(algo, rhs)
|
2019-10-22 23:32:41 +00:00
|
|
|
}
|
2024-06-21 14:27:43 +00:00
|
|
|
_ => return Err(Error::Other("Missing + in cid URI.")),
|
2019-07-31 11:51:18 +00:00
|
|
|
};
|
2024-06-21 14:27:43 +00:00
|
|
|
let hash = Hash::from_hex(algo, hex).map_err(Error::text_parse_error)?;
|
2019-07-31 11:51:18 +00:00
|
|
|
Ok(ContentId { hash })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-26 17:35:56 +00:00
|
|
|
impl FromXmlText for ContentId {
|
|
|
|
fn from_xml_text(value: String) -> Result<Self, Error> {
|
|
|
|
value.parse().map_err(Error::text_parse_error)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-09 15:01:42 +00:00
|
|
|
impl AsXmlText for ContentId {
|
|
|
|
fn as_xml_text(&self) -> Result<Cow<'_, str>, Error> {
|
2024-06-26 17:35:56 +00:00
|
|
|
let algo = match self.hash.algo {
|
|
|
|
Algo::Sha_1 => "sha1",
|
|
|
|
Algo::Sha_256 => "sha256",
|
|
|
|
_ => unimplemented!(),
|
|
|
|
};
|
2024-07-09 15:01:42 +00:00
|
|
|
Ok(Cow::Owned(format!(
|
|
|
|
"{}+{}@bob.xmpp.org",
|
|
|
|
algo,
|
|
|
|
self.hash.to_hex()
|
|
|
|
)))
|
2024-06-26 17:35:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-31 11:51:18 +00:00
|
|
|
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()))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-26 17:35:56 +00:00
|
|
|
/// Request for an uncached cid file.
|
2024-07-09 15:01:42 +00:00
|
|
|
#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
|
2024-06-26 17:35:56 +00:00
|
|
|
#[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>,
|
|
|
|
}
|
2019-07-31 11:51:18 +00:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
2019-09-25 08:28:44 +00:00
|
|
|
use crate::Element;
|
2024-06-21 14:27:43 +00:00
|
|
|
use xso::error::FromElementError;
|
2019-07-31 11:51:18 +00:00
|
|
|
|
|
|
|
#[cfg(target_pointer_width = "32")]
|
|
|
|
#[test]
|
|
|
|
fn test_size() {
|
2024-02-27 11:27:31 +00:00
|
|
|
assert_size!(ContentId, 24);
|
|
|
|
assert_size!(Data, 56);
|
2019-07-31 11:51:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(target_pointer_width = "64")]
|
|
|
|
#[test]
|
|
|
|
fn test_size() {
|
2024-02-27 11:27:31 +00:00
|
|
|
assert_size!(ContentId, 48);
|
|
|
|
assert_size!(Data, 112);
|
2019-07-31 11:51:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_simple() {
|
2019-10-22 23:32:41 +00:00
|
|
|
let cid: ContentId = "sha1+8f35fef110ffc5df08d579a50083ff9308fb6242@bob.xmpp.org"
|
|
|
|
.parse()
|
|
|
|
.unwrap();
|
2019-07-31 11:51:18 +00:00
|
|
|
assert_eq!(cid.hash.algo, Algo::Sha_1);
|
2019-10-22 23:32:41 +00:00
|
|
|
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"
|
|
|
|
);
|
2019-07-31 11:51:18 +00:00
|
|
|
|
|
|
|
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);
|
2019-10-22 23:32:41 +00:00
|
|
|
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"
|
|
|
|
);
|
2019-07-31 11:51:18 +00:00
|
|
|
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 {
|
2024-06-21 14:27:43 +00:00
|
|
|
Error::Other(string) => string,
|
2019-07-31 11:51:18 +00:00
|
|
|
_ => panic!(),
|
|
|
|
};
|
|
|
|
assert_eq!(message, "Missing @ in cid URI.");
|
|
|
|
|
|
|
|
let error = "Hello world@bob.xmpp.org".parse::<ContentId>().unwrap_err();
|
|
|
|
let message = match error {
|
2024-06-21 14:27:43 +00:00
|
|
|
Error::Other(string) => string,
|
2019-07-31 11:51:18 +00:00
|
|
|
_ => panic!(),
|
|
|
|
};
|
|
|
|
assert_eq!(message, "Missing + in cid URI.");
|
|
|
|
|
2019-10-22 23:32:41 +00:00
|
|
|
let error = "sha1+1234@coucou.linkmauve.fr"
|
|
|
|
.parse::<ContentId>()
|
|
|
|
.unwrap_err();
|
2019-07-31 11:51:18 +00:00
|
|
|
let message = match error {
|
2024-06-21 14:27:43 +00:00
|
|
|
Error::Other(string) => string,
|
2019-07-31 11:51:18 +00:00
|
|
|
_ => panic!(),
|
|
|
|
};
|
|
|
|
assert_eq!(message, "Wrong domain for cid URI.");
|
|
|
|
|
2019-10-22 23:32:41 +00:00
|
|
|
let error = "sha1+invalid@bob.xmpp.org"
|
|
|
|
.parse::<ContentId>()
|
|
|
|
.unwrap_err();
|
2019-07-31 11:51:18 +00:00
|
|
|
let message = match error {
|
2024-06-21 14:27:43 +00:00
|
|
|
Error::TextParseError(error) if error.is::<std::num::ParseIntError>() => error,
|
2019-07-31 11:51:18 +00:00
|
|
|
_ => panic!(),
|
|
|
|
};
|
2020-01-21 22:46:00 +00:00
|
|
|
assert_eq!(message.to_string(), "invalid digit found in string");
|
2019-07-31 11:51:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn unknown_child() {
|
2024-06-26 17:35:56 +00:00
|
|
|
let elem: Element = "<data xmlns='urn:xmpp:bob' cid='sha1+8f35fef110ffc5df08d579a50083ff9308fb6242@bob.xmpp.org'><coucou/></data>"
|
2019-07-31 11:51:18 +00:00
|
|
|
.parse()
|
|
|
|
.unwrap();
|
|
|
|
let error = Data::try_from(elem).unwrap_err();
|
|
|
|
let message = match error {
|
2024-06-21 14:27:43 +00:00
|
|
|
FromElementError::Invalid(Error::Other(string)) => string,
|
2019-07-31 11:51:18 +00:00
|
|
|
_ => panic!(),
|
|
|
|
};
|
2024-06-26 17:35:56 +00:00
|
|
|
assert_eq!(message, "Unknown child in Data element.");
|
2019-07-31 11:51:18 +00:00
|
|
|
}
|
|
|
|
}
|