1 parent
96a32ea8fb
commit
a643e7e3ab
106 files changed
+2223
-4669
No files matched your search
+7
-25
@@ -1,7 +1,5 @@
|
||||
use super::types::EmailAddress;
|
||||
|
||||
// ---- Helper: convert NameAttribute to string ------------------------------
|
||||
|
||||
pub(crate) fn name_attribute_to_string(attr: &async_imap::types::NameAttribute<'_>) -> String {
|
||||
use async_imap::types::NameAttribute;
|
||||
match attr {
|
||||
@@ -21,8 +19,6 @@ pub(crate) fn name_attribute_to_string(attr: &async_imap::types::NameAttribute<'
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Helper: convert Flag to string ---------------------------------------
|
||||
|
||||
pub(crate) fn flag_to_string(flag: &async_imap::types::Flag<'_>) -> String {
|
||||
use async_imap::types::Flag;
|
||||
match flag {
|
||||
@@ -37,11 +33,7 @@ pub(crate) fn flag_to_string(flag: &async_imap::types::Flag<'_>) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Helper: decode RFC 2047 encoded words --------------------------------
|
||||
|
||||
/// Decode RFC 2047 encoded-word sequences like `=?utf-8?b?SGVsbG8=?=`.
|
||||
pub(crate) fn decode_rfc2047(input: &str) -> String {
|
||||
// Quick check: if there's no encoded-word marker, return as-is.
|
||||
if !input.contains("=?") {
|
||||
return input.to_string();
|
||||
}
|
||||
@@ -50,11 +42,10 @@ pub(crate) fn decode_rfc2047(input: &str) -> String {
|
||||
let mut remaining = input;
|
||||
|
||||
while let Some(start) = remaining.find("=?") {
|
||||
// Add any text before the encoded word.
|
||||
result.push_str(&remaining[..start]);
|
||||
|
||||
let after_start = &remaining[start + 2..];
|
||||
// Find charset?encoding?text?= pattern.
|
||||
|
||||
if let Some(q1) = after_start.find('?') {
|
||||
let charset = &after_start[..q1];
|
||||
let after_charset = &after_start[q1 + 1..];
|
||||
@@ -77,15 +68,13 @@ pub(crate) fn decode_rfc2047(input: &str) -> String {
|
||||
let text = decode_charset(charset, &bytes);
|
||||
result.push_str(&text);
|
||||
} else {
|
||||
// Failed to decode — keep the original encoded word.
|
||||
let full_len = 2 + q1 + 1 + q2 + 1 + end + 2;
|
||||
result.push_str(&remaining[start..start + full_len]);
|
||||
}
|
||||
|
||||
// Skip past the encoded word.
|
||||
let skip = q1 + 1 + q2 + 1 + end + 2;
|
||||
remaining = &after_start[skip..];
|
||||
// Strip whitespace between consecutive encoded words (RFC 2047 §6.2).
|
||||
|
||||
if remaining.starts_with("=?") || remaining.trim_start().starts_with("=?") {
|
||||
remaining = remaining.trim_start();
|
||||
}
|
||||
@@ -94,7 +83,6 @@ pub(crate) fn decode_rfc2047(input: &str) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
// Malformed encoded word — keep the `=?` and move past it.
|
||||
result.push_str("=?");
|
||||
remaining = after_start;
|
||||
}
|
||||
@@ -103,7 +91,6 @@ pub(crate) fn decode_rfc2047(input: &str) -> String {
|
||||
result
|
||||
}
|
||||
|
||||
/// Decode quoted-printable as used in RFC 2047 headers (underscores = spaces).
|
||||
pub(crate) fn decode_quoted_printable_header(input: &str) -> Option<Vec<u8>> {
|
||||
let mut bytes = Vec::with_capacity(input.len());
|
||||
let mut chars = input.bytes();
|
||||
@@ -124,20 +111,19 @@ pub(crate) fn decode_quoted_printable_header(input: &str) -> Option<Vec<u8>> {
|
||||
Some(bytes)
|
||||
}
|
||||
|
||||
/// Best-effort charset decoding. UTF-8 and Latin-1 are the most common.
|
||||
pub(crate) fn decode_charset(charset: &str, bytes: &[u8]) -> String {
|
||||
match charset.to_ascii_lowercase().as_str() {
|
||||
"utf-8" | "utf8" => String::from_utf8_lossy(bytes).into_owned(),
|
||||
"iso-8859-1" | "latin1" | "latin-1" => bytes.iter().map(|&b| b as char).collect(),
|
||||
// For other charsets, try UTF-8 first, then fall back to Latin-1.
|
||||
|
||||
_ => String::from_utf8(bytes.to_vec())
|
||||
.unwrap_or_else(|_| bytes.iter().map(|&b| b as char).collect()),
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Helper: convert imap_proto::Address to EmailAddress ------------------
|
||||
|
||||
pub(crate) fn imap_address_to_email(addr: &async_imap::imap_proto::types::Address<'_>) -> EmailAddress {
|
||||
pub(crate) fn imap_address_to_email(
|
||||
addr: &async_imap::imap_proto::types::Address<'_>,
|
||||
) -> EmailAddress {
|
||||
let name = addr
|
||||
.name
|
||||
.as_ref()
|
||||
@@ -164,24 +150,20 @@ pub(crate) fn imap_address_to_email(addr: &async_imap::imap_proto::types::Addres
|
||||
EmailAddress { name, address }
|
||||
}
|
||||
|
||||
// ---- Helper: detect attachments from BODYSTRUCTURE -----------------------
|
||||
|
||||
pub(crate) fn has_attachments(bs: &async_imap::imap_proto::types::BodyStructure<'_>) -> bool {
|
||||
use async_imap::imap_proto::types::BodyStructure;
|
||||
match bs {
|
||||
BodyStructure::Basic { common, .. } => {
|
||||
// Check for explicit "attachment" disposition.
|
||||
if let Some(ref disp) = common.disposition
|
||||
&& disp.ty.eq_ignore_ascii_case("attachment")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// Non-text basic parts (image, application, audio, video) are likely attachments.
|
||||
|
||||
let ty = common.ty.ty.to_ascii_lowercase();
|
||||
matches!(ty.as_str(), "application" | "image" | "audio" | "video")
|
||||
}
|
||||
BodyStructure::Text { common, .. } => {
|
||||
// Text parts with "attachment" disposition count.
|
||||
if let Some(ref disp) = common.disposition {
|
||||
return disp.ty.eq_ignore_ascii_case("attachment");
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user