1use std::{borrow::Cow, sync::Arc, time::SystemTime};
25
26use ahash::AHashMap;
27use mail_parser::Message;
28
29use crate::{
30 compiler::grammar::{instruction::Instruction, Capability},
31 Context, Envelope, Event, Input, Metadata, Runtime, Sieve, SpamStatus, VirusStatus,
32 MAX_LOCAL_VARIABLES, MAX_MATCH_VARIABLES,
33};
34
35use super::{
36 actions::action_include::IncludeResult,
37 tests::{test_envelope::parse_envelope_address, TestResult},
38 RuntimeError, Variable,
39};
40
41#[derive(Clone, Debug)]
42pub(crate) struct ScriptStack {
43 pub(crate) script: Arc<Sieve>,
44 pub(crate) prev_pos: usize,
45 pub(crate) prev_vars_local: Vec<Variable>,
46 pub(crate) prev_vars_match: Vec<Variable>,
47}
48
49impl<'x> Context<'x> {
50 #[cfg(not(test))]
51 pub(crate) fn new(runtime: &'x Runtime, message: Message<'x>) -> Self {
52 Context {
53 #[cfg(test)]
54 runtime: runtime.clone(),
55 #[cfg(not(test))]
56 runtime,
57 message,
58 part: 0,
59 part_iter: Vec::new().into_iter(),
60 part_iter_stack: Vec::new(),
61 pos: usize::MAX,
62 test_result: false,
63 script_cache: AHashMap::new(),
64 script_stack: Vec::with_capacity(0),
65 vars_global: AHashMap::new(),
66 vars_env: AHashMap::new(),
67 vars_local: Vec::with_capacity(0),
68 vars_match: Vec::with_capacity(0),
69 expr_stack: Vec::with_capacity(16),
70 expr_pos: 0,
71 envelope: Vec::new(),
72 metadata: Vec::new(),
73 message_size: usize::MAX,
74 final_event: Event::Keep {
75 flags: Vec::with_capacity(0),
76 message_id: 0,
77 }
78 .into(),
79 queued_events: vec![].into_iter(),
80 has_changes: false,
81 user_address: "".into(),
82 user_full_name: "".into(),
83 current_time: SystemTime::now()
84 .duration_since(SystemTime::UNIX_EPOCH)
85 .map(|d| d.as_secs())
86 .unwrap_or(0) as i64,
87 num_redirects: 0,
88 num_instructions: 0,
89 num_out_messages: 0,
90 last_message_id: 0,
91 main_message_id: 0,
92 virus_status: VirusStatus::Unknown,
93 spam_status: SpamStatus::Unknown,
94 }
95 }
96
97 #[allow(clippy::while_let_on_iterator)]
98 pub fn run(&mut self, input: Input) -> Option<Result<Event, RuntimeError>> {
99 match input {
100 Input::True => self.test_result ^= true,
101 Input::False => self.test_result ^= false,
102 Input::FncResult(result) => {
103 self.expr_stack.push(result);
104 }
105 Input::Script { name, script } => {
106 let num_vars = script.num_vars;
107 let num_match_vars = script.num_match_vars;
108
109 if num_match_vars <= MAX_MATCH_VARIABLES && num_vars <= MAX_LOCAL_VARIABLES {
110 if self.message_size == usize::MAX {
111 self.message_size = self.message.raw_message.len();
112 }
113
114 self.script_cache.insert(name, script.clone());
115 self.script_stack.push(ScriptStack {
116 script,
117 prev_pos: self.pos,
118 prev_vars_local: std::mem::replace(
119 &mut self.vars_local,
120 vec![Variable::default(); num_vars],
121 ),
122 prev_vars_match: std::mem::replace(
123 &mut self.vars_match,
124 vec![Variable::default(); num_match_vars],
125 ),
126 });
127 self.pos = 0;
128 self.test_result = false;
129 }
130 }
131 }
132
133 if let Some(event) = self.queued_events.next() {
135 return Some(Ok(event));
136 }
137
138 let mut current_script = self.script_stack.last()?.script.clone();
139 let mut iter = current_script.instructions.get(self.pos..)?.iter();
140
141 'outer: loop {
142 while let Some(instruction) = iter.next() {
143 self.num_instructions += 1;
144 if self.num_instructions > self.runtime.cpu_limit {
145 self.finish_loop();
146 return Some(Err(RuntimeError::CPULimitReached));
147 }
148 self.pos += 1;
149
150 match instruction {
151 Instruction::Jz(jmp_pos) => {
152 if !self.test_result {
153 debug_assert!(*jmp_pos > self.pos - 1);
154 self.pos = *jmp_pos;
155 iter = current_script.instructions.get(self.pos..)?.iter();
156 continue;
157 }
158 }
159 Instruction::Jnz(jmp_pos) => {
160 if self.test_result {
161 debug_assert!(*jmp_pos > self.pos - 1);
162 self.pos = *jmp_pos;
163 iter = current_script.instructions.get(self.pos..)?.iter();
164 continue;
165 }
166 }
167 Instruction::Jmp(jmp_pos) => {
168 debug_assert_ne!(*jmp_pos, self.pos - 1);
169 self.pos = *jmp_pos;
170 iter = current_script.instructions.get(self.pos..)?.iter();
171 continue;
172 }
173 Instruction::Test(test) => match test.exec(self) {
174 TestResult::Bool(result) => {
175 self.test_result = result;
176 }
177 TestResult::Event { event, is_not } => {
178 self.test_result = is_not;
179 return Some(Ok(event));
180 }
181 TestResult::Error(err) => {
182 self.finish_loop();
183 return Some(Err(err));
184 }
185 },
186 Instruction::Eval(expr) => match self.eval_expression(expr) {
187 Ok(result) => {
188 self.test_result = result.to_bool();
189 }
190 Err(event) => {
191 return Some(Ok(event));
192 }
193 },
194 Instruction::Clear(clear) => {
195 if clear.local_vars_num > 0 {
196 if let Some(local_vars) = self.vars_local.get_mut(
197 clear.local_vars_idx as usize
198 ..(clear.local_vars_idx + clear.local_vars_num) as usize,
199 ) {
200 for local_var in local_vars.iter_mut() {
201 if !local_var.is_empty() {
202 *local_var = Variable::default();
203 }
204 }
205 } else {
206 debug_assert!(false, "Failed to clear local variables: {clear:?}");
207 }
208 }
209 if clear.match_vars != 0 {
210 self.clear_match_variables(clear.match_vars);
211 }
212 }
213 Instruction::Keep(keep) => {
214 let next_event = self.build_message_id();
215 self.final_event = Event::Keep {
216 flags: self.get_local_or_global_flags(&keep.flags),
217 message_id: self.main_message_id,
218 }
219 .into();
220 if let Some(next_event) = next_event {
221 return Some(Ok(next_event));
222 }
223 }
224 Instruction::FileInto(fi) => {
225 fi.exec(self);
226 if let Some(event) = self.queued_events.next() {
227 return Some(Ok(event));
228 }
229 }
230 Instruction::Redirect(redirect) => {
231 redirect.exec(self);
232 if let Some(event) = self.queued_events.next() {
233 return Some(Ok(event));
234 }
235 }
236 Instruction::Discard => {
237 self.final_event = Event::Discard.into();
238 }
239 Instruction::Stop => {
240 self.script_stack.clear();
241 break 'outer;
242 }
243 Instruction::Reject(reject) => {
244 self.final_event = None;
245 return Some(Ok(Event::Reject {
246 extended: reject.ereject,
247 reason: self.eval_value(&reject.reason).to_string().into_owned(),
248 }));
249 }
250 Instruction::ForEveryPart(fep) => {
251 if let Some(next_part) = self.part_iter.next() {
252 self.part = next_part;
253 } else if let Some((prev_part, prev_part_iter)) = self.part_iter_stack.pop()
254 {
255 debug_assert!(fep.jz_pos > self.pos - 1);
256 self.part_iter = prev_part_iter;
257 self.part = prev_part;
258 self.pos = fep.jz_pos;
259 iter = current_script.instructions.get(self.pos..)?.iter();
260 continue;
261 } else {
262 self.part = 0;
263 #[cfg(test)]
264 panic!("ForEveryPart executed without items on stack.");
265 }
266 }
267 Instruction::ForEveryPartPush => {
268 let part_iter = self
269 .find_nested_parts_ids(self.part_iter_stack.is_empty())
270 .into_iter();
271 self.part_iter_stack
272 .push((self.part, std::mem::replace(&mut self.part_iter, part_iter)));
273 }
274 Instruction::ForEveryPartPop(num_pops) => {
275 debug_assert!(
276 *num_pops > 0 && *num_pops <= self.part_iter_stack.len(),
277 "Pop out of range: {} with {} items.",
278 num_pops,
279 self.part_iter_stack.len()
280 );
281 for _ in 0..*num_pops {
282 if let Some((prev_part, prev_part_iter)) = self.part_iter_stack.pop() {
283 self.part_iter = prev_part_iter;
284 self.part = prev_part;
285 } else {
286 break;
287 }
288 }
289 }
290 Instruction::While(while_) => match self.eval_expression(&while_.expr) {
291 Ok(result) => {
292 if !result.to_bool() {
293 debug_assert!(while_.jz_pos > self.pos - 1);
294 self.pos = while_.jz_pos;
295 iter = current_script.instructions.get(self.pos..)?.iter();
296 continue;
297 }
298 }
299 Err(event) => {
300 return Some(Ok(event));
301 }
302 },
303 Instruction::Let(let_) => match self.eval_expression(&let_.expr) {
304 Ok(result) => {
305 self.set_variable(&let_.name, result);
306 }
307 Err(event) => {
308 return Some(Ok(event));
309 }
310 },
311
312 Instruction::Replace(replace) => replace.exec(self),
313 Instruction::Enclose(enclose) => enclose.exec(self),
314 Instruction::ExtractText(extract) => {
315 extract.exec(self);
316 if let Some(event) = self.queued_events.next() {
317 return Some(Ok(event));
318 }
319 }
320 Instruction::AddHeader(add_header) => add_header.exec(self),
321 Instruction::DeleteHeader(delete_header) => delete_header.exec(self),
322 Instruction::Set(set) => {
323 set.exec(self);
324 if let Some(event) = self.queued_events.next() {
325 return Some(Ok(event));
326 }
327 }
328 Instruction::Notify(notify) => {
329 notify.exec(self);
330 if let Some(event) = self.queued_events.next() {
331 return Some(Ok(event));
332 }
333 }
334 Instruction::Vacation(vacation) => {
335 vacation.exec(self);
336 if let Some(event) = self.queued_events.next() {
337 return Some(Ok(event));
338 }
339 }
340 Instruction::EditFlags(flags) => flags.exec(self),
341 Instruction::Include(include) => match include.exec(self) {
342 IncludeResult::Cached(script) => {
343 self.script_stack.push(ScriptStack {
344 script: script.clone(),
345 prev_pos: self.pos,
346 prev_vars_local: std::mem::replace(
347 &mut self.vars_local,
348 vec![Variable::default(); script.num_vars],
349 ),
350 prev_vars_match: std::mem::replace(
351 &mut self.vars_match,
352 vec![Variable::default(); script.num_match_vars],
353 ),
354 });
355 self.pos = 0;
356 current_script = script;
357 iter = current_script.instructions.iter();
358 continue;
359 }
360 IncludeResult::Event(event) => {
361 return Some(Ok(event));
362 }
363 IncludeResult::Error(err) => {
364 self.finish_loop();
365 return Some(Err(err));
366 }
367 IncludeResult::None => (),
368 },
369 Instruction::Convert(convert) => {
370 convert.exec(self);
371 }
372 Instruction::Return => {
373 break;
374 }
375 Instruction::Require(capabilities) => {
376 for capability in capabilities {
377 if !self.runtime.allowed_capabilities.contains(capability) {
378 self.finish_loop();
379 return Some(Err(
380 if let Capability::Other(not_supported) = capability {
381 RuntimeError::CapabilityNotSupported(not_supported.clone())
382 } else {
383 RuntimeError::CapabilityNotAllowed(capability.clone())
384 },
385 ));
386 }
387 }
388 }
389 Instruction::Error(err) => {
390 self.finish_loop();
391 return Some(Err(RuntimeError::ScriptErrorMessage(
392 self.eval_value(&err.message).to_string().into_owned(),
393 )));
394 }
395 Instruction::Invalid(invalid) => {
396 self.finish_loop();
397 return Some(Err(RuntimeError::InvalidInstruction(invalid.clone())));
398 }
399 #[cfg(test)]
400 Instruction::TestCmd(arguments) => {
401 return Some(Ok(Event::Function {
402 id: u32::MAX,
403 arguments: arguments
404 .iter()
405 .map(|s| self.eval_value(s).to_owned())
406 .collect(),
407 }));
408 }
409 }
410 }
411
412 if let Some(prev_script) = self.script_stack.pop() {
413 self.pos = prev_script.prev_pos;
414 self.vars_local = prev_script.prev_vars_local;
415 self.vars_match = prev_script.prev_vars_match;
416 }
417
418 if let Some(script_stack) = self.script_stack.last() {
419 current_script = script_stack.script.clone();
420 iter = current_script.instructions.get(self.pos..)?.iter();
421 } else {
422 break;
423 }
424 }
425
426 match self.final_event.take() {
427 Some(Event::Keep {
428 mut flags,
429 message_id,
430 }) => {
431 let create_event = if self.has_changes {
432 self.build_message_id()
433 } else {
434 None
435 };
436
437 let global_flags = self.get_global_flags();
438 if flags.is_empty() && !global_flags.is_empty() {
439 flags = global_flags;
440 }
441 if let Some(create_event) = create_event {
442 self.queued_events = vec![
443 create_event,
444 Event::Keep {
445 flags,
446 message_id: self.main_message_id,
447 },
448 ]
449 .into_iter();
450 self.queued_events.next().map(Ok)
451 } else {
452 Some(Ok(Event::Keep { flags, message_id }))
453 }
454 }
455 Some(event) => Some(Ok(event)),
456 _ => None,
457 }
458 }
459
460 pub(crate) fn finish_loop(&mut self) {
461 self.script_stack.clear();
462 if let Some(event) = self.final_event.take() {
463 self.queued_events = if let Event::Keep {
464 mut flags,
465 message_id,
466 } = event
467 {
468 let global_flags = self.get_global_flags();
469 if flags.is_empty() && !global_flags.is_empty() {
470 flags = global_flags;
471 }
472
473 if self.has_changes {
474 if let Some(event) = self.build_message_id() {
475 vec![
476 event,
477 Event::Keep {
478 flags,
479 message_id: self.main_message_id,
480 },
481 ]
482 } else {
483 vec![Event::Keep { flags, message_id }]
484 }
485 } else {
486 vec![Event::Keep { flags, message_id }]
487 }
488 } else {
489 vec![event]
490 }
491 .into_iter();
492 }
493 }
494
495 pub fn set_envelope(
496 &mut self,
497 envelope: impl TryInto<Envelope>,
498 value: impl Into<Cow<'x, str>>,
499 ) {
500 if let Ok(envelope) = envelope.try_into() {
501 if matches!(&envelope, Envelope::From | Envelope::To) {
502 let value: Cow<str> = value.into();
503 if let Some(value) = parse_envelope_address(value.as_ref()) {
504 self.envelope.push((envelope, value.to_string().into()));
505 }
506 } else {
507 self.envelope.push((envelope, Variable::from(value.into())));
508 }
509 }
510 }
511
512 pub fn with_vars_env(mut self, vars_env: AHashMap<Cow<'static, str>, Variable>) -> Self {
513 self.vars_env = vars_env;
514 self
515 }
516
517 pub fn with_envelope_list(mut self, envelope: Vec<(Envelope, Variable)>) -> Self {
518 self.envelope = envelope;
519 self
520 }
521
522 pub fn with_envelope(
523 mut self,
524 envelope: impl TryInto<Envelope>,
525 value: impl Into<Cow<'x, str>>,
526 ) -> Self {
527 self.set_envelope(envelope, value);
528 self
529 }
530
531 pub fn clear_envelope(&mut self) {
532 self.envelope.clear()
533 }
534
535 pub fn set_user_address(&mut self, from: impl Into<Cow<'x, str>>) {
536 self.user_address = from.into();
537 }
538
539 pub fn with_user_address(mut self, from: impl Into<Cow<'x, str>>) -> Self {
540 self.set_user_address(from);
541 self
542 }
543
544 pub fn set_user_full_name(&mut self, name: &str) {
545 let mut name_ = String::with_capacity(name.len());
546 for ch in name.chars() {
547 if ['\"', '\\'].contains(&ch) {
548 name_.push('\\');
549 }
550 name_.push(ch);
551 }
552 self.user_full_name = name_.into();
553 }
554
555 pub fn with_user_full_name(mut self, name: &str) -> Self {
556 self.set_user_full_name(name);
557 self
558 }
559
560 pub fn set_env_variable(
561 &mut self,
562 name: impl Into<Cow<'static, str>>,
563 value: impl Into<Variable>,
564 ) {
565 self.vars_env.insert(name.into(), value.into());
566 }
567
568 pub fn with_env_variable(
569 mut self,
570 name: impl Into<Cow<'static, str>>,
571 value: impl Into<Variable>,
572 ) -> Self {
573 self.set_env_variable(name, value);
574 self
575 }
576
577 pub fn set_global_variable(
578 &mut self,
579 name: impl Into<Cow<'static, str>>,
580 value: impl Into<Variable>,
581 ) {
582 self.vars_env.insert(name.into(), value.into());
583 }
584
585 pub fn with_global_variable(
586 mut self,
587 name: impl Into<Cow<'static, str>>,
588 value: impl Into<Variable>,
589 ) -> Self {
590 self.set_global_variable(name, value);
591 self
592 }
593
594 pub fn set_medatata(
595 &mut self,
596 name: impl Into<Metadata<String>>,
597 value: impl Into<Cow<'x, str>>,
598 ) {
599 self.metadata.push((name.into(), value.into()));
600 }
601
602 pub fn with_metadata(
603 mut self,
604 name: impl Into<Metadata<String>>,
605 value: impl Into<Cow<'x, str>>,
606 ) -> Self {
607 self.set_medatata(name, value);
608 self
609 }
610
611 pub fn set_spam_status(&mut self, status: impl Into<SpamStatus>) {
612 self.spam_status = status.into();
613 }
614
615 pub fn with_spam_status(mut self, status: impl Into<SpamStatus>) -> Self {
616 self.set_spam_status(status);
617 self
618 }
619
620 pub fn set_virus_status(&mut self, status: impl Into<VirusStatus>) {
621 self.virus_status = status.into();
622 }
623
624 pub fn with_virus_status(mut self, status: impl Into<VirusStatus>) -> Self {
625 self.set_virus_status(status);
626 self
627 }
628
629 pub fn take_message(&mut self) -> Message<'x> {
630 std::mem::take(&mut self.message)
631 }
632
633 pub fn has_message_changed(&self) -> bool {
634 self.main_message_id > 0
635 }
636
637 pub(crate) fn user_from_field(&self) -> String {
638 if !self.user_full_name.is_empty() {
639 format!("\"{}\" <{}>", self.user_full_name, self.user_address)
640 } else {
641 self.user_address.to_string()
642 }
643 }
644
645 pub fn global_variable_names(&self) -> impl Iterator<Item = &str> {
646 self.vars_global.keys().map(|k| k.as_ref())
647 }
648
649 pub fn global_variable(&self, name: &str) -> Option<&Variable> {
650 self.vars_global.get(name)
651 }
652
653 pub fn message(&self) -> &Message<'x> {
654 &self.message
655 }
656
657 pub fn part(&self) -> usize {
658 self.part
659 }
660}
661
662#[cfg(test)]
663impl<'x> Context<'x> {
664 pub(crate) fn new(runtime: &'x Runtime, message: Message<'x>) -> Self {
665 Context {
666 runtime: runtime.clone(),
667 message,
668 part: 0,
669 part_iter: Vec::new().into_iter(),
670 part_iter_stack: Vec::new(),
671 pos: usize::MAX,
672 test_result: false,
673 script_cache: AHashMap::new(),
674 script_stack: Vec::with_capacity(0),
675 vars_global: AHashMap::new(),
676 vars_env: AHashMap::new(),
677 vars_local: Vec::with_capacity(0),
678 vars_match: Vec::with_capacity(0),
679 expr_stack: Vec::with_capacity(16),
680 expr_pos: 0,
681 envelope: Vec::new(),
682 metadata: Vec::new(),
683 message_size: usize::MAX,
684 final_event: Event::Keep {
685 flags: Vec::with_capacity(0),
686 message_id: 0,
687 }
688 .into(),
689 queued_events: vec![].into_iter(),
690 has_changes: false,
691 user_address: "".into(),
692 user_full_name: "".into(),
693 current_time: SystemTime::now()
694 .duration_since(SystemTime::UNIX_EPOCH)
695 .map(|d| d.as_secs())
696 .unwrap_or(0) as i64,
697 num_redirects: 0,
698 num_instructions: 0,
699 num_out_messages: 0,
700 last_message_id: 0,
701 main_message_id: 0,
702 virus_status: VirusStatus::Unknown,
703 spam_status: SpamStatus::Unknown,
704 }
705 }
706}