email_sanitization.rs
101 lignes · 3337 octets
//! Email content sanitization utilities //! //! This module provides utilities for sanitizing email content to prevent XSS attacks //! and ensure email content is safe for rendering. /// Sanitize email content to prevent XSS attacks /// /// This function escapes HTML special characters in user-provided content. /// It's a safety measure in addition to template escaping (Askama escape = "html"). /// /// # Arguments /// /// * `content` - Content to sanitize /// /// # Returns /// /// Sanitized content with HTML special characters escaped /// /// # Examples /// /// ``` /// use rustwarden_core::services::email_sanitization::sanitize_email_content; /// /// let malicious = "<script>alert('XSS')</script>"; /// let safe = sanitize_email_content(malicious); /// assert_eq!(safe, "<script>alert('XSS')</script>"); /// ``` pub fn sanitize_email_content(content: &str) -> String { content .replace('&', "&") .replace('<', "<") .replace('>', ">") .replace('"', """) .replace('\'', "'") } /// Mask an email address for log output (e.g. `jo***@ex***`). /// /// Keeps at most the first two characters of the local part and of the /// domain so log lines, audit messages and hook metadata never contain a /// full address (SIEM-friendly). /// /// Shared helper: used by /// [`EmailChangeService`](crate::services::email_change_service::EmailChangeService) /// for its `audit_logs` rows and `on_audit_event` payloads. pub(crate) fn mask_email(email: &str) -> String { match email.split_once('@') { Some((local, domain)) => { let local_visible: String = local.chars().take(2).collect(); let domain_visible: String = domain.chars().take(2).collect(); format!("{local_visible}***@{domain_visible}***") } None => "***".to_string(), } } /// Validate email address format /// /// # Arguments /// /// * `email` - Email address to validate /// /// # Returns /// /// `true` if email format is valid, `false` otherwise /// SEC-L7: Uses the `validator` crate for robust RFC-compliant email validation. pub fn is_valid_email_format(email: &str) -> bool { validator::ValidateEmail::validate_email(&email.to_string()) } #[cfg(test)] mod tests { use super::*; #[test] fn test_sanitize_email_content() { // Test that function exists and works let content = "test content"; let sanitized = sanitize_email_content(content); assert_eq!(sanitized, content); } #[test] fn test_is_valid_email_format() { assert!(is_valid_email_format("user@example.com")); assert!(is_valid_email_format("test.user@example.co.uk")); assert!(!is_valid_email_format("invalid-email")); assert!(!is_valid_email_format("user@")); assert!(!is_valid_email_format("@example.com")); assert!(!is_valid_email_format("user @example.com")); // Contains space } #[test] fn mask_email_never_leaks_full_address() { assert_eq!(mask_email("john.doe@example.com"), "jo***@ex***"); assert_eq!(mask_email("a@b.c"), "a***@b.***"); assert_eq!(mask_email("no-at-sign"), "***"); // Multi-byte local part must not panic. assert_eq!(mask_email("émile@exemple.fr"), "ém***@ex***"); } }
GitRust