Import upstream v0.16.22, stripped
Upstream commit: 474dd0229cb20cf513036619781ed97bd8073c3f Enterprise-only files removed or emptied: 63 Enterprise-only snippets removed: 117 in 50 files Dangling module declarations removed: 5 Cargo edits turning enterprise off: 14 Verification: clean Enterprise feature gates left for rebuilt features: 19 in 18 files Produced by tools/fork/strip.py. The full report is in docs/fork/strip-reports/ on main.
This commit is contained in:
commit
7dae9b29fd
1650 files changed
+485521
No files matched your search
@@ -0,0 +1,395 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::message::metadata::{
|
||||
ArchivedMessageMetadataContents, ArchivedMetadataHeaderValue, ArchivedMetadataPartType,
|
||||
PART_ENCODING_BASE64, PART_ENCODING_QP, PART_SIZE_MASK,
|
||||
};
|
||||
use jmap_proto::object::email::{EmailProperty, EmailValue};
|
||||
use jmap_tools::{Map, Value};
|
||||
use mail_parser::{HeaderValue, MessagePart, MimeHeaders, PartType};
|
||||
use types::blob::BlobId;
|
||||
use utils::chained_bytes::ChainedBytes;
|
||||
|
||||
use super::headers::HeaderToValue;
|
||||
|
||||
pub trait ToBodyPart {
|
||||
fn to_body_part(
|
||||
&self,
|
||||
part_id: u32,
|
||||
properties: &[EmailProperty],
|
||||
raw_message: &ChainedBytes<'_>,
|
||||
blob_id: &BlobId,
|
||||
blob_body_offset: isize,
|
||||
) -> Value<'static, EmailProperty, EmailValue>;
|
||||
}
|
||||
|
||||
impl ToBodyPart for Vec<MessagePart<'_>> {
|
||||
fn to_body_part(
|
||||
&self,
|
||||
part_id: u32,
|
||||
properties: &[EmailProperty],
|
||||
raw_message: &ChainedBytes<'_>,
|
||||
blob_id: &BlobId,
|
||||
blob_body_offset: isize,
|
||||
) -> Value<'static, EmailProperty, EmailValue> {
|
||||
let mut parts = vec![part_id].into_iter();
|
||||
let mut parts_stack = Vec::new();
|
||||
let mut subparts = Vec::with_capacity(1);
|
||||
|
||||
loop {
|
||||
if let Some((part_id, part)) = parts
|
||||
.next()
|
||||
.map(|part_id| (part_id, &self[part_id as usize]))
|
||||
{
|
||||
let mut values = Map::with_capacity(properties.len());
|
||||
let multipart = if let PartType::Multipart(parts) = &part.body {
|
||||
parts.into()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
for property in properties {
|
||||
let value = match property {
|
||||
EmailProperty::PartId if multipart.is_none() => part_id.to_string().into(),
|
||||
EmailProperty::BlobId if multipart.is_none() => {
|
||||
let base_offset = blob_id.start_offset() as isize + blob_body_offset;
|
||||
BlobId::new_section(
|
||||
blob_id.hash.clone(),
|
||||
blob_id.class.clone(),
|
||||
(part.offset_body as isize + base_offset) as usize,
|
||||
(part.offset_end as isize + base_offset) as usize,
|
||||
part.encoding as u8,
|
||||
)
|
||||
.into()
|
||||
}
|
||||
EmailProperty::Size if multipart.is_none() => match &part.body {
|
||||
PartType::Text(text) | PartType::Html(text) => text.len(),
|
||||
PartType::Binary(bin) | PartType::InlineBinary(bin) => bin.len(),
|
||||
PartType::Message(message) => message.root_part().raw_len() as usize,
|
||||
PartType::Multipart(_) => 0,
|
||||
}
|
||||
.into(),
|
||||
EmailProperty::Name => part.attachment_name().map(|v| v.to_string()).into(),
|
||||
EmailProperty::Type => part
|
||||
.content_type()
|
||||
.map(|ct| {
|
||||
ct.subtype()
|
||||
.map(|st| format!("{}/{}", ct.ctype(), st))
|
||||
.unwrap_or_else(|| ct.ctype().to_string())
|
||||
})
|
||||
.or_else(|| match &part.body {
|
||||
PartType::Text(_) => Some("text/plain".to_string()),
|
||||
PartType::Html(_) => Some("text/html".to_string()),
|
||||
PartType::Message(_) => Some("message/rfc822".to_string()),
|
||||
_ => None,
|
||||
})
|
||||
.into(),
|
||||
EmailProperty::Charset => part
|
||||
.content_type()
|
||||
.and_then(|ct| ct.attribute("charset"))
|
||||
.or(match &part.body {
|
||||
PartType::Text(_) | PartType::Html(_) => Some("us-ascii"),
|
||||
_ => None,
|
||||
})
|
||||
.map(|v| v.to_string())
|
||||
.into(),
|
||||
EmailProperty::Disposition => part
|
||||
.content_disposition()
|
||||
.map(|cd| cd.ctype())
|
||||
.map(|v| v.to_string())
|
||||
.into(),
|
||||
EmailProperty::Cid => part.content_id().map(|v| v.to_string()).into(),
|
||||
EmailProperty::Language => match part.content_language() {
|
||||
HeaderValue::Text(text) => vec![text.to_string()].into(),
|
||||
HeaderValue::TextList(list) => list
|
||||
.iter()
|
||||
.map(|text| text.to_string().into())
|
||||
.collect::<Vec<Value<'static, EmailProperty, EmailValue>>>()
|
||||
.into(),
|
||||
_ => Value::Null,
|
||||
},
|
||||
EmailProperty::Location => {
|
||||
part.content_location().map(|v| v.to_string()).into()
|
||||
}
|
||||
EmailProperty::Header(_) => {
|
||||
part.headers.header_to_value(property, raw_message)
|
||||
}
|
||||
EmailProperty::Headers => part.headers.headers_to_value(raw_message),
|
||||
EmailProperty::SubParts => continue,
|
||||
_ => Value::Null,
|
||||
};
|
||||
values.insert_unchecked(property.clone(), value);
|
||||
}
|
||||
|
||||
subparts.push(values);
|
||||
|
||||
if let Some(multipart) = multipart {
|
||||
if parts_stack.len() == 10_000 {
|
||||
debug_assert!(false, "Too much nesting in message metadata");
|
||||
return Value::Null;
|
||||
}
|
||||
let multipart = multipart.clone();
|
||||
parts_stack.push((
|
||||
parts,
|
||||
std::mem::replace(&mut subparts, Vec::with_capacity(multipart.len())),
|
||||
));
|
||||
parts = multipart.into_iter();
|
||||
}
|
||||
} else if let Some((prev_parts, mut prev_subparts)) = parts_stack.pop() {
|
||||
prev_subparts
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.insert_unchecked(EmailProperty::SubParts, subparts);
|
||||
parts = prev_parts;
|
||||
subparts = prev_subparts;
|
||||
} else {
|
||||
return subparts.pop().map(Into::into).unwrap_or_default();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToBodyPart for ArchivedMessageMetadataContents {
|
||||
fn to_body_part(
|
||||
&self,
|
||||
part_id: u32,
|
||||
properties: &[EmailProperty],
|
||||
raw_message: &ChainedBytes<'_>,
|
||||
blob_id: &BlobId,
|
||||
blob_body_offset: isize,
|
||||
) -> Value<'static, EmailProperty, EmailValue> {
|
||||
let mut parts = vec![part_id].into_iter();
|
||||
let mut parts_stack = Vec::new();
|
||||
let mut subparts = Vec::with_capacity(1);
|
||||
|
||||
loop {
|
||||
if let Some((part_id, part)) = parts
|
||||
.next()
|
||||
.map(|part_id| (part_id, &self.parts[part_id as usize]))
|
||||
{
|
||||
let mut values = Map::with_capacity(properties.len());
|
||||
let multipart = if let ArchivedMetadataPartType::Multipart(parts) = &part.body {
|
||||
parts.into()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
for property in properties {
|
||||
let value = match property {
|
||||
EmailProperty::PartId if multipart.is_none() => part_id.to_string().into(),
|
||||
EmailProperty::BlobId if multipart.is_none() => {
|
||||
let base_offset = blob_id.start_offset() as isize + blob_body_offset;
|
||||
let flags = part.flags.to_native();
|
||||
let encoding = if flags & PART_ENCODING_BASE64 != 0 {
|
||||
2
|
||||
} else if flags & PART_ENCODING_QP != 0 {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
BlobId::new_section(
|
||||
blob_id.hash.clone(),
|
||||
blob_id.class.clone(),
|
||||
(u32::from(part.offset_body) as isize + base_offset) as usize,
|
||||
(u32::from(part.offset_end) as isize + base_offset) as usize,
|
||||
encoding,
|
||||
)
|
||||
.into()
|
||||
}
|
||||
EmailProperty::Size if multipart.is_none() => {
|
||||
(part.flags.to_native() & PART_SIZE_MASK).into()
|
||||
}
|
||||
EmailProperty::Name => part.attachment_name().map(|v| v.to_string()).into(),
|
||||
EmailProperty::Type => part
|
||||
.content_type()
|
||||
.map(|ct| {
|
||||
ct.subtype()
|
||||
.map(|st| format!("{}/{}", ct.ctype(), st))
|
||||
.unwrap_or_else(|| ct.ctype().to_string())
|
||||
})
|
||||
.or_else(|| match &part.body {
|
||||
ArchivedMetadataPartType::Text => Some("text/plain".to_string()),
|
||||
ArchivedMetadataPartType::Html => Some("text/html".to_string()),
|
||||
ArchivedMetadataPartType::Message(_) => {
|
||||
Some("message/rfc822".to_string())
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.into(),
|
||||
EmailProperty::Charset => {
|
||||
part.content_type()
|
||||
.and_then(|ct| ct.attribute("charset"))
|
||||
.or(match &part.body {
|
||||
ArchivedMetadataPartType::Text
|
||||
| ArchivedMetadataPartType::Html => Some("us-ascii"),
|
||||
_ => None,
|
||||
})
|
||||
.map(|v| v.to_string())
|
||||
.into()
|
||||
}
|
||||
EmailProperty::Disposition => part
|
||||
.content_disposition()
|
||||
.map(|cd| cd.ctype())
|
||||
.map(|v| v.to_string())
|
||||
.into(),
|
||||
EmailProperty::Cid => part.content_id().map(|v| v.to_string()).into(),
|
||||
EmailProperty::Language => match part.content_language() {
|
||||
ArchivedMetadataHeaderValue::Text(text) => {
|
||||
vec![text.to_string()].into()
|
||||
}
|
||||
ArchivedMetadataHeaderValue::TextList(list) => list
|
||||
.iter()
|
||||
.map(|text| text.to_string().into())
|
||||
.collect::<Vec<Value<'static, EmailProperty, EmailValue>>>()
|
||||
.into(),
|
||||
_ => Value::Null,
|
||||
},
|
||||
EmailProperty::Location => {
|
||||
part.content_location().map(|v| v.to_string()).into()
|
||||
}
|
||||
EmailProperty::Header(_) => part.header_to_value(property, raw_message),
|
||||
EmailProperty::Headers => part.headers_to_value(raw_message),
|
||||
EmailProperty::SubParts => continue,
|
||||
_ => Value::Null,
|
||||
};
|
||||
values.insert_unchecked(property.clone(), value);
|
||||
}
|
||||
|
||||
subparts.push(values);
|
||||
|
||||
if let Some(multipart) = multipart {
|
||||
if parts_stack.len() == 10_000 {
|
||||
debug_assert!(false, "Too much nesting in message metadata");
|
||||
return Value::Null;
|
||||
}
|
||||
let multipart = multipart
|
||||
.iter()
|
||||
.map(|id| u16::from(id) as u32)
|
||||
.collect::<Vec<_>>();
|
||||
parts_stack.push((
|
||||
parts,
|
||||
std::mem::replace(&mut subparts, Vec::with_capacity(multipart.len())),
|
||||
));
|
||||
parts = multipart.into_iter();
|
||||
}
|
||||
} else if let Some((prev_parts, mut prev_subparts)) = parts_stack.pop() {
|
||||
prev_subparts
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.insert_unchecked(EmailProperty::SubParts, subparts);
|
||||
parts = prev_parts;
|
||||
subparts = prev_subparts;
|
||||
} else {
|
||||
return subparts.pop().map(Into::into).unwrap_or_default();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait TruncateBody {
|
||||
fn truncate(&self, max_len: usize) -> (bool, String);
|
||||
}
|
||||
|
||||
impl TruncateBody for PartType<'_> {
|
||||
fn truncate(&self, max_len: usize) -> (bool, String) {
|
||||
match self {
|
||||
PartType::Text(text) => truncate_plain(text, max_len),
|
||||
PartType::Html(html) => truncate_html(html, max_len),
|
||||
PartType::Binary(bytes) | PartType::InlineBinary(bytes) => {
|
||||
PartType::Text(String::from_utf8_lossy(bytes)).truncate(max_len)
|
||||
}
|
||||
_ => (false, "".into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn truncate_plain(text: &str, mut max_len: usize) -> (bool, String) {
|
||||
if max_len != 0 && text.len() > max_len {
|
||||
let add_dots = max_len > 6;
|
||||
if add_dots {
|
||||
max_len -= 3;
|
||||
}
|
||||
let mut result = String::with_capacity(max_len);
|
||||
for ch in text.chars() {
|
||||
if ch != '\r' {
|
||||
if ch.len_utf8() + result.len() > max_len {
|
||||
break;
|
||||
}
|
||||
result.push(ch);
|
||||
}
|
||||
}
|
||||
if add_dots {
|
||||
result.push_str("...");
|
||||
}
|
||||
(true, result)
|
||||
} else {
|
||||
(false, text.replace('\r', ""))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn truncate_html(html: &str, mut max_len: usize) -> (bool, String) {
|
||||
if max_len != 0 && html.len() > max_len {
|
||||
let add_dots = max_len > 6;
|
||||
if add_dots {
|
||||
max_len -= 3;
|
||||
}
|
||||
|
||||
let mut result = String::with_capacity(max_len);
|
||||
let mut in_tag = false;
|
||||
let mut in_comment = false;
|
||||
let mut last_tag_end_pos = 0;
|
||||
let mut cr_count = 0;
|
||||
for (pos, ch) in html.char_indices() {
|
||||
let mut set_last_tag = 0;
|
||||
match ch {
|
||||
'<' if !in_tag => {
|
||||
in_tag = true;
|
||||
if let Some("!--") = html.get(pos + 1..pos + 4) {
|
||||
in_comment = true;
|
||||
}
|
||||
set_last_tag = pos;
|
||||
}
|
||||
'>' if in_tag => {
|
||||
if in_comment {
|
||||
if let Some("--") = html.get(pos - 2..pos) {
|
||||
in_comment = false;
|
||||
in_tag = false;
|
||||
set_last_tag = pos + 1;
|
||||
}
|
||||
} else {
|
||||
in_tag = false;
|
||||
set_last_tag = pos + 1;
|
||||
}
|
||||
}
|
||||
'\r' => {
|
||||
cr_count += 1;
|
||||
continue;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
if ch.len_utf8() + pos - cr_count > max_len {
|
||||
result.push_str(
|
||||
&html[0..if (in_tag || set_last_tag > 0) && last_tag_end_pos > 0 {
|
||||
last_tag_end_pos
|
||||
} else {
|
||||
pos
|
||||
}]
|
||||
.replace('\r', ""),
|
||||
);
|
||||
if add_dots {
|
||||
result.push_str("...");
|
||||
}
|
||||
break;
|
||||
} else if set_last_tag > 0 {
|
||||
last_tag_end_pos = set_last_tag;
|
||||
}
|
||||
}
|
||||
(true, result)
|
||||
} else {
|
||||
(false, html.replace('\r', ""))
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user