diff --git a/server/src/api/community.rs b/server/src/api/community.rs index 80cc2b6..936e54c 100644 --- a/server/src/api/community.rs +++ b/server/src/api/community.rs @@ -176,11 +176,18 @@ impl Perform for Oper { Err(_e) => return Err(APIError::err("not_logged_in").into()), }; - if has_slurs(&data.name) - || has_slurs(&data.title) - || (data.description.is_some() && has_slurs(&data.description.to_owned().unwrap())) - { - return Err(APIError::err("no_slurs").into()); + if let Err(slurs) = slur_check(&data.name) { + return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); + } + + if let Err(slurs) = slur_check(&data.title) { + return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); + } + + if let Some(description) = &data.description { + if let Err(slurs) = slur_check(description) { + return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); + } } let user_id = claims.id; @@ -242,8 +249,18 @@ impl Perform for Oper { fn perform(&self, conn: &PgConnection) -> Result { let data: &EditCommunity = &self.data; - if has_slurs(&data.name) || has_slurs(&data.title) { - return Err(APIError::err("no_slurs").into()); + if let Err(slurs) = slur_check(&data.name) { + return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); + } + + if let Err(slurs) = slur_check(&data.title) { + return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); + } + + if let Some(description) = &data.description { + if let Err(slurs) = slur_check(description) { + return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); + } } let claims = match Claims::decode(&data.auth) { diff --git a/server/src/api/mod.rs b/server/src/api/mod.rs index cb09d7f..155c706 100644 --- a/server/src/api/mod.rs +++ b/server/src/api/mod.rs @@ -17,7 +17,9 @@ use crate::db::user_mention::*; use crate::db::user_mention_view::*; use crate::db::user_view::*; use crate::db::*; -use crate::{extract_usernames, has_slurs, naive_from_unix, naive_now, remove_slurs}; +use crate::{ + extract_usernames, naive_from_unix, naive_now, remove_slurs, slur_check, slurs_vec_to_str, +}; use diesel::PgConnection; use failure::Error; use serde::{Deserialize, Serialize}; diff --git a/server/src/api/post.rs b/server/src/api/post.rs index 086705b..bd276be 100644 --- a/server/src/api/post.rs +++ b/server/src/api/post.rs @@ -88,8 +88,14 @@ impl Perform for Oper { Err(_e) => return Err(APIError::err("not_logged_in").into()), }; - if has_slurs(&data.name) || (data.body.is_some() && has_slurs(&data.body.to_owned().unwrap())) { - return Err(APIError::err("no_slurs").into()); + if let Err(slurs) = slur_check(&data.name) { + return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); + } + + if let Some(body) = &data.body { + if let Err(slurs) = slur_check(body) { + return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); + } } let user_id = claims.id; @@ -298,8 +304,15 @@ impl Perform for Oper { impl Perform for Oper { fn perform(&self, conn: &PgConnection) -> Result { let data: &EditPost = &self.data; - if has_slurs(&data.name) || (data.body.is_some() && has_slurs(&data.body.to_owned().unwrap())) { - return Err(APIError::err("no_slurs").into()); + + if let Err(slurs) = slur_check(&data.name) { + return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); + } + + if let Some(body) = &data.body { + if let Err(slurs) = slur_check(body) { + return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); + } } let claims = match Claims::decode(&data.auth) { diff --git a/server/src/api/site.rs b/server/src/api/site.rs index dfbd5ff..ef1a282 100644 --- a/server/src/api/site.rs +++ b/server/src/api/site.rs @@ -186,10 +186,14 @@ impl Perform for Oper { Err(_e) => return Err(APIError::err("not_logged_in").into()), }; - if has_slurs(&data.name) - || (data.description.is_some() && has_slurs(&data.description.to_owned().unwrap())) - { - return Err(APIError::err("no_slurs").into()); + if let Err(slurs) = slur_check(&data.name) { + return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); + } + + if let Some(description) = &data.description { + if let Err(slurs) = slur_check(description) { + return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); + } } let user_id = claims.id; @@ -229,10 +233,14 @@ impl Perform for Oper { Err(_e) => return Err(APIError::err("not_logged_in").into()), }; - if has_slurs(&data.name) - || (data.description.is_some() && has_slurs(&data.description.to_owned().unwrap())) - { - return Err(APIError::err("no_slurs").into()); + if let Err(slurs) = slur_check(&data.name) { + return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); + } + + if let Some(description) = &data.description { + if let Err(slurs) = slur_check(description) { + return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); + } } let user_id = claims.id; diff --git a/server/src/api/user.rs b/server/src/api/user.rs index 0b1abb6..99072a7 100644 --- a/server/src/api/user.rs +++ b/server/src/api/user.rs @@ -240,8 +240,8 @@ impl Perform for Oper { return Err(APIError::err("passwords_dont_match").into()); } - if has_slurs(&data.username) { - return Err(APIError::err("no_slurs").into()); + if let Err(slurs) = slur_check(&data.username) { + return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); } // Make sure there are no admins diff --git a/server/src/lib.rs b/server/src/lib.rs index a9bd5da..2e6b6fd 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -62,8 +62,24 @@ pub fn remove_slurs(test: &str) -> String { SLUR_REGEX.replace_all(test, "*removed*").to_string() } -pub fn has_slurs(test: &str) -> bool { - SLUR_REGEX.is_match(test) +pub fn slur_check(test: &str) -> Result<(), Vec<&str>> { + let mut matches: Vec<&str> = SLUR_REGEX.find_iter(test).map(|mat| mat.as_str()).collect(); + + // Unique + matches.sort_unstable(); + matches.dedup(); + + if matches.is_empty() { + Ok(()) + } else { + Err(matches) + } +} + +pub fn slurs_vec_to_str(slurs: Vec<&str>) -> String { + let start = "No slurs - "; + let combined = &slurs.join(", "); + [start, combined].concat() } pub fn extract_usernames(test: &str) -> Vec<&str> { @@ -127,7 +143,7 @@ pub fn send_email( #[cfg(test)] mod tests { - use crate::{extract_usernames, has_slurs, is_email_regex, remove_slurs}; + use crate::{extract_usernames, is_email_regex, remove_slurs, slur_check, slurs_vec_to_str}; #[test] fn test_email() { @@ -138,15 +154,29 @@ mod tests { #[test] fn test_slur_filter() { let test = - "coons test dindu ladyboy tranny retardeds. Capitalized Niggerz. This is a bunch of other safe text.".to_string(); + "coons test dindu ladyboy tranny retardeds. Capitalized Niggerz. This is a bunch of other safe text."; let slur_free = "No slurs here"; assert_eq!( remove_slurs(&test), "*removed* test *removed* *removed* *removed* *removed*. Capitalized *removed*. This is a bunch of other safe text." .to_string() ); - assert!(has_slurs(&test)); - assert!(!has_slurs(slur_free)); + + let has_slurs_vec = vec![ + "Niggerz", + "coons", + "dindu", + "ladyboy", + "retardeds", + "tranny", + ]; + let has_slurs_err_str = "No slurs - Niggerz, coons, dindu, ladyboy, retardeds, tranny"; + + assert_eq!(slur_check(test), Err(has_slurs_vec)); + assert_eq!(slur_check(slur_free), Ok(())); + if let Err(slur_vec) = slur_check(test) { + assert_eq!(&slurs_vec_to_str(slur_vec), has_slurs_err_str); + } } #[test]