sieve/compiler/grammar/
mod.rs

1/*
2 * Copyright (c) 2020-2023, Stalwart Labs Ltd.
3 *
4 * This file is part of the Stalwart Sieve Interpreter.
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as
8 * published by the Free Software Foundation, either version 3 of
9 * the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Affero General Public License for more details.
15 * in the LICENSE file at the top-level directory of this distribution.
16 * You should have received a copy of the GNU Affero General Public License
17 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 *
19 * You can be released from the requirements of the AGPLv3 license by
20 * purchasing a commercial license. Please contact licensing@stalw.art
21 * for more details.
22*/
23
24use std::fmt::Display;
25
26use phf::phf_map;
27use serde::{Deserialize, Serialize};
28
29use self::{expr::Expression, instruction::CompilerState};
30
31use super::{
32    lexer::{tokenizer::TokenInfo, word::Word, Token},
33    CompileError, ErrorType, Regex, Value,
34};
35
36pub mod actions;
37pub mod expr;
38pub mod instruction;
39pub mod test;
40pub mod tests;
41
42#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
43pub enum Capability {
44    Envelope,
45    EnvelopeDsn,
46    EnvelopeDeliverBy,
47    FileInto,
48    EncodedCharacter,
49    Comparator(Comparator),
50    Other(String),
51    Body,
52    Convert,
53    Copy,
54    Relational,
55    Date,
56    Index,
57    Duplicate,
58    Variables,
59    EditHeader,
60    ForEveryPart,
61    Mime,
62    Replace,
63    Enclose,
64    ExtractText,
65    Enotify,
66    RedirectDsn,
67    RedirectDeliverBy,
68    Environment,
69    Reject,
70    Ereject,
71    ExtLists,
72    SubAddress,
73    Vacation,
74    VacationSeconds,
75    Fcc,
76    Mailbox,
77    MailboxId,
78    MboxMetadata,
79    ServerMetadata,
80    SpecialUse,
81    Imap4Flags,
82    Ihave,
83    ImapSieve,
84    Include,
85    Regex,
86    SpamTest,
87    SpamTestPlus,
88    VirusTest,
89
90    // Extensions
91    Expressions,
92    While,
93}
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
96pub enum AddressPart {
97    LocalPart,
98    Domain,
99    All,
100    User,
101    Detail,
102    Name,
103}
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
106pub enum MatchType {
107    Is,
108    Contains,
109    Matches(u64),
110    Regex(u64),
111    Value(RelationalMatch),
112    Count(RelationalMatch),
113    List,
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
117pub(crate) enum RelationalMatch {
118    Gt,
119    Ge,
120    Lt,
121    Le,
122    Eq,
123    Ne,
124}
125
126#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
127pub enum Comparator {
128    Elbonia,
129    Octet,
130    AsciiCaseMap,
131    AsciiNumeric,
132    Other(String),
133}
134
135#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
136pub struct Clear {
137    pub local_vars_idx: u32,
138    pub local_vars_num: u32,
139    pub match_vars: u64,
140}
141
142#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
143pub struct Invalid {
144    pub(crate) name: String,
145    pub(crate) line_num: usize,
146    pub(crate) line_pos: usize,
147}
148
149#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
150pub(crate) struct While {
151    pub expr: Vec<Expression>,
152    pub jz_pos: usize,
153}
154
155impl<'x> CompilerState<'x> {
156    #[inline(always)]
157    pub fn expect_instruction_end(&mut self) -> Result<(), CompileError> {
158        self.tokens.expect_token(Token::Semicolon)
159    }
160
161    pub fn ignore_instruction(&mut self) -> Result<(), CompileError> {
162        // Skip entire instruction
163        let mut curly_count = 0;
164        loop {
165            let token_info = self.tokens.unwrap_next()?;
166            match token_info.token {
167                Token::Semicolon if curly_count == 0 => {
168                    break;
169                }
170                Token::CurlyOpen => {
171                    curly_count += 1;
172                }
173                Token::CurlyClose => match curly_count {
174                    0 => {
175                        return Err(token_info.expected("instruction"));
176                    }
177                    1 => {
178                        break;
179                    }
180                    _ => curly_count -= 1,
181                },
182                _ => (),
183            }
184        }
185
186        Ok(())
187    }
188
189    pub fn ignore_test(&mut self) -> Result<(), CompileError> {
190        let mut d_count = 0;
191        while let Some(token_info) = self.tokens.peek() {
192            match token_info?.token {
193                Token::ParenthesisOpen => {
194                    d_count += 1;
195                }
196                Token::ParenthesisClose => {
197                    if d_count == 0 {
198                        break;
199                    } else {
200                        d_count -= 1;
201                    }
202                }
203                Token::Comma => {
204                    if d_count == 0 {
205                        break;
206                    }
207                }
208                Token::CurlyOpen => {
209                    break;
210                }
211                _ => (),
212            }
213            self.tokens.next();
214        }
215
216        Ok(())
217    }
218
219    pub fn parse_match_type(&mut self, word: Word) -> Result<MatchType, CompileError> {
220        match word {
221            Word::Is => Ok(MatchType::Is),
222            Word::Contains => Ok(MatchType::Contains),
223            Word::Matches => {
224                self.block.match_test_pos.push(self.instructions.len());
225                Ok(MatchType::Matches(0))
226            }
227            Word::Regex => {
228                self.block.match_test_pos.push(self.instructions.len());
229                Ok(MatchType::Regex(0))
230            }
231            Word::List => Ok(MatchType::List),
232            _ => {
233                let token_info = self.tokens.unwrap_next()?;
234                if let Token::StringConstant(text) = &token_info.token {
235                    if let Some(relational) = RELATIONAL.get(text.to_string().as_ref()) {
236                        return Ok(if word == Word::Value {
237                            MatchType::Value(*relational)
238                        } else {
239                            MatchType::Count(*relational)
240                        });
241                    }
242                }
243                Err(token_info.expected("relational match"))
244            }
245        }
246    }
247
248    pub(crate) fn parse_comparator(&mut self) -> Result<Comparator, CompileError> {
249        let comparator = self.tokens.expect_static_string()?;
250        Ok(if let Some(comparator) = COMPARATOR.get(&comparator) {
251            comparator.clone()
252        } else {
253            Comparator::Other(comparator)
254        })
255    }
256
257    pub(crate) fn parse_static_strings(&mut self) -> Result<Vec<String>, CompileError> {
258        let token_info = self.tokens.unwrap_next()?;
259        match token_info.token {
260            Token::BracketOpen => {
261                let mut strings = Vec::new();
262                loop {
263                    let token_info = self.tokens.unwrap_next()?;
264                    match token_info.token {
265                        Token::StringConstant(string) => {
266                            strings.push(string.into_string());
267                        }
268                        Token::Comma => (),
269                        Token::BracketClose if !strings.is_empty() => break,
270                        _ => return Err(token_info.expected("constant string")),
271                    }
272                }
273                Ok(strings)
274            }
275            Token::StringConstant(string) => Ok(vec![string.into_string()]),
276            _ => Err(token_info.expected("'[' or constant string")),
277        }
278    }
279
280    pub fn parse_string(&mut self) -> Result<Value, CompileError> {
281        let next_token = self.tokens.unwrap_next()?;
282        match next_token.token {
283            Token::StringConstant(s) => Ok(Value::from(s)),
284            Token::StringVariable(s) => {
285                self.tokenize_string(&s, true)
286                    .map_err(|error_type| CompileError {
287                        line_num: next_token.line_num,
288                        line_pos: next_token.line_pos,
289                        error_type,
290                    })
291            }
292            Token::BracketOpen => {
293                let mut items = self.parse_string_list(false)?;
294                match items.pop() {
295                    Some(s) if items.is_empty() => Ok(s),
296                    _ => Err(next_token.expected("string")),
297                }
298            }
299            _ => Err(next_token.expected("string")),
300        }
301    }
302
303    pub(crate) fn parse_strings(&mut self, allow_empty: bool) -> Result<Vec<Value>, CompileError> {
304        let token_info = self.tokens.unwrap_next()?;
305        match token_info.token {
306            Token::BracketOpen => self.parse_string_list(allow_empty),
307            Token::StringConstant(s) => Ok(vec![Value::from(s)]),
308            Token::StringVariable(s) => {
309                self.tokenize_string(&s, true)
310                    .map(|s| vec![s])
311                    .map_err(|error_type| CompileError {
312                        line_num: token_info.line_num,
313                        line_pos: token_info.line_pos,
314                        error_type,
315                    })
316            }
317            _ => Err(token_info.expected("'[' or string")),
318        }
319    }
320
321    pub(crate) fn parse_string_token(
322        &mut self,
323        token_info: TokenInfo,
324    ) -> Result<Value, CompileError> {
325        match token_info.token {
326            Token::StringConstant(s) => Ok(Value::from(s)),
327            Token::StringVariable(s) => {
328                self.tokenize_string(&s, true)
329                    .map_err(|error_type| CompileError {
330                        line_num: token_info.line_num,
331                        line_pos: token_info.line_pos,
332                        error_type,
333                    })
334            }
335            _ => Err(token_info.expected("string")),
336        }
337    }
338
339    pub(crate) fn parse_strings_token(
340        &mut self,
341        token_info: TokenInfo,
342    ) -> Result<Vec<Value>, CompileError> {
343        match token_info.token {
344            Token::StringConstant(s) => Ok(vec![Value::from(s)]),
345            Token::StringVariable(s) => {
346                self.tokenize_string(&s, true)
347                    .map(|s| vec![s])
348                    .map_err(|error_type| CompileError {
349                        line_num: token_info.line_num,
350                        line_pos: token_info.line_pos,
351                        error_type,
352                    })
353            }
354            Token::BracketOpen => self.parse_string_list(false),
355            _ => Err(token_info.expected("string")),
356        }
357    }
358
359    pub(crate) fn parse_string_list(
360        &mut self,
361        allow_empty: bool,
362    ) -> Result<Vec<Value>, CompileError> {
363        let mut strings = Vec::new();
364        loop {
365            let token_info = self.tokens.unwrap_next()?;
366            match token_info.token {
367                Token::StringConstant(s) => {
368                    strings.push(Value::from(s));
369                }
370                Token::StringVariable(s) => {
371                    strings.push(self.tokenize_string(&s, true).map_err(|error_type| {
372                        CompileError {
373                            line_num: token_info.line_num,
374                            line_pos: token_info.line_pos,
375                            error_type,
376                        }
377                    })?);
378                }
379                Token::Comma => (),
380                Token::BracketClose if !strings.is_empty() || allow_empty => break,
381                _ => return Err(token_info.expected("string or string list")),
382            }
383        }
384        Ok(strings)
385    }
386
387    #[inline(always)]
388    pub(crate) fn has_capability(&self, capability: &Capability) -> bool {
389        [&self.block]
390            .into_iter()
391            .chain(self.block_stack.iter())
392            .any(|b| b.capabilities.contains(capability))
393            || (capability != &Capability::Ihave && self.compiler.no_capability_check)
394    }
395
396    #[inline(always)]
397    pub(crate) fn reset_param_check(&mut self) {
398        self.param_check.fill(false);
399    }
400
401    #[inline(always)]
402    pub(crate) fn validate_argument(
403        &mut self,
404        arg_num: usize,
405        capability: Option<Capability>,
406        line_num: usize,
407        line_pos: usize,
408    ) -> Result<(), CompileError> {
409        if arg_num > 0 {
410            if let Some(param) = self.param_check.get_mut(arg_num - 1) {
411                if !*param {
412                    *param = true;
413                } else {
414                    return Err(CompileError {
415                        line_num,
416                        line_pos,
417                        error_type: ErrorType::DuplicatedParameter,
418                    });
419                }
420            } else {
421                #[cfg(test)]
422                panic!("Argument out of range {arg_num}");
423            }
424        }
425        if let Some(capability) = capability {
426            if !self.has_capability(&capability) {
427                return Err(CompileError {
428                    line_num,
429                    line_pos,
430                    error_type: ErrorType::UndeclaredCapability(capability),
431                });
432            }
433        }
434
435        Ok(())
436    }
437
438    pub(crate) fn validate_match(
439        &mut self,
440        match_type: &MatchType,
441        key_list: &mut [Value],
442    ) -> Result<(), CompileError> {
443        if matches!(match_type, MatchType::Regex(_)) {
444            for key in key_list {
445                if let Value::Text(expr) = key {
446                    match fancy_regex::Regex::new(expr) {
447                        Ok(regex) => {
448                            *key = Value::Regex(Regex {
449                                regex,
450                                expr: expr.to_string(),
451                            });
452                        }
453                        Err(err) => {
454                            return Err(self
455                                .tokens
456                                .unwrap_next()?
457                                .custom(ErrorType::InvalidRegex(format!("{expr}: {err}"))));
458                        }
459                    }
460                }
461            }
462        }
463        Ok(())
464    }
465}
466
467impl Capability {
468    pub fn parse(capability: &str) -> Capability {
469        if let Some(capability) = CAPABILITIES.get(capability) {
470            capability.clone()
471        } else if let Some(comparator) = capability.strip_prefix("comparator-") {
472            Capability::Comparator(Comparator::Other(comparator.to_string()))
473        } else {
474            Capability::Other(capability.to_string())
475        }
476    }
477
478    pub fn all() -> &'static [Capability] {
479        &[
480            Capability::Envelope,
481            Capability::EnvelopeDsn,
482            Capability::EnvelopeDeliverBy,
483            Capability::FileInto,
484            Capability::EncodedCharacter,
485            Capability::Comparator(Comparator::Elbonia),
486            Capability::Comparator(Comparator::AsciiCaseMap),
487            Capability::Comparator(Comparator::AsciiNumeric),
488            Capability::Comparator(Comparator::Octet),
489            Capability::Body,
490            Capability::Convert,
491            Capability::Copy,
492            Capability::Relational,
493            Capability::Date,
494            Capability::Index,
495            Capability::Duplicate,
496            Capability::Variables,
497            Capability::EditHeader,
498            Capability::ForEveryPart,
499            Capability::Mime,
500            Capability::Replace,
501            Capability::Enclose,
502            Capability::ExtractText,
503            Capability::Enotify,
504            Capability::RedirectDsn,
505            Capability::RedirectDeliverBy,
506            Capability::Environment,
507            Capability::Reject,
508            Capability::Ereject,
509            Capability::ExtLists,
510            Capability::SubAddress,
511            Capability::Vacation,
512            Capability::VacationSeconds,
513            Capability::Fcc,
514            Capability::Mailbox,
515            Capability::MailboxId,
516            Capability::MboxMetadata,
517            Capability::ServerMetadata,
518            Capability::SpecialUse,
519            Capability::Imap4Flags,
520            Capability::Ihave,
521            Capability::ImapSieve,
522            Capability::Include,
523            Capability::Regex,
524            Capability::SpamTest,
525            Capability::SpamTestPlus,
526            Capability::VirusTest,
527        ]
528    }
529}
530
531static RELATIONAL: phf::Map<&'static str, RelationalMatch> = phf_map! {
532    "gt" => RelationalMatch::Gt,
533    "ge" => RelationalMatch::Ge,
534    "lt" => RelationalMatch::Lt,
535    "le" => RelationalMatch::Le,
536    "eq" => RelationalMatch::Eq,
537    "ne" => RelationalMatch::Ne,
538};
539
540static COMPARATOR: phf::Map<&'static str, Comparator> = phf_map! {
541    "i;octet" => Comparator::Octet,
542    "i;ascii-casemap" => Comparator::AsciiCaseMap,
543    "i;ascii-numeric" => Comparator::AsciiNumeric,
544};
545
546impl Invalid {
547    pub fn name(&self) -> &str {
548        &self.name
549    }
550
551    pub fn line_num(&self) -> usize {
552        self.line_num
553    }
554
555    pub fn line_pos(&self) -> usize {
556        self.line_pos
557    }
558}
559
560impl From<&str> for Capability {
561    fn from(value: &str) -> Self {
562        Capability::parse(value)
563    }
564}
565
566impl From<String> for Capability {
567    fn from(value: String) -> Self {
568        Capability::parse(&value)
569    }
570}
571
572impl Display for Capability {
573    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
574        match self {
575            Capability::Envelope => f.write_str("envelope"),
576            Capability::EnvelopeDsn => f.write_str("envelope-dsn"),
577            Capability::EnvelopeDeliverBy => f.write_str("envelope-deliverby"),
578            Capability::FileInto => f.write_str("fileinto"),
579            Capability::EncodedCharacter => f.write_str("encoded-character"),
580            Capability::Comparator(Comparator::Elbonia) => f.write_str("comparator-elbonia"),
581            Capability::Comparator(Comparator::Octet) => f.write_str("comparator-i;octet"),
582            Capability::Comparator(Comparator::AsciiCaseMap) => {
583                f.write_str("comparator-i;ascii-casemap")
584            }
585            Capability::Comparator(Comparator::AsciiNumeric) => {
586                f.write_str("comparator-i;ascii-numeric")
587            }
588            Capability::Comparator(Comparator::Other(comparator)) => f.write_str(comparator),
589            Capability::Body => f.write_str("body"),
590            Capability::Convert => f.write_str("convert"),
591            Capability::Copy => f.write_str("copy"),
592            Capability::Relational => f.write_str("relational"),
593            Capability::Date => f.write_str("date"),
594            Capability::Index => f.write_str("index"),
595            Capability::Duplicate => f.write_str("duplicate"),
596            Capability::Variables => f.write_str("variables"),
597            Capability::EditHeader => f.write_str("editheader"),
598            Capability::ForEveryPart => f.write_str("foreverypart"),
599            Capability::Mime => f.write_str("mime"),
600            Capability::Replace => f.write_str("replace"),
601            Capability::Enclose => f.write_str("enclose"),
602            Capability::ExtractText => f.write_str("extracttext"),
603            Capability::Enotify => f.write_str("enotify"),
604            Capability::RedirectDsn => f.write_str("redirect-dsn"),
605            Capability::RedirectDeliverBy => f.write_str("redirect-deliverby"),
606            Capability::Environment => f.write_str("environment"),
607            Capability::Reject => f.write_str("reject"),
608            Capability::Ereject => f.write_str("ereject"),
609            Capability::ExtLists => f.write_str("extlists"),
610            Capability::SubAddress => f.write_str("subaddress"),
611            Capability::Vacation => f.write_str("vacation"),
612            Capability::VacationSeconds => f.write_str("vacation-seconds"),
613            Capability::Fcc => f.write_str("fcc"),
614            Capability::Mailbox => f.write_str("mailbox"),
615            Capability::MailboxId => f.write_str("mailboxid"),
616            Capability::MboxMetadata => f.write_str("mboxmetadata"),
617            Capability::ServerMetadata => f.write_str("servermetadata"),
618            Capability::SpecialUse => f.write_str("special-use"),
619            Capability::Imap4Flags => f.write_str("imap4flags"),
620            Capability::Ihave => f.write_str("ihave"),
621            Capability::ImapSieve => f.write_str("imapsieve"),
622            Capability::Include => f.write_str("include"),
623            Capability::Regex => f.write_str("regex"),
624            Capability::SpamTest => f.write_str("spamtest"),
625            Capability::SpamTestPlus => f.write_str("spamtestplus"),
626            Capability::VirusTest => f.write_str("virustest"),
627            Capability::While => f.write_str("vnd.stalwart.while"),
628            Capability::Expressions => f.write_str("vnd.stalwart.expressions"),
629            Capability::Other(capability) => f.write_str(capability),
630        }
631    }
632}
633
634static CAPABILITIES: phf::Map<&'static str, Capability> = phf_map! {
635    "envelope" => Capability::Envelope,
636    "envelope-dsn" => Capability::EnvelopeDsn,
637    "envelope-deliverby" => Capability::EnvelopeDeliverBy,
638    "fileinto" => Capability::FileInto,
639    "encoded-character" => Capability::EncodedCharacter,
640    "comparator-elbonia" => Capability::Comparator(Comparator::Elbonia),
641    "comparator-i;octet" => Capability::Comparator(Comparator::Octet),
642    "comparator-i;ascii-casemap" => Capability::Comparator(Comparator::AsciiCaseMap),
643    "comparator-i;ascii-numeric" => Capability::Comparator(Comparator::AsciiNumeric),
644    "body" => Capability::Body,
645    "convert" => Capability::Convert,
646    "copy" => Capability::Copy,
647    "relational" => Capability::Relational,
648    "date" => Capability::Date,
649    "index" => Capability::Index,
650    "duplicate" => Capability::Duplicate,
651    "variables" => Capability::Variables,
652    "editheader" => Capability::EditHeader,
653    "foreverypart" => Capability::ForEveryPart,
654    "mime" => Capability::Mime,
655    "replace" => Capability::Replace,
656    "enclose" => Capability::Enclose,
657    "extracttext" => Capability::ExtractText,
658    "enotify" => Capability::Enotify,
659    "redirect-dsn" => Capability::RedirectDsn,
660    "redirect-deliverby" => Capability::RedirectDeliverBy,
661    "environment" => Capability::Environment,
662    "reject" => Capability::Reject,
663    "ereject" => Capability::Ereject,
664    "extlists" => Capability::ExtLists,
665    "subaddress" => Capability::SubAddress,
666    "vacation" => Capability::Vacation,
667    "vacation-seconds" => Capability::VacationSeconds,
668    "fcc" => Capability::Fcc,
669    "mailbox" => Capability::Mailbox,
670    "mailboxid" => Capability::MailboxId,
671    "mboxmetadata" => Capability::MboxMetadata,
672    "servermetadata" => Capability::ServerMetadata,
673    "special-use" => Capability::SpecialUse,
674    "imap4flags" => Capability::Imap4Flags,
675    "ihave" => Capability::Ihave,
676    "imapsieve" => Capability::ImapSieve,
677    "include" => Capability::Include,
678    "regex" => Capability::Regex,
679    "spamtest" => Capability::SpamTest,
680    "spamtestplus" => Capability::SpamTestPlus,
681    "virustest" => Capability::VirusTest,
682
683    // Extensions
684    "vnd.stalwart.while" => Capability::While,
685    "vnd.stalwart.expressions" => Capability::Expressions,
686};