From 0548e6e72a4ccc33fa65d7bce44abdbbb4f9daee Mon Sep 17 00:00:00 2001 From: silverpill Date: Thu, 6 Oct 2022 21:16:52 +0000 Subject: [PATCH] Disallow tags in bio --- src/models/posts/mod.rs | 1 + src/models/posts/types.rs | 23 +++--------------- src/models/posts/validators.rs | 40 +++++++++++++++++++++++++++++++ src/models/profiles/validators.rs | 16 ++++++------- src/utils/html.rs | 16 ++++++------- 5 files changed, 59 insertions(+), 37 deletions(-) create mode 100644 src/models/posts/validators.rs diff --git a/src/models/posts/mod.rs b/src/models/posts/mod.rs index 5fffd09..190925f 100644 --- a/src/models/posts/mod.rs +++ b/src/models/posts/mod.rs @@ -3,3 +3,4 @@ pub mod helpers; pub mod mentions; pub mod queries; pub mod types; +mod validators; diff --git a/src/models/posts/types.rs b/src/models/posts/types.rs index a0518be..40aa66d 100644 --- a/src/models/posts/types.rs +++ b/src/models/posts/types.rs @@ -10,7 +10,7 @@ use crate::database::int_enum::{int_enum_from_sql, int_enum_to_sql}; use crate::errors::{ConversionError, DatabaseError, ValidationError}; use crate::models::attachments::types::DbMediaAttachment; use crate::models::profiles::types::DbActorProfile; -use crate::utils::html::clean_html_strict; +use super::validators::clean_content; #[derive(Clone, Debug, PartialEq)] pub enum Visibility { @@ -263,15 +263,7 @@ impl PostCreateData { /// Validate and clean post data (only for local posts). pub fn clean(&mut self, character_limit: usize) -> Result<(), ValidationError> { assert!(self.object_id.is_none()); - if self.content.chars().count() > character_limit { - return Err(ValidationError("post is too long")); - }; - let content_safe = clean_html_strict(&self.content); - let content_trimmed = content_safe.trim(); - if content_trimmed.is_empty() { - return Err(ValidationError("post can not be empty")); - }; - self.content = content_trimmed.to_string(); + self.content = clean_content(&self.content, character_limit)?; Ok(()) } } @@ -288,16 +280,7 @@ mod tests { const POST_CHARACTER_LIMIT: usize = 1000; #[test] - fn test_post_data_empty() { - let mut post_data_1 = PostCreateData { - content: " ".to_string(), - ..Default::default() - }; - assert_eq!(post_data_1.clean(POST_CHARACTER_LIMIT).is_ok(), false); - } - - #[test] - fn test_post_data_trimming() { + fn test_post_data_clean() { let mut post_data_2 = PostCreateData { content: "test ".to_string(), ..Default::default() diff --git a/src/models/posts/validators.rs b/src/models/posts/validators.rs new file mode 100644 index 0000000..8cf17a1 --- /dev/null +++ b/src/models/posts/validators.rs @@ -0,0 +1,40 @@ +use crate::errors::ValidationError; +use crate::utils::html::clean_html_strict; + +const CONTENT_ALLOWED_TAGS: [&str; 4] = ["a", "br", "pre", "code"]; + +pub fn clean_content( + content: &str, + character_limit: usize, +) -> Result { + if content.chars().count() > character_limit { + return Err(ValidationError("post is too long")); + }; + let content_safe = clean_html_strict(content, &CONTENT_ALLOWED_TAGS); + let content_trimmed = content_safe.trim(); + if content_trimmed.is_empty() { + return Err(ValidationError("post can not be empty")); + }; + Ok(content_trimmed.to_string()) +} + +#[cfg(test)] +mod tests { + use super::*; + + const POST_CHARACTER_LIMIT: usize = 1000; + + #[test] + fn test_clean_content_empty() { + let content = " "; + let result = clean_content(content, POST_CHARACTER_LIMIT); + assert_eq!(result.is_ok(), false); + } + + #[test] + fn test_clean_content_trimming() { + let content = "test "; + let cleaned = clean_content(content, POST_CHARACTER_LIMIT).unwrap(); + assert_eq!(cleaned, "test"); + } +} diff --git a/src/models/profiles/validators.rs b/src/models/profiles/validators.rs index a27771a..5e5ce3d 100644 --- a/src/models/profiles/validators.rs +++ b/src/models/profiles/validators.rs @@ -4,6 +4,11 @@ use crate::utils::html::{clean_html, clean_html_strict}; use super::types::ExtraField; const USERNAME_RE: &str = r"^[a-zA-Z0-9_\.-]+$"; +const DISPLAY_NAME_MAX_LENGTH: usize = 200; +const BIO_MAX_LENGTH: usize = 10000; +const BIO_ALLOWED_TAGS: [&str; 2] = ["a", "br"]; +const FIELD_NAME_MAX_SIZE: usize = 500; +const FIELD_VALUE_MAX_SIZE: usize = 5000; pub fn validate_username(username: &str) -> Result<(), ValidationError> { if username.is_empty() { @@ -19,8 +24,6 @@ pub fn validate_username(username: &str) -> Result<(), ValidationError> { Ok(()) } -const DISPLAY_NAME_MAX_LENGTH: usize = 200; - pub fn validate_display_name(display_name: &str) -> Result<(), ValidationError> { @@ -30,8 +33,6 @@ pub fn validate_display_name(display_name: &str) Ok(()) } -const BIO_MAX_LENGTH: usize = 10000; - pub fn clean_bio(bio: &str, is_remote: bool) -> Result { let cleaned_bio = if is_remote { // Remote profile @@ -42,14 +43,11 @@ pub fn clean_bio(bio: &str, is_remote: bool) -> Result if bio.chars().count() > BIO_MAX_LENGTH { return Err(ValidationError("bio is too long")); }; - clean_html_strict(bio) + clean_html_strict(bio, &BIO_ALLOWED_TAGS) }; Ok(cleaned_bio) } -const FIELD_NAME_MAX_SIZE: usize = 500; -const FIELD_VALUE_MAX_SIZE: usize = 5000; - /// Validates extra fields and removes fields with empty labels pub fn clean_extra_fields(extra_fields: &[ExtraField]) -> Result, ValidationError> @@ -57,7 +55,7 @@ pub fn clean_extra_fields(extra_fields: &[ExtraField]) let mut cleaned_extra_fields = vec![]; for mut field in extra_fields.iter().cloned() { field.name = field.name.trim().to_string(); - field.value = clean_html_strict(&field.value); + field.value = clean_html_strict(&field.value, &BIO_ALLOWED_TAGS); if field.name.is_empty() { continue; }; diff --git a/src/utils/html.rs b/src/utils/html.rs index c9dd9c9..3541370 100644 --- a/src/utils/html.rs +++ b/src/utils/html.rs @@ -1,4 +1,5 @@ use std::collections::HashSet; +use std::iter::FromIterator; use ammonia::Builder; @@ -12,13 +13,12 @@ pub fn clean_html(unsafe_html: &str) -> String { safe_html } -pub fn clean_html_strict(unsafe_html: &str) -> String { - let mut allowed_tags = HashSet::new(); - allowed_tags.insert("a"); - allowed_tags.insert("br"); - allowed_tags.insert("pre"); - allowed_tags.insert("code"); - +pub fn clean_html_strict( + unsafe_html: &str, + allowed_tags: &[&str], +) -> String { + let allowed_tags = + HashSet::from_iter(allowed_tags.iter().copied()); let safe_html = Builder::default() .tags(allowed_tags) .clean(unsafe_html) @@ -47,7 +47,7 @@ mod tests { #[test] fn test_clean_html_strict() { let unsafe_html = r#"

test bold with link and code

"#; - let safe_html = clean_html_strict(unsafe_html); + let safe_html = clean_html_strict(unsafe_html, &["a", "br", "code"]); assert_eq!(safe_html, r#"test bold with link and code"#); }