65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
|
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
|
-
+
+
|
/// Initializes the mail server: sets up the Telegram API client and
/// validates all required configuration values.
///
/// # Arguments
/// * `settings` - Parsed application configuration.
///
/// # Errors
/// Returns an error if required configuration values are missing or invalid.
/// Returns an error if required configuration values are missing or invalid,
/// Telegram API client creation fails, or the address regex cannot be built.
pub fn new (settings: config::Config) -> Result<MailServer> {
let api_key = settings.get_string("api_key")
.context("[smtp2tg.toml] missing \"api_key\" parameter.\n")?;
let mut recipients = HashMap::new();
for (name, value) in settings.get_table("recipients")
.context("[smtp2tg.toml] missing table \"recipients\".\n")?
{
|
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
|
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
|
-
+
|
}
let re_domains = domains.iter().map(|s| escape(s))
.collect::<Vec<String>>().join("|");
let address = RegexBuilder::new(&format!("^[a-z0-9][a-z0-9.-]*(@({re_domains}))?$"))
.case_insensitive(true).build().stack()?;
Ok(MailServer {
data: vec!(),
data: vec![],
headers: None,
tg,
fields,
address,
})
}
|
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
|
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
|
+
+
+
+
+
+
+
-
+
+
+
+
+
+
|
pub fn get_id (&self, name: &str) -> Result<&ChatPeerId> {
if self.address.is_match(name) {
Ok(self.tg.get(name).unwrap_or(&self.tg.default))
} else {
bail!("Email address {name:?} is not from an allowed domain.");
} }
/// Forwards the buffered email to each distinct destination chat, using the
/// default chat for allowed addresses without a configured recipient.
///
/// Inlines the first plain-text body if the escaped message is below 4096
/// bytes. Sends remaining text parts and decoded attachments as documents;
/// HTML body parts are omitted. Leaves the buffered data and envelope intact.
///
/// Attempt to deliver one message
/// # Errors
/// Returns an error for a missing envelope, no recipients, a disallowed
/// address, mail parsing or part extraction failures, or rejected closing
/// `pre`/`code` tags in text passed to validation. Propagates media-group
/// construction and Telegram request errors, including diagnostic sends.
/// Delivery to earlier chats is not rolled back if a later send fails.
async fn relay_mail (&self) -> Result<()> {
if let Some(headers) = &self.headers {
let mail = mail_parser::MessageParser::new().parse(&self.data)
.context("Failed to parse mail.")?;
// Adding all known addresses to recipient list, for anyone else adding default
// Also if list is empty also adding default
|
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
|
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
|
-
-
-
+
+
-
-
-
-
+
+
-
-
-
+
-
-
-
-
-
-
-
-
-
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
|
&& let Some(date) = mail.date()
{
reply.push(format!("<u><i>Date:</i></u> <code>{date}</code>"));
}
reply.push("</blockquote><pre>".into());
let reply = reply.join("\n");
let html_parts = mail.html_body_count();
let text_parts = mail.text_body_count();
let attachments = mail.attachment_count();
let text_parts = mail.text_body().len();
let html_parts = mail.html_body().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();
// Display the first text part as the body if it fits.
let mut body = String::new();
/*
* actually I don't wanna parse that html stuff
if html_parts > 0 {
if text_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 {
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: </pre>
if text.len() < 4096 - ( reply.len() + 6 ) {
body = text.to_string();
body = text.into_owned();
text_num = 1;
}
};
let msg = format!("{}{}</pre>", reply, body);
// and let's collect all other attachment parts
let mut files_to_send = vec![];
// Send remaining text-body parts followed by attachments.
let files_to_send: Vec<_> = mail.text_body().skip(text_num)
/*
* 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_part(text_num.try_into().stack()?)
.context("Failed to get text part from message.")?);
text_num += 1;
}
while file_num < attachments {
files_to_send.push(mail.attachment(file_num.try_into().stack()?)
.chain(mail.attachments()).collect();
.context("Failed to get file part from message.")?);
file_num += 1;
}
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<u8> = chunk.contents().to_vec();
let data: Vec<u8> = chunk.decoded().to_vec();
let mut filename: Option<String> = None;
for header in chunk.headers() {
if header.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?;
},
};
};
for header in chunk.headers().iter()
.filter(|header| header.raw_name().eq_ignore_ascii_case("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()
};
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?;
}
}
|