From efb259764ef132e5d7464f54d6a3d2eb81828e8f Mon Sep 17 00:00:00 2001 From: trisua Date: Mon, 19 May 2025 19:31:12 -0400 Subject: [PATCH] fix: username and community title validation --- Cargo.lock | 1 + crates/app/src/image.rs | 7 +++- .../src/routes/api/v1/communities/emojis.rs | 12 ++---- .../src/routes/api/v1/communities/posts.rs | 3 +- crates/core/Cargo.toml | 1 + crates/core/src/database/auth.rs | 40 +++++++++++++++---- crates/core/src/database/communities.rs | 40 ++++++++++++++++--- 7 files changed, 79 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 03d309c..64e8257 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3324,6 +3324,7 @@ dependencies = [ "md-5", "pathbufd", "redis", + "regex", "reqwest", "rusqlite", "serde", diff --git a/crates/app/src/image.rs b/crates/app/src/image.rs index 51ee0f1..75b231c 100644 --- a/crates/app/src/image.rs +++ b/crates/app/src/image.rs @@ -167,7 +167,7 @@ pub fn save_buffer(path: &str, bytes: Vec, format: image::ImageFormat) -> st const WEBP_ENCODE_QUALITY: f32 = 85.0; /// Create a WEBP image buffer given an input of `bytes`. -pub fn save_webp_buffer(path: &str, bytes: Vec) -> std::io::Result<()> { +pub fn save_webp_buffer(path: &str, bytes: Vec, quality: Option) -> std::io::Result<()> { let img = match image::load_from_memory(&bytes) { Ok(i) => i, Err(_) => { @@ -188,7 +188,10 @@ pub fn save_webp_buffer(path: &str, bytes: Vec) -> std::io::Result<()> { } }; - let mem = encoder.encode(WEBP_ENCODE_QUALITY); + let mem = encoder.encode(match quality { + Some(q) => q, + None => WEBP_ENCODE_QUALITY, + }); if std::fs::write(path, &*mem).is_err() { return Err(std::io::Error::new( diff --git a/crates/app/src/routes/api/v1/communities/emojis.rs b/crates/app/src/routes/api/v1/communities/emojis.rs index c8d1713..eacd26d 100644 --- a/crates/app/src/routes/api/v1/communities/emojis.rs +++ b/crates/app/src/routes/api/v1/communities/emojis.rs @@ -1,10 +1,8 @@ use std::fs::exists; - -use image::ImageFormat; use pathbufd::PathBufD; use crate::{ get_user_from_token, - image::{save_buffer, Image}, + image::{save_webp_buffer, Image}, routes::api::v1::{auth::images::read_image, UpdateEmojiName}, State, }; @@ -146,11 +144,9 @@ pub async fn create_request( return Json(Error::MiscError(e.to_string()).into()); } } else { - if let Err(e) = save_buffer( - &upload.path(&data.0).to_string(), - img.0.to_vec(), - ImageFormat::WebP, - ) { + if let Err(e) = + save_webp_buffer(&upload.path(&data.0).to_string(), img.0.to_vec(), None) + { return Json(Error::MiscError(e.to_string()).into()); } } diff --git a/crates/app/src/routes/api/v1/communities/posts.rs b/crates/app/src/routes/api/v1/communities/posts.rs index 4346e49..ea510ef 100644 --- a/crates/app/src/routes/api/v1/communities/posts.rs +++ b/crates/app/src/routes/api/v1/communities/posts.rs @@ -108,7 +108,8 @@ pub async fn create_request( Err(e) => return Json(e.into()), }; - if let Err(e) = save_webp_buffer(&upload.path(&data.0).to_string(), image.to_vec()) + if let Err(e) = + save_webp_buffer(&upload.path(&data.0).to_string(), image.to_vec(), None) { return Json(Error::MiscError(e.to_string()).into()); } diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 9af2d84..a92d976 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -34,3 +34,4 @@ tokio-postgres = { version = "0.7.13", optional = true } bb8-postgres = { version = "0.9.0", optional = true } base64 = "0.22.1" emojis = "0.6.4" +regex = "1.11.1" diff --git a/crates/core/src/database/auth.rs b/crates/core/src/database/auth.rs index 37d4ee5..ee169d6 100644 --- a/crates/core/src/database/auth.rs +++ b/crates/core/src/database/auth.rs @@ -128,14 +128,15 @@ impl DataManager { return Err(Error::MiscError("This username cannot be used".to_string())); } - if data.username.contains(" ") { - return Err(Error::MiscError("Name cannot contain spaces".to_string())); - } else if data.username.contains("%") { - return Err(Error::MiscError("Name cannot contain \"%\"".to_string())); - } else if data.username.contains("?") { - return Err(Error::MiscError("Name cannot contain \"?\"".to_string())); - } else if data.username.contains("&") { - return Err(Error::MiscError("Name cannot contain \"&\"".to_string())); + let regex = regex::RegexBuilder::new(r"[^\w_\-\.!]+") + .multi_line(true) + .build() + .unwrap(); + + if regex.captures(&data.username).is_some() { + return Err(Error::MiscError( + "This username contains invalid characters".to_string(), + )); } // make sure username isn't taken @@ -436,6 +437,29 @@ impl DataManager { } pub async fn update_user_username(&self, id: usize, to: String, user: User) -> Result<()> { + // check value + if to.len() < 2 { + return Err(Error::DataTooShort("username".to_string())); + } else if to.len() > 32 { + return Err(Error::DataTooLong("username".to_string())); + } + + if self.0.banned_usernames.contains(&to) { + return Err(Error::MiscError("This username cannot be used".to_string())); + } + + let regex = regex::RegexBuilder::new(r"[^\w_\-\.!]+") + .multi_line(true) + .build() + .unwrap(); + + if regex.captures(&to).is_some() { + return Err(Error::MiscError( + "This username contains invalid characters".to_string(), + )); + } + + // ... let conn = match self.connect().await { Ok(c) => c, Err(e) => return Err(Error::DatabaseConnection(e.to_string())), diff --git a/crates/core/src/database/communities.rs b/crates/core/src/database/communities.rs index 542d1c4..a7e8754 100644 --- a/crates/core/src/database/communities.rs +++ b/crates/core/src/database/communities.rs @@ -209,16 +209,21 @@ impl DataManager { return Err(Error::DataTooLong("title".to_string())); } - if !data.title.is_ascii() | data.title.contains(" ") { - return Err(Error::MiscError( - "Title contains characters which aren't allowed".to_string(), - )); - } - if self.0.banned_usernames.contains(&data.title) { return Err(Error::MiscError("This title cannot be used".to_string())); } + let regex = regex::RegexBuilder::new(r"[^\w_\-\.!]+") + .multi_line(true) + .build() + .unwrap(); + + if regex.captures(&data.title).is_some() { + return Err(Error::MiscError( + "This title contains invalid characters".to_string(), + )); + } + // check number of communities let owner = self.get_user_by_id(data.owner).await?; @@ -382,6 +387,29 @@ impl DataManager { } pub async fn update_community_title(&self, id: usize, user: User, title: &str) -> Result<()> { + // check values + if title.len() < 2 { + return Err(Error::DataTooShort("title".to_string())); + } else if title.len() > 32 { + return Err(Error::DataTooLong("title".to_string())); + } + + if self.0.banned_usernames.contains(&title.to_string()) { + return Err(Error::MiscError("This title cannot be used".to_string())); + } + + let regex = regex::RegexBuilder::new(r"[^\w_\-\.!]+") + .multi_line(true) + .build() + .unwrap(); + + if regex.captures(&title).is_some() { + return Err(Error::MiscError( + "This title contains invalid characters".to_string(), + )); + } + + // ... let y = self.get_community_by_id(id).await?; if user.id != y.owner {