mirror of
https://github.com/pimalaya/himalaya.git
synced 2026-06-17 13:17:55 +08:00
decode envelope subject and addresses
This commit is contained in:
Generated
+133
-3
@@ -20,6 +20,12 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.21"
|
||||
@@ -76,6 +82,15 @@ version = "1.0.102"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
|
||||
[[package]]
|
||||
name = "ar_archive_writer"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b"
|
||||
dependencies = [
|
||||
"object",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ariadne"
|
||||
version = "0.2.0"
|
||||
@@ -189,6 +204,16 @@ version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "charset"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1f927b07c74ba84c7e5fe4db2baeb3e996ab2688992e39ac68ce3220a677c7e"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"encoding_rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.44"
|
||||
@@ -198,6 +223,20 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chumsky"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ba4a05c9ce83b07de31b31c874e87c069881ac4355db9e752e3a55c11ec75a6"
|
||||
dependencies = [
|
||||
"hashbrown 0.15.5",
|
||||
"regex-automata 0.3.9",
|
||||
"serde",
|
||||
"stacker",
|
||||
"unicode-ident",
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.60"
|
||||
@@ -414,6 +453,15 @@ version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_filter"
|
||||
version = "1.0.0"
|
||||
@@ -556,6 +604,8 @@ version = "0.15.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"equivalent",
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
@@ -593,6 +643,7 @@ dependencies = [
|
||||
"log",
|
||||
"native-tls",
|
||||
"pimalaya-toolbox",
|
||||
"rfc2047-decoder",
|
||||
"rustls",
|
||||
"rustls-platform-verifier",
|
||||
"secrecy",
|
||||
@@ -1021,6 +1072,15 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.37.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
@@ -1191,6 +1251,16 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psm"
|
||||
version = "0.1.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3852766467df634d74f0b2d7819bf8dc483a0eb2e3b0f50f756f9cfe8b0d18d8"
|
||||
dependencies = [
|
||||
"ar_archive_writer",
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.44"
|
||||
@@ -1200,6 +1270,12 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quoted_printable"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "640c9bd8497b02465aeef5375144c26062e0dcd5939dfcbb0f5db76cb8c17c73"
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.3.0"
|
||||
@@ -1270,8 +1346,19 @@ checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
"regex-automata 0.4.14",
|
||||
"regex-syntax 0.8.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax 0.7.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1282,15 +1369,35 @@ checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
"regex-syntax 0.8.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
||||
|
||||
[[package]]
|
||||
name = "rfc2047-decoder"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "504ea008279c473dfeafce327fa26bf8825463cfcc06ef82f866187e81334d16"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"charset",
|
||||
"chumsky",
|
||||
"memchr",
|
||||
"quoted_printable",
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.14"
|
||||
@@ -1587,6 +1694,20 @@ version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
||||
|
||||
[[package]]
|
||||
name = "stacker"
|
||||
version = "0.1.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d74a23609d509411d10e2176dc2a4346e3b4aea2e7b1869f19fdedbc71c013"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"psm",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
@@ -2010,6 +2131,15 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.60.2"
|
||||
|
||||
@@ -46,6 +46,7 @@ io-imap = { version = "0.0.1", default-features = false, optional = true }
|
||||
io-process = { version = "0.0.2", default-features = false }
|
||||
io-stream = { version = "0.0.2", default-features = false, features = ["std"] }
|
||||
log = "0.4"
|
||||
rfc2047-decoder = "1"
|
||||
native-tls = { version = "0.2", optional = true }
|
||||
pimalaya-toolbox = { version = "0.0.4", default-features = false, features = ["config", "terminal", "secret", "command"] }
|
||||
rustls = { version = "0.23", default-features = false, optional = true }
|
||||
|
||||
@@ -17,7 +17,7 @@ use serde::{Serialize, Serializer};
|
||||
use crate::{
|
||||
config::ImapConfig,
|
||||
imap::{
|
||||
envelope::command::list::format_addresses,
|
||||
envelope::command::list::{decode_mime, format_addresses},
|
||||
mailbox::arg::name::MailboxNameOptionalFlag,
|
||||
stream,
|
||||
},
|
||||
@@ -125,7 +125,7 @@ impl EnvelopeDetailTable {
|
||||
detail.date = String::from_utf8_lossy(d.as_ref()).to_string();
|
||||
}
|
||||
if let Some(s) = &env.subject.0 {
|
||||
detail.subject = String::from_utf8_lossy(s.as_ref()).to_string();
|
||||
detail.subject = decode_mime(&String::from_utf8_lossy(s.as_ref()));
|
||||
}
|
||||
if let Some(m) = &env.message_id.0 {
|
||||
detail.message_id = String::from_utf8_lossy(m.as_ref()).to_string();
|
||||
|
||||
@@ -7,16 +7,31 @@ use io_imap::{
|
||||
coroutines::{fetch::*, select::*},
|
||||
types::{
|
||||
core::Vec1,
|
||||
envelope::Address,
|
||||
fetch::{MacroOrMessageDataItemNames, MessageDataItem, MessageDataItemName},
|
||||
sequence::SequenceSet,
|
||||
},
|
||||
};
|
||||
use io_stream::runtimes::std::handle;
|
||||
use log::debug;
|
||||
use pimalaya_toolbox::terminal::printer::Printer;
|
||||
use rfc2047_decoder::{Decoder, RecoverStrategy};
|
||||
use serde::{Serialize, Serializer};
|
||||
|
||||
use crate::{config::ImapConfig, imap::mailbox::arg::name::MailboxNameOptionalArg, imap::stream};
|
||||
|
||||
/// Decode RFC 2047 MIME-encoded string, falling back to original on error.
|
||||
pub fn decode_mime(s: &str) -> String {
|
||||
let decoder = Decoder::new().too_long_encoded_word_strategy(RecoverStrategy::Decode);
|
||||
match decoder.decode(s.as_bytes()) {
|
||||
Ok(s) => s,
|
||||
Err(err) => {
|
||||
debug!("cannot decode rfc2047 string `{s}`: {err}");
|
||||
s.to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// List message envelopes in a mailbox.
|
||||
///
|
||||
/// This command displays envelopes for messages in the specified
|
||||
@@ -58,9 +73,8 @@ impl ListEnvelopesCommand {
|
||||
let sequence_set: SequenceSet = self.sequence.parse()?;
|
||||
|
||||
// FETCH envelopes
|
||||
let item_names = MacroOrMessageDataItemNames::MessageDataItemNames(vec![
|
||||
MessageDataItemName::Envelope,
|
||||
]);
|
||||
let item_names =
|
||||
MacroOrMessageDataItemNames::MessageDataItemNames(vec![MessageDataItemName::Envelope]);
|
||||
|
||||
let mut arg = None;
|
||||
let mut coroutine = ImapFetch::new(context, sequence_set, item_names, self.uid);
|
||||
@@ -119,9 +133,9 @@ impl EnvelopesTable {
|
||||
date = String::from_utf8_lossy(d.as_ref()).to_string();
|
||||
}
|
||||
if let Some(s) = &env.subject.0 {
|
||||
subject = String::from_utf8_lossy(s.as_ref()).to_string();
|
||||
subject = decode_mime(String::from_utf8_lossy(s.as_ref()).as_ref());
|
||||
}
|
||||
from = format_addresses(&env.from);
|
||||
from = format_addresses_short(&env.from);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -138,10 +152,7 @@ impl EnvelopesTable {
|
||||
|
||||
entries.sort_by_key(|e| e.id);
|
||||
|
||||
Self {
|
||||
entries,
|
||||
uid_mode,
|
||||
}
|
||||
Self { entries, uid_mode }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,10 +195,8 @@ impl Serialize for EnvelopesTable {
|
||||
}
|
||||
}
|
||||
|
||||
use io_imap::types::envelope::Address;
|
||||
|
||||
pub fn format_address(addr: &Address<'_>) -> String {
|
||||
// NString wraps Option<IString>, access via .0
|
||||
/// Format email address from mailbox and host parts.
|
||||
fn format_email(addr: &Address<'_>) -> String {
|
||||
let mailbox = addr
|
||||
.mailbox
|
||||
.0
|
||||
@@ -200,24 +209,49 @@ pub fn format_address(addr: &Address<'_>) -> String {
|
||||
.as_ref()
|
||||
.map(|h| String::from_utf8_lossy(h.as_ref()).to_string())
|
||||
.unwrap_or_default();
|
||||
let name = addr
|
||||
.name
|
||||
.0
|
||||
.as_ref()
|
||||
.map(|n| String::from_utf8_lossy(n.as_ref()).to_string());
|
||||
|
||||
let email = if !mailbox.is_empty() && !host.is_empty() {
|
||||
if !mailbox.is_empty() && !host.is_empty() {
|
||||
format!("{mailbox}@{host}")
|
||||
} else {
|
||||
mailbox
|
||||
};
|
||||
|
||||
match name {
|
||||
Some(n) if !n.is_empty() => format!("{n} <{email}>"),
|
||||
_ => email,
|
||||
}
|
||||
}
|
||||
|
||||
/// Short format for list view (name OR email, not both).
|
||||
pub fn format_address_short(addr: &Address<'_>) -> String {
|
||||
// If name exists, show decoded name only
|
||||
if let Some(n) = &addr.name.0 {
|
||||
let name = decode_mime(&String::from_utf8_lossy(n.as_ref()));
|
||||
if !name.is_empty() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
// Otherwise show email
|
||||
format_email(addr)
|
||||
}
|
||||
|
||||
/// Full format for detailed view (Name <email> or email).
|
||||
pub fn format_address(addr: &Address<'_>) -> String {
|
||||
let email = format_email(addr);
|
||||
if let Some(n) = &addr.name.0 {
|
||||
let name = decode_mime(&String::from_utf8_lossy(n.as_ref()));
|
||||
if !name.is_empty() {
|
||||
return format!("{name} <{email}>");
|
||||
}
|
||||
}
|
||||
email
|
||||
}
|
||||
|
||||
/// Short addresses formatter for list view.
|
||||
pub fn format_addresses_short(addrs: &[Address<'_>]) -> String {
|
||||
addrs
|
||||
.iter()
|
||||
.map(format_address_short)
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
}
|
||||
|
||||
/// Full addresses formatter for detailed view.
|
||||
pub fn format_addresses(addrs: &[Address<'_>]) -> String {
|
||||
addrs
|
||||
.iter()
|
||||
|
||||
@@ -17,7 +17,9 @@ use serde::{Serialize, Serializer};
|
||||
use crate::{
|
||||
config::ImapConfig,
|
||||
imap::{
|
||||
envelope::command::search::parse_query, mailbox::arg::name::MailboxNameOptionalArg, stream,
|
||||
envelope::command::{list::decode_mime, search::parse_query},
|
||||
mailbox::arg::name::MailboxNameOptionalArg,
|
||||
stream,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -188,7 +190,7 @@ fn fetch_subjects(
|
||||
MessageDataItem::Envelope(env) => {
|
||||
// NString wraps Option<IString>, access via .0
|
||||
if let Some(s) = &env.subject.0 {
|
||||
subject = String::from_utf8_lossy(s.as_ref()).to_string();
|
||||
subject = decode_mime(&String::from_utf8_lossy(s.as_ref()));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
||||
Reference in New Issue
Block a user