Perform post content cleaning at the end of validation process
This commit is contained in:
parent
a67a7b9b17
commit
acad0f260b
4 changed files with 39 additions and 9 deletions
|
@ -58,7 +58,6 @@ async fn create_status(
|
||||||
let current_user = get_current_user(db_client, auth.token()).await?;
|
let current_user = get_current_user(db_client, auth.token()).await?;
|
||||||
let instance = config.instance();
|
let instance = config.instance();
|
||||||
let mut post_data = PostCreateData::try_from(status_data.into_inner())?;
|
let mut post_data = PostCreateData::try_from(status_data.into_inner())?;
|
||||||
post_data.clean()?;
|
|
||||||
// Mentions
|
// Mentions
|
||||||
let mention_map = find_mentioned_profiles(
|
let mention_map = find_mentioned_profiles(
|
||||||
db_client,
|
db_client,
|
||||||
|
@ -184,6 +183,8 @@ async fn create_status(
|
||||||
// Remove duplicate mentions
|
// Remove duplicate mentions
|
||||||
post_data.mentions.sort();
|
post_data.mentions.sort();
|
||||||
post_data.mentions.dedup();
|
post_data.mentions.dedup();
|
||||||
|
// Clean content
|
||||||
|
post_data.clean()?;
|
||||||
// Create post
|
// Create post
|
||||||
let mut post = create_post(db_client, ¤t_user.id, post_data).await?;
|
let mut post = create_post(db_client, ¤t_user.id, post_data).await?;
|
||||||
post.in_reply_to = maybe_in_reply_to.map(|mut in_reply_to| {
|
post.in_reply_to = maybe_in_reply_to.map(|mut in_reply_to| {
|
||||||
|
|
|
@ -2,7 +2,7 @@ use crate::errors::ValidationError;
|
||||||
use crate::utils::html::clean_html_strict;
|
use crate::utils::html::clean_html_strict;
|
||||||
|
|
||||||
pub const CONTENT_MAX_SIZE: usize = 100000;
|
pub const CONTENT_MAX_SIZE: usize = 100000;
|
||||||
const CONTENT_ALLOWED_TAGS: [&str; 7] = [
|
const CONTENT_ALLOWED_TAGS: [&str; 8] = [
|
||||||
"a",
|
"a",
|
||||||
"br",
|
"br",
|
||||||
"pre",
|
"pre",
|
||||||
|
@ -10,8 +10,17 @@ const CONTENT_ALLOWED_TAGS: [&str; 7] = [
|
||||||
"strong",
|
"strong",
|
||||||
"em",
|
"em",
|
||||||
"p",
|
"p",
|
||||||
|
"span",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
fn content_allowed_classes() -> Vec<(&'static str, Vec<&'static str>)> {
|
||||||
|
vec![
|
||||||
|
("a", vec!["hashtag", "mention", "u-url"]),
|
||||||
|
("span", vec!["h-card"]),
|
||||||
|
("p", vec!["inline-quote"]),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
pub fn clean_content(
|
pub fn clean_content(
|
||||||
content: &str,
|
content: &str,
|
||||||
) -> Result<String, ValidationError> {
|
) -> Result<String, ValidationError> {
|
||||||
|
@ -20,7 +29,11 @@ pub fn clean_content(
|
||||||
if content.len() > CONTENT_MAX_SIZE {
|
if content.len() > CONTENT_MAX_SIZE {
|
||||||
return Err(ValidationError("post is too long"));
|
return Err(ValidationError("post is too long"));
|
||||||
};
|
};
|
||||||
let content_safe = clean_html_strict(content, &CONTENT_ALLOWED_TAGS);
|
let content_safe = clean_html_strict(
|
||||||
|
content,
|
||||||
|
&CONTENT_ALLOWED_TAGS,
|
||||||
|
content_allowed_classes(),
|
||||||
|
);
|
||||||
let content_trimmed = content_safe.trim();
|
let content_trimmed = content_safe.trim();
|
||||||
if content_trimmed.is_empty() {
|
if content_trimmed.is_empty() {
|
||||||
return Err(ValidationError("post can not be empty"));
|
return Err(ValidationError("post can not be empty"));
|
||||||
|
|
|
@ -43,7 +43,7 @@ pub fn clean_bio(bio: &str, is_remote: bool) -> Result<String, ValidationError>
|
||||||
if bio.chars().count() > BIO_MAX_LENGTH {
|
if bio.chars().count() > BIO_MAX_LENGTH {
|
||||||
return Err(ValidationError("bio is too long"));
|
return Err(ValidationError("bio is too long"));
|
||||||
};
|
};
|
||||||
clean_html_strict(bio, &BIO_ALLOWED_TAGS)
|
clean_html_strict(bio, &BIO_ALLOWED_TAGS, vec![])
|
||||||
};
|
};
|
||||||
Ok(cleaned_bio)
|
Ok(cleaned_bio)
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,7 @@ pub fn clean_extra_fields(extra_fields: &[ExtraField])
|
||||||
let mut cleaned_extra_fields = vec![];
|
let mut cleaned_extra_fields = vec![];
|
||||||
for mut field in extra_fields.iter().cloned() {
|
for mut field in extra_fields.iter().cloned() {
|
||||||
field.name = field.name.trim().to_string();
|
field.name = field.name.trim().to_string();
|
||||||
field.value = clean_html_strict(&field.value, &BIO_ALLOWED_TAGS);
|
field.value = clean_html_strict(&field.value, &BIO_ALLOWED_TAGS, vec![]);
|
||||||
if field.name.is_empty() {
|
if field.name.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::collections::HashSet;
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::iter::FromIterator;
|
use std::iter::FromIterator;
|
||||||
|
|
||||||
use ammonia::Builder;
|
use ammonia::Builder;
|
||||||
|
@ -18,11 +18,20 @@ pub fn clean_html(unsafe_html: &str) -> String {
|
||||||
pub fn clean_html_strict(
|
pub fn clean_html_strict(
|
||||||
unsafe_html: &str,
|
unsafe_html: &str,
|
||||||
allowed_tags: &[&str],
|
allowed_tags: &[&str],
|
||||||
|
allowed_classes: Vec<(&'static str, Vec<&'static str>)>,
|
||||||
) -> String {
|
) -> String {
|
||||||
let allowed_tags =
|
let allowed_tags =
|
||||||
HashSet::from_iter(allowed_tags.iter().copied());
|
HashSet::from_iter(allowed_tags.iter().copied());
|
||||||
|
let mut allowed_classes_map = HashMap::new();
|
||||||
|
for (tag, classes) in allowed_classes {
|
||||||
|
allowed_classes_map.insert(
|
||||||
|
tag,
|
||||||
|
HashSet::from_iter(classes.into_iter()),
|
||||||
|
);
|
||||||
|
};
|
||||||
let safe_html = Builder::default()
|
let safe_html = Builder::default()
|
||||||
.tags(allowed_tags)
|
.tags(allowed_tags)
|
||||||
|
.allowed_classes(allowed_classes_map)
|
||||||
.link_rel(Some("noopener"))
|
.link_rel(Some("noopener"))
|
||||||
.clean(unsafe_html)
|
.clean(unsafe_html)
|
||||||
.to_string();
|
.to_string();
|
||||||
|
@ -56,9 +65,16 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_clean_html_strict() {
|
fn test_clean_html_strict() {
|
||||||
let unsafe_html = r#"<p>test <b>bold</b><script>dangerous</script> with <a href="https://example.com" target="_blank" rel="noopener">link</a> and <code>code</code></p>"#;
|
let unsafe_html = r#"<p><span class="h-card"><a href="https://example.com/user" class="u-url mention" rel="ugc">@<span>user</span></a></span> test <b>bold</b><script>dangerous</script> with <a href="https://example.com" target="_blank" rel="noopener">link</a> and <code>code</code></p>"#;
|
||||||
let safe_html = clean_html_strict(unsafe_html, &["a", "br", "code"]);
|
let safe_html = clean_html_strict(
|
||||||
assert_eq!(safe_html, r#"test bold with <a href="https://example.com" rel="noopener">link</a> and <code>code</code>"#);
|
unsafe_html,
|
||||||
|
&["a", "br", "code", "p", "span"],
|
||||||
|
vec![
|
||||||
|
("a", vec!["mention", "u-url"]),
|
||||||
|
("span", vec!["h-card"]),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
assert_eq!(safe_html, r#"<p><span class="h-card"><a href="https://example.com/user" class="u-url mention" rel="noopener">@<span>user</span></a></span> test bold with <a href="https://example.com" rel="noopener">link</a> and <code>code</code></p>"#);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
Loading…
Reference in a new issue