#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum HashAlgorithm {
Sha256,
Sha384,
Sha512,
}
impl HashAlgorithm {
fn as_str(self) -> &'static str {
match self {
HashAlgorithm::Sha256 => "sha256",
HashAlgorithm::Sha384 => "sha384",
HashAlgorithm::Sha512 => "sha512",
}
}
const ALL: [HashAlgorithm; 3] = [
HashAlgorithm::Sha256,
HashAlgorithm::Sha384,
HashAlgorithm::Sha512,
];
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct HashExpression {
pub algorithm: HashAlgorithm,
pub value: String,
}
pub fn parse_hash_expression(input: &str) -> Option<HashExpression> {
for algorithm in HashAlgorithm::ALL {
if let Some(value) = input
.strip_prefix(algorithm.as_str())
.and_then(|rest| rest.strip_prefix('-'))
{
return if is_valid_base64_value(value) {
Some(HashExpression {
algorithm,
value: value.to_string(),
})
} else {
None
};
}
}
None
}
pub(crate) fn is_valid_base64_value(s: &str) -> bool {
let bytes = s.as_bytes();
let body_len = bytes
.iter()
.take_while(|&&b| b.is_ascii_alphanumeric() || matches!(b, b'+' | b'/' | b'-' | b'_'))
.count();
if body_len == 0 {
return false;
}
let padding = &bytes[body_len..];
padding.len() <= 2 && padding.iter().all(|&b| b == b'=')
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_each_algorithm() {
assert_eq!(
parse_hash_expression("sha256-abc123"),
Some(HashExpression {
algorithm: HashAlgorithm::Sha256,
value: "abc123".to_string(),
})
);
assert_eq!(
parse_hash_expression("sha384-abc123").map(|h| h.algorithm),
Some(HashAlgorithm::Sha384)
);
assert_eq!(
parse_hash_expression("sha512-abc123").map(|h| h.algorithm),
Some(HashAlgorithm::Sha512)
);
}
#[test]
fn accepts_base64_with_padding_and_url_safe_chars() {
assert!(parse_hash_expression("sha256-abc+/12==").is_some());
assert!(parse_hash_expression("sha256-abc-_12").is_some());
}
#[test]
fn rejects_unknown_algorithm() {
assert_eq!(parse_hash_expression("sha1-abc123"), None);
assert_eq!(parse_hash_expression("md5-abc123"), None);
}
#[test]
fn rejects_empty_or_invalid_base64_value() {
assert_eq!(parse_hash_expression("sha256-"), None);
assert_eq!(parse_hash_expression("sha256-abc def"), None);
assert_eq!(parse_hash_expression("sha256-abc==="), None); }
}