You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
meli/melib/src/sieve/runtime/tests/mime.rs

222 lines
7.9 KiB
Rust

/*
* Copyright (c) 2020-2023, Stalwart Labs Ltd.
*
* This file is part of the Stalwart Sieve Interpreter.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
* in the LICENSE file at the top-level directory of this distribution.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* You can be released from the requirements of the AGPLv3 license by
* purchasing a commercial license. Please contact licensing@stalw.art
* for more details.
*/
use std::slice::Iter;
use mail_parser::{Message, MessagePart, MimeHeaders, PartType};
use crate::sieve::Context;
#[derive(Debug)]
pub(crate) enum ContentTypeFilter {
Type(String),
TypeSubtype((String, String)),
}
pub(crate) struct SubpartIterator<'x> {
ctx: &'x Context<'x>,
iter: Iter<'x, usize>,
iter_stack: Vec<Iter<'x, usize>>,
anychild: bool,
}
impl<'x> SubpartIterator<'x> {
pub(crate) fn new(ctx: &'x Context<'x>, parts: &'x [usize], anychild: bool) -> Self {
SubpartIterator {
ctx,
iter: parts.iter(),
iter_stack: Vec::new(),
anychild,
}
}
#[allow(clippy::should_implement_trait)]
pub fn next(&mut self) -> Option<(usize, &MessagePart<'x>)> {
loop {
if let Some(&part_id) = self.iter.next() {
let subpart = self.ctx.message.parts.get(part_id)?;
match &subpart.body {
PartType::Multipart(subparts) if self.anychild => {
self.iter_stack
.push(std::mem::replace(&mut self.iter, subparts.iter()));
}
_ => (),
}
return Some((part_id, subpart));
}
if let Some(prev_iter) = self.iter_stack.pop() {
self.iter = prev_iter;
} else {
return None;
}
}
}
}
impl<'x> Context<'x> {
pub(crate) fn find_nested_parts<'z: 'x>(
&'z self,
mut message: &'x Message<'x>,
ct_filter: &[ContentTypeFilter],
visitor_fnc: &mut impl FnMut(&MessagePart, &[u8]) -> bool,
) -> bool {
let mut iter_stack = Vec::new();
let mut iter = vec![self.part].into_iter();
loop {
while let Some(part_id) = iter.next() {
if let Some(subpart) = message.parts.get(part_id) {
let process_part = if !ct_filter.is_empty() {
let mut process_part = false;
let (ct, cst) = if let Some(ct) = subpart.content_type() {
(ct.c_type.as_ref(), ct.c_subtype.as_deref().unwrap_or(""))
} else {
match &subpart.body {
PartType::Text(_) => ("text", "plain"),
PartType::Html(_) => ("text", "html"),
PartType::Message(_) => ("message", "rfc822"),
PartType::Multipart(_) => ("multipart", "mixed"),
_ => ("application", "octet-stream"),
}
};
for ctf in ct_filter {
match ctf {
ContentTypeFilter::Type(name) => {
if name.eq_ignore_ascii_case(ct) {
process_part = true;
break;
}
}
ContentTypeFilter::TypeSubtype((name, subname)) => {
if name.eq_ignore_ascii_case(ct)
&& subname.eq_ignore_ascii_case(cst)
{
process_part = true;
break;
}
}
}
}
process_part
} else {
true
};
if process_part && visitor_fnc(subpart, message.raw_message.as_ref()) {
return true;
}
match &subpart.body {
PartType::Multipart(subparts) => {
iter_stack.push((
std::mem::replace(&mut iter, subparts.clone().into_iter()),
None,
));
}
PartType::Message(next_message) => {
iter_stack.push((
std::mem::replace(&mut iter, vec![0].into_iter()),
Some(message),
));
message = next_message;
}
_ => (),
}
}
}
if let Some((prev_iter, prev_message)) = iter_stack.pop() {
iter = prev_iter;
if let Some(prev_message) = prev_message {
message = prev_message;
}
} else {
break;
}
}
false
}
pub(crate) fn find_nested_parts_ids(&self, include_current: bool) -> Vec<usize> {
if self.part == 0 {
if include_current {
(0..self.message.parts.len()).collect()
} else if self.message.parts.len() > 1 {
(1..self.message.parts.len()).collect()
} else {
Vec::new()
}
} else {
let mut part_ids = Vec::new();
let mut iter_stack = Vec::new();
if include_current {
part_ids.push(self.part);
}
if let Some(PartType::Multipart(subparts)) =
self.message.parts.get(self.part).map(|p| &p.body)
{
let mut iter = subparts.iter();
loop {
while let Some(&part_id) = iter.next() {
part_ids.push(part_id);
if let Some(PartType::Multipart(subparts)) =
self.message.parts.get(part_id).map(|p| &p.body)
{
iter_stack.push(std::mem::replace(&mut iter, subparts.iter()));
}
}
if let Some(prev_iter) = iter_stack.pop() {
iter = prev_iter;
} else {
break;
}
}
}
part_ids
}
}
}
impl ContentTypeFilter {
pub(crate) fn parse(ct: &str) -> Option<ContentTypeFilter> {
let mut iter = ct.split('/');
let name = iter.next()?;
if let Some(sub_name) = iter.next() {
if !name.is_empty() && !sub_name.is_empty() && iter.next().is_none() {
Some(ContentTypeFilter::TypeSubtype((
name.to_string(),
sub_name.to_string(),
)))
} else {
None
}
} else if !name.is_empty() {
Some(ContentTypeFilter::Type(name.to_string()))
} else {
None
}
}
}