1
0
mirror of https://gitlab.com/xmpp-rs/xmpp-rs.git synced 2024-06-26 17:08:26 +02:00
xmpp-rs/parsers/src/hashes.rs
Jonas Schäfer 2f7d5edb8a Make TryFrom<Element> chainable
This allows constructs like:

```rust
let residual = match Iq::try_from(stanza) {
  Ok(iq) => return handle_iq(..),
  Err(Error::TypeMismatch(_, _, v)) => v,
  Err(other) => return handle_parse_error(..),
};
let residual = match Message::try_from(stanza) {
  ..
};
let residual = ..
log::warn!("unhandled object: {:?}", residual);
```

The interesting part of this is that this could be used in a loop over a
Vec<Box<dyn FnMut(Element) -> ControlFlow<SomeResult, Element>>, i.e. in
a parsing loop for a generic XML/XMPP stream.

The advantage is that the stanza.is() check runs only once (in
check_self!) and doesn't need to be duplicated outside, and it reduces
the use of magic strings.
2024-03-03 14:50:29 +00:00

277 lines
8.1 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright (c) 2017 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 crate::util::error::Error;
use crate::util::text_node_codecs::{Base64, Codec};
use base64::{engine::general_purpose::STANDARD as Base64Engine, Engine};
use minidom::IntoAttributeValue;
use std::num::ParseIntError;
use std::ops::{Deref, DerefMut};
use std::str::FromStr;
/// List of the algorithms we support, or Unknown.
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Algo {
/// The Secure Hash Algorithm 1, with known vulnerabilities, do not use it.
///
/// See <https://www.rfc-editor.org/rfc/rfc3174>
Sha_1,
/// The Secure Hash Algorithm 2, in its 256-bit version.
///
/// See <https://www.rfc-editor.org/rfc/rfc6234>
Sha_256,
/// The Secure Hash Algorithm 2, in its 512-bit version.
///
/// See <https://www.rfc-editor.org/rfc/rfc6234>
Sha_512,
/// The Secure Hash Algorithm 3, based on Keccak, in its 256-bit version.
///
/// See <https://keccak.team/files/Keccak-submission-3.pdf>
Sha3_256,
/// The Secure Hash Algorithm 3, based on Keccak, in its 512-bit version.
///
/// See <https://keccak.team/files/Keccak-submission-3.pdf>
Sha3_512,
/// The BLAKE2 hash algorithm, for a 256-bit output.
///
/// See <https://www.rfc-editor.org/rfc/rfc7693>
Blake2b_256,
/// The BLAKE2 hash algorithm, for a 512-bit output.
///
/// See <https://www.rfc-editor.org/rfc/rfc7693>
Blake2b_512,
/// An unknown hash not in this list, you can probably reject it.
Unknown(String),
}
impl FromStr for Algo {
type Err = Error;
fn from_str(s: &str) -> Result<Algo, Error> {
Ok(match s {
"" => return Err(Error::ParseError("'algo' argument cant be empty.")),
"sha-1" => Algo::Sha_1,
"sha-256" => Algo::Sha_256,
"sha-512" => Algo::Sha_512,
"sha3-256" => Algo::Sha3_256,
"sha3-512" => Algo::Sha3_512,
"blake2b-256" => Algo::Blake2b_256,
"blake2b-512" => Algo::Blake2b_512,
value => Algo::Unknown(value.to_owned()),
})
}
}
impl From<Algo> for String {
fn from(algo: Algo) -> String {
String::from(match algo {
Algo::Sha_1 => "sha-1",
Algo::Sha_256 => "sha-256",
Algo::Sha_512 => "sha-512",
Algo::Sha3_256 => "sha3-256",
Algo::Sha3_512 => "sha3-512",
Algo::Blake2b_256 => "blake2b-256",
Algo::Blake2b_512 => "blake2b-512",
Algo::Unknown(text) => return text,
})
}
}
impl IntoAttributeValue for Algo {
fn into_attribute_value(self) -> Option<String> {
Some(String::from(self))
}
}
generate_element!(
/// This element represents a hash of some data, defined by the hash
/// algorithm used and the computed value.
Hash, "hash", HASHES,
attributes: [
/// The algorithm used to create this hash.
algo: Required<Algo> = "algo"
],
text: (
/// The hash value, as a vector of bytes.
hash: Base64
)
);
impl Hash {
/// Creates a [struct@Hash] element with the given algo and data.
pub fn new(algo: Algo, hash: Vec<u8>) -> Hash {
Hash { algo, hash }
}
/// Like [new](#method.new) but takes base64-encoded data before decoding
/// it.
pub fn from_base64(algo: Algo, hash: &str) -> Result<Hash, Error> {
Ok(Hash::new(algo, Base64Engine.decode(hash)?))
}
/// Like [new](#method.new) but takes hex-encoded data before decoding it.
pub fn from_hex(algo: Algo, hex: &str) -> Result<Hash, ParseIntError> {
let mut bytes = vec![];
for i in 0..hex.len() / 2 {
let byte = u8::from_str_radix(&hex[2 * i..2 * i + 2], 16)?;
bytes.push(byte);
}
Ok(Hash::new(algo, bytes))
}
/// Like [new](#method.new) but takes hex-encoded data before decoding it.
pub fn from_colon_separated_hex(algo: Algo, hex: &str) -> Result<Hash, ParseIntError> {
let mut bytes = vec![];
for i in 0..(1 + hex.len()) / 3 {
let byte = u8::from_str_radix(&hex[3 * i..3 * i + 2], 16)?;
if 3 * i + 2 < hex.len() {
assert_eq!(&hex[3 * i + 2..3 * i + 3], ":");
}
bytes.push(byte);
}
Ok(Hash::new(algo, bytes))
}
/// Formats this hash into base64.
pub fn to_base64(&self) -> String {
Base64Engine.encode(&self.hash[..])
}
/// Formats this hash into hexadecimal.
pub fn to_hex(&self) -> String {
self.hash
.iter()
.map(|byte| format!("{:02x}", byte))
.collect::<Vec<_>>()
.join("")
}
/// Formats this hash into colon-separated hexadecimal.
pub fn to_colon_separated_hex(&self) -> String {
self.hash
.iter()
.map(|byte| format!("{:02x}", byte))
.collect::<Vec<_>>()
.join(":")
}
}
/// Helper for parsing and serialising a SHA-1 attribute.
#[derive(Debug, Clone, PartialEq)]
pub struct Sha1HexAttribute(Hash);
impl FromStr for Sha1HexAttribute {
type Err = ParseIntError;
fn from_str(hex: &str) -> Result<Self, Self::Err> {
let hash = Hash::from_hex(Algo::Sha_1, hex)?;
Ok(Sha1HexAttribute(hash))
}
}
impl IntoAttributeValue for Sha1HexAttribute {
fn into_attribute_value(self) -> Option<String> {
Some(self.to_hex())
}
}
impl DerefMut for Sha1HexAttribute {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl Deref for Sha1HexAttribute {
type Target = Hash;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Element;
#[cfg(target_pointer_width = "32")]
#[test]
fn test_size() {
assert_size!(Algo, 12);
assert_size!(Hash, 24);
}
#[cfg(target_pointer_width = "64")]
#[test]
fn test_size() {
assert_size!(Algo, 24);
assert_size!(Hash, 48);
}
#[test]
fn test_simple() {
let elem: Element = "<hash xmlns='urn:xmpp:hashes:2' algo='sha-256'>2XarmwTlNxDAMkvymloX3S5+VbylNrJt/l5QyPa+YoU=</hash>".parse().unwrap();
let hash = Hash::try_from(elem).unwrap();
assert_eq!(hash.algo, Algo::Sha_256);
assert_eq!(
hash.hash,
Base64Engine
.decode("2XarmwTlNxDAMkvymloX3S5+VbylNrJt/l5QyPa+YoU=")
.unwrap()
);
}
#[test]
fn value_serialisation() {
let elem: Element = "<hash xmlns='urn:xmpp:hashes:2' algo='sha-256'>2XarmwTlNxDAMkvymloX3S5+VbylNrJt/l5QyPa+YoU=</hash>".parse().unwrap();
let hash = Hash::try_from(elem).unwrap();
assert_eq!(
hash.to_base64(),
"2XarmwTlNxDAMkvymloX3S5+VbylNrJt/l5QyPa+YoU="
);
assert_eq!(
hash.to_hex(),
"d976ab9b04e53710c0324bf29a5a17dd2e7e55bca536b26dfe5e50c8f6be6285"
);
assert_eq!(hash.to_colon_separated_hex(), "d9:76:ab:9b:04:e5:37:10:c0:32:4b:f2:9a:5a:17:dd:2e:7e:55:bc:a5:36:b2:6d:fe:5e:50:c8:f6:be:62:85");
}
#[test]
fn test_unknown() {
let elem: Element = "<replace xmlns='urn:xmpp:message-correct:0'/>"
.parse()
.unwrap();
let error = Hash::try_from(elem.clone()).unwrap_err();
let returned_elem = match error {
Error::TypeMismatch(_, _, elem) => elem,
_ => panic!(),
};
assert_eq!(elem, returned_elem);
}
#[test]
fn test_invalid_child() {
let elem: Element = "<hash xmlns='urn:xmpp:hashes:2'><coucou/></hash>"
.parse()
.unwrap();
let error = Hash::try_from(elem).unwrap_err();
let message = match error {
Error::ParseError(string) => string,
_ => panic!(),
};
assert_eq!(message, "Unknown child in hash element.");
}
}