Skip to main content

mailrs_sieve_core/
ast.rs

1//! RFC 5228 §3-4 AST.
2
3/// One Sieve command — identifier + args + optional nested block.
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub struct Command {
6    /// Command identifier (e.g. `"if"`, `"keep"`, `"fileinto"`).
7    pub name: String,
8    /// Positional + tagged + test arguments, in source order.
9    pub args: Vec<Argument>,
10    /// Block body (`{ … }`) — empty for non-block commands.
11    pub block: Vec<Command>,
12}
13
14/// Argument to a command or a nested test.
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub enum Argument {
17    /// `:identifier` tag.
18    Tag(String),
19    /// Numeric literal (with K/M/G already applied).
20    Number(u64),
21    /// String literal — quoted or multi-line.
22    String(String),
23    /// Bracketed string list `[ "a", "b" ]`.
24    StringList(Vec<String>),
25    /// Nested test (`header :is "Subject" "spam"`,
26    /// `allof(...)`, `anyof(...)`, `not test`, …).
27    Test(Test),
28    /// Nested test list (`allof(t1, t2)`, `anyof(t1, t2)`).
29    TestList(Vec<Test>),
30}
31
32/// One test expression. The match-types + address-parts get
33/// extracted into the dedicated fields so the evaluator doesn't
34/// scan the arg list every time.
35#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct Test {
37    /// Test identifier (`"header"`, `"address"`, `"size"`, …).
38    pub name: String,
39    /// Tag arguments (`:is`, `:domain`, `:localpart`, …) in
40    /// source order.
41    pub tags: Vec<String>,
42    /// Positional args (numeric / string / string-list).
43    pub args: Vec<Argument>,
44    /// Nested tests (for `allof`, `anyof`, `not`).
45    pub children: Vec<Test>,
46}
47
48/// Standardised match-type for the evaluator.
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum MatchType {
51    /// `:is` — exact case-insensitive match.
52    Is,
53    /// `:contains` — substring case-insensitive match.
54    Contains,
55    /// `:matches` — wildcard match (`*` / `?`).
56    Matches,
57}
58
59impl MatchType {
60    /// Pick the first match-type tag from a `tags` slice, defaulting to `:is`.
61    pub fn from_tags(tags: &[String]) -> Self {
62        for t in tags {
63            match t.as_str() {
64                "is" => return Self::Is,
65                "contains" => return Self::Contains,
66                "matches" => return Self::Matches,
67                _ => {}
68            }
69        }
70        Self::Is
71    }
72}
73
74/// One action the evaluator emits.
75#[derive(Debug, Clone, PartialEq, Eq)]
76pub enum Action {
77    /// Keep the message in the default mailbox (the implicit
78    /// default when no action fires). The `flags` vector carries
79    /// any IMAP flags set by RFC 5232 `imap4flags` extension
80    /// (empty when the extension is not used).
81    Keep {
82        /// IMAP flags to attach to the kept copy (RFC 5232).
83        flags: Vec<String>,
84    },
85    /// Discard the message — drop without notice.
86    Discard,
87    /// File into a named mailbox (RFC 5228 `fileinto` ext) with
88    /// optional IMAP flags (RFC 5232 `imap4flags` ext — empty
89    /// when the extension is not used).
90    FileInto {
91        /// Destination mailbox name.
92        mailbox: String,
93        /// IMAP flags to attach to the filed copy (RFC 5232).
94        flags: Vec<String>,
95    },
96    /// Forward / redirect to another address.
97    Redirect(String),
98    /// Reject with the given reason string.
99    Reject(String),
100    /// RFC 5230 `vacation` — generate an automatic reply. The
101    /// stateful parts (dedup window, recipient detection, reply
102    /// message build) are the caller's job; this engine only
103    /// surfaces the parsed action.
104    Vacation(VacationAction),
105}
106
107/// RFC 5230 `vacation` action — everything the caller needs to
108/// generate the auto-reply.
109///
110/// Fields with `Option` default to "use the server-defined
111/// value" (per RFC 5230 §4.1–4.5). `addresses` and `mime` default
112/// to `Vec::new()` / `false`.
113#[derive(Debug, Clone, PartialEq, Eq)]
114pub struct VacationAction {
115    /// The auto-reply body (positional argument — required).
116    pub reason: String,
117    /// `:days <n>` or `:seconds <n>` — the dedup window.
118    /// `None` = server default (RFC 5230 §4.1 hints at 7 days).
119    pub period: Option<VacationPeriod>,
120    /// `:subject <s>` — overrides the default `Auto:`-prefixed
121    /// Subject (RFC 5230 §4.2).
122    pub subject: Option<String>,
123    /// `:from <addr>` — overrides the default `From:` of the auto-
124    /// reply (RFC 5230 §4.3); must be a path the mailbox owner can
125    /// legitimately use.
126    pub from: Option<String>,
127    /// `:addresses [<a>, <b>, …]` — alternate recipient addresses
128    /// the user may be addressed at; the auto-reply is only sent
129    /// if one of these matches the incoming envelope-to (RFC 5230
130    /// §4.4).
131    pub addresses: Vec<String>,
132    /// `:mime` — when true the `reason` is a full RFC 2822 MIME
133    /// entity; otherwise it's plain text (RFC 5230 §4.5).
134    pub mime: bool,
135    /// `:handle <h>` — handle for the dedup index (RFC 5230 §4.6);
136    /// distinct reasons sharing one handle are deduplicated as one.
137    pub handle: Option<String>,
138}
139
140/// The `:days` / `:seconds` window on a `vacation` action.
141#[derive(Debug, Clone, Copy, PartialEq, Eq)]
142pub enum VacationPeriod {
143    /// `:days <n>` — RFC 5230 §4.1.
144    Days(u64),
145    /// `:seconds <n>` — RFC 6131 extension (sub-day windows).
146    Seconds(u64),
147}
148
149/// RFC 5228 §5.4 envelope context — caller-supplied state the
150/// `envelope` test inspects.
151///
152/// `from` is the SMTP MAIL FROM (RFC 5321 reverse-path).
153/// `to` is the list of RCPT TO (one envelope test may match any).
154/// `auth` is the RFC 5228 §5.4 SASL-authenticated identity.
155#[derive(Debug, Clone, Default, PartialEq, Eq)]
156pub struct Envelope {
157    /// SMTP MAIL FROM (RFC 5321 reverse-path).
158    pub from: Option<String>,
159    /// SMTP RCPT TO list (RFC 5321 forward-path); the envelope
160    /// test on `"to"` matches if ANY recipient matches.
161    pub to: Vec<String>,
162    /// RFC 5228 §5.4 SASL `:auth` identity.
163    pub auth: Option<String>,
164}