mod common; use bspds::notifications::{ SendError, is_valid_phone_number, sanitize_header_value, }; use bspds::oauth::templates::{login_page, error_page, success_page}; use bspds::image::{ImageProcessor, ImageError}; #[test] fn test_sanitize_header_value_removes_crlf() { let malicious = "Injected\r\nBcc: attacker@evil.com"; let sanitized = sanitize_header_value(malicious); assert!(!sanitized.contains('\r'), "CR should be removed"); assert!(!sanitized.contains('\n'), "LF should be removed"); assert!(sanitized.contains("Injected"), "Original content should be preserved"); assert!(sanitized.contains("Bcc:"), "Text after newline should be on same line (no header injection)"); } #[test] fn test_sanitize_header_value_preserves_content() { let normal = "Normal Subject Line"; let sanitized = sanitize_header_value(normal); assert_eq!(sanitized, "Normal Subject Line"); } #[test] fn test_sanitize_header_value_trims_whitespace() { let padded = " Subject "; let sanitized = sanitize_header_value(padded); assert_eq!(sanitized, "Subject"); } #[test] fn test_sanitize_header_value_handles_multiple_newlines() { let input = "Line1\r\nLine2\nLine3\rLine4"; let sanitized = sanitize_header_value(input); assert!(!sanitized.contains('\r'), "CR should be removed"); assert!(!sanitized.contains('\n'), "LF should be removed"); assert!(sanitized.contains("Line1"), "Content before newlines preserved"); assert!(sanitized.contains("Line4"), "Content after newlines preserved"); } #[test] fn test_email_header_injection_sanitization() { let header_injection = "Normal Subject\r\nBcc: attacker@evil.com\r\nX-Injected: value"; let sanitized = sanitize_header_value(header_injection); let lines: Vec<&str> = sanitized.split("\r\n").collect(); assert_eq!(lines.len(), 1, "Should be a single line after sanitization"); assert!(sanitized.contains("Normal Subject"), "Original content preserved"); assert!(sanitized.contains("Bcc:"), "Content after CRLF preserved as same line text"); assert!(sanitized.contains("X-Injected:"), "All content on same line"); } #[test] fn test_valid_phone_number_accepts_correct_format() { assert!(is_valid_phone_number("+1234567890")); assert!(is_valid_phone_number("+12025551234")); assert!(is_valid_phone_number("+442071234567")); assert!(is_valid_phone_number("+4915123456789")); assert!(is_valid_phone_number("+1")); } #[test] fn test_valid_phone_number_rejects_missing_plus() { assert!(!is_valid_phone_number("1234567890")); assert!(!is_valid_phone_number("12025551234")); } #[test] fn test_valid_phone_number_rejects_empty() { assert!(!is_valid_phone_number("")); } #[test] fn test_valid_phone_number_rejects_just_plus() { assert!(!is_valid_phone_number("+")); } #[test] fn test_valid_phone_number_rejects_too_long() { assert!(!is_valid_phone_number("+12345678901234567890123")); } #[test] fn test_valid_phone_number_rejects_letters() { assert!(!is_valid_phone_number("+abc123")); assert!(!is_valid_phone_number("+1234abc")); assert!(!is_valid_phone_number("+a")); } #[test] fn test_valid_phone_number_rejects_spaces() { assert!(!is_valid_phone_number("+1234 5678")); assert!(!is_valid_phone_number("+ 1234567890")); assert!(!is_valid_phone_number("+1 ")); } #[test] fn test_valid_phone_number_rejects_special_chars() { assert!(!is_valid_phone_number("+123-456-7890")); assert!(!is_valid_phone_number("+1(234)567890")); assert!(!is_valid_phone_number("+1.234.567.890")); } #[test] fn test_signal_recipient_command_injection_blocked() { let malicious_inputs = vec![ "+123; rm -rf /", "+123 && cat /etc/passwd", "+123`id`", "+123$(whoami)", "+123|cat /etc/shadow", "+123\n--help", "+123\r\n--version", "+123--help", ]; for input in malicious_inputs { assert!(!is_valid_phone_number(input), "Malicious input '{}' should be rejected", input); } } #[test] fn test_image_file_size_limit_enforced() { let processor = ImageProcessor::new(); let oversized_data: Vec = vec![0u8; 11 * 1024 * 1024]; let result = processor.process(&oversized_data, "image/jpeg"); match result { Err(ImageError::FileTooLarge { .. }) => {} Err(other) => { let msg = format!("{:?}", other); if !msg.to_lowercase().contains("size") && !msg.to_lowercase().contains("large") { panic!("Expected FileTooLarge error, got: {:?}", other); } } Ok(_) => panic!("Should reject files over size limit"), } } #[test] fn test_image_file_size_limit_configurable() { let processor = ImageProcessor::new().with_max_file_size(1024); let data: Vec = vec![0u8; 2048]; let result = processor.process(&data, "image/jpeg"); assert!(result.is_err(), "Should reject files over configured limit"); } #[test] fn test_oauth_template_xss_escaping_client_id() { let malicious_client_id = ""; let html = login_page(malicious_client_id, None, None, "test-uri", None, None); assert!(!html.contains(""; let html = login_page("client123", None, Some(malicious_scope), "test-uri", None, None); assert!(!html.contains(""; let html = login_page("client123", None, None, "test-uri", Some(malicious_error), None); assert!(!html.contains(""; let malicious_desc = ""; let html = error_page(malicious_error, Some(malicious_desc)); assert!(!html.contains(""; let html = success_page(Some(malicious_name)); assert!(!html.contains("