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