6 个版本 (重大更新)

0.5.0 2024 年 3 月 28 日
0.4.0 2023 年 12 月 28 日
0.3.1 2023 年 6 月 2 日
0.2.1 2023 年 1 月 29 日
0.1.0 2022 年 10 月 21 日

#43邮件 类别中

Download history 100/week @ 2024-04-28 74/week @ 2024-05-05 101/week @ 2024-05-12 60/week @ 2024-05-19 51/week @ 2024-05-26 68/week @ 2024-06-02 51/week @ 2024-06-09 84/week @ 2024-06-16 56/week @ 2024-06-23 99/week @ 2024-06-30 64/week @ 2024-07-07 38/week @ 2024-07-14 25/week @ 2024-07-21 129/week @ 2024-07-28 57/week @ 2024-08-04 67/week @ 2024-08-11

每月 282 次下载

AGPL-3.0-only

760KB
17K SLoC

sieve

crates.io build docs.rs License: AGPL v3

sieve 是一个快速且安全的 Sieve 过滤器解释器,支持所有 已注册的 Sieve 扩展

使用示例

use sieve::{runtime::RuntimeError, Action, Compiler, Event, Input, Runtime};

// Sieve script to execute
let text_script = br#"
require ["fileinto", "body", "imap4flags"];

if body :contains "tps" {
    setflag "$tps_reports";
}

if header :matches "List-ID" "*<*@*" {
    fileinto "INBOX.lists.${2}"; stop;
}
"#;

// Message to filter
let raw_message = r#"From: Sales Mailing List <[email protected]>
To: John Doe <[email protected]>
List-ID: <[email protected]>
Subject: TPS Reports

We're putting new coversheets on all the TPS reports before they go out now.
So if you could go ahead and try to remember to do that from now on, that'd be great. All right! 
"#;

// Compile
let compiler = Compiler::new();
let script = compiler.compile(text_script).unwrap();

// Build runtime
let runtime = Runtime::new();

// Create filter instance
let mut instance = runtime.filter(raw_message.as_bytes());
let mut input = Input::script("my-script", script);
let mut messages: Vec<String> = Vec::new();

// Start event loop
while let Some(result) = instance.run(input) {
    match result {
        Ok(event) => match event {
            Event::IncludeScript { name, optional } => {
                // NOTE: Just for demonstration purposes, script name needs to be validated first.
                if let Ok(bytes) = std::fs::read(name.as_str()) {
                    let script = compiler.compile(&bytes).unwrap();
                    input = Input::script(name, script);
                } else if optional {
                    input = Input::False;
                } else {
                    panic!("Script {} not found.", name);
                }
            }
            Event::MailboxExists { .. } => {
                // Set to true if the mailbox exists
                input = false.into();
            }
            Event::ListContains { .. } => {
                // Set to true if the list(s) contains an entry
                input = false.into();
            }
            Event::DuplicateId { .. } => {
                // Set to true if the ID is duplicate
                input = false.into();
            }
            Event::Execute { command, arguments } => {
                println!(
                    "Script executed command {:?} with parameters {:?}",
                    command, arguments
                );
                // Set to true if the script succeeded
                input = false.into();
            }

            Event::Keep { flags, message_id } => {
                println!(
                    "Keep message '{}' with flags {:?}.",
                    if message_id > 0 {
                        messages[message_id - 1].as_str()
                    } else {
                        raw_message
                    },
                    flags
                );
                input = true.into();
            }
            Event::Discard => {
                println!("Discard message.");
                input = true.into();
            }
            Event::Reject { reason, .. } => {
                println!("Reject message with reason {:?}.", reason);
                input = true.into();
            }
            Event::FileInto {
                folder,
                flags,
                message_id,
                ..
            } => {
                println!(
                    "File message '{}' in folder {:?} with flags {:?}.",
                    if message_id > 0 {
                        messages[message_id - 1].as_str()
                    } else {
                        raw_message
                    },
                    folder,
                    flags
                );
                input = true.into();
            }
            Event::SendMessage {
                recipient,
                message_id,
                ..
            } => {
                println!(
                    "Send message '{}' to {:?}.",
                    if message_id > 0 {
                        messages[message_id - 1].as_str()
                    } else {
                        raw_message
                    },
                    recipient
                );
                input = true.into();
            }
            Event::Notify {
                message, method, ..
            } => {
                println!("Notify URI {:?} with message {:?}", method, message);
                input = true.into();
            }
            Event::CreatedMessage { message, .. } => {
                messages.push(String::from_utf8(message).unwrap());
                input = true.into();
            }

            #[cfg(test)]
            _ => unreachable!(),
        },
        Err(error) => {
            match error {
                RuntimeError::TooManyIncludes => {
                    eprintln!("Too many included scripts.");
                }
                RuntimeError::InvalidInstruction(instruction) => {
                    eprintln!(
                        "Invalid instruction {:?} found at {}:{}.",
                        instruction.name(),
                        instruction.line_num(),
                        instruction.line_pos()
                    );
                }
                RuntimeError::ScriptErrorMessage(message) => {
                    eprintln!("Script called the 'error' function with {:?}", message);
                }
                RuntimeError::CapabilityNotAllowed(capability) => {
                    eprintln!(
                        "Capability {:?} has been disabled by the administrator.",
                        capability
                    );
                }
                RuntimeError::CapabilityNotSupported(capability) => {
                    eprintln!("Capability {:?} not supported.", capability);
                }
                RuntimeError::CPULimitReached => {
                    eprintln!("Script exceeded the configured CPU limit.");
                }
            }
            input = true.into();
        }
    }
}

测试 & 模糊测试

运行测试套件

 $ cargo test --all-features

使用 cargo-fuzz 模糊测试库

 $ cargo +nightly fuzz run sieve

遵循的 RFC

许可证

根据自由软件基金会发布的 GNU Affero 通用公共许可证 的条款许可,许可证版本为 3 或更新的版本。有关更多详细信息,请参阅 LICENSE

您可以通过购买商业许可证来免除AGPLv3许可证的要求。有关更多信息,请联系 [email protected]

版权(C)2020-2023,Stalwart Labs Ltd.

依赖项

~4–13MB
~120K SLoC