|
|
|
@ -1,4 +1,4 @@
|
|
|
|
|
use crate::error::{LemmyError, LemmyResult};
|
|
|
|
|
use crate::error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult};
|
|
|
|
|
use itertools::Itertools;
|
|
|
|
|
use once_cell::sync::Lazy;
|
|
|
|
|
use regex::{Regex, RegexBuilder};
|
|
|
|
@ -90,7 +90,7 @@ pub fn is_valid_actor_name(name: &str, actor_name_max_length: usize) -> LemmyRes
|
|
|
|
|
&& VALID_ACTOR_NAME_REGEX.is_match(name)
|
|
|
|
|
&& !has_newline(name);
|
|
|
|
|
if !check {
|
|
|
|
|
Err(LemmyError::from_message("invalid_name"))
|
|
|
|
|
Err(LemmyErrorType::InvalidName.into())
|
|
|
|
|
} else {
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
@ -104,7 +104,7 @@ pub fn is_valid_display_name(name: &str, actor_name_max_length: usize) -> LemmyR
|
|
|
|
|
&& name.chars().count() <= actor_name_max_length
|
|
|
|
|
&& !has_newline(name);
|
|
|
|
|
if !check {
|
|
|
|
|
Err(LemmyError::from_message("invalid_username"))
|
|
|
|
|
Err(LemmyErrorType::InvalidDisplayName.into())
|
|
|
|
|
} else {
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
@ -113,7 +113,7 @@ pub fn is_valid_display_name(name: &str, actor_name_max_length: usize) -> LemmyR
|
|
|
|
|
pub fn is_valid_matrix_id(matrix_id: &str) -> LemmyResult<()> {
|
|
|
|
|
let check = VALID_MATRIX_ID_REGEX.is_match(matrix_id) && !has_newline(matrix_id);
|
|
|
|
|
if !check {
|
|
|
|
|
Err(LemmyError::from_message("invalid_matrix_id"))
|
|
|
|
|
Err(LemmyErrorType::InvalidMatrixId.into())
|
|
|
|
|
} else {
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
@ -122,7 +122,7 @@ pub fn is_valid_matrix_id(matrix_id: &str) -> LemmyResult<()> {
|
|
|
|
|
pub fn is_valid_post_title(title: &str) -> LemmyResult<()> {
|
|
|
|
|
let check = VALID_POST_TITLE_REGEX.is_match(title) && !has_newline(title);
|
|
|
|
|
if !check {
|
|
|
|
|
Err(LemmyError::from_message("invalid_post_title"))
|
|
|
|
|
Err(LemmyErrorType::InvalidPostTitle.into())
|
|
|
|
|
} else {
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
@ -138,7 +138,7 @@ pub fn is_valid_body_field(body: &Option<String>, post: bool) -> LemmyResult<()>
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if !check {
|
|
|
|
|
Err(LemmyError::from_message("invalid_body_field"))
|
|
|
|
|
Err(LemmyErrorType::InvalidBodyField.into())
|
|
|
|
|
} else {
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
@ -148,7 +148,7 @@ pub fn is_valid_body_field(body: &Option<String>, post: bool) -> LemmyResult<()>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn is_valid_bio_field(bio: &str) -> LemmyResult<()> {
|
|
|
|
|
max_length_check(bio, BIO_MAX_LENGTH, String::from("bio_length_overflow"))
|
|
|
|
|
max_length_check(bio, BIO_MAX_LENGTH, LemmyErrorType::BioLengthOverflow)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Checks the site name length, the limit as defined in the DB.
|
|
|
|
@ -157,8 +157,8 @@ pub fn site_name_length_check(name: &str) -> LemmyResult<()> {
|
|
|
|
|
name,
|
|
|
|
|
SITE_NAME_MIN_LENGTH,
|
|
|
|
|
SITE_NAME_MAX_LENGTH,
|
|
|
|
|
String::from("site_name_required"),
|
|
|
|
|
String::from("site_name_length_overflow"),
|
|
|
|
|
LemmyErrorType::SiteNameRequired,
|
|
|
|
|
LemmyErrorType::SiteNameLengthOverflow,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -167,13 +167,13 @@ pub fn site_description_length_check(description: &str) -> LemmyResult<()> {
|
|
|
|
|
max_length_check(
|
|
|
|
|
description,
|
|
|
|
|
SITE_DESCRIPTION_MAX_LENGTH,
|
|
|
|
|
String::from("site_description_length_overflow"),
|
|
|
|
|
LemmyErrorType::SiteDescriptionLengthOverflow,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn max_length_check(item: &str, max_length: usize, msg: String) -> LemmyResult<()> {
|
|
|
|
|
fn max_length_check(item: &str, max_length: usize, error_type: LemmyErrorType) -> LemmyResult<()> {
|
|
|
|
|
if item.len() > max_length {
|
|
|
|
|
Err(LemmyError::from_message(&msg))
|
|
|
|
|
Err(error_type.into())
|
|
|
|
|
} else {
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
@ -183,13 +183,13 @@ fn min_max_length_check(
|
|
|
|
|
item: &str,
|
|
|
|
|
min_length: usize,
|
|
|
|
|
max_length: usize,
|
|
|
|
|
min_msg: String,
|
|
|
|
|
max_msg: String,
|
|
|
|
|
min_msg: LemmyErrorType,
|
|
|
|
|
max_msg: LemmyErrorType,
|
|
|
|
|
) -> LemmyResult<()> {
|
|
|
|
|
if item.len() > max_length {
|
|
|
|
|
Err(LemmyError::from_message(&max_msg))
|
|
|
|
|
Err(max_msg.into())
|
|
|
|
|
} else if item.len() < min_length {
|
|
|
|
|
Err(LemmyError::from_message(&min_msg))
|
|
|
|
|
Err(min_msg.into())
|
|
|
|
|
} else {
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
@ -209,14 +209,14 @@ pub fn build_and_check_regex(regex_str_opt: &Option<&str>) -> LemmyResult<Option
|
|
|
|
|
RegexBuilder::new(regex_str)
|
|
|
|
|
.case_insensitive(true)
|
|
|
|
|
.build()
|
|
|
|
|
.map_err(|e| LemmyError::from_error_message(e, "invalid_regex"))
|
|
|
|
|
.with_lemmy_type(LemmyErrorType::InvalidRegex)
|
|
|
|
|
.and_then(|regex| {
|
|
|
|
|
// NOTE: It is difficult to know, in the universe of user-crafted regex, which ones
|
|
|
|
|
// may match against any string text. To keep it simple, we'll match the regex
|
|
|
|
|
// against an innocuous string - a single number - which should help catch a regex
|
|
|
|
|
// that accidentally matches against all strings.
|
|
|
|
|
if regex.is_match("1") {
|
|
|
|
|
return Err(LemmyError::from_message("permissive_regex"));
|
|
|
|
|
return Err(LemmyErrorType::PermissiveRegex.into());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(Some(regex))
|
|
|
|
@ -249,13 +249,13 @@ pub fn check_totp_2fa_valid(
|
|
|
|
|
// Throw an error if their token is missing
|
|
|
|
|
let token = totp_token
|
|
|
|
|
.as_deref()
|
|
|
|
|
.ok_or_else(|| LemmyError::from_message("missing_totp_token"))?;
|
|
|
|
|
.ok_or(LemmyErrorType::MissingTotpToken)?;
|
|
|
|
|
|
|
|
|
|
let totp = build_totp_2fa(site_name, username, totp_secret)?;
|
|
|
|
|
|
|
|
|
|
let check_passed = totp.check_current(token)?;
|
|
|
|
|
if !check_passed {
|
|
|
|
|
return Err(LemmyError::from_message("incorrect_totp token"));
|
|
|
|
|
return Err(LemmyErrorType::IncorrectTotpToken.into());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -270,7 +270,7 @@ pub fn build_totp_2fa(site_name: &str, username: &str, secret: &str) -> Result<T
|
|
|
|
|
let sec = Secret::Raw(secret.as_bytes().to_vec());
|
|
|
|
|
let sec_bytes = sec
|
|
|
|
|
.to_bytes()
|
|
|
|
|
.map_err(|_| LemmyError::from_message("Couldnt parse totp secret"))?;
|
|
|
|
|
.map_err(|_| LemmyErrorType::CouldntParseTotpSecret)?;
|
|
|
|
|
|
|
|
|
|
TOTP::new(
|
|
|
|
|
totp_rs::Algorithm::SHA256,
|
|
|
|
@ -281,7 +281,7 @@ pub fn build_totp_2fa(site_name: &str, username: &str, secret: &str) -> Result<T
|
|
|
|
|
Some(site_name.to_string()),
|
|
|
|
|
username.to_string(),
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| LemmyError::from_error_message(e, "Couldnt generate TOTP"))
|
|
|
|
|
.with_lemmy_type(LemmyErrorType::CouldntGenerateTotp)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn check_site_visibility_valid(
|
|
|
|
@ -294,9 +294,7 @@ pub fn check_site_visibility_valid(
|
|
|
|
|
let federation_enabled = new_federation_enabled.unwrap_or(current_federation_enabled);
|
|
|
|
|
|
|
|
|
|
if private_instance && federation_enabled {
|
|
|
|
|
return Err(LemmyError::from_message(
|
|
|
|
|
"cant_enable_private_instance_and_federation_together",
|
|
|
|
|
));
|
|
|
|
|
return Err(LemmyErrorType::CantEnablePrivateInstanceAndFederationTogether.into());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
@ -305,7 +303,7 @@ pub fn check_site_visibility_valid(
|
|
|
|
|
pub fn check_url_scheme(url: &Option<Url>) -> LemmyResult<()> {
|
|
|
|
|
if let Some(url) = url {
|
|
|
|
|
if url.scheme() != "http" && url.scheme() != "https" {
|
|
|
|
|
return Err(LemmyError::from_message("invalid_url_scheme"));
|
|
|
|
|
return Err(LemmyErrorType::InvalidUrlScheme.into());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
@ -314,22 +312,25 @@ pub fn check_url_scheme(url: &Option<Url>) -> LemmyResult<()> {
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::build_totp_2fa;
|
|
|
|
|
use crate::utils::validation::{
|
|
|
|
|
build_and_check_regex,
|
|
|
|
|
check_site_visibility_valid,
|
|
|
|
|
check_url_scheme,
|
|
|
|
|
clean_url_params,
|
|
|
|
|
generate_totp_2fa_secret,
|
|
|
|
|
is_valid_actor_name,
|
|
|
|
|
is_valid_bio_field,
|
|
|
|
|
is_valid_display_name,
|
|
|
|
|
is_valid_matrix_id,
|
|
|
|
|
is_valid_post_title,
|
|
|
|
|
site_description_length_check,
|
|
|
|
|
site_name_length_check,
|
|
|
|
|
BIO_MAX_LENGTH,
|
|
|
|
|
SITE_DESCRIPTION_MAX_LENGTH,
|
|
|
|
|
SITE_NAME_MAX_LENGTH,
|
|
|
|
|
use crate::{
|
|
|
|
|
error::LemmyErrorType,
|
|
|
|
|
utils::validation::{
|
|
|
|
|
build_and_check_regex,
|
|
|
|
|
check_site_visibility_valid,
|
|
|
|
|
check_url_scheme,
|
|
|
|
|
clean_url_params,
|
|
|
|
|
generate_totp_2fa_secret,
|
|
|
|
|
is_valid_actor_name,
|
|
|
|
|
is_valid_bio_field,
|
|
|
|
|
is_valid_display_name,
|
|
|
|
|
is_valid_matrix_id,
|
|
|
|
|
is_valid_post_title,
|
|
|
|
|
site_description_length_check,
|
|
|
|
|
site_name_length_check,
|
|
|
|
|
BIO_MAX_LENGTH,
|
|
|
|
|
SITE_DESCRIPTION_MAX_LENGTH,
|
|
|
|
|
SITE_NAME_MAX_LENGTH,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
use url::Url;
|
|
|
|
|
|
|
|
|
@ -409,9 +410,9 @@ mod tests {
|
|
|
|
|
&(0..SITE_NAME_MAX_LENGTH + 1)
|
|
|
|
|
.map(|_| 'A')
|
|
|
|
|
.collect::<String>(),
|
|
|
|
|
"site_name_length_overflow",
|
|
|
|
|
LemmyErrorType::SiteNameLengthOverflow,
|
|
|
|
|
),
|
|
|
|
|
(&String::new(), "site_name_required"),
|
|
|
|
|
(&String::new(), LemmyErrorType::SiteNameRequired),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
valid_names.iter().for_each(|valid_name| {
|
|
|
|
@ -425,15 +426,15 @@ mod tests {
|
|
|
|
|
|
|
|
|
|
invalid_names
|
|
|
|
|
.iter()
|
|
|
|
|
.for_each(|&(invalid_name, expected_err)| {
|
|
|
|
|
.for_each(|(invalid_name, expected_err)| {
|
|
|
|
|
let result = site_name_length_check(invalid_name);
|
|
|
|
|
|
|
|
|
|
assert!(result.is_err());
|
|
|
|
|
assert!(
|
|
|
|
|
result
|
|
|
|
|
.unwrap_err()
|
|
|
|
|
.message
|
|
|
|
|
.eq(&Some(String::from(expected_err))),
|
|
|
|
|
.error_type
|
|
|
|
|
.eq(&Some(expected_err.clone())),
|
|
|
|
|
"Testing {}, expected error {}",
|
|
|
|
|
invalid_name,
|
|
|
|
|
expected_err
|
|
|
|
@ -452,8 +453,8 @@ mod tests {
|
|
|
|
|
invalid_result.is_err()
|
|
|
|
|
&& invalid_result
|
|
|
|
|
.unwrap_err()
|
|
|
|
|
.message
|
|
|
|
|
.eq(&Some(String::from("bio_length_overflow")))
|
|
|
|
|
.error_type
|
|
|
|
|
.eq(&Some(LemmyErrorType::BioLengthOverflow))
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -476,8 +477,8 @@ mod tests {
|
|
|
|
|
invalid_result.is_err()
|
|
|
|
|
&& invalid_result
|
|
|
|
|
.unwrap_err()
|
|
|
|
|
.message
|
|
|
|
|
.eq(&Some(String::from("site_description_length_overflow")))
|
|
|
|
|
.error_type
|
|
|
|
|
.eq(&Some(LemmyErrorType::SiteDescriptionLengthOverflow))
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -495,22 +496,22 @@ mod tests {
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_too_permissive_slur_regex() {
|
|
|
|
|
let match_everything_regexes = [
|
|
|
|
|
(&Some("["), "invalid_regex"),
|
|
|
|
|
(&Some("(foo|bar|)"), "permissive_regex"),
|
|
|
|
|
(&Some(".*"), "permissive_regex"),
|
|
|
|
|
(&Some("["), LemmyErrorType::InvalidRegex),
|
|
|
|
|
(&Some("(foo|bar|)"), LemmyErrorType::PermissiveRegex),
|
|
|
|
|
(&Some(".*"), LemmyErrorType::PermissiveRegex),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
match_everything_regexes
|
|
|
|
|
.iter()
|
|
|
|
|
.for_each(|&(regex_str, expected_err)| {
|
|
|
|
|
.for_each(|(regex_str, expected_err)| {
|
|
|
|
|
let result = build_and_check_regex(regex_str);
|
|
|
|
|
|
|
|
|
|
assert!(result.is_err());
|
|
|
|
|
assert!(
|
|
|
|
|
result
|
|
|
|
|
.unwrap_err()
|
|
|
|
|
.message
|
|
|
|
|
.eq(&Some(String::from(expected_err))),
|
|
|
|
|
.error_type
|
|
|
|
|
.eq(&Some(expected_err.clone())),
|
|
|
|
|
"Testing regex {:?}, expected error {}",
|
|
|
|
|
regex_str,
|
|
|
|
|
expected_err
|
|
|
|
|