Sanitize html (#3708)
authorNutomic <me@nutomic.com>
Wed, 26 Jul 2023 18:01:15 +0000 (20:01 +0200)
committerGitHub <noreply@github.com>
Wed, 26 Jul 2023 18:01:15 +0000 (14:01 -0400)
* HTML sanitization in apub code

* Sanitize API inputs

* fmt

* Dont allow html a, img tags

---------

Co-authored-by: Dessalines <dessalines@users.noreply.github.com>
41 files changed:
Cargo.lock
api_tests/src/post.spec.ts
crates/api/src/comment_report/create.rs
crates/api/src/community/ban.rs
crates/api/src/community/hide.rs
crates/api/src/lib.rs
crates/api/src/local_user/ban_person.rs
crates/api/src/local_user/save_settings.rs
crates/api/src/post_report/create.rs
crates/api/src/private_message_report/create.rs
crates/api/src/site/purge/comment.rs
crates/api/src/site/purge/community.rs
crates/api/src/site/purge/person.rs
crates/api/src/site/purge/post.rs
crates/api/src/site/registration_applications/approve.rs
crates/api_common/Cargo.toml
crates/api_common/src/utils.rs
crates/api_crud/src/comment/create.rs
crates/api_crud/src/comment/update.rs
crates/api_crud/src/community/create.rs
crates/api_crud/src/community/update.rs
crates/api_crud/src/custom_emoji/create.rs
crates/api_crud/src/custom_emoji/update.rs
crates/api_crud/src/post/create.rs
crates/api_crud/src/post/update.rs
crates/api_crud/src/private_message/create.rs
crates/api_crud/src/private_message/update.rs
crates/api_crud/src/site/create.rs
crates/api_crud/src/site/update.rs
crates/api_crud/src/user/create.rs
crates/apub/src/activities/block/block_user.rs
crates/apub/src/activities/block/undo_block_user.rs
crates/apub/src/activities/community/report.rs
crates/apub/src/activities/deletion/delete.rs
crates/apub/src/objects/comment.rs
crates/apub/src/objects/instance.rs
crates/apub/src/objects/person.rs
crates/apub/src/objects/post.rs
crates/apub/src/objects/private_message.rs
crates/apub/src/protocol/objects/group.rs
crates/db_schema/src/utils.rs

index 0597cfeb577d5aa98752950d790fb92c8963d49e..6785fe50bcb515727b9b4f53c9cbbff36d013e86 100644 (file)
@@ -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"
index 532841b13fe9b383e20c700e82969529bd99285f..42173dba81959126caab13bc9d567ba3c842d3cb 100644 (file)
@@ -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 = "<script>alert('xss');</script> 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");
+});
index 3a89e1014b977aef488c93262f2cfdcdc8c68800..190e47a1e5d2a1247eb3b4e6a3e46a2566a74414 100644 (file)
@@ -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)
index 33f6ef83305db2cc4e0868ad1abd66dd2a1c0668..95c2bbc04175a1b043ae12bf34e7e10cd351e479 100644 (file)
@@ -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,
     };
index 313e3d84a5917b6a09c3887fc701d8a78cf184a3..4c05a71cfdc9dc73245d9f96fa2566c3b4741d3d 100644 (file)
@@ -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),
     };
 
index 9d3cf211c233ef5da1df63408310b18cb69ef840..b297f503f6f9d4abc2115255a55e8ca567bd5fff 100644 (file)
@@ -60,7 +60,7 @@ pub(crate) fn captcha_as_wav_base64(captcha: &Captcha) -> Result<String, LemmyEr
   Ok(base64.encode(output_buffer.into_inner()))
 }
 
-/// Check size of report and remove whitespace
+/// Check size of report
 pub(crate) fn check_report_reason(reason: &str, local_site: &LocalSite) -> Result<(), LemmyError> {
   let slur_regex = &local_site_to_slur_regex(local_site);
 
index 83768bc23d207db86021a76d65b35724a47ff6f4..77e8e805680d56becda7ac62c628474c20b74b71 100644 (file)
@@ -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,
     };
