fix in smtp and split rs files

This commit is contained in:
Mateusz Gruszczyński
2026-07-27 22:56:56 +02:00
parent cc3b172e56
commit d6c1c52310
30 changed files with 4327 additions and 4387 deletions
+100
View File
@@ -0,0 +1,100 @@
use lettre::message::Mailbox;
use crate::state::{SmtpConfig, SmtpSecurity};
use super::values::ConfigValues;
pub(super) fn load_smtp(
values: &ConfigValues,
) -> Result<Option<SmtpConfig>, Box<dyn std::error::Error>> {
let Some(host) = values.optional("SMTP_HOST") else {
return Ok(None);
};
let port = values.get("SMTP_PORT", "587").parse()?;
let security = match values.optional("SMTP_SECURITY") {
Some(value) => parse_smtp_security(&value)?,
None => smtp_security_for_port(port),
};
Ok(Some(SmtpConfig {
host,
port,
security,
username: values.get("SMTP_USERNAME", ""),
password: values.get("SMTP_PASSWORD", ""),
from: normalize_smtp_from(values.required("SMTP_FROM", "SMTP_HOST is set")?)?,
public_url: values.required("PUBLIC_URL", "SMTP_HOST is set")?,
}))
}
fn smtp_security_for_port(port: u16) -> SmtpSecurity {
match port {
465 => SmtpSecurity::Tls,
587 => SmtpSecurity::StartTls,
_ => SmtpSecurity::None,
}
}
fn parse_smtp_security(value: &str) -> Result<SmtpSecurity, Box<dyn std::error::Error>> {
match value.trim().to_ascii_lowercase().as_str() {
"none" | "plain" => Ok(SmtpSecurity::None),
"starttls" => Ok(SmtpSecurity::StartTls),
"tls" | "ssl" | "smtps" => Ok(SmtpSecurity::Tls),
_ => Err("SMTP_SECURITY must be one of: none, starttls, tls".into()),
}
}
fn normalize_smtp_from(value: String) -> Result<String, Box<dyn std::error::Error>> {
let trimmed = value.trim();
if trimmed.is_empty() {
return Err("SMTP_FROM cannot be empty".into());
}
if trimmed.parse::<Mailbox>().is_ok() {
return Ok(trimmed.to_owned());
}
let unquoted = match (trimmed.as_bytes().first(), trimmed.as_bytes().last()) {
(Some(b'"'), Some(b'"')) | (Some(b'\''), Some(b'\'')) if trimmed.len() >= 2 => {
trimmed[1..trimmed.len() - 1].trim()
}
_ => return Err(format!("SMTP_FROM is not a valid mailbox: {trimmed}").into()),
};
if unquoted.is_empty() {
return Err("SMTP_FROM cannot be empty".into());
}
unquoted
.parse::<Mailbox>()
.map_err(|error| format!("SMTP_FROM is not a valid mailbox: {error}"))?;
Ok(unquoted.to_owned())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn detects_security_from_standard_ports() {
assert_eq!(smtp_security_for_port(25), SmtpSecurity::None);
assert_eq!(smtp_security_for_port(465), SmtpSecurity::Tls);
assert_eq!(smtp_security_for_port(587), SmtpSecurity::StartTls);
assert_eq!(smtp_security_for_port(2525), SmtpSecurity::None);
}
#[test]
fn accepts_supported_security_modes() {
assert_eq!(parse_smtp_security("none").unwrap(), SmtpSecurity::None);
assert_eq!(parse_smtp_security("starttls").unwrap(), SmtpSecurity::StartTls);
assert_eq!(parse_smtp_security("tls").unwrap(), SmtpSecurity::Tls);
assert!(parse_smtp_security("auto").is_err());
}
#[test]
fn normalizes_smtp_from() {
assert_eq!(
normalize_smtp_from(" \"RustPad <rustpad@notes.example>\" ".to_owned()).unwrap(),
"RustPad <rustpad@notes.example>"
);
assert!(normalize_smtp_from("RustPad".to_owned()).is_err());
}
}