3个版本
0.4.2 | 2024年4月27日 |
---|---|
0.4.1 | 2024年4月26日 |
0.4.0 | 2024年4月26日 |
#25 in 邮件
760KB
17K SLoC
筛选器
筛选器 是一个快速且安全的Rust 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 - 垃圾邮件测试和病毒测试扩展
- RFC 5260 - 日期和索引扩展
- RFC 5293 - 编辑标题扩展
- RFC 5429 - 拒绝和扩展拒绝扩展
- RFC 5435 - 通知扩展
- RFC 5463 - Ihave扩展
- RFC 5490 - 检查邮箱状态和访问邮箱元数据的扩展
- RFC 5703 - MIME部分测试、迭代、提取、替换和封装
- RFC 6009 - 交货状态通知和交货时间扩展
- 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通用公共许可证(AGPL)的条款下许可,许可证版本为3,或(根据您的选择)任何后续版本。有关更多详细信息,请参阅LICENSE。
您可以通过购买商业许可证来免除AGPLv3许可证的要求。有关更多详细信息,请联系[email protected]。
版权
版权(C)2020-2023,Stalwart Labs Ltd。
依赖项
~5–13MB
~121K SLoC