sieve/compiler/grammar/tests/
test_header.rs1use mail_parser::HeaderName;
8
9
10use crate::compiler::{
11 grammar::{
12 actions::action_mime::MimeOpts,
13 instruction::{CompilerState, MapLocalVars},
14 Capability, Comparator,
15 },
16 lexer::{word::Word, Token},
17 CompileError, ErrorType, Value,
18};
19
20use crate::compiler::grammar::{test::Test, MatchType};
21
22#[derive(Debug, Clone, PartialEq, Eq)]
23#[cfg_attr(
24 any(test, feature = "serde"),
25 derive(serde::Serialize, serde::Deserialize)
26)]
27#[cfg_attr(
28 feature = "rkyv",
29 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
30)]
31pub(crate) struct TestHeader {
32 pub header_list: Vec<Value>,
33 pub key_list: Vec<Value>,
34 pub match_type: MatchType,
35 pub comparator: Comparator,
36 pub index: Option<i32>,
37
38 pub mime_opts: MimeOpts<Value>,
39 pub mime_anychild: bool,
40 pub is_not: bool,
41}
42
43impl CompilerState<'_> {
44 pub(crate) fn parse_test_header(&mut self) -> Result<Test, CompileError> {
45 let mut match_type = MatchType::Is;
46 let mut comparator = Comparator::AsciiCaseMap;
47 let mut header_list = None;
48 let mut key_list;
49 let mut index = None;
50 let mut index_last = false;
51
52 let mut mime = false;
53 let mut mime_opts = MimeOpts::None;
54 let mut mime_anychild = false;
55
56 loop {
57 let token_info = self.tokens.unwrap_next()?;
58 match token_info.token {
59 Token::Tag(
60 word @ (Word::Is
61 | Word::Contains
62 | Word::Matches
63 | Word::Value
64 | Word::Count
65 | Word::Regex
66 | Word::List),
67 ) => {
68 self.validate_argument(
69 1,
70 match word {
71 Word::Value | Word::Count => Capability::Relational.into(),
72 Word::Regex => Capability::Regex.into(),
73 Word::List => Capability::ExtLists.into(),
74 _ => None,
75 },
76 token_info.line_num,
77 token_info.line_pos,
78 )?;
79
80 match_type = self.parse_match_type(word)?;
81 }
82 Token::Tag(Word::Comparator) => {
83 self.validate_argument(2, None, token_info.line_num, token_info.line_pos)?;
84
85 comparator = self.parse_comparator()?;
86 }
87 Token::Tag(Word::Index) => {
88 self.validate_argument(
89 3,
90 Capability::Index.into(),
91 token_info.line_num,
92 token_info.line_pos,
93 )?;
94
95 index = (self.tokens.expect_number(u16::MAX as usize)? as i32).into();
96 }
97 Token::Tag(Word::Last) => {
98 self.validate_argument(
99 4,
100 Capability::Index.into(),
101 token_info.line_num,
102 token_info.line_pos,
103 )?;
104
105 index_last = true;
106 }
107 Token::Tag(Word::Mime) => {
108 self.validate_argument(
109 5,
110 Capability::Mime.into(),
111 token_info.line_num,
112 token_info.line_pos,
113 )?;
114 mime = true;
115 }
116 Token::Tag(Word::AnyChild) => {
117 self.validate_argument(
118 6,
119 Capability::Mime.into(),
120 token_info.line_num,
121 token_info.line_pos,
122 )?;
123 mime_anychild = true;
124 }
125 Token::Tag(
126 word @ (Word::Type | Word::Subtype | Word::ContentType | Word::Param),
127 ) => {
128 self.validate_argument(
129 7,
130 Capability::Mime.into(),
131 token_info.line_num,
132 token_info.line_pos,
133 )?;
134 mime_opts = self.parse_mimeopts(word)?;
135 }
136 _ => {
137 if header_list.is_none() {
138 let headers = self.parse_strings_token(token_info)?;
139 for header in &headers {
140 if let Value::Text(header_name) = &header {
141 if HeaderName::parse(header_name.as_ref()).is_none() {
142 return Err(self
143 .tokens
144 .unwrap_next()?
145 .custom(ErrorType::InvalidHeaderName));
146 }
147 }
148 }
149 header_list = headers.into();
150 } else {
151 key_list = self.parse_strings_token(token_info)?;
152 break;
153 }
154 }
155 }
156 }
157
158 if !mime && (mime_anychild || mime_opts != MimeOpts::None) {
159 return Err(self.tokens.unwrap_next()?.missing_tag(":mime"));
160 }
161 self.validate_match(&match_type, &mut key_list)?;
162
163 Ok(Test::Header(TestHeader {
164 header_list: header_list.unwrap(),
165 key_list,
166 match_type,
167 comparator,
168 index: if index_last { index.map(|i| -i) } else { index },
169 mime_opts,
170 mime_anychild,
171 is_not: false,
172 }))
173 }
174}
175
176impl MapLocalVars for MimeOpts<Value> {
177 fn map_local_vars(&mut self, last_id: usize) {
178 if let MimeOpts::Param(value) = self {
179 value.map_local_vars(last_id)
180 }
181 }
182}