dipper 0.5.3

An out-of-the-box modular dependency injection web application framework.
Documentation
use core::{str, time::Duration};

use ::base64::prelude::*;
use anyhow::Ok;
use header::ContentTransferEncoding;
use lettre::message::*;
use quoted_printable::ParseMode;

pub use crate::fnd::totp::TOTP;

/// TOTP email creater
pub trait TotpEmailMessage {
    fn email_subject(&self) -> String;
    fn email_body(&self, totp_secret_base32: String, seconds: u64) -> anyhow::Result<SinglePart>;
    fn email_message(
        &self,
        from: Mailbox,
        to: Mailbox,
        totp_secret_base32: String,
        seconds: u64,
    ) -> anyhow::Result<Message> {
        let msg = Message::builder()
            .from(from)
            .to(to)
            .subject(self.email_subject())
            .singlepart(self.email_body(totp_secret_base32, seconds)?)?;
        Ok(msg)
    }
}

pub struct DefaultTotpMessageCN {
    app_name: String,
    use_html: bool,
}
impl DefaultTotpMessageCN {
    pub const fn new(app_name: String, use_html: bool) -> Self {
        DefaultTotpMessageCN { app_name, use_html }
    }
}
impl TotpEmailMessage for DefaultTotpMessageCN {
    fn email_subject(&self) -> String {
        format!("验证码: {} 验证您的账户", self.app_name)
    }

    fn email_body(&self, totp_secret_base32: String, seconds: u64) -> anyhow::Result<SinglePart> {
        let code = TOTP::default().with_step(seconds).gen_current(totp_secret_base32)?;
        let duration = humantime::format_duration(Duration::from_secs(seconds)).to_string();
        let app_name = &self.app_name;
        if self.use_html {
            const HTML: &str = include_str!("default_code_msg.html");
            #[allow(clippy::literal_string_with_formatting_args)]
            let body = HTML
                .replace("{code}", &code)
                .replace("{duration}", &duration)
                .replace("{app_name}", app_name);
            Ok(SinglePart::html(body))
        } else {
            let body = format!(
                r###"
        尊敬的用户,
        
        您好!感谢您使用{app_name}。您的验证码是:
        
        {code}
        
        请在 {duration} 内输入该验证码以完成验证。
        
        如果您没有请求此验证码,请忽略此邮件。为了确保您的账户安全,请勿将验证码透露给他人。
        
        如有任何问题或需要进一步帮助,请联系我们的客服支持团队。
        
        此致,
        
        {app_name}团队
        
        注意:本邮件为系统自动发送,请勿直接回复。
        "###
            );
            Ok(SinglePart::plain(body))
        }
    }
}

pub fn display_body_plaintext(single_part: SinglePart) -> anyhow::Result<String> {
    let Some(maybe_encoding) = single_part.headers().get::<ContentTransferEncoding>() else {
        return Ok(String::from_utf8(single_part.formatted())?);
    };
    let content = str::from_utf8(single_part.raw_body())?;
    let decoded_content = match maybe_encoding {
        ContentTransferEncoding::Base64 => {
            BASE64_STANDARD.decode(content.chars().filter(|c| !c.is_whitespace()).collect::<String>())?
        }
        ContentTransferEncoding::QuotedPrintable => quoted_printable::decode(content, ParseMode::Strict)?,
        ContentTransferEncoding::SevenBit => todo!(),
        ContentTransferEncoding::EightBit => todo!(),
        ContentTransferEncoding::Binary => todo!(),
    };
    let decoded_string =
        single_part.headers().to_string() + "\r\n" + str::from_utf8(decoded_content.as_slice())? + "\r\n";
    Ok(decoded_string)
}

#[cfg(test)]
mod tests {
    use super::*;
    fn totp_email_msg(use_html: bool) {
        let totp_msg = DefaultTotpMessageCN::new("Dipper".to_owned(), use_html);
        let subject = totp_msg.email_subject();
        let totp_secret_base32 = TOTP::gen_secret_base32();
        let body = totp_msg.email_body(totp_secret_base32, 30 * 60).unwrap();
        println!("subject={subject}\nbody={}", display_body_plaintext(body).unwrap());
    }

    #[test]
    fn echo_current_plaintext() {
        totp_email_msg(false)
    }

    #[test]
    fn echo_current_html() {
        totp_email_msg(true)
    }
}