vsmtp-rule-engine 2.0.1-rc.4

Next-gen MTA. Secured, Faster and Greener
Documentation
/*
 * vSMTP mail transfer agent
 * Copyright (C) 2022 viridIT SAS
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see https://www.gnu.org/licenses/.
 *
*/

use crate::api::{mail_context::message_id, Context, EngineResult, Message, Server};
use rhai::plugin::{
    mem, Dynamic, EvalAltResult, FnAccess, FnNamespace, ImmutableString, Module, NativeCallContext,
    PluginFunction, RhaiResult, TypeId,
};

pub use fs::*;

/// APIs to interact with the file system.
#[rhai::plugin::export_module]
mod fs {
    use crate::get_global;

    /// Export the current raw message to a file as an `eml` file.
    /// The message id of the email is used to name the file.
    ///
    /// # Args
    ///
    /// * `dir` - the directory where to store the email. Relative to the
    /// application path.
    ///
    /// # Effective smtp stage
    ///
    /// `preq` and onwards.
    ///
    /// # Examples
    ///
    /// ```
    /// # let dir = tempfile::tempdir().expect("fs api: failed to create tmpdir");
    /// # let mut config = vsmtp_test::config::local_test();
    /// # config.app.dirpath = dir.path().into();
    ///
    /// # vsmtp_test::vsl::run_with_msg_and_config(
    /// # |builder| Ok(builder.add_root_filter_rules(r#"
    /// #{
    ///     preq: [
    ///        action "write to file" || fs::write("archives"),
    ///     ]
    /// }
    /// # "#)?.build()),
    /// # None,
    /// # config,
    /// # );
    /// # eprintln!("{:?}", dir.path());
    /// # assert!(std::path::PathBuf::from_iter([
    /// #     dir.path(),
    /// #     &std::path::Path::new("archives")
    /// # ]).exists());
    /// ```
    #[allow(clippy::needless_pass_by_value)]
    #[rhai_fn(name = "write", return_raw)]
    pub fn write_str(ncc: NativeCallContext, dir: &str) -> EngineResult<()> {
        super::write(
            &get_global!(ncc, srv)?,
            &get_global!(ncc, ctx)?,
            &get_global!(ncc, msg)?,
            dir,
        )
    }

    /// Write the content of the current email with it's metadata in a json file.
    /// The message id of the email is used to name the file.
    ///
    /// # Args
    ///
    /// * `dir` - the directory where to store the email. Relative to the
    /// application path.
    ///
    /// # Effective smtp stage
    ///
    /// `preq` and onwards.
    ///
    /// # Examples
    ///
    /// ```
    /// # let dir = tempfile::tempdir().expect("fs api: failed to create tmpdir");
    /// # let mut config = vsmtp_test::config::local_test();
    /// # config.app.dirpath = dir.path().into();
    ///
    /// # vsmtp_test::vsl::run_with_msg_and_config(
    /// # |builder| Ok(builder.add_root_filter_rules(r#"
    /// #{
    ///     preq: [
    ///        action "write to file" || fs::dump("metadata"),
    ///     ]
    /// }
    /// # "#)?.build()),
    /// # None,
    /// # config,
    /// # );
    /// # eprintln!("{:?}", dir.path());
    /// # assert!(std::path::PathBuf::from_iter([
    /// #     dir.path(),
    /// #     &std::path::Path::new("metadata")
    /// # ]).exists());
    /// ```
    #[allow(clippy::needless_pass_by_value)]
    #[rhai_fn(name = "dump", return_raw)]
    pub fn dump_str(ncc: NativeCallContext, dir: &str) -> EngineResult<()> {
        super::dump(&get_global!(ncc, srv)?, &get_global!(ncc, ctx)?, dir)
    }
}

// TODO: handle canonicalization
fn write(srv: &Server, ctx: &Context, message: &Message, dir: &str) -> EngineResult<()> {
    let mut dir = srv.config.app.dirpath.join(dir);
    std::fs::create_dir_all(&dir).map_err::<Box<EvalAltResult>, _>(|err| {
        format!("cannot create folder '{}': {err}", dir.display()).into()
    })?;

    dir.push(format!("{}.eml", message_id(ctx)?));

    let file = std::fs::OpenOptions::new()
        .create(true)
        .write(true)
        .open(&dir)
        .map_err::<Box<EvalAltResult>, _>(|err| {
            format!("failed to write email at {}: {err}", dir.display()).into()
        })?;
    let mut writer = std::io::LineWriter::new(file);

    let body = &message
        .read()
        .map_err::<Box<EvalAltResult>, _>(|e| e.to_string().into())?;

    std::io::Write::write_all(&mut writer, body.inner().to_string().as_bytes())
        .map_err(|err| format!("failed to write email at {dir:?}: {err}").into())
}

fn dump(srv: &Server, ctx: &Context, dir: &str) -> EngineResult<()> {
    let mut dir = srv.config.app.dirpath.join(dir);
    std::fs::create_dir_all(&dir).map_err::<Box<EvalAltResult>, _>(|err| {
        format!("cannot create folder '{}': {err}", dir.display()).into()
    })?;

    dir.push(format!("{}.json", message_id(ctx)?));

    let mut file = std::fs::OpenOptions::new()
        .create(true)
        .write(true)
        .open(&dir)
        .map_err::<Box<EvalAltResult>, _>(|err| {
            format!("failed to dump email at {}: {err}", dir.display()).into()
        })?;

    std::io::Write::write_all(
        &mut file,
        serde_json::to_string_pretty(&*vsl_guard_ok!(ctx.read()))
            .map_err::<Box<EvalAltResult>, _>(|err| {
                format!("failed to dump email at {dir:?}: {err}").into()
            })?
            .as_bytes(),
    )
    .map_err(|err| format!("failed to dump email at {dir:?}: {err}").into())
}