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 在 邮件 类别中
每月 282 次下载
760KB
17K SLoC
sieve
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
- RFC 5228 - Sieve:电子邮件过滤语言
- RFC 3894 - 无副作用地复制
- RFC 5173 - 正文扩展
- RFC 5183 - 环境扩展
- RFC 5229 - 变量扩展
- RFC 5230 - 休假扩展
- RFC 5231 - 关系扩展
- RFC 5232 - Imap4flags 扩展
- RFC 5233 - 子地址扩展
- RFC 5235 - Spamtest 和 Virustest 扩展
- RFC 5260 - 日期和索引扩展
- RFC 5293 - Editheader 扩展
- RFC 5429 - Reject 和 Extended Reject 扩展
- RFC 5435 - 通知扩展
- RFC 5463 - Ihave 扩展
- RFC 5490 - 检查邮箱状态和访问邮箱元数据的扩展
- RFC 5703 - MIME 部分测试、迭代、提取、替换和封装
- RFC 6009 - 递送状态通知和 Deliver-By 扩展
- RFC 6131 - Sieve 休假扩展:“秒”参数
- RFC 6134 - 外部存储列表
- RFC 6558 - 在递送之前转换消息
- RFC 6609 - Include 扩展
- RFC 7352 - 检测重复递送
- RFC 8579 - 递送到特殊用途邮箱
- RFC 8580 - 文件副本 (FCC)
- RFC 9042 - 通过 MAILBOXID 递送
- REGEX-01 - 正则表达式扩展 (draft-ietf-sieve-regex-01)
许可证
根据自由软件基金会发布的 GNU Affero 通用公共许可证 的条款许可,许可证版本为 3 或更新的版本。有关更多详细信息,请参阅 LICENSE。
您可以通过购买商业许可证来免除AGPLv3许可证的要求。有关更多信息,请联系 [email protected]。
版权
版权(C)2020-2023,Stalwart Labs Ltd.
依赖项
~4–13MB
~120K SLoC