index c5038eb7982a6e104f4e91fa8b0f7d45e59f13d8..152c11ad12e1e9c6c0840bb82603ad905d1a1369 100644 (file)
@@ -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)
index 16c994d3b95cf484ea73ff2a21fd109b568719a5..a4081015ca6152dc3b79298c3e35838036756113 100644 (file)
@@ -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)
index 88511bcf71021f0b45a45683de86268fff69eef7..4ca1d7cd6265c58b24fd6ed787fbcb1660f56652 100644 (file)
@@ -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)
index 9334961e981b710f3bd13efb6991ea55e003a875..bfaf9cbb0772e9614fe33cd163828a2af35f895a 100644 (file)
@@ -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,
index 56e757176b5bb65c8bfdf1b86b4897ef801dfce0..bd8d9d386829467f0fabae6f57788cd4f044e217 100644 (file)
@@ -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,
index fa884147f25f1c560bbc5f9efd0d0f2a6283eb67..838b36070d364611f8344f6280beb261eb3b7ec5 100644 (file)
@@ -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,
index 6824e408b5354d2bc5ebe74c4f883fefd6c756bf..ee0a3af094d95f179950e55bb00ed99de8e41f1a 100644 (file)
@@ -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,
index 1a8521ca9351488ebe3d04f256198313d70309b2..227f93243dacec0f6ff86fbaad5f240c3a20f02e 100644 (file)
@@ -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,
index 8a23a4cb2475425b59c54ad26e2b53b8c8ea658a..d74acd1363a43b8dcf7573f8c4d2ca191220f42f 100644 (file)
@@ -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 }
index d259b9e4c1c0c14130c17843ff82a3e57ab615d2..8ccb7d3fed5dc482d00b73af1a8283dad9ad47dd 100644 (file)
@@ -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<DbUrl, ParseError> {
 pub fn generate_moderators_url(community_id: &DbUrl) -> Result<DbUrl, LemmyError> {
   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<String>) -> Option<String> {
+  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("<script>alert(1);</script> hello");
+    assert_eq!(sanitized, " hello");
+    let sanitized = sanitize_html("<img src='http://example.com'> test");
+    assert_eq!(sanitized, " test");
+  }
+}
index 098d1a66421886ac845e1f3a170239b6db70bf87..4a7513a4b50f0dc2bbfe2859836652bb2c6444a5 100644 (file)
@@ -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,
index 0129e87c2e50ea99183924e815cb3c3a22aa273d..558965f62fd8ac67434f152c6a4383792b7bb224 100644 (file)
@@ -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();
index 77ab833b9116a2ac37f4e8a5629e76c1c7b2ceed..7c84a21502bfa68067cc3a637c280fb0b6f25af6 100644 (file)
@@ -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)
index 62c3776f4ae27c8995669ab4729fd5e2489cc8a3..128be036fd241529c64e4d76a7907b0c77ecea4e 100644 (file)
@@ -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<PersonId> =
@@ -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)
index dcf4fe7f9321101e24b74c185e8df89317c73ab6..93e7114aef2cd875a3c0ab7a0ea75fc8aeb4045b 100644 (file)
@@ -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?;
index 7db3a528253e358a0e94fee4063c8aeda47f7886..93708c379d7a083e48076115c00c47662d00a214 100644 (file)
@@ -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?;
index 458fdb24827d5e1d0711e63a3b7d72a367d2c653..264cdbc829d9cdff851b538562ccc6aedcf826d6 100644 (file)
@@ -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)
index fbbadbc61278795aa0c4f730a286d1cadda5da05..f3be5f6af903a830a756d0e85a3c09e6d34cfb6d 100644 (file)
@@ -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)
index 48f6bdd23089ed44e6647c5880c054a37616a1a7..3b1a625f63b4284a9d0b3223b77c997f664014d5 100644 (file)
@@ -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;
index 4abf6f3ccf226632cd667818521c5ee0f5bbc959..09b50540dba830f5f3c2a9bfb7217a3dbc1607af 100644 (file)
@@ -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(),
     )
