xmpp-parsers: Migrate Caps to xso

The hash member has been split into a hash and a ver members, to reflect
the attributes.
This commit is contained in:
Emmanuel Gil Peyrot 2025-01-25 16:15:11 +01:00
commit c0e45b6a0f
2 changed files with 27 additions and 45 deletions

View file

@ -39,6 +39,8 @@ XXXX-YY-ZZ RELEASER <admin@example.com>
- message::Message id field is now Option<message::Id> instead of
Option<String> (!504)
- message_correction::Replace id field is now Id instead of String (!504)
- `Caps::hash` has been split into `Caps::hash` (the algo) and
`Caps::ver` (the actual hash), to reflect the attributes (!517)
* New parsers/serialisers:
- Stream Features (RFC 6120) (!400)
- Spam Reporting (XEP-0377) (!506)

View file

@ -4,6 +4,8 @@
// 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 xso::{AsXml, FromXml};
use crate::data_forms::DataForm;
use crate::disco::{DiscoInfoQuery, DiscoInfoResult, Feature, Identity};
use crate::hashes::{Algo, Hash};
@ -12,70 +14,46 @@ use crate::presence::PresencePayload;
use base64::{engine::general_purpose::STANDARD as Base64, Engine};
use blake2::Blake2bVar;
use digest::{Digest, Update, VariableOutput};
use minidom::Element;
use sha1::Sha1;
use sha2::{Sha256, Sha512};
use sha3::{Sha3_256, Sha3_512};
use xso::error::{Error, FromElementError};
/// Represents a capability hash for a given client.
#[derive(Debug, Clone)]
///
/// Warning: This protocol is insecure, you may want to switch to
/// [ecaps2](../ecaps2/index.html) instead, see [this
/// email](https://mail.jabber.org/pipermail/security/2009-July/000812.html).
#[derive(FromXml, AsXml, Debug, Clone)]
#[xml(namespace = ns::CAPS, name = "c")]
pub struct Caps {
/// Deprecated list of additional feature bundles.
#[xml(attribute(default))]
pub ext: Option<String>,
/// A URI identifying an XMPP application.
#[xml(attribute)]
pub node: String,
/// The algorithm of the hash of these caps.
#[xml(attribute)]
pub hash: Algo,
/// The hash of that applications
/// [disco#info](../disco/struct.DiscoInfoResult.html).
///
/// Warning: This protocol is insecure, you may want to switch to
/// [ecaps2](../ecaps2/index.html) instead, see [this
/// email](https://mail.jabber.org/pipermail/security/2009-July/000812.html).
pub hash: Hash,
#[xml(attribute(codec = Base64))]
pub ver: Vec<u8>,
}
impl PresencePayload for Caps {}
impl TryFrom<Element> for Caps {
type Error = FromElementError;
fn try_from(elem: Element) -> Result<Caps, FromElementError> {
check_self!(elem, "c", CAPS, "caps");
check_no_children!(elem, "caps");
check_no_unknown_attributes!(elem, "caps", ["hash", "ver", "ext", "node"]);
let ver: String = get_attr!(elem, "ver", Required);
let hash = Hash {
algo: get_attr!(elem, "hash", Required),
hash: Base64.decode(ver).map_err(Error::text_parse_error)?,
};
Ok(Caps {
ext: get_attr!(elem, "ext", Option),
node: get_attr!(elem, "node", Required),
hash,
})
}
}
impl From<Caps> for Element {
fn from(caps: Caps) -> Element {
Element::builder("c", ns::CAPS)
.attr("ext", caps.ext)
.attr("hash", caps.hash.algo)
.attr("node", caps.node)
.attr("ver", Base64.encode(&caps.hash.hash))
.build()
}
}
impl Caps {
/// Create a Caps element from its node and hash.
pub fn new<N: Into<String>>(node: N, hash: Hash) -> Caps {
Caps {
ext: None,
node: node.into(),
hash,
hash: hash.algo,
ver: hash.hash,
}
}
}
@ -207,7 +185,7 @@ pub fn hash_caps(data: &[u8], algo: Algo) -> Result<Hash, String> {
/// caps hash.
pub fn query_caps(caps: Caps) -> DiscoInfoQuery {
DiscoInfoQuery {
node: Some(format!("{}#{}", caps.node, Base64.encode(&caps.hash.hash))),
node: Some(format!("{}#{}", caps.node, Base64.encode(&caps.ver))),
}
}
@ -215,6 +193,8 @@ pub fn query_caps(caps: Caps) -> DiscoInfoQuery {
mod tests {
use super::*;
use crate::caps;
use minidom::Element;
use xso::error::{Error, FromElementError};
#[cfg(target_pointer_width = "32")]
#[test]
@ -233,9 +213,9 @@ mod tests {
let elem: Element = "<c xmlns='http://jabber.org/protocol/caps' hash='sha-256' node='coucou' ver='K1Njy3HZBThlo4moOD5gBGhn0U0oK7/CbfLlIUDi6o4='/>".parse().unwrap();
let caps = Caps::try_from(elem).unwrap();
assert_eq!(caps.node, String::from("coucou"));
assert_eq!(caps.hash.algo, Algo::Sha_256);
assert_eq!(caps.hash, Algo::Sha_256);
assert_eq!(
caps.hash.hash,
caps.ver,
Base64
.decode("K1Njy3HZBThlo4moOD5gBGhn0U0oK7/CbfLlIUDi6o4=")
.unwrap()
@ -245,13 +225,13 @@ mod tests {
#[cfg(not(feature = "disable-validation"))]
#[test]
fn test_invalid_child() {
let elem: Element = "<c xmlns='http://jabber.org/protocol/caps'><hash xmlns='urn:xmpp:hashes:2' algo='sha-256'>K1Njy3HZBThlo4moOD5gBGhn0U0oK7/CbfLlIUDi6o4=</hash></c>".parse().unwrap();
let elem: Element = "<c xmlns='http://jabber.org/protocol/caps' node='coucou' hash='sha-256' ver='K1Njy3HZBThlo4moOD5gBGhn0U0oK7/CbfLlIUDi6o4='><hash xmlns='urn:xmpp:hashes:2' algo='sha-256'>K1Njy3HZBThlo4moOD5gBGhn0U0oK7/CbfLlIUDi6o4=</hash></c>".parse().unwrap();
let error = Caps::try_from(elem).unwrap_err();
let message = match error {
FromElementError::Invalid(Error::Other(string)) => string,
_ => panic!(),
};
assert_eq!(message, "Unknown child in caps element.");
assert_eq!(message, "Unknown child in Caps element.");
}
#[test]