this repo has no description
1pub const MAX_EMAIL_LENGTH: usize = 254;
2pub const MAX_LOCAL_PART_LENGTH: usize = 64;
3pub const MAX_DOMAIN_LENGTH: usize = 253;
4pub const MAX_DOMAIN_LABEL_LENGTH: usize = 63;
5const EMAIL_LOCAL_SPECIAL_CHARS: &str = ".!#$%&'*+/=?^_`{|}~-";
6
7pub const MIN_HANDLE_LENGTH: usize = 3;
8pub const MAX_HANDLE_LENGTH: usize = 253;
9
10#[derive(Debug, PartialEq)]
11pub enum HandleValidationError {
12 Empty,
13 TooShort,
14 TooLong,
15 InvalidCharacters,
16 StartsWithInvalidChar,
17 EndsWithInvalidChar,
18 ContainsSpaces,
19 BannedWord,
20}
21
22impl std::fmt::Display for HandleValidationError {
23 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 match self {
25 Self::Empty => write!(f, "Handle cannot be empty"),
26 Self::TooShort => write!(
27 f,
28 "Handle must be at least {} characters",
29 MIN_HANDLE_LENGTH
30 ),
31 Self::TooLong => write!(
32 f,
33 "Handle exceeds maximum length of {} characters",
34 MAX_HANDLE_LENGTH
35 ),
36 Self::InvalidCharacters => write!(
37 f,
38 "Handle contains invalid characters. Only alphanumeric characters and hyphens are allowed"
39 ),
40 Self::StartsWithInvalidChar => {
41 write!(f, "Handle cannot start with a hyphen")
42 }
43 Self::EndsWithInvalidChar => write!(f, "Handle cannot end with a hyphen"),
44 Self::ContainsSpaces => write!(f, "Handle cannot contain spaces"),
45 Self::BannedWord => write!(f, "Inappropriate language in handle"),
46 }
47 }
48}
49
50pub fn validate_short_handle(handle: &str) -> Result<String, HandleValidationError> {
51 let handle = handle.trim();
52
53 if handle.is_empty() {
54 return Err(HandleValidationError::Empty);
55 }
56
57 if handle.contains(' ') || handle.contains('\t') || handle.contains('\n') {
58 return Err(HandleValidationError::ContainsSpaces);
59 }
60
61 if handle.len() < MIN_HANDLE_LENGTH {
62 return Err(HandleValidationError::TooShort);
63 }
64
65 if handle.len() > MAX_HANDLE_LENGTH {
66 return Err(HandleValidationError::TooLong);
67 }
68
69 if let Some(first_char) = handle.chars().next()
70 && first_char == '-'
71 {
72 return Err(HandleValidationError::StartsWithInvalidChar);
73 }
74
75 if let Some(last_char) = handle.chars().last()
76 && last_char == '-'
77 {
78 return Err(HandleValidationError::EndsWithInvalidChar);
79 }
80
81 for c in handle.chars() {
82 if !c.is_ascii_alphanumeric() && c != '-' {
83 return Err(HandleValidationError::InvalidCharacters);
84 }
85 }
86
87 if crate::moderation::has_explicit_slur(handle) {
88 return Err(HandleValidationError::BannedWord);
89 }
90
91 Ok(handle.to_lowercase())
92}
93
94pub fn is_valid_email(email: &str) -> bool {
95 let email = email.trim();
96 if email.is_empty() || email.len() > MAX_EMAIL_LENGTH {
97 return false;
98 }
99 let parts: Vec<&str> = email.rsplitn(2, '@').collect();
100 if parts.len() != 2 {
101 return false;
102 }
103 let domain = parts[0];
104 let local = parts[1];
105 if local.is_empty() || local.len() > MAX_LOCAL_PART_LENGTH {
106 return false;
107 }
108 if local.starts_with('.') || local.ends_with('.') {
109 return false;
110 }
111 if local.contains("..") {
112 return false;
113 }
114 for c in local.chars() {
115 if !c.is_ascii_alphanumeric() && !EMAIL_LOCAL_SPECIAL_CHARS.contains(c) {
116 return false;
117 }
118 }
119 if domain.is_empty() || domain.len() > MAX_DOMAIN_LENGTH {
120 return false;
121 }
122 if !domain.contains('.') {
123 return false;
124 }
125 for label in domain.split('.') {
126 if label.is_empty() || label.len() > MAX_DOMAIN_LABEL_LENGTH {
127 return false;
128 }
129 if label.starts_with('-') || label.ends_with('-') {
130 return false;
131 }
132 for c in label.chars() {
133 if !c.is_ascii_alphanumeric() && c != '-' {
134 return false;
135 }
136 }
137 }
138 true
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn test_valid_handles() {
147 assert_eq!(validate_short_handle("alice"), Ok("alice".to_string()));
148 assert_eq!(validate_short_handle("bob123"), Ok("bob123".to_string()));
149 assert_eq!(
150 validate_short_handle("user-name"),
151 Ok("user-name".to_string())
152 );
153 assert_eq!(
154 validate_short_handle("UPPERCASE"),
155 Ok("uppercase".to_string())
156 );
157 assert_eq!(
158 validate_short_handle("MixedCase123"),
159 Ok("mixedcase123".to_string())
160 );
161 assert_eq!(validate_short_handle("abc"), Ok("abc".to_string()));
162 }
163
164 #[test]
165 fn test_invalid_handles() {
166 assert_eq!(validate_short_handle(""), Err(HandleValidationError::Empty));
167 assert_eq!(
168 validate_short_handle(" "),
169 Err(HandleValidationError::Empty)
170 );
171 assert_eq!(
172 validate_short_handle("ab"),
173 Err(HandleValidationError::TooShort)
174 );
175 assert_eq!(
176 validate_short_handle("a"),
177 Err(HandleValidationError::TooShort)
178 );
179 assert_eq!(
180 validate_short_handle("test spaces"),
181 Err(HandleValidationError::ContainsSpaces)
182 );
183 assert_eq!(
184 validate_short_handle("test\ttab"),
185 Err(HandleValidationError::ContainsSpaces)
186 );
187 assert_eq!(
188 validate_short_handle("-starts"),
189 Err(HandleValidationError::StartsWithInvalidChar)
190 );
191 assert_eq!(
192 validate_short_handle("_starts"),
193 Err(HandleValidationError::InvalidCharacters)
194 );
195 assert_eq!(
196 validate_short_handle("ends-"),
197 Err(HandleValidationError::EndsWithInvalidChar)
198 );
199 assert_eq!(
200 validate_short_handle("ends_"),
201 Err(HandleValidationError::InvalidCharacters)
202 );
203 assert_eq!(
204 validate_short_handle("user_name"),
205 Err(HandleValidationError::InvalidCharacters)
206 );
207 assert_eq!(
208 validate_short_handle("test@user"),
209 Err(HandleValidationError::InvalidCharacters)
210 );
211 assert_eq!(
212 validate_short_handle("test!user"),
213 Err(HandleValidationError::InvalidCharacters)
214 );
215 assert_eq!(
216 validate_short_handle("test.user"),
217 Err(HandleValidationError::InvalidCharacters)
218 );
219 }
220
221 #[test]
222 fn test_handle_trimming() {
223 assert_eq!(validate_short_handle(" alice "), Ok("alice".to_string()));
224 }
225
226 #[test]
227 fn test_valid_emails() {
228 assert!(is_valid_email("user@example.com"));
229 assert!(is_valid_email("user.name@example.com"));
230 assert!(is_valid_email("user+tag@example.com"));
231 assert!(is_valid_email("user@sub.example.com"));
232 assert!(is_valid_email("USER@EXAMPLE.COM"));
233 assert!(is_valid_email("user123@example123.com"));
234 assert!(is_valid_email("a@b.co"));
235 }
236 #[test]
237 fn test_invalid_emails() {
238 assert!(!is_valid_email(""));
239 assert!(!is_valid_email("user"));
240 assert!(!is_valid_email("user@"));
241 assert!(!is_valid_email("@example.com"));
242 assert!(!is_valid_email("user@example"));
243 assert!(!is_valid_email("user@@example.com"));
244 assert!(!is_valid_email("user@.example.com"));
245 assert!(!is_valid_email("user@example..com"));
246 assert!(!is_valid_email(".user@example.com"));
247 assert!(!is_valid_email("user.@example.com"));
248 assert!(!is_valid_email("user..name@example.com"));
249 assert!(!is_valid_email("user@-example.com"));
250 assert!(!is_valid_email("user@example-.com"));
251 }
252 #[test]
253 fn test_trimmed_whitespace() {
254 assert!(is_valid_email(" user@example.com "));
255 }
256}