]> Untitled Git - lemmy.git/blobdiff - crates/api/src/lib.rs
Sanitize html (#3708)
[lemmy.git] / crates / api / src / lib.rs
index dbff533eabdd56051e7f237a6210e3b299302c31..b297f503f6f9d4abc2115255a55e8ca567bd5fff 100644 (file)
@@ -1,77 +1,13 @@
-use actix_web::{web, web::Data};
+use actix_web::web::Data;
+use base64::{engine::general_purpose::STANDARD_NO_PAD as base64, Engine};
 use captcha::Captcha;
-use lemmy_api_common::{
-  comment::{
-    CreateCommentLike,
-    CreateCommentReport,
-    ListCommentReports,
-    ResolveCommentReport,
-    SaveComment,
-  },
-  community::{
-    AddModToCommunity,
-    BanFromCommunity,
-    BlockCommunity,
-    FollowCommunity,
-    TransferCommunity,
-  },
-  person::{
-    AddAdmin,
-    BanPerson,
-    BlockPerson,
-    ChangePassword,
-    GetBannedPersons,
-    GetCaptcha,
-    GetPersonMentions,
-    GetReplies,
-    GetReportCount,
-    GetUnreadCount,
-    Login,
-    MarkAllAsRead,
-    MarkCommentReplyAsRead,
-    MarkPersonMentionAsRead,
-    PasswordChangeAfterReset,
-    PasswordReset,
-    SaveUserSettings,
-    VerifyEmail,
-  },
-  post::{
-    CreatePostLike,
-    CreatePostReport,
-    GetSiteMetadata,
-    ListPostReports,
-    LockPost,
-    MarkPostAsRead,
-    ResolvePostReport,
-    SavePost,
-    StickyPost,
-  },
-  private_message::{
-    CreatePrivateMessageReport,
-    ListPrivateMessageReports,
-    MarkPrivateMessageAsRead,
-    ResolvePrivateMessageReport,
-  },
-  site::{
-    ApproveRegistrationApplication,
-    GetModlog,
-    GetUnreadRegistrationApplicationCount,
-    LeaveAdmin,
-    ListRegistrationApplications,
-    PurgeComment,
-    PurgeCommunity,
-    PurgePerson,
-    PurgePost,
-    ResolveObject,
-    Search,
-  },
-  utils::local_site_to_slur_regex,
-  websocket::{CommunityJoin, ModJoin, PostJoin, UserJoin},
-};
+use lemmy_api_common::{context::LemmyContext, utils::local_site_to_slur_regex};
 use lemmy_db_schema::source::local_site::LocalSite;
-use lemmy_utils::{error::LemmyError, utils::check_slurs, ConnectionId};
-use lemmy_websocket::{serialize_websocket_message, LemmyContext, UserOperation};
-use serde::Deserialize;
+use lemmy_utils::{
+  error::{LemmyError, LemmyErrorExt, LemmyErrorType},
+  utils::slurs::check_slurs,
+};
+use std::io::Cursor;
 
 mod comment;
 mod comment_report;
@@ -82,226 +18,67 @@ mod post_report;
 mod private_message;
 mod private_message_report;
 mod site;
-mod websocket;
 
 #[async_trait::async_trait(?Send)]
 pub trait Perform {
-  type Response: serde::ser::Serialize + Send;
+  type Response: serde::ser::Serialize + Send + Clone + Sync;
 
-  async fn perform(
-    &self,
-    context: &Data<LemmyContext>,
-    websocket_id: Option<ConnectionId>,
-  ) -> Result<Self::Response, LemmyError>;
-}
-
-pub async fn match_websocket_operation(
-  context: LemmyContext,
-  id: ConnectionId,
-  op: UserOperation,
-  data: &str,
-) -> Result<String, LemmyError> {
-  match op {
-    // User ops
-    UserOperation::Login => do_websocket_operation::<Login>(context, id, op, data).await,
-    UserOperation::GetCaptcha => do_websocket_operation::<GetCaptcha>(context, id, op, data).await,
-    UserOperation::GetReplies => do_websocket_operation::<GetReplies>(context, id, op, data).await,
-    UserOperation::AddAdmin => do_websocket_operation::<AddAdmin>(context, id, op, data).await,
-    UserOperation::GetUnreadRegistrationApplicationCount => {
-      do_websocket_operation::<GetUnreadRegistrationApplicationCount>(context, id, op, data).await
-    }
-    UserOperation::ListRegistrationApplications => {
-      do_websocket_operation::<ListRegistrationApplications>(context, id, op, data).await
-    }
-    UserOperation::ApproveRegistrationApplication => {
-      do_websocket_operation::<ApproveRegistrationApplication>(context, id, op, data).await
-    }
-    UserOperation::BanPerson => do_websocket_operation::<BanPerson>(context, id, op, data).await,
-    UserOperation::GetBannedPersons => {
-      do_websocket_operation::<GetBannedPersons>(context, id, op, data).await
-    }
-    UserOperation::BlockPerson => {
-      do_websocket_operation::<BlockPerson>(context, id, op, data).await
-    }
-    UserOperation::GetPersonMentions => {
-      do_websocket_operation::<GetPersonMentions>(context, id, op, data).await
-    }
-    UserOperation::MarkPersonMentionAsRead => {
-      do_websocket_operation::<MarkPersonMentionAsRead>(context, id, op, data).await
-    }
-    UserOperation::MarkCommentReplyAsRead => {
-      do_websocket_operation::<MarkCommentReplyAsRead>(context, id, op, data).await
-    }
-    UserOperation::MarkAllAsRead => {
-      do_websocket_operation::<MarkAllAsRead>(context, id, op, data).await
-    }
-    UserOperation::PasswordReset => {
-      do_websocket_operation::<PasswordReset>(context, id, op, data).await
-    }
-    UserOperation::PasswordChange => {
-      do_websocket_operation::<PasswordChangeAfterReset>(context, id, op, data).await
-    }
-    UserOperation::UserJoin => do_websocket_operation::<UserJoin>(context, id, op, data).await,
-    UserOperation::PostJoin => do_websocket_operation::<PostJoin>(context, id, op, data).await,
-    UserOperation::CommunityJoin => {
-      do_websocket_operation::<CommunityJoin>(context, id, op, data).await
-    }
-    UserOperation::ModJoin => do_websocket_operation::<ModJoin>(context, id, op, data).await,
-    UserOperation::SaveUserSettings => {
-      do_websocket_operation::<SaveUserSettings>(context, id, op, data).await
-    }
-    UserOperation::ChangePassword => {
-      do_websocket_operation::<ChangePassword>(context, id, op, data).await
-    }
-    UserOperation::GetReportCount => {
-      do_websocket_operation::<GetReportCount>(context, id, op, data).await
-    }
-    UserOperation::GetUnreadCount => {
-      do_websocket_operation::<GetUnreadCount>(context, id, op, data).await
-    }
-    UserOperation::VerifyEmail => {
-      do_websocket_operation::<VerifyEmail>(context, id, op, data).await
-    }
-
-    // Private Message ops
-    UserOperation::MarkPrivateMessageAsRead => {
-      do_websocket_operation::<MarkPrivateMessageAsRead>(context, id, op, data).await
-    }
-    UserOperation::CreatePrivateMessageReport => {
-      do_websocket_operation::<CreatePrivateMessageReport>(context, id, op, data).await
-    }
-    UserOperation::ResolvePrivateMessageReport => {
-      do_websocket_operation::<ResolvePrivateMessageReport>(context, id, op, data).await
-    }
-    UserOperation::ListPrivateMessageReports => {
-      do_websocket_operation::<ListPrivateMessageReports>(context, id, op, data).await
-    }
-
-    // Site ops
-    UserOperation::GetModlog => do_websocket_operation::<GetModlog>(context, id, op, data).await,
-    UserOperation::PurgePerson => {
-      do_websocket_operation::<PurgePerson>(context, id, op, data).await
-    }
-    UserOperation::PurgeCommunity => {
-      do_websocket_operation::<PurgeCommunity>(context, id, op, data).await
-    }
-    UserOperation::PurgePost => do_websocket_operation::<PurgePost>(context, id, op, data).await,
-    UserOperation::PurgeComment => {
-      do_websocket_operation::<PurgeComment>(context, id, op, data).await
-    }
-    UserOperation::Search => do_websocket_operation::<Search>(context, id, op, data).await,
-    UserOperation::ResolveObject => {
-      do_websocket_operation::<ResolveObject>(context, id, op, data).await
-    }
-    UserOperation::TransferCommunity => {
-      do_websocket_operation::<TransferCommunity>(context, id, op, data).await
-    }
-    UserOperation::LeaveAdmin => do_websocket_operation::<LeaveAdmin>(context, id, op, data).await,
-
-    // Community ops
-    UserOperation::FollowCommunity => {
-      do_websocket_operation::<FollowCommunity>(context, id, op, data).await
-    }
-    UserOperation::BlockCommunity => {
-      do_websocket_operation::<BlockCommunity>(context, id, op, data).await
-    }
-    UserOperation::BanFromCommunity => {
-      do_websocket_operation::<BanFromCommunity>(context, id, op, data).await
-    }
-    UserOperation::AddModToCommunity => {
-      do_websocket_operation::<AddModToCommunity>(context, id, op, data).await
-    }
-
-    // Post ops
-    UserOperation::LockPost => do_websocket_operation::<LockPost>(context, id, op, data).await,
-    UserOperation::StickyPost => do_websocket_operation::<StickyPost>(context, id, op, data).await,
-    UserOperation::CreatePostLike => {
-      do_websocket_operation::<CreatePostLike>(context, id, op, data).await
-    }
-    UserOperation::MarkPostAsRead => {
-      do_websocket_operation::<MarkPostAsRead>(context, id, op, data).await
-    }
-    UserOperation::SavePost => do_websocket_operation::<SavePost>(context, id, op, data).await,
-    UserOperation::CreatePostReport => {
-      do_websocket_operation::<CreatePostReport>(context, id, op, data).await
-    }
-    UserOperation::ListPostReports => {
-      do_websocket_operation::<ListPostReports>(context, id, op, data).await
-    }
-    UserOperation::ResolvePostReport => {
-      do_websocket_operation::<ResolvePostReport>(context, id, op, data).await
-    }
-    UserOperation::GetSiteMetadata => {
-      do_websocket_operation::<GetSiteMetadata>(context, id, op, data).await
-    }
-
-    // Comment ops
-    UserOperation::SaveComment => {
-      do_websocket_operation::<SaveComment>(context, id, op, data).await
-    }
-    UserOperation::CreateCommentLike => {
-      do_websocket_operation::<CreateCommentLike>(context, id, op, data).await
-    }
-    UserOperation::CreateCommentReport => {
-      do_websocket_operation::<CreateCommentReport>(context, id, op, data).await
-    }
-    UserOperation::ListCommentReports => {
-      do_websocket_operation::<ListCommentReports>(context, id, op, data).await
-    }
-    UserOperation::ResolveCommentReport => {
-      do_websocket_operation::<ResolveCommentReport>(context, id, op, data).await
-    }
-  }
-}
-
-async fn do_websocket_operation<'a, 'b, Data>(
-  context: LemmyContext,
-  id: ConnectionId,
-  op: UserOperation,
-  data: &str,
-) -> Result<String, LemmyError>
-where
-  for<'de> Data: Deserialize<'de> + 'a,
-  Data: Perform,
-{
-  let parsed_data: Data = serde_json::from_str(data)?;
-  let res = parsed_data
-    .perform(&web::Data::new(context), Some(id))
-    .await?;
-  serialize_websocket_message(&op, &res)
+  async fn perform(&self, context: &Data<LemmyContext>) -> Result<Self::Response, LemmyError>;
 }
 
 /// Converts the captcha to a base64 encoded wav audio file
-pub(crate) fn captcha_as_wav_base64(captcha: &Captcha) -> String {
+pub(crate) fn captcha_as_wav_base64(captcha: &Captcha) -> Result<String, LemmyError> {
   let letters = captcha.as_wav();
 
-  let mut concat_letters: Vec<u8> = Vec::new();
-
+  // Decode each wav file, concatenate the samples
+  let mut concat_samples: Vec<i16> = Vec::new();
+  let mut any_header: Option<wav::Header> = None;
   for letter in letters {
-    let bytes = letter.unwrap_or_default();
-    concat_letters.extend(bytes);
+    let mut cursor = Cursor::new(letter.unwrap_or_default());
+    let (header, samples) = wav::read(&mut cursor)?;
+    any_header = Some(header);
+    if let Some(samples16) = samples.as_sixteen() {
+      concat_samples.extend(samples16);
+    } else {
+      return Err(LemmyErrorType::CouldntCreateAudioCaptcha)?;
+    }
   }
 
-  // Convert to base64
-  base64::encode(concat_letters)
+  // Encode the concatenated result as a wav file
+  let mut output_buffer = Cursor::new(vec![]);
+  let header = match any_header {
+    Some(header) => header,
+    None => return Err(LemmyErrorType::CouldntCreateAudioCaptcha)?,
+  };
+  wav::write(
+    header,
+    &wav::BitDepth::Sixteen(concat_samples),
+    &mut output_buffer,
+  )
+  .with_lemmy_type(LemmyErrorType::CouldntCreateAudioCaptcha)?;
+
+  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);
 
   check_slurs(reason, slur_regex)?;
   if reason.is_empty() {
-    return Err(LemmyError::from_message("report_reason_required"));
+    return Err(LemmyErrorType::ReportReasonRequired)?;
   }
   if reason.chars().count() > 1000 {
-    return Err(LemmyError::from_message("report_too_long"));
+    return Err(LemmyErrorType::ReportTooLong)?;
   }
   Ok(())
 }
 
 #[cfg(test)]
 mod tests {
+  #![allow(clippy::unwrap_used)]
+  #![allow(clippy::indexing_slicing)]
+
   use lemmy_api_common::utils::check_validator_time;
   use lemmy_db_schema::{
     source::{
@@ -320,10 +97,13 @@ mod tests {
   #[serial]
   async fn test_should_not_validate_user_token_after_password_change() {
     let pool = &build_db_pool_for_tests().await;
+    let pool = &mut pool.into();
     let secret = Secret::init(pool).await.unwrap();
     let settings = &SETTINGS.to_owned();
 
-    let inserted_instance = Instance::create(pool, "my_domain.tld").await.unwrap();
+    let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string())
+      .await
+      .unwrap();
 
     let new_person = PersonInsertForm::builder()
       .name("Gerry9812".into())