use regex::Regex;
use rocket::{figment::providers::Env, response::status::BadRequest, serde::Deserialize, Config};
use std::sync::LazyLock;
type SpamCheckResult = Result<(), BadRequest<String>>;
#[derive(Deserialize)]
#[serde(crate = "rocket::serde")]
struct SpamConfig {
allowlist: Option<Vec<String>>,
blocklist: Option<Vec<String>>,
blocking: Option<bool>,
}
#[derive(Deserialize)]
#[serde(crate = "rocket::serde")]
struct SFSEmailResponse {
value: String,
appears: usize,
}
#[derive(Deserialize)]
#[serde(crate = "rocket::serde")]
struct StopForumSpamJsonResponse {
success: u8,
email: SFSEmailResponse,
error: Option<String>,
}
static URL_RE: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"([\w+]+\:\/\/)?([\w\d-]+\.)*[\w-]+[\.\:]\w+([\/\?\=\&\#\.]?[\w-]+)*\/?")
.expect("Invalid regular expression provided.")
});
static SPAM_CONFIG: LazyLock<rocket::figment::Figment> = LazyLock::new(|| {
Config::figment()
.select("spam")
.merge(Env::prefixed("FORMULATE_SPAM_"))
});
pub fn check_stop_forum_spam(form_email: &str, error_msg: &str) -> SpamCheckResult {
let config: SpamConfig = match SPAM_CONFIG.extract::<SpamConfig>() {
Ok(config) => config,
Err(err) => return Err(BadRequest(err.to_string())),
};
if config.blocking.is_none() {
return Ok(());
}
if config.blocking.unwrap() {
let sfs_api_url = format!("http://api.stopforumspam.org/api?email={form_email}&json");
let result = ureq::post(&sfs_api_url)
.set("Content-Type", "application/x-www-form-urlencoded")
.call();
if result.is_err() {
return Ok(());
}
let result = result
.expect("Error getting API response.")
.into_json::<StopForumSpamJsonResponse>();
if result.is_err() {
return Ok(());
}
let result = result.unwrap();
if result.error.is_none()
&& result.success == 1
&& result.email.appears > 0
&& result.email.value.eq(form_email)
{
Err(BadRequest(error_msg.to_string()))
} else {
Ok(())
}
} else {
Ok(())
}
}
pub fn check_for_spam_blocklist(form_message: &str, error_msg: &str) -> SpamCheckResult {
match SPAM_CONFIG.extract::<SpamConfig>() {
Ok(config) => {
if config.blocklist.is_none() {
Ok(())
} else {
let looks_like_spam = config
.blocklist
.unwrap()
.iter()
.any(|url| form_message.contains(url));
if looks_like_spam {
Err(BadRequest(error_msg.to_string()))
} else {
Ok(())
}
}
}
Err(config_err) => Err(BadRequest(config_err.to_string())),
}
}
pub fn check_for_spam_allowlist(form_message: &str, error_msg: &str) -> SpamCheckResult {
if let Ok(config) = SPAM_CONFIG.extract::<SpamConfig>() {
if let Some(allowlist) = &config.allowlist {
let has_only_allowed_urls = URL_RE
.find_iter(form_message)
.all(|url| allowlist.iter().any(|domain| url.as_str().contains(domain)));
if has_only_allowed_urls {
return Ok(());
}
return Err(BadRequest(error_msg.to_string()));
}
}
Ok(())
}