From 3471f3533cb724b2cf6953d563aadfcc9f66c1d2 Mon Sep 17 00:00:00 2001 From: Nutomic Date: Wed, 26 Jul 2023 20:01:15 +0200 Subject: [PATCH] Sanitize html (#3708) * HTML sanitization in apub code * Sanitize API inputs * fmt * Dont allow html a, img tags --------- Co-authored-by: Dessalines --- Cargo.lock | 20 ++++++ api_tests/src/post.spec.ts | 19 +++++ crates/api/src/comment_report/create.rs | 13 ++-- crates/api/src/community/ban.rs | 9 ++- crates/api/src/community/hide.rs | 4 +- crates/api/src/lib.rs | 2 +- crates/api/src/local_user/ban_person.rs | 4 +- crates/api/src/local_user/save_settings.rs | 16 +++-- crates/api/src/post_report/create.rs | 13 ++-- .../api/src/private_message_report/create.rs | 8 +-- crates/api/src/site/purge/comment.rs | 4 +- crates/api/src/site/purge/community.rs | 4 +- crates/api/src/site/purge/person.rs | 4 +- crates/api/src/site/purge/post.rs | 4 +- .../site/registration_applications/approve.rs | 2 +- crates/api_common/Cargo.toml | 2 + crates/api_common/src/utils.rs | 71 ++++++++++++------- crates/api_crud/src/comment/create.rs | 10 +-- crates/api_crud/src/comment/update.rs | 15 ++-- crates/api_crud/src/community/create.rs | 18 +++-- crates/api_crud/src/community/update.rs | 15 ++-- crates/api_crud/src/custom_emoji/create.rs | 12 ++-- crates/api_crud/src/custom_emoji/update.rs | 9 ++- crates/api_crud/src/post/create.rs | 11 ++- crates/api_crud/src/post/update.rs | 16 ++++- crates/api_crud/src/private_message/create.rs | 13 ++-- crates/api_crud/src/private_message/update.rs | 9 +-- crates/api_crud/src/site/create.rs | 24 +++++-- crates/api_crud/src/site/update.rs | 29 +++++--- crates/api_crud/src/user/create.rs | 4 +- .../apub/src/activities/block/block_user.rs | 6 +- .../src/activities/block/undo_block_user.rs | 6 +- .../apub/src/activities/community/report.rs | 6 +- crates/apub/src/activities/deletion/delete.rs | 4 +- crates/apub/src/objects/comment.rs | 10 ++- crates/apub/src/objects/instance.rs | 13 +++- crates/apub/src/objects/person.rs | 13 ++-- crates/apub/src/objects/post.rs | 12 +++- crates/apub/src/objects/private_message.rs | 10 ++- crates/apub/src/protocol/objects/group.rs | 16 +++-- crates/db_schema/src/utils.rs | 10 +-- 41 files changed, 339 insertions(+), 151 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0597cfeb5..6785fe50b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -386,6 +386,19 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "ammonia" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e6d1c7838db705c9b756557ee27c384ce695a1c51a6fe528784cb1c6840170" +dependencies = [ + "html5ever 0.26.0", + "maplit", + "once_cell", + "tendril", + "url", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -2574,6 +2587,7 @@ version = "0.18.1" dependencies = [ "activitypub_federation", "actix-web", + "ammonia", "anyhow", "chrono", "encoding", @@ -2988,6 +3002,12 @@ dependencies = [ "libc", ] +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + [[package]] name = "markdown-it" version = "0.5.1" diff --git a/api_tests/src/post.spec.ts b/api_tests/src/post.spec.ts index 532841b13..42173dba8 100644 --- a/api_tests/src/post.spec.ts +++ b/api_tests/src/post.spec.ts @@ -36,6 +36,7 @@ import { resolveCommunity, } from "./shared"; import { PostView } from "lemmy-js-client/dist/types/PostView"; +import { CreatePost } from "lemmy-js-client/dist/types/CreatePost"; let betaCommunity: CommunityView | undefined; @@ -504,3 +505,21 @@ test("Report a post", async () => { expect(betaReport.original_post_body).toBe(alphaReport.original_post_body); expect(betaReport.reason).toBe(alphaReport.reason); }); + +test("Sanitize HTML", async () => { + let betaCommunity = (await resolveBetaCommunity(beta)).community; + if (!betaCommunity) { + throw "Missing beta community"; + } + + let name = randomString(5); + let body = " hello"; + let form: CreatePost = { + name, + body, + auth: beta.auth, + community_id: betaCommunity.community.id, + }; + let post = await beta.client.createPost(form); + expect(post.post_view.post.body).toBe(" hello"); +}); diff --git a/crates/api/src/comment_report/create.rs b/crates/api/src/comment_report/create.rs index 3a89e1014..190e47a1e 100644 --- a/crates/api/src/comment_report/create.rs +++ b/crates/api/src/comment_report/create.rs @@ -3,7 +3,12 @@ use actix_web::web::Data; use lemmy_api_common::{ comment::{CommentReportResponse, CreateCommentReport}, context::LemmyContext, - utils::{check_community_ban, local_user_view_from_jwt, send_new_report_email_to_admins}, + utils::{ + check_community_ban, + local_user_view_from_jwt, + sanitize_html, + send_new_report_email_to_admins, + }, }; use lemmy_db_schema::{ source::{ @@ -29,8 +34,8 @@ impl Perform for CreateCommentReport { let local_user_view = local_user_view_from_jwt(&data.auth, context).await?; let local_site = LocalSite::read(&mut context.pool()).await?; - let reason = self.reason.trim(); - check_report_reason(reason, &local_site)?; + let reason = sanitize_html(self.reason.trim()); + check_report_reason(&reason, &local_site)?; let person_id = local_user_view.person.id; let comment_id = data.comment_id; @@ -42,7 +47,7 @@ impl Perform for CreateCommentReport { creator_id: person_id, comment_id, original_comment_text: comment_view.comment.content, - reason: reason.to_owned(), + reason, }; let report = CommentReport::report(&mut context.pool(), &report_form) diff --git a/crates/api/src/community/ban.rs b/crates/api/src/community/ban.rs index 33f6ef833..95c2bbc04 100644 --- a/crates/api/src/community/ban.rs +++ b/crates/api/src/community/ban.rs @@ -3,7 +3,12 @@ use actix_web::web::Data; use lemmy_api_common::{ community::{BanFromCommunity, BanFromCommunityResponse}, context::LemmyContext, - utils::{is_mod_or_admin, local_user_view_from_jwt, remove_user_data_in_community}, + utils::{ + is_mod_or_admin, + local_user_view_from_jwt, + remove_user_data_in_community, + sanitize_html_opt, + }, }; use lemmy_db_schema::{ source::{ @@ -81,7 +86,7 @@ impl Perform for BanFromCommunity { mod_person_id: local_user_view.person.id, other_person_id: data.person_id, community_id: data.community_id, - reason: data.reason.clone(), + reason: sanitize_html_opt(&data.reason), banned: Some(data.ban), expires, }; diff --git a/crates/api/src/community/hide.rs b/crates/api/src/community/hide.rs index 313e3d84a..4c05a71cf 100644 --- a/crates/api/src/community/hide.rs +++ b/crates/api/src/community/hide.rs @@ -4,7 +4,7 @@ use lemmy_api_common::{ build_response::build_community_response, community::{CommunityResponse, HideCommunity}, context::LemmyContext, - utils::{is_admin, local_user_view_from_jwt}, + utils::{is_admin, local_user_view_from_jwt, sanitize_html_opt}, }; use lemmy_db_schema::{ source::{ @@ -34,7 +34,7 @@ impl Perform for HideCommunity { let mod_hide_community_form = ModHideCommunityForm { community_id: data.community_id, mod_person_id: local_user_view.person.id, - reason: data.reason.clone(), + reason: sanitize_html_opt(&data.reason), hidden: Some(data.hidden), }; diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index 9d3cf211c..b297f503f 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -60,7 +60,7 @@ pub(crate) fn captcha_as_wav_base64(captcha: &Captcha) -> Result Result<(), LemmyError> { let slur_regex = &local_site_to_slur_regex(local_site); diff --git a/crates/api/src/local_user/ban_person.rs b/crates/api/src/local_user/ban_person.rs index 83768bc23..77e8e8056 100644 --- a/crates/api/src/local_user/ban_person.rs +++ b/crates/api/src/local_user/ban_person.rs @@ -3,7 +3,7 @@ use actix_web::web::Data; use lemmy_api_common::{ context::LemmyContext, person::{BanPerson, BanPersonResponse}, - utils::{is_admin, local_user_view_from_jwt, remove_user_data}, + utils::{is_admin, local_user_view_from_jwt, remove_user_data, sanitize_html_opt}, }; use lemmy_db_schema::{ source::{ @@ -63,7 +63,7 @@ impl Perform for BanPerson { let form = ModBanForm { mod_person_id: local_user_view.person.id, other_person_id: data.person_id, - reason: data.reason.clone(), + reason: sanitize_html_opt(&data.reason), banned: Some(data.ban), expires, }; diff --git a/crates/api/src/local_user/save_settings.rs b/crates/api/src/local_user/save_settings.rs index c5038eb79..152c11ad1 100644 --- a/crates/api/src/local_user/save_settings.rs +++ b/crates/api/src/local_user/save_settings.rs @@ -3,7 +3,7 @@ use actix_web::web::Data; use lemmy_api_common::{ context::LemmyContext, person::{LoginResponse, SaveUserSettings}, - utils::{local_user_view_from_jwt, send_verification_email}, + utils::{local_user_view_from_jwt, sanitize_html_opt, send_verification_email}, }; use lemmy_db_schema::{ source::{ @@ -37,13 +37,16 @@ impl Perform for SaveUserSettings { let local_user_view = local_user_view_from_jwt(&data.auth, context).await?; let site_view = SiteView::read_local(&mut context.pool()).await?; + let bio = sanitize_html_opt(&data.bio); + let display_name = sanitize_html_opt(&data.display_name); + let avatar = diesel_option_overwrite_to_url(&data.avatar)?; let banner = diesel_option_overwrite_to_url(&data.banner)?; - let bio = diesel_option_overwrite(&data.bio); - let display_name = diesel_option_overwrite(&data.display_name); - let matrix_user_id = diesel_option_overwrite(&data.matrix_user_id); + let bio = diesel_option_overwrite(bio); + let display_name = diesel_option_overwrite(display_name); + let matrix_user_id = diesel_option_overwrite(data.matrix_user_id.clone()); let email_deref = data.email.as_deref().map(str::to_lowercase); - let email = diesel_option_overwrite(&email_deref); + let email = diesel_option_overwrite(email_deref.clone()); if let Some(Some(email)) = &email { let previous_email = local_user_view.local_user.email.clone().unwrap_or_default(); @@ -85,6 +88,7 @@ impl Perform for SaveUserSettings { let person_id = local_user_view.person.id; let default_listing_type = data.default_listing_type; let default_sort_type = data.default_sort_type; + let theme = sanitize_html_opt(&data.theme); let person_form = PersonUpdateForm::builder() .display_name(display_name) @@ -130,7 +134,7 @@ impl Perform for SaveUserSettings { .show_scores(data.show_scores) .default_sort_type(default_sort_type) .default_listing_type(default_listing_type) - .theme(data.theme.clone()) + .theme(theme) .interface_language(data.interface_language.clone()) .totp_2fa_secret(totp_2fa_secret) .totp_2fa_url(totp_2fa_url) diff --git a/crates/api/src/post_report/create.rs b/crates/api/src/post_report/create.rs index 16c994d3b..a4081015c 100644 --- a/crates/api/src/post_report/create.rs +++ b/crates/api/src/post_report/create.rs @@ -3,7 +3,12 @@ use actix_web::web::Data; use lemmy_api_common::{ context::LemmyContext, post::{CreatePostReport, PostReportResponse}, - utils::{check_community_ban, local_user_view_from_jwt, send_new_report_email_to_admins}, + utils::{ + check_community_ban, + local_user_view_from_jwt, + sanitize_html, + send_new_report_email_to_admins, + }, }; use lemmy_db_schema::{ source::{ @@ -26,8 +31,8 @@ impl Perform for CreatePostReport { let local_user_view = local_user_view_from_jwt(&data.auth, context).await?; let local_site = LocalSite::read(&mut context.pool()).await?; - let reason = self.reason.trim(); - check_report_reason(reason, &local_site)?; + let reason = sanitize_html(self.reason.trim()); + check_report_reason(&reason, &local_site)?; let person_id = local_user_view.person.id; let post_id = data.post_id; @@ -41,7 +46,7 @@ impl Perform for CreatePostReport { original_post_name: post_view.post.name, original_post_url: post_view.post.url, original_post_body: post_view.post.body, - reason: reason.to_owned(), + reason, }; let report = PostReport::report(&mut context.pool(), &report_form) diff --git a/crates/api/src/private_message_report/create.rs b/crates/api/src/private_message_report/create.rs index 88511bcf7..4ca1d7cd6 100644 --- a/crates/api/src/private_message_report/create.rs +++ b/crates/api/src/private_message_report/create.rs @@ -3,7 +3,7 @@ use actix_web::web::Data; use lemmy_api_common::{ context::LemmyContext, private_message::{CreatePrivateMessageReport, PrivateMessageReportResponse}, - utils::{local_user_view_from_jwt, send_new_report_email_to_admins}, + utils::{local_user_view_from_jwt, sanitize_html, send_new_report_email_to_admins}, }; use lemmy_db_schema::{ source::{ @@ -25,8 +25,8 @@ impl Perform for CreatePrivateMessageReport { let local_user_view = local_user_view_from_jwt(&self.auth, context).await?; let local_site = LocalSite::read(&mut context.pool()).await?; - let reason = self.reason.trim(); - check_report_reason(reason, &local_site)?; + let reason = sanitize_html(self.reason.trim()); + check_report_reason(&reason, &local_site)?; let person_id = local_user_view.person.id; let private_message_id = self.private_message_id; @@ -36,7 +36,7 @@ impl Perform for CreatePrivateMessageReport { creator_id: person_id, private_message_id, original_pm_text: private_message.content, - reason: reason.to_owned(), + reason: reason.clone(), }; let report = PrivateMessageReport::report(&mut context.pool(), &report_form) diff --git a/crates/api/src/site/purge/comment.rs b/crates/api/src/site/purge/comment.rs index 9334961e9..bfaf9cbb0 100644 --- a/crates/api/src/site/purge/comment.rs +++ b/crates/api/src/site/purge/comment.rs @@ -3,7 +3,7 @@ use actix_web::web::Data; use lemmy_api_common::{ context::LemmyContext, site::{PurgeComment, PurgeItemResponse}, - utils::{is_admin, local_user_view_from_jwt}, + utils::{is_admin, local_user_view_from_jwt, sanitize_html_opt}, }; use lemmy_db_schema::{ source::{ @@ -38,7 +38,7 @@ impl Perform for PurgeComment { Comment::delete(&mut context.pool(), comment_id).await?; // Mod tables - let reason = data.reason.clone(); + let reason = sanitize_html_opt(&data.reason); let form = AdminPurgeCommentForm { admin_person_id: local_user_view.person.id, reason, diff --git a/crates/api/src/site/purge/community.rs b/crates/api/src/site/purge/community.rs index 56e757176..bd8d9d386 100644 --- a/crates/api/src/site/purge/community.rs +++ b/crates/api/src/site/purge/community.rs @@ -4,7 +4,7 @@ use lemmy_api_common::{ context::LemmyContext, request::purge_image_from_pictrs, site::{PurgeCommunity, PurgeItemResponse}, - utils::{is_admin, local_user_view_from_jwt, purge_image_posts_for_community}, + utils::{is_admin, local_user_view_from_jwt, purge_image_posts_for_community, sanitize_html_opt}, }; use lemmy_db_schema::{ source::{ @@ -55,7 +55,7 @@ impl Perform for PurgeCommunity { Community::delete(&mut context.pool(), community_id).await?; // Mod tables - let reason = data.reason.clone(); + let reason = sanitize_html_opt(&data.reason); let form = AdminPurgeCommunityForm { admin_person_id: local_user_view.person.id, reason, diff --git a/crates/api/src/site/purge/person.rs b/crates/api/src/site/purge/person.rs index fa884147f..838b36070 100644 --- a/crates/api/src/site/purge/person.rs +++ b/crates/api/src/site/purge/person.rs @@ -4,7 +4,7 @@ use lemmy_api_common::{ context::LemmyContext, request::purge_image_from_pictrs, site::{PurgeItemResponse, PurgePerson}, - utils::{is_admin, local_user_view_from_jwt, purge_image_posts_for_person}, + utils::{is_admin, local_user_view_from_jwt, purge_image_posts_for_person, sanitize_html_opt}, }; use lemmy_db_schema::{ source::{ @@ -54,7 +54,7 @@ impl Perform for PurgePerson { Person::delete(&mut context.pool(), person_id).await?; // Mod tables - let reason = data.reason.clone(); + let reason = sanitize_html_opt(&data.reason); let form = AdminPurgePersonForm { admin_person_id: local_user_view.person.id, reason, diff --git a/crates/api/src/site/purge/post.rs b/crates/api/src/site/purge/post.rs index 6824e408b..ee0a3af09 100644 --- a/crates/api/src/site/purge/post.rs +++ b/crates/api/src/site/purge/post.rs @@ -4,7 +4,7 @@ use lemmy_api_common::{ context::LemmyContext, request::purge_image_from_pictrs, site::{PurgeItemResponse, PurgePost}, - utils::{is_admin, local_user_view_from_jwt}, + utils::{is_admin, local_user_view_from_jwt, sanitize_html_opt}, }; use lemmy_db_schema::{ source::{ @@ -50,7 +50,7 @@ impl Perform for PurgePost { Post::delete(&mut context.pool(), post_id).await?; // Mod tables - let reason = data.reason.clone(); + let reason = sanitize_html_opt(&data.reason); let form = AdminPurgePostForm { admin_person_id: local_user_view.person.id, reason, diff --git a/crates/api/src/site/registration_applications/approve.rs b/crates/api/src/site/registration_applications/approve.rs index 1a8521ca9..227f93243 100644 --- a/crates/api/src/site/registration_applications/approve.rs +++ b/crates/api/src/site/registration_applications/approve.rs @@ -30,7 +30,7 @@ impl Perform for ApproveRegistrationApplication { is_admin(&local_user_view)?; // Update the registration with reason, admin_id - let deny_reason = diesel_option_overwrite(&data.deny_reason); + let deny_reason = diesel_option_overwrite(data.deny_reason.clone()); let app_form = RegistrationApplicationUpdateForm { admin_id: Some(Some(local_user_view.person.id)), deny_reason, diff --git a/crates/api_common/Cargo.toml b/crates/api_common/Cargo.toml index 8a23a4cb2..d74acd136 100644 --- a/crates/api_common/Cargo.toml +++ b/crates/api_common/Cargo.toml @@ -34,6 +34,7 @@ full = [ "actix-web", "futures", "once_cell", + "ammonia", ] [dependencies] @@ -66,3 +67,4 @@ once_cell = { workspace = true, optional = true } actix-web = { workspace = true, optional = true } # necessary for wasmt compilation getrandom = { version = "0.2.10", features = ["js"] } +ammonia = { version = "3.3.0", optional = true } diff --git a/crates/api_common/src/utils.rs b/crates/api_common/src/utils.rs index d259b9e4c..8ccb7d3fe 100644 --- a/crates/api_common/src/utils.rs +++ b/crates/api_common/src/utils.rs @@ -729,31 +729,6 @@ pub async fn delete_user_account( Ok(()) } -#[cfg(test)] -mod tests { - #![allow(clippy::unwrap_used)] - #![allow(clippy::indexing_slicing)] - - use crate::utils::{honeypot_check, password_length_check}; - - #[test] - #[rustfmt::skip] - fn password_length() { - assert!(password_length_check("Õ¼¾°3yË,o¸ãtÌÈú|ÇÁÙAøüÒI©·¤(T]/ð>æºWæ[C¤bªWöaÃÎñ·{=û³&§½K/c").is_ok()); - assert!(password_length_check("1234567890").is_ok()); - assert!(password_length_check("short").is_err()); - assert!(password_length_check("looooooooooooooooooooooooooooooooooooooooooooooooooooooooooong").is_err()); - } - - #[test] - fn honeypot() { - assert!(honeypot_check(&None).is_ok()); - assert!(honeypot_check(&Some(String::new())).is_ok()); - assert!(honeypot_check(&Some("1".to_string())).is_err()); - assert!(honeypot_check(&Some("message".to_string())).is_err()); - } -} - pub enum EndpointType { Community, Person, @@ -819,3 +794,49 @@ pub fn generate_featured_url(actor_id: &DbUrl) -> Result { pub fn generate_moderators_url(community_id: &DbUrl) -> Result { Ok(Url::parse(&format!("{community_id}/moderators"))?.into()) } + +/// Sanitize HTML with default options. Additionally, dont allow bypassing markdown +/// links and images +pub fn sanitize_html(data: &str) -> String { + ammonia::Builder::default() + .rm_tags(&["a", "img"]) + .clean(data) + .to_string() +} + +pub fn sanitize_html_opt(data: &Option) -> Option { + data.as_ref().map(|d| sanitize_html(d)) +} + +#[cfg(test)] +mod tests { + #![allow(clippy::unwrap_used)] + #![allow(clippy::indexing_slicing)] + + use crate::utils::{honeypot_check, password_length_check, sanitize_html}; + + #[test] + #[rustfmt::skip] + fn password_length() { + assert!(password_length_check("Õ¼¾°3yË,o¸ãtÌÈú|ÇÁÙAøüÒI©·¤(T]/ð>æºWæ[C¤bªWöaÃÎñ·{=û³&§½K/c").is_ok()); + assert!(password_length_check("1234567890").is_ok()); + assert!(password_length_check("short").is_err()); + assert!(password_length_check("looooooooooooooooooooooooooooooooooooooooooooooooooooooooooong").is_err()); + } + + #[test] + fn honeypot() { + assert!(honeypot_check(&None).is_ok()); + assert!(honeypot_check(&Some(String::new())).is_ok()); + assert!(honeypot_check(&Some("1".to_string())).is_err()); + assert!(honeypot_check(&Some("message".to_string())).is_err()); + } + + #[test] + fn test_sanitize_html() { + let sanitized = sanitize_html(" hello"); + assert_eq!(sanitized, " hello"); + let sanitized = sanitize_html(" test"); + assert_eq!(sanitized, " test"); + } +} diff --git a/crates/api_crud/src/comment/create.rs b/crates/api_crud/src/comment/create.rs index 098d1a664..4a7513a4b 100644 --- a/crates/api_crud/src/comment/create.rs +++ b/crates/api_crud/src/comment/create.rs @@ -12,6 +12,7 @@ use lemmy_api_common::{ get_post, local_site_to_slur_regex, local_user_view_from_jwt, + sanitize_html, EndpointType, }, }; @@ -47,11 +48,12 @@ impl PerformCrud for CreateComment { let local_user_view = local_user_view_from_jwt(&data.auth, context).await?; let local_site = LocalSite::read(&mut context.pool()).await?; - let content_slurs_removed = remove_slurs( + let content = remove_slurs( &data.content.clone(), &local_site_to_slur_regex(&local_site), ); - is_valid_body_field(&Some(content_slurs_removed.clone()), false)?; + is_valid_body_field(&Some(content.clone()), false)?; + let content = sanitize_html(&content); // Check for a community ban let post_id = data.post_id; @@ -104,7 +106,7 @@ impl PerformCrud for CreateComment { }; let comment_form = CommentInsertForm::builder() - .content(content_slurs_removed.clone()) + .content(content.clone()) .post_id(data.post_id) .creator_id(local_user_view.person.id) .language_id(language_id) @@ -135,7 +137,7 @@ impl PerformCrud for CreateComment { .with_lemmy_type(LemmyErrorType::CouldntCreateComment)?; // Scan the comment for user mentions, add those rows - let mentions = scrape_text_for_mentions(&content_slurs_removed); + let mentions = scrape_text_for_mentions(&content); let recipient_ids = send_local_notifs( mentions, &updated_comment, diff --git a/crates/api_crud/src/comment/update.rs b/crates/api_crud/src/comment/update.rs index 0129e87c2..558965f62 100644 --- a/crates/api_crud/src/comment/update.rs +++ b/crates/api_crud/src/comment/update.rs @@ -4,7 +4,12 @@ use lemmy_api_common::{ build_response::{build_comment_response, send_local_notifs}, comment::{CommentResponse, EditComment}, context::LemmyContext, - utils::{check_community_ban, local_site_to_slur_regex, local_user_view_from_jwt}, + utils::{ + check_community_ban, + local_site_to_slur_regex, + local_user_view_from_jwt, + sanitize_html_opt, + }, }; use lemmy_db_schema::{ source::{ @@ -59,16 +64,16 @@ impl PerformCrud for EditComment { .await?; // Update the Content - let content_slurs_removed = data + let content = data .content .as_ref() .map(|c| remove_slurs(c, &local_site_to_slur_regex(&local_site))); - - is_valid_body_field(&content_slurs_removed, false)?; + is_valid_body_field(&content, false)?; + let content = sanitize_html_opt(&content); let comment_id = data.comment_id; let form = CommentUpdateForm::builder() - .content(content_slurs_removed) + .content(content) .language_id(data.language_id) .updated(Some(Some(naive_now()))) .build(); diff --git a/crates/api_crud/src/community/create.rs b/crates/api_crud/src/community/create.rs index 77ab833b9..7c84a2150 100644 --- a/crates/api_crud/src/community/create.rs +++ b/crates/api_crud/src/community/create.rs @@ -13,6 +13,8 @@ use lemmy_api_common::{ is_admin, local_site_to_slur_regex, local_user_view_from_jwt, + sanitize_html, + sanitize_html_opt, EndpointType, }, }; @@ -59,10 +61,14 @@ impl PerformCrud for CreateCommunity { let icon = diesel_option_overwrite_to_url_create(&data.icon)?; let banner = diesel_option_overwrite_to_url_create(&data.banner)?; + let name = sanitize_html(&data.name); + let title = sanitize_html(&data.title); + let description = sanitize_html_opt(&data.description); + let slur_regex = local_site_to_slur_regex(&local_site); - check_slurs(&data.name, &slur_regex)?; - check_slurs(&data.title, &slur_regex)?; - check_slurs_opt(&data.description, &slur_regex)?; + check_slurs(&name, &slur_regex)?; + check_slurs(&title, &slur_regex)?; + check_slurs_opt(&description, &slur_regex)?; is_valid_actor_name(&data.name, local_site.actor_name_max_length as usize)?; is_valid_body_field(&data.description, false)?; @@ -83,9 +89,9 @@ impl PerformCrud for CreateCommunity { let keypair = generate_actor_keypair()?; let community_form = CommunityInsertForm::builder() - .name(data.name.clone()) - .title(data.title.clone()) - .description(data.description.clone()) + .name(name) + .title(title) + .description(description) .icon(icon) .banner(banner) .nsfw(data.nsfw) diff --git a/crates/api_crud/src/community/update.rs b/crates/api_crud/src/community/update.rs index 62c3776f4..128be036f 100644 --- a/crates/api_crud/src/community/update.rs +++ b/crates/api_crud/src/community/update.rs @@ -4,7 +4,7 @@ use lemmy_api_common::{ build_response::build_community_response, community::{CommunityResponse, EditCommunity}, context::LemmyContext, - utils::{local_site_to_slur_regex, local_user_view_from_jwt}, + utils::{local_site_to_slur_regex, local_user_view_from_jwt, sanitize_html_opt}, }; use lemmy_db_schema::{ newtypes::PersonId, @@ -32,15 +32,18 @@ impl PerformCrud for EditCommunity { let local_user_view = local_user_view_from_jwt(&data.auth, context).await?; let local_site = LocalSite::read(&mut context.pool()).await?; - let icon = diesel_option_overwrite_to_url(&data.icon)?; - let banner = diesel_option_overwrite_to_url(&data.banner)?; - let description = diesel_option_overwrite(&data.description); - let slur_regex = local_site_to_slur_regex(&local_site); check_slurs_opt(&data.title, &slur_regex)?; check_slurs_opt(&data.description, &slur_regex)?; is_valid_body_field(&data.description, false)?; + let title = sanitize_html_opt(&data.title); + let description = sanitize_html_opt(&data.description); + + let icon = diesel_option_overwrite_to_url(&data.icon)?; + let banner = diesel_option_overwrite_to_url(&data.banner)?; + let description = diesel_option_overwrite(description); + // Verify its a mod (only mods can edit it) let community_id = data.community_id; let mods: Vec = @@ -64,7 +67,7 @@ impl PerformCrud for EditCommunity { } let community_form = CommunityUpdateForm::builder() - .title(data.title.clone()) + .title(title) .description(description) .icon(icon) .banner(banner) diff --git a/crates/api_crud/src/custom_emoji/create.rs b/crates/api_crud/src/custom_emoji/create.rs index dcf4fe7f9..93e7114ae 100644 --- a/crates/api_crud/src/custom_emoji/create.rs +++ b/crates/api_crud/src/custom_emoji/create.rs @@ -3,7 +3,7 @@ use actix_web::web::Data; use lemmy_api_common::{ context::LemmyContext, custom_emoji::{CreateCustomEmoji, CustomEmojiResponse}, - utils::{is_admin, local_user_view_from_jwt}, + utils::{is_admin, local_user_view_from_jwt, sanitize_html}, }; use lemmy_db_schema::source::{ custom_emoji::{CustomEmoji, CustomEmojiInsertForm}, @@ -26,11 +26,15 @@ impl PerformCrud for CreateCustomEmoji { // Make sure user is an admin is_admin(&local_user_view)?; + let shortcode = sanitize_html(data.shortcode.to_lowercase().trim()); + let alt_text = sanitize_html(&data.alt_text); + let category = sanitize_html(&data.category); + let emoji_form = CustomEmojiInsertForm::builder() .local_site_id(local_site.id) - .shortcode(data.shortcode.to_lowercase().trim().to_string()) - .alt_text(data.alt_text.to_string()) - .category(data.category.to_string()) + .shortcode(shortcode) + .alt_text(alt_text) + .category(category) .image_url(data.clone().image_url.into()) .build(); let emoji = CustomEmoji::create(&mut context.pool(), &emoji_form).await?; diff --git a/crates/api_crud/src/custom_emoji/update.rs b/crates/api_crud/src/custom_emoji/update.rs index 7db3a5282..93708c379 100644 --- a/crates/api_crud/src/custom_emoji/update.rs +++ b/crates/api_crud/src/custom_emoji/update.rs @@ -3,7 +3,7 @@ use actix_web::web::Data; use lemmy_api_common::{ context::LemmyContext, custom_emoji::{CustomEmojiResponse, EditCustomEmoji}, - utils::{is_admin, local_user_view_from_jwt}, + utils::{is_admin, local_user_view_from_jwt, sanitize_html}, }; use lemmy_db_schema::source::{ custom_emoji::{CustomEmoji, CustomEmojiUpdateForm}, @@ -26,10 +26,13 @@ impl PerformCrud for EditCustomEmoji { // Make sure user is an admin is_admin(&local_user_view)?; + let alt_text = sanitize_html(&data.alt_text); + let category = sanitize_html(&data.category); + let emoji_form = CustomEmojiUpdateForm::builder() .local_site_id(local_site.id) - .alt_text(data.alt_text.to_string()) - .category(data.category.to_string()) + .alt_text(alt_text) + .category(category) .image_url(data.clone().image_url.into()) .build(); let emoji = CustomEmoji::update(&mut context.pool(), data.id, &emoji_form).await?; diff --git a/crates/api_crud/src/post/create.rs b/crates/api_crud/src/post/create.rs index 458fdb248..264cdbc82 100644 --- a/crates/api_crud/src/post/create.rs +++ b/crates/api_crud/src/post/create.rs @@ -14,6 +14,8 @@ use lemmy_api_common::{ local_site_to_slur_regex, local_user_view_from_jwt, mark_post_as_read, + sanitize_html, + sanitize_html_opt, EndpointType, }, }; @@ -91,6 +93,11 @@ pub async fn create_post( .map(|u| (u.title, u.description, u.embed_video_url)) .unwrap_or_default(); + let name = sanitize_html(data.name.trim()); + let body = sanitize_html_opt(&data.body); + let embed_title = sanitize_html_opt(&embed_title); + let embed_description = sanitize_html_opt(&embed_description); + // Only need to check if language is allowed in case user set it explicitly. When using default // language, it already only returns allowed languages. CommunityLanguage::is_allowed_community_language( @@ -114,9 +121,9 @@ pub async fn create_post( }; let post_form = PostInsertForm::builder() - .name(data.name.trim().to_owned()) + .name(name) .url(url) - .body(data.body.clone()) + .body(body) .community_id(data.community_id) .creator_id(local_user_view.person.id) .nsfw(data.nsfw) diff --git a/crates/api_crud/src/post/update.rs b/crates/api_crud/src/post/update.rs index fbbadbc61..f3be5f6af 100644 --- a/crates/api_crud/src/post/update.rs +++ b/crates/api_crud/src/post/update.rs @@ -5,7 +5,12 @@ use lemmy_api_common::{ context::LemmyContext, post::{EditPost, PostResponse}, request::fetch_site_data, - utils::{check_community_ban, local_site_to_slur_regex, local_user_view_from_jwt}, + utils::{ + check_community_ban, + local_site_to_slur_regex, + local_user_view_from_jwt, + sanitize_html_opt, + }, }; use lemmy_db_schema::{ source::{ @@ -39,7 +44,6 @@ impl PerformCrud for EditPost { // TODO No good way to handle a clear. // Issue link: https://github.com/LemmyNet/lemmy/issues/2287 let url = Some(data_url.map(clean_url_params).map(Into::into)); - let body = diesel_option_overwrite(&data.body); let slur_regex = local_site_to_slur_regex(&local_site); check_slurs_opt(&data.name, &slur_regex)?; @@ -75,6 +79,12 @@ impl PerformCrud for EditPost { .map(|u| (Some(u.title), Some(u.description), Some(u.embed_video_url))) .unwrap_or_default(); + let name = sanitize_html_opt(&data.name); + let body = sanitize_html_opt(&data.body); + let body = diesel_option_overwrite(body); + let embed_title = embed_title.map(|e| sanitize_html_opt(&e)); + let embed_description = embed_description.map(|e| sanitize_html_opt(&e)); + let language_id = self.language_id; CommunityLanguage::is_allowed_community_language( &mut context.pool(), @@ -84,7 +94,7 @@ impl PerformCrud for EditPost { .await?; let post_form = PostUpdateForm::builder() - .name(data.name.clone()) + .name(name) .url(url) .body(body) .nsfw(data.nsfw) diff --git a/crates/api_crud/src/private_message/create.rs b/crates/api_crud/src/private_message/create.rs index 48f6bdd23..3b1a625f6 100644 --- a/crates/api_crud/src/private_message/create.rs +++ b/crates/api_crud/src/private_message/create.rs @@ -9,6 +9,7 @@ use lemmy_api_common::{ get_interface_language, local_site_to_slur_regex, local_user_view_from_jwt, + sanitize_html, send_email_to_user, EndpointType, }, @@ -39,11 +40,9 @@ impl PerformCrud for CreatePrivateMessage { let local_user_view = local_user_view_from_jwt(&data.auth, context).await?; let local_site = LocalSite::read(&mut context.pool()).await?; - let content_slurs_removed = remove_slurs( - &data.content.clone(), - &local_site_to_slur_regex(&local_site), - ); - is_valid_body_field(&Some(content_slurs_removed.clone()), false)?; + let content = sanitize_html(&data.content); + let content = remove_slurs(&content, &local_site_to_slur_regex(&local_site)); + is_valid_body_field(&Some(content.clone()), false)?; check_person_block( local_user_view.person.id, @@ -53,7 +52,7 @@ impl PerformCrud for CreatePrivateMessage { .await?; let private_message_form = PrivateMessageInsertForm::builder() - .content(content_slurs_removed.clone()) + .content(content.clone()) .creator_id(local_user_view.person.id) .recipient_id(data.recipient_id) .build(); @@ -92,7 +91,7 @@ impl PerformCrud for CreatePrivateMessage { send_email_to_user( &local_recipient, &lang.notification_private_message_subject(sender_name), - &lang.notification_private_message_body(inbox_link, &content_slurs_removed, sender_name), + &lang.notification_private_message_body(inbox_link, &content, sender_name), context.settings(), ) .await; diff --git a/crates/api_crud/src/private_message/update.rs b/crates/api_crud/src/private_message/update.rs index 4abf6f3cc..09b50540d 100644 --- a/crates/api_crud/src/private_message/update.rs +++ b/crates/api_crud/src/private_message/update.rs @@ -3,7 +3,7 @@ use actix_web::web::Data; use lemmy_api_common::{ context::LemmyContext, private_message::{EditPrivateMessage, PrivateMessageResponse}, - utils::{local_site_to_slur_regex, local_user_view_from_jwt}, + utils::{local_site_to_slur_regex, local_user_view_from_jwt, sanitize_html}, }; use lemmy_db_schema::{ source::{ @@ -41,15 +41,16 @@ impl PerformCrud for EditPrivateMessage { } // Doing the update - let content_slurs_removed = remove_slurs(&data.content, &local_site_to_slur_regex(&local_site)); - is_valid_body_field(&Some(content_slurs_removed.clone()), false)?; + let content = sanitize_html(&data.content); + let content = remove_slurs(&content, &local_site_to_slur_regex(&local_site)); + is_valid_body_field(&Some(content.clone()), false)?; let private_message_id = data.private_message_id; PrivateMessage::update( &mut context.pool(), private_message_id, &PrivateMessageUpdateForm::builder() - .content(Some(content_slurs_removed)) + .content(Some(content)) .updated(Some(Some(naive_now()))) .build(), ) diff --git a/crates/api_crud/src/site/create.rs b/crates/api_crud/src/site/create.rs index 540b3c6c1..98d111a1d 100644 --- a/crates/api_crud/src/site/create.rs +++ b/crates/api_crud/src/site/create.rs @@ -12,6 +12,8 @@ use lemmy_api_common::{ is_admin, local_site_rate_limit_to_rate_limit_config, local_user_view_from_jwt, + sanitize_html, + sanitize_html_opt, }, }; use lemmy_db_schema::{ @@ -59,10 +61,14 @@ impl PerformCrud for CreateSite { let actor_id: DbUrl = Url::parse(&context.settings().get_protocol_and_hostname())?.into(); let inbox_url = Some(generate_site_inbox_url(&actor_id)?); let keypair = generate_actor_keypair()?; + let name = sanitize_html(&data.name); + let sidebar = sanitize_html_opt(&data.sidebar); + let description = sanitize_html_opt(&data.description); + let site_form = SiteUpdateForm::builder() - .name(Some(data.name.clone())) - .sidebar(diesel_option_overwrite(&data.sidebar)) - .description(diesel_option_overwrite(&data.description)) + .name(Some(name)) + .sidebar(diesel_option_overwrite(sidebar)) + .description(diesel_option_overwrite(description)) .icon(diesel_option_overwrite_to_url(&data.icon)?) .banner(diesel_option_overwrite_to_url(&data.banner)?) .actor_id(Some(actor_id)) @@ -76,6 +82,10 @@ impl PerformCrud for CreateSite { Site::update(&mut context.pool(), site_id, &site_form).await?; + let application_question = sanitize_html_opt(&data.application_question); + let default_theme = sanitize_html_opt(&data.default_theme); + let legal_information = sanitize_html_opt(&data.legal_information); + let local_site_form = LocalSiteUpdateForm::builder() // Set the site setup to true .site_setup(Some(true)) @@ -84,15 +94,15 @@ impl PerformCrud for CreateSite { .enable_nsfw(data.enable_nsfw) .community_creation_admin_only(data.community_creation_admin_only) .require_email_verification(data.require_email_verification) - .application_question(diesel_option_overwrite(&data.application_question)) + .application_question(diesel_option_overwrite(application_question)) .private_instance(data.private_instance) - .default_theme(data.default_theme.clone()) + .default_theme(default_theme) .default_post_listing_type(data.default_post_listing_type) - .legal_information(diesel_option_overwrite(&data.legal_information)) + .legal_information(diesel_option_overwrite(legal_information)) .application_email_admins(data.application_email_admins) .hide_modlog_mod_names(data.hide_modlog_mod_names) .updated(Some(Some(naive_now()))) - .slur_filter_regex(diesel_option_overwrite(&data.slur_filter_regex)) + .slur_filter_regex(diesel_option_overwrite(data.slur_filter_regex.clone())) .actor_name_max_length(data.actor_name_max_length) .federation_enabled(data.federation_enabled) .captcha_enabled(data.captcha_enabled) diff --git a/crates/api_crud/src/site/update.rs b/crates/api_crud/src/site/update.rs index ea3c53aa7..2b8ce4c0f 100644 --- a/crates/api_crud/src/site/update.rs +++ b/crates/api_crud/src/site/update.rs @@ -6,7 +6,12 @@ use actix_web::web::Data; use lemmy_api_common::{ context::LemmyContext, site::{EditSite, SiteResponse}, - utils::{is_admin, local_site_rate_limit_to_rate_limit_config, local_user_view_from_jwt}, + utils::{ + is_admin, + local_site_rate_limit_to_rate_limit_config, + local_user_view_from_jwt, + sanitize_html_opt, + }, }; use lemmy_db_schema::{ source::{ @@ -59,10 +64,14 @@ impl PerformCrud for EditSite { SiteLanguage::update(&mut context.pool(), discussion_languages.clone(), &site).await?; } + let name = sanitize_html_opt(&data.name); + let sidebar = sanitize_html_opt(&data.sidebar); + let description = sanitize_html_opt(&data.description); + let site_form = SiteUpdateForm::builder() - .name(data.name.clone()) - .sidebar(diesel_option_overwrite(&data.sidebar)) - .description(diesel_option_overwrite(&data.description)) + .name(name) + .sidebar(diesel_option_overwrite(sidebar)) + .description(diesel_option_overwrite(description)) .icon(diesel_option_overwrite_to_url(&data.icon)?) .banner(diesel_option_overwrite_to_url(&data.banner)?) .updated(Some(Some(naive_now()))) @@ -74,21 +83,25 @@ impl PerformCrud for EditSite { // Diesel will throw an error for empty update forms .ok(); + let application_question = sanitize_html_opt(&data.application_question); + let default_theme = sanitize_html_opt(&data.default_theme); + let legal_information = sanitize_html_opt(&data.legal_information); + let local_site_form = LocalSiteUpdateForm::builder() .enable_downvotes(data.enable_downvotes) .registration_mode(data.registration_mode) .enable_nsfw(data.enable_nsfw) .community_creation_admin_only(data.community_creation_admin_only) .require_email_verification(data.require_email_verification) - .application_question(diesel_option_overwrite(&data.application_question)) + .application_question(diesel_option_overwrite(application_question)) .private_instance(data.private_instance) - .default_theme(data.default_theme.clone()) + .default_theme(default_theme) .default_post_listing_type(data.default_post_listing_type) - .legal_information(diesel_option_overwrite(&data.legal_information)) + .legal_information(diesel_option_overwrite(legal_information)) .application_email_admins(data.application_email_admins) .hide_modlog_mod_names(data.hide_modlog_mod_names) .updated(Some(Some(naive_now()))) - .slur_filter_regex(diesel_option_overwrite(&data.slur_filter_regex)) + .slur_filter_regex(diesel_option_overwrite(data.slur_filter_regex.clone())) .actor_name_max_length(data.actor_name_max_length) .federation_enabled(data.federation_enabled) .captcha_enabled(data.captcha_enabled) diff --git a/crates/api_crud/src/user/create.rs b/crates/api_crud/src/user/create.rs index caba9bd8a..f2af6940e 100644 --- a/crates/api_crud/src/user/create.rs +++ b/crates/api_crud/src/user/create.rs @@ -11,6 +11,7 @@ use lemmy_api_common::{ honeypot_check, local_site_to_slur_regex, password_length_check, + sanitize_html, send_new_applicant_email_to_admins, send_verification_email, EndpointType, @@ -92,6 +93,7 @@ impl PerformCrud for Register { let slur_regex = local_site_to_slur_regex(&local_site); check_slurs(&data.username, &slur_regex)?; check_slurs_opt(&data.answer, &slur_regex)?; + let username = sanitize_html(&data.username); let actor_keypair = generate_actor_keypair()?; is_valid_actor_name(&data.username, local_site.actor_name_max_length as usize)?; @@ -111,7 +113,7 @@ impl PerformCrud for Register { // Register the new person let person_form = PersonInsertForm::builder() - .name(data.username.clone()) + .name(username) .actor_id(Some(actor_id.clone())) .private_key(Some(actor_keypair.private_key)) .public_key(actor_keypair.public_key) diff --git a/crates/apub/src/activities/block/block_user.rs b/crates/apub/src/activities/block/block_user.rs index 55642f862..abfab8456 100644 --- a/crates/apub/src/activities/block/block_user.rs +++ b/crates/apub/src/activities/block/block_user.rs @@ -23,7 +23,7 @@ use anyhow::anyhow; use chrono::NaiveDateTime; use lemmy_api_common::{ context::LemmyContext, - utils::{remove_user_data, remove_user_data_in_community}, + utils::{remove_user_data, remove_user_data_in_community, sanitize_html_opt}, }; use lemmy_db_schema::{ source::{ @@ -177,7 +177,7 @@ impl ActivityHandler for BlockUser { let form = ModBanForm { mod_person_id: mod_person.id, other_person_id: blocked_person.id, - reason: self.summary, + reason: sanitize_html_opt(&self.summary), banned: Some(true), expires, }; @@ -211,7 +211,7 @@ impl ActivityHandler for BlockUser { mod_person_id: mod_person.id, other_person_id: blocked_person.id, community_id: community.id, - reason: self.summary, + reason: sanitize_html_opt(&self.summary), banned: Some(true), expires, }; diff --git a/crates/apub/src/activities/block/undo_block_user.rs b/crates/apub/src/activities/block/undo_block_user.rs index f68349794..2ebd053ba 100644 --- a/crates/apub/src/activities/block/undo_block_user.rs +++ b/crates/apub/src/activities/block/undo_block_user.rs @@ -17,7 +17,7 @@ use activitypub_federation::{ protocol::verification::verify_domains_match, traits::{ActivityHandler, Actor}, }; -use lemmy_api_common::context::LemmyContext; +use lemmy_api_common::{context::LemmyContext, utils::sanitize_html_opt}; use lemmy_db_schema::{ source::{ community::{CommunityPersonBan, CommunityPersonBanForm}, @@ -116,7 +116,7 @@ impl ActivityHandler for UndoBlockUser { let form = ModBanForm { mod_person_id: mod_person.id, other_person_id: blocked_person.id, - reason: self.object.summary, + reason: sanitize_html_opt(&self.object.summary), banned: Some(false), expires, }; @@ -135,7 +135,7 @@ impl ActivityHandler for UndoBlockUser { mod_person_id: mod_person.id, other_person_id: blocked_person.id, community_id: community.id, - reason: self.object.summary, + reason: sanitize_html_opt(&self.object.summary), banned: Some(false), expires, }; diff --git a/crates/apub/src/activities/community/report.rs b/crates/apub/src/activities/community/report.rs index 67b84644e..22a8c12be 100644 --- a/crates/apub/src/activities/community/report.rs +++ b/crates/apub/src/activities/community/report.rs @@ -16,7 +16,7 @@ use lemmy_api_common::{ comment::{CommentReportResponse, CreateCommentReport}, context::LemmyContext, post::{CreatePostReport, PostReportResponse}, - utils::local_user_view_from_jwt, + utils::{local_user_view_from_jwt, sanitize_html}, }; use lemmy_db_schema::{ source::{ @@ -131,7 +131,7 @@ impl ActivityHandler for Report { post_id: post.id, original_post_name: post.name.clone(), original_post_url: post.url.clone(), - reason: self.summary, + reason: sanitize_html(&self.summary), original_post_body: post.body.clone(), }; PostReport::report(&mut context.pool(), &report_form).await?; @@ -141,7 +141,7 @@ impl ActivityHandler for Report { creator_id: actor.id, comment_id: comment.id, original_comment_text: comment.content.clone(), - reason: self.summary, + reason: sanitize_html(&self.summary), }; CommentReport::report(&mut context.pool(), &report_form).await?; } diff --git a/crates/apub/src/activities/deletion/delete.rs b/crates/apub/src/activities/deletion/delete.rs index fcdede8d7..06f7463ae 100644 --- a/crates/apub/src/activities/deletion/delete.rs +++ b/crates/apub/src/activities/deletion/delete.rs @@ -8,7 +8,7 @@ use crate::{ protocol::{activities::deletion::delete::Delete, IdOrNestedObject}, }; use activitypub_federation::{config::Data, kinds::activity::DeleteType, traits::ActivityHandler}; -use lemmy_api_common::context::LemmyContext; +use lemmy_api_common::{context::LemmyContext, utils::sanitize_html_opt}; use lemmy_db_schema::{ source::{ comment::{Comment, CommentUpdateForm}, @@ -105,6 +105,8 @@ pub(in crate::activities) async fn receive_remove_action( reason: Option, context: &Data, ) -> Result<(), LemmyError> { + let reason = sanitize_html_opt(&reason); + match DeletableObjects::read_from_db(object, context).await? { DeletableObjects::Community(community) => { if community.local { diff --git a/crates/apub/src/objects/comment.rs b/crates/apub/src/objects/comment.rs index 2954de096..3b05ed394 100644 --- a/crates/apub/src/objects/comment.rs +++ b/crates/apub/src/objects/comment.rs @@ -16,7 +16,10 @@ use activitypub_federation::{ traits::Object, }; use chrono::NaiveDateTime; -use lemmy_api_common::{context::LemmyContext, utils::local_site_opt_to_slur_regex}; +use lemmy_api_common::{ + context::LemmyContext, + utils::{local_site_opt_to_slur_regex, sanitize_html}, +}; use lemmy_db_schema::{ source::{ comment::{Comment, CommentInsertForm, CommentUpdateForm}, @@ -154,14 +157,15 @@ impl Object for ApubComment { let local_site = LocalSite::read(&mut context.pool()).await.ok(); let slur_regex = &local_site_opt_to_slur_regex(&local_site); - let content_slurs_removed = remove_slurs(&content, slur_regex); + let content = remove_slurs(&content, slur_regex); + let content = sanitize_html(&content); let language_id = LanguageTag::to_language_id_single(note.language, &mut context.pool()).await?; let form = CommentInsertForm { creator_id: creator.id, post_id: post.id, - content: content_slurs_removed, + content, removed: None, published: note.published.map(|u| u.naive_local()), updated: note.updated.map(|u| u.naive_local()), diff --git a/crates/apub/src/objects/instance.rs b/crates/apub/src/objects/instance.rs index 7933d4705..52fc210b0 100644 --- a/crates/apub/src/objects/instance.rs +++ b/crates/apub/src/objects/instance.rs @@ -16,7 +16,10 @@ use activitypub_federation::{ traits::{Actor, Object}, }; use chrono::NaiveDateTime; -use lemmy_api_common::{context::LemmyContext, utils::local_site_opt_to_slur_regex}; +use lemmy_api_common::{ + context::LemmyContext, + utils::{local_site_opt_to_slur_regex, sanitize_html_opt}, +}; use lemmy_db_schema::{ newtypes::InstanceId, source::{ @@ -129,13 +132,17 @@ impl Object for ApubSite { let domain = apub.id.inner().domain().expect("group id has domain"); let instance = DbInstance::read_or_create(&mut data.pool(), domain.to_string()).await?; + let sidebar = read_from_string_or_source_opt(&apub.content, &None, &apub.source); + let sidebar = sanitize_html_opt(&sidebar); + let description = sanitize_html_opt(&apub.summary); + let site_form = SiteInsertForm { name: apub.name.clone(), - sidebar: read_from_string_or_source_opt(&apub.content, &None, &apub.source), + sidebar, updated: apub.updated.map(|u| u.clone().naive_local()), icon: apub.icon.clone().map(|i| i.url.into()), banner: apub.image.clone().map(|i| i.url.into()), - description: apub.summary.clone(), + description, actor_id: Some(apub.id.clone().into()), last_refreshed_at: Some(naive_now()), inbox_url: Some(apub.inbox.clone().into()), diff --git a/crates/apub/src/objects/person.rs b/crates/apub/src/objects/person.rs index d28f8c7cf..2c238fb56 100644 --- a/crates/apub/src/objects/person.rs +++ b/crates/apub/src/objects/person.rs @@ -19,7 +19,7 @@ use activitypub_federation::{ use chrono::NaiveDateTime; use lemmy_api_common::{ context::LemmyContext, - utils::{generate_outbox_url, local_site_opt_to_slur_regex}, + utils::{generate_outbox_url, local_site_opt_to_slur_regex, sanitize_html, sanitize_html_opt}, }; use lemmy_db_schema::{ source::person::{Person as DbPerson, PersonInsertForm, PersonUpdateForm}, @@ -138,12 +138,17 @@ impl Object for ApubPerson { ) -> Result { let instance_id = fetch_instance_actor_for_object(&person.id, context).await?; + let name = sanitize_html(&person.preferred_username); + let display_name = sanitize_html_opt(&person.name); + let bio = read_from_string_or_source_opt(&person.summary, &None, &person.source); + let bio = sanitize_html_opt(&bio); + // Some Mastodon users have `name: ""` (empty string), need to convert that to `None` // https://github.com/mastodon/mastodon/issues/25233 - let display_name = person.name.filter(|n| !n.is_empty()); + let display_name = display_name.filter(|n| !n.is_empty()); let person_form = PersonInsertForm { - name: person.preferred_username, + name, display_name, banned: None, ban_expires: None, @@ -153,7 +158,7 @@ impl Object for ApubPerson { published: person.published.map(|u| u.naive_local()), updated: person.updated.map(|u| u.naive_local()), actor_id: Some(person.id.into()), - bio: read_from_string_or_source_opt(&person.summary, &None, &person.source), + bio, local: Some(false), admin: Some(false), bot_account: Some(person.kind == UserTypes::Service), diff --git a/crates/apub/src/objects/post.rs b/crates/apub/src/objects/post.rs index 48b573d30..f04e07ded 100644 --- a/crates/apub/src/objects/post.rs +++ b/crates/apub/src/objects/post.rs @@ -25,7 +25,13 @@ use html2md::parse_html; use lemmy_api_common::{ context::LemmyContext, request::fetch_site_data, - utils::{is_mod_or_admin, local_site_opt_to_sensitive, local_site_opt_to_slur_regex}, + utils::{ + is_mod_or_admin, + local_site_opt_to_sensitive, + local_site_opt_to_slur_regex, + sanitize_html, + sanitize_html_opt, + }, }; use lemmy_db_schema::{ self, @@ -228,6 +234,10 @@ impl Object for ApubPost { let language_id = LanguageTag::to_language_id_single(page.language, &mut context.pool()).await?; + let name = sanitize_html(&name); + let embed_title = sanitize_html_opt(&embed_title); + let embed_description = sanitize_html_opt(&embed_description); + PostInsertForm { name, url: url.map(Into::into), diff --git a/crates/apub/src/objects/private_message.rs b/crates/apub/src/objects/private_message.rs index 69a2638ad..a51cfe6b7 100644 --- a/crates/apub/src/objects/private_message.rs +++ b/crates/apub/src/objects/private_message.rs @@ -12,7 +12,10 @@ use activitypub_federation::{ traits::Object, }; use chrono::NaiveDateTime; -use lemmy_api_common::{context::LemmyContext, utils::check_person_block}; +use lemmy_api_common::{ + context::LemmyContext, + utils::{check_person_block, sanitize_html}, +}; use lemmy_db_schema::{ source::{ person::Person, @@ -118,10 +121,13 @@ impl Object for ApubPrivateMessage { let recipient = note.to[0].dereference(context).await?; check_person_block(creator.id, recipient.id, &mut context.pool()).await?; + let content = read_from_string_or_source(¬e.content, &None, ¬e.source); + let content = sanitize_html(&content); + let form = PrivateMessageInsertForm { creator_id: creator.id, recipient_id: recipient.id, - content: read_from_string_or_source(¬e.content, &None, ¬e.source), + content, published: note.published.map(|u| u.naive_local()), updated: note.updated.map(|u| u.naive_local()), deleted: Some(false), diff --git a/crates/apub/src/protocol/objects/group.rs b/crates/apub/src/protocol/objects/group.rs index 77cafc828..9c679fdf1 100644 --- a/crates/apub/src/protocol/objects/group.rs +++ b/crates/apub/src/protocol/objects/group.rs @@ -23,7 +23,10 @@ use activitypub_federation::{ }, }; use chrono::{DateTime, FixedOffset}; -use lemmy_api_common::{context::LemmyContext, utils::local_site_opt_to_slur_regex}; +use lemmy_api_common::{ + context::LemmyContext, + utils::{local_site_opt_to_slur_regex, sanitize_html, sanitize_html_opt}, +}; use lemmy_db_schema::{ newtypes::InstanceId, source::community::{CommunityInsertForm, CommunityUpdateForm}, @@ -94,10 +97,15 @@ impl Group { } pub(crate) fn into_insert_form(self, instance_id: InstanceId) -> CommunityInsertForm { + let name = sanitize_html(&self.preferred_username); + let title = sanitize_html(&self.name.unwrap_or(self.preferred_username)); + let description = read_from_string_or_source_opt(&self.summary, &None, &self.source); + let description = sanitize_html_opt(&description); + CommunityInsertForm { - name: self.preferred_username.clone(), - title: self.name.unwrap_or(self.preferred_username), - description: read_from_string_or_source_opt(&self.summary, &None, &self.source), + name, + title, + description, removed: None, published: self.published.map(|u| u.naive_local()), updated: self.updated.map(|u| u.naive_local()), diff --git a/crates/db_schema/src/utils.rs b/crates/db_schema/src/utils.rs index cd2005ad0..7e8204de9 100644 --- a/crates/db_schema/src/utils.rs +++ b/crates/db_schema/src/utils.rs @@ -197,12 +197,12 @@ pub fn is_email_regex(test: &str) -> bool { EMAIL_REGEX.is_match(test) } -pub fn diesel_option_overwrite(opt: &Option) -> Option> { +pub fn diesel_option_overwrite(opt: Option) -> Option> { match opt { // An empty string is an erase Some(unwrapped) => { if !unwrapped.eq("") { - Some(Some(unwrapped.clone())) + Some(Some(unwrapped)) } else { Some(None) } @@ -445,10 +445,10 @@ mod tests { #[test] fn test_diesel_option_overwrite() { - assert_eq!(diesel_option_overwrite(&None), None); - assert_eq!(diesel_option_overwrite(&Some(String::new())), Some(None)); + assert_eq!(diesel_option_overwrite(None), None); + assert_eq!(diesel_option_overwrite(Some(String::new())), Some(None)); assert_eq!( - diesel_option_overwrite(&Some("test".to_string())), + diesel_option_overwrite(Some("test".to_string())), Some(Some("test".to_string())) ); }