index 540b3c6c1518d685127029542abd6f7117e0f007..98d111a1de8e22cb1ef27f61d32415b71cc5c20a 100644 (file)
@@ -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)
index ea3c53aa7fa4bd054c848c57750253b87d4eec2b..2b8ce4c0f3f1957252944d760db5b1769bef10ec 100644 (file)
@@ -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)
index caba9bd8ab44c4eeaa81429e5e0315d249764c0a..f2af6940e05867d6c92f846876401fc58c5c06b5 100644 (file)
@@ -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)
index 55642f86238e717202f6bdab3788be64923780ce..abfab84565fd1f09d1ca895f84ae61f61e3d7ff9 100644 (file)
@@ -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,
         };
index f683497945e52974d2061dbbb494db41a6bcd679..2ebd053baae945fec0c47b60395fb6a2c930ca74 100644 (file)
@@ -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,
         };
index 67b84644e6b473078d7833823cdcb4780828178c..22a8c12be116f53b215ac081e438ff1bc2e01ef5 100644 (file)
@@ -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?;
       }
index fcdede8d76745460653ea97874f3317633146b61..06f7463ae0f12dfe163677153e726aaf846da746 100644 (file)
@@ -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<String>,
   context: &Data<LemmyContext>,
 ) -> Result<(), LemmyError> {
+  let reason = sanitize_html_opt(&reason);
+
   match DeletableObjects::read_from_db(object, context).await? {
     DeletableObjects::Community(community) => {
       if community.local {
index 2954de0968cab4af742a911b161cfabfdb889c00..3b05ed3946e0bb6bc00f4959d70905e6155bcbf8 100644 (file)
@@ -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()),
index 7933d47057fde7732615922c9ba4c507f92d113f..52fc210b069c945a13a13edc51633c37a7d4dd78 100644 (file)
@@ -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()),
index d28f8c7cf335982d07b74753ea447c9c06eed703..2c238fb56c309d4c23accaa81633e06f4190fae6 100644 (file)
@@ -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<ApubPerson, LemmyError> {
     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),
index 48b573d30ab0dfdfd58398cdd2159ce7ee266a85..f04e07ded3b9961ad39e36720133946dc3d89e9d 100644 (file)
@@ -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),
index 69a2638ad09dfc745539fcae548c1c1165aec931..a51cfe6b77c2cad5008a062cc1a424a85ec968ae 100644 (file)
@@ -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(&note.content, &None, &note.source);
+    let content = sanitize_html(&content);
+
     let form = PrivateMessageInsertForm {
       creator_id: creator.id,
       recipient_id: recipient.id,
-      content: read_from_string_or_source(&note.content, &None, &note.source),
+      content,
       published: note.published.map(|u| u.naive_local()),
       updated: note.updated.map(|u| u.naive_local()),
       deleted: Some(false),
index 77cafc828305e30741aec62b44b9fb8297399f6e..9c679fdf10a6bd6b5f87281e1e616fe67305dd05 100644 (file)
@@ -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()),
index cd2005ad072f11bc13517921980ea62ab82a723d..7e8204de9a7844154d8c3f919f71de6640f6547d 100644 (file)
@@ -197,12 +197,12 @@ pub fn is_email_regex(test: &str) -> bool {
   EMAIL_REGEX.is_match(test)
 }
 
-pub fn diesel_option_overwrite(opt: &Option<String>) -> Option<Option<String>> {
+pub fn diesel_option_overwrite(opt: Option<String>) -> Option<Option<String>> {
   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()))
     );
   }