Index: src/mail.rs
==================================================================
--- src/mail.rs
+++ src/mail.rs
@@ -185,94 +185,59 @@
reply.push("
".into());
let reply = reply.join("\n");
let text_parts = mail.text_body().len();
let html_parts = mail.html_body().len();
- let attachments = mail.attachments().len();
if html_parts != text_parts {
self.tg.debug(&format!("Hm, we have {html_parts} HTML parts and {text_parts} text parts.")).await?;
}
- //let mut html_num = 0;
let mut text_num = 0;
- let mut file_num = 0;
- // let's display first html or text part as body
- let mut body: String = "".into();
- /*
- * actually I don't wanna parse that html stuff
- if html_parts > 0 {
- let text = mail.body_html(0).stack()?;
- if text.len() < 4096 - header_size {
- body = text;
- html_num = 1;
- }
- };
- */
- if body.is_empty() && text_parts > 0 {
+ // Display the first text part as the body if it fits.
+ let mut body = String::new();
+ if text_parts > 0 {
let text = mail.body_text(0)
.context("Failed to extract text from message")?
.replace("\r\n", "\n");
let text = validate(&text).stack()?;
// 6:
// - (headers)
// - (mail text)
// - 6:
if text.len() < 4096 - ( reply.len() + 6 ) {
- body = text.to_string();
+ body = text.into_owned();
text_num = 1;
}
};
let msg = format!("{}{}", reply, body);
- // and let's collect all other attachment parts
- let mut files_to_send = vec![];
- /*
- * let's just skip html parts for now, they just duplicate text?
- while html_num < html_parts {
- files_to_send.push(mail.html_part(html_num).stack()?);
- html_num += 1;
- }
- */
- while text_num < text_parts {
- files_to_send.push(mail.text_body().nth(text_num)
- .context("Failed to get text part from message.")?);
- text_num += 1;
- }
- while file_num < attachments {
- files_to_send.push(mail.attachments().nth(file_num)
- .context("Failed to get file part from message.")?);
- file_num += 1;
- }
+ // Send remaining text-body parts followed by attachments.
+ let files_to_send: Vec<_> = mail.text_body().skip(text_num)
+ .chain(mail.attachments()).collect();
for chat in rcpt {
if !files_to_send.is_empty() {
let mut files = vec![];
- // let mut first_one = true;
for chunk in &files_to_send {
let data: Vec = chunk.decoded().to_vec();
let mut filename: Option = None;
- for header in chunk.headers() {
- if header.raw_name() == "Content-Type" {
- match header.value() {
- mail_parser::HeaderValue::ContentType(contenttype) => {
- if let Some(fname) = contenttype.attribute("name") {
- filename = Some(fname.to_owned());
- }
- },
- _ => {
- self.tg.debug("Attachment has bad ContentType header.").await?;
- },
- };
- };
- };
- let filename = if let Some(fname) = filename {
- fname
- } else {
- "Attachment.txt".into()
+ for header in chunk.headers().iter()
+ .filter(|header| header.raw_name() == "Content-Type")
+ {
+ match header.value() {
+ mail_parser::HeaderValue::ContentType(contenttype) => {
+ if let Some(fname) = contenttype.attribute("name") {
+ filename = Some(fname.to_owned());
+ }
+ },
+ _ => {
+ self.tg.debug("Attachment has bad ContentType header.").await?;
+ },
+ };
};
files.push(Attachment {
data: Cursor::new(data),
- name: filename,
+ name: filename.unwrap_or_else(|| "Attachment.txt".into()),
});
}
self.tg.sendgroup(chat, files, &msg).await?;
} else {
self.tg.send(chat, &msg).await?;
Index: src/main.rs
==================================================================
--- src/main.rs
+++ src/main.rs
@@ -11,13 +11,11 @@
os::unix::fs::PermissionsExt,
path::Path,
};
use clap::Parser;
-use smol::{
- fs::metadata,
-};
+use smol::fs::metadata;
use stacked_errors::{
Result,
StackableErr,
bail,
};
@@ -49,39 +47,42 @@
///
/// # Panics
/// Panics if configuring or serving the SMTP server fails.
async fn main (ex: &LocalExecutor<'_>) -> Result<()> {
let args = Args::parse();
- let config_file = Path::new(&args.config);
- if !config_file.try_exists().stack()? {
- bail!("Configuration file not found: {config_file:?}\n\
- Hint: Ensure the file exists and the path is correct.");
+ let settings = {
+ // Load configuration defaults and overrides after checking file permissions.
+ let config_file = Path::new(&args.config);
+ if !config_file.try_exists().stack()? {
+ bail!("Configuration file not found: {config_file:?}\n\
+ Hint: Ensure the file exists and the path is correct.");
+ };
+ {
+ let meta = metadata(config_file).await.stack()?;
+ if (!0o100600 & meta.permissions().mode()) > 0 {
+ bail!("Configuration file permissions are insecure {config_file:?}\n\
+ Current permissions: {:o}\n\
+ Required: 0600 (owner read/write only).\n\
+ Fix with: chmod 600 {config_file:?}",
+ meta.permissions().mode());
+ } }
+ config::Config::builder()
+ .set_default("api_gateway", "https://api.telegram.org").stack()?
+ .set_default("fields", vec!["date", "from", "subject"]).stack()?
+ .set_default("hostname", "smtp.2.tg").stack()?
+ .set_default("listen_on", "0.0.0.0:1025").stack()?
+ .set_default("domains", vec!["localhost",
+ hostname::get().context("Failed to get current hostname")?
+ .to_str().context("Can't convert hostname to string, bad UTF-8?")?]).stack()?
+ .add_source(config::File::from(config_file))
+ .build()
+ .with_context(|| format!(
+ "Failed to parse configuration file: {config_file:?}\n\
+ Check syntax against smtp2tg.toml.example.\n\
+ Common issues: missing quotes, trailing commas in inline tables, or invalid types."
+ ))?
};
- {
- let meta = metadata(config_file).await.stack()?;
- if (!0o100600 & meta.permissions().mode()) > 0 {
- bail!("Configuration file permissions are insecure {config_file:?}\n\
- Current permissions: {:o}\n\
- Required: 0600 (owner read/write only).\n\
- Fix with: chmod 600 {config_file:?}",
- meta.permissions().mode());
- } }
- let settings: config::Config = config::Config::builder()
- .set_default("api_gateway", "https://api.telegram.org").stack()?
- .set_default("fields", vec!["date", "from", "subject"]).stack()?
- .set_default("hostname", "smtp.2.tg").stack()?
- .set_default("listen_on", "0.0.0.0:1025").stack()?
- .set_default("domains", vec!["localhost",
- hostname::get().context("Failed to get current hostname")?
- .to_str().context("Can't convert hostname to string, bad UTF-8?")?]).stack()?
- .add_source(config::File::from(config_file))
- .build()
- .with_context(|| format!(
- "Failed to parse configuration file: {config_file:?}\n\
- Check syntax against smtp2tg.toml.example.\n\
- Common issues: missing quotes, trailing commas in inline tables, or invalid types."
- ))?;
let listen_on = settings.get_string("listen_on").stack()?;
let server_name = settings.get_string("hostname").stack()?;
let core = MailServer::new(settings)?;
let mut server = mailin_embedded::Server::new(core);