#[derive(Debug, thiserror::Error)]
pub enum EmailError {
#[error("Invalid email format: {0}")]
InvalidEmailFormat(String),
#[error("Canonicalization failed: {0}")]
CanonicalizationFailed(String),
#[error("Missing JACS signature attachment")]
MissingJacsSignature,
#[error("Invalid JACS document: {0}")]
InvalidJacsDocument(String),
#[error("Signature verification failed: {0}")]
SignatureVerificationFailed(String),
#[error("Content tampered: {0}")]
ContentTampered(String),
#[error("Chain verification failed: {0}")]
ChainVerificationFailed(String),
#[error("Algorithm mismatch: {0}")]
AlgorithmMismatch(String),
#[error("Email too large: {size} bytes (max {max} bytes)")]
EmailTooLarge {
size: usize,
max: usize,
},
#[error("Unsupported feature: {0}")]
UnsupportedFeature(String),
}
pub const MAX_EMAIL_SIZE: usize = 25 * 1024 * 1024;
pub fn check_email_size(raw_email: &[u8]) -> Result<(), EmailError> {
if raw_email.len() > MAX_EMAIL_SIZE {
return Err(EmailError::EmailTooLarge {
size: raw_email.len(),
max: MAX_EMAIL_SIZE,
});
}
Ok(())
}
impl From<EmailError> for crate::error::JacsError {
fn from(e: EmailError) -> Self {
use crate::error::JacsError;
match &e {
EmailError::SignatureVerificationFailed(_) | EmailError::AlgorithmMismatch(_) => {
JacsError::CryptoError(e.to_string())
}
EmailError::InvalidEmailFormat(_)
| EmailError::CanonicalizationFailed(_)
| EmailError::MissingJacsSignature
| EmailError::InvalidJacsDocument(_)
| EmailError::ContentTampered(_)
| EmailError::EmailTooLarge { .. }
| EmailError::UnsupportedFeature(_) => JacsError::ValidationError(e.to_string()),
EmailError::ChainVerificationFailed(_) => JacsError::DocumentError(e.to_string()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn invalid_email_format_error() {
let err = EmailError::InvalidEmailFormat("missing From header".to_string());
assert_eq!(err.to_string(), "Invalid email format: missing From header");
}
#[test]
fn canonicalization_failed_error() {
let err = EmailError::CanonicalizationFailed("duplicate From header".to_string());
assert_eq!(
err.to_string(),
"Canonicalization failed: duplicate From header"
);
}
#[test]
fn missing_jacs_signature_error() {
let err = EmailError::MissingJacsSignature;
assert_eq!(err.to_string(), "Missing JACS signature attachment");
}
#[test]
fn invalid_jacs_document_error() {
let err = EmailError::InvalidJacsDocument("hash mismatch".to_string());
assert_eq!(err.to_string(), "Invalid JACS document: hash mismatch");
}
#[test]
fn signature_verification_failed_error() {
let err = EmailError::SignatureVerificationFailed("wrong key".to_string());
assert_eq!(err.to_string(), "Signature verification failed: wrong key");
}
#[test]
fn content_tampered_error() {
let err = EmailError::ContentTampered("body hash mismatch".to_string());
assert_eq!(err.to_string(), "Content tampered: body hash mismatch");
}
#[test]
fn chain_verification_failed_error() {
let err = EmailError::ChainVerificationFailed("broken parent link".to_string());
assert_eq!(
err.to_string(),
"Chain verification failed: broken parent link"
);
}
#[test]
fn email_too_large_error() {
let err = EmailError::EmailTooLarge {
size: 30_000_000,
max: MAX_EMAIL_SIZE,
};
assert_eq!(
err.to_string(),
"Email too large: 30000000 bytes (max 26214400 bytes)"
);
}
#[test]
fn algorithm_mismatch_error() {
let err = EmailError::AlgorithmMismatch("expected ed25519, got rsa".to_string());
assert_eq!(
err.to_string(),
"Algorithm mismatch: expected ed25519, got rsa"
);
}
#[test]
fn all_errors_implement_display_and_error() {
let errors: Vec<Box<dyn std::error::Error>> = vec![
Box::new(EmailError::InvalidEmailFormat("test".to_string())),
Box::new(EmailError::CanonicalizationFailed("test".to_string())),
Box::new(EmailError::MissingJacsSignature),
Box::new(EmailError::InvalidJacsDocument("test".to_string())),
Box::new(EmailError::SignatureVerificationFailed("test".to_string())),
Box::new(EmailError::ContentTampered("test".to_string())),
Box::new(EmailError::ChainVerificationFailed("test".to_string())),
Box::new(EmailError::AlgorithmMismatch("test".to_string())),
Box::new(EmailError::EmailTooLarge { size: 100, max: 50 }),
Box::new(EmailError::UnsupportedFeature("test".to_string())),
];
assert_eq!(errors.len(), 10);
for err in &errors {
assert!(!err.to_string().is_empty());
}
}
#[test]
fn check_email_size_within_limit() {
let small_email = vec![0u8; 1000];
assert!(check_email_size(&small_email).is_ok());
}
#[test]
fn check_email_size_exceeds_limit() {
let big_email = vec![0u8; MAX_EMAIL_SIZE + 1];
let result = check_email_size(&big_email);
assert!(result.is_err());
match result.unwrap_err() {
EmailError::EmailTooLarge { size, max } => {
assert_eq!(size, MAX_EMAIL_SIZE + 1);
assert_eq!(max, MAX_EMAIL_SIZE);
}
_ => panic!("Expected EmailTooLarge"),
}
}
#[test]
fn email_error_converts_to_jacs_error() {
let jacs_err: crate::error::JacsError = EmailError::MissingJacsSignature.into();
assert!(
matches!(jacs_err, crate::error::JacsError::ValidationError(_)),
"MissingJacsSignature should map to ValidationError, got: {:?}",
jacs_err
);
assert!(format!("{}", jacs_err).contains("Missing JACS signature attachment"));
let jacs_err: crate::error::JacsError =
EmailError::SignatureVerificationFailed("bad sig".into()).into();
assert!(matches!(jacs_err, crate::error::JacsError::CryptoError(_)));
let jacs_err: crate::error::JacsError =
EmailError::ChainVerificationFailed("broken link".into()).into();
assert!(matches!(
jacs_err,
crate::error::JacsError::DocumentError(_)
));
}
}