Validate content of incoming Note objects
This commit is contained in:
parent
2747e7b174
commit
6fc319f7dd
5 changed files with 39 additions and 8 deletions
|
@ -337,7 +337,9 @@ paths:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
status:
|
status:
|
||||||
description: Text content of the post.
|
description: |
|
||||||
|
Text content of the post.
|
||||||
|
Allowed HTML tags: a, br, pre, code.
|
||||||
type: string
|
type: string
|
||||||
'media_ids[]':
|
'media_ids[]':
|
||||||
description: Array of Attachment ids to be attached as media.
|
description: Array of Attachment ids to be attached as media.
|
||||||
|
|
|
@ -37,6 +37,7 @@ use crate::models::relationships::queries::{
|
||||||
unfollow,
|
unfollow,
|
||||||
};
|
};
|
||||||
use crate::models::users::queries::get_user_by_name;
|
use crate::models::users::queries::get_user_by_name;
|
||||||
|
use crate::utils::html::clean_html;
|
||||||
use super::activity::{Object, Activity, create_activity_accept_follow};
|
use super::activity::{Object, Activity, create_activity_accept_follow};
|
||||||
use super::actor::Actor;
|
use super::actor::Actor;
|
||||||
use super::constants::AP_PUBLIC;
|
use super::constants::AP_PUBLIC;
|
||||||
|
@ -132,6 +133,16 @@ fn get_object_id(object: Value) -> Result<String, ValidationError> {
|
||||||
Ok(object_id)
|
Ok(object_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CONTENT_MAX_SIZE: usize = 100000;
|
||||||
|
|
||||||
|
fn clean_note_content(content: &str) -> Result<String, ValidationError> {
|
||||||
|
if content.len() > CONTENT_MAX_SIZE {
|
||||||
|
return Err(ValidationError("content is too long"));
|
||||||
|
};
|
||||||
|
let content_safe = clean_html(content);
|
||||||
|
Ok(content_safe)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn process_note(
|
pub async fn process_note(
|
||||||
config: &Config,
|
config: &Config,
|
||||||
db_client: &mut impl GenericClient,
|
db_client: &mut impl GenericClient,
|
||||||
|
@ -221,6 +232,7 @@ pub async fn process_note(
|
||||||
).await?;
|
).await?;
|
||||||
let content = object.content
|
let content = object.content
|
||||||
.ok_or(ValidationError("no content"))?;
|
.ok_or(ValidationError("no content"))?;
|
||||||
|
let content_cleaned = clean_note_content(&content)?;
|
||||||
let mut attachments: Vec<Uuid> = Vec::new();
|
let mut attachments: Vec<Uuid> = Vec::new();
|
||||||
if let Some(list) = object.attachment {
|
if let Some(list) = object.attachment {
|
||||||
let mut downloaded = vec![];
|
let mut downloaded = vec![];
|
||||||
|
@ -375,7 +387,7 @@ pub async fn process_note(
|
||||||
Visibility::Direct
|
Visibility::Direct
|
||||||
};
|
};
|
||||||
let post_data = PostCreateData {
|
let post_data = PostCreateData {
|
||||||
content,
|
content: content_cleaned,
|
||||||
in_reply_to_id,
|
in_reply_to_id,
|
||||||
repost_of_id: None,
|
repost_of_id: None,
|
||||||
visibility,
|
visibility,
|
||||||
|
|
|
@ -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::errors::{ConversionError, DatabaseError, ValidationError};
|
||||||
use crate::models::attachments::types::DbMediaAttachment;
|
use crate::models::attachments::types::DbMediaAttachment;
|
||||||
use crate::models::profiles::types::DbActorProfile;
|
use crate::models::profiles::types::DbActorProfile;
|
||||||
use crate::utils::html::clean_html;
|
use crate::utils::html::clean_html_strict;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum Visibility {
|
pub enum Visibility {
|
||||||
|
@ -220,7 +220,7 @@ impl PostCreateData {
|
||||||
if self.content.chars().count() > character_limit {
|
if self.content.chars().count() > character_limit {
|
||||||
return Err(ValidationError("post is too long"));
|
return Err(ValidationError("post is too long"));
|
||||||
};
|
};
|
||||||
let content_safe = clean_html(&self.content);
|
let content_safe = clean_html_strict(&self.content);
|
||||||
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"));
|
||||||
|
|
|
@ -11,7 +11,7 @@ use uuid::Uuid;
|
||||||
use crate::activitypub::actor::Actor;
|
use crate::activitypub::actor::Actor;
|
||||||
use crate::activitypub::views::get_actor_url;
|
use crate::activitypub::views::get_actor_url;
|
||||||
use crate::errors::ValidationError;
|
use crate::errors::ValidationError;
|
||||||
use crate::utils::html::clean_html;
|
use crate::utils::html::clean_html_strict;
|
||||||
use super::validators::{
|
use super::validators::{
|
||||||
validate_username,
|
validate_username,
|
||||||
validate_display_name,
|
validate_display_name,
|
||||||
|
@ -173,12 +173,12 @@ pub struct ProfileUpdateData {
|
||||||
impl ProfileUpdateData {
|
impl ProfileUpdateData {
|
||||||
pub fn clean(&mut self) -> Result<(), ValidationError> {
|
pub fn clean(&mut self) -> Result<(), ValidationError> {
|
||||||
// Validate and clean bio
|
// Validate and clean bio
|
||||||
self.bio = self.bio.as_ref().map(|val| clean_html(val));
|
self.bio = self.bio.as_ref().map(|val| clean_html_strict(val));
|
||||||
// Clean extra fields and remove fields with empty labels
|
// Clean extra fields and remove fields with empty labels
|
||||||
self.extra_fields = self.extra_fields.iter().cloned()
|
self.extra_fields = self.extra_fields.iter().cloned()
|
||||||
.map(|mut field| {
|
.map(|mut field| {
|
||||||
field.name = field.name.trim().to_string();
|
field.name = field.name.trim().to_string();
|
||||||
field.value = clean_html(&field.value);
|
field.value = clean_html_strict(&field.value);
|
||||||
field
|
field
|
||||||
})
|
})
|
||||||
.filter(|field| !field.name.is_empty())
|
.filter(|field| !field.name.is_empty())
|
||||||
|
|
|
@ -3,6 +3,16 @@ use std::collections::HashSet;
|
||||||
use ammonia::Builder;
|
use ammonia::Builder;
|
||||||
|
|
||||||
pub fn clean_html(unsafe_html: &str) -> String {
|
pub fn clean_html(unsafe_html: &str) -> String {
|
||||||
|
let safe_html = Builder::default()
|
||||||
|
.add_generic_attributes(&["class"])
|
||||||
|
.add_tag_attributes("a", &["rel", "target"])
|
||||||
|
.link_rel(None)
|
||||||
|
.clean(unsafe_html)
|
||||||
|
.to_string();
|
||||||
|
safe_html
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clean_html_strict(unsafe_html: &str) -> String {
|
||||||
let mut allowed_tags = HashSet::new();
|
let mut allowed_tags = HashSet::new();
|
||||||
allowed_tags.insert("a");
|
allowed_tags.insert("a");
|
||||||
allowed_tags.insert("br");
|
allowed_tags.insert("br");
|
||||||
|
@ -22,8 +32,15 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_clean_html() {
|
fn test_clean_html() {
|
||||||
let unsafe_html = r#"<p>test <b>bold</b><script>dangerous</script> with <a href="https://example.com">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</p>"#;
|
||||||
let safe_html = clean_html(unsafe_html);
|
let safe_html = clean_html(unsafe_html);
|
||||||
|
assert_eq!(safe_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</p>"#);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_clean_html_strict() {
|
||||||
|
let unsafe_html = r#"<p>test <b>bold</b><script>dangerous</script> with <a href="https://example.com">link</a> and <code>code</code></p>"#;
|
||||||
|
let safe_html = clean_html_strict(unsafe_html);
|
||||||
assert_eq!(safe_html, r#"test bold with <a href="https://example.com" rel="noopener noreferrer">link</a> and <code>code</code>"#);
|
assert_eq!(safe_html, r#"test bold with <a href="https://example.com" rel="noopener noreferrer">link</a> and <code>code</code>"#);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue