1use std::{path::Path, sync::Arc};
2
3use lettre::{
4 message::{header::ContentType, Attachment, Mailbox, MultiPart, SinglePart},
5 transport::smtp::{
6 authentication::{Credentials, Mechanism},
7 client::{Tls, TlsParametersBuilder},
8 },
9 AsyncFileTransport, AsyncSendmailTransport, AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor,
10};
11use serde::Serialize;
12use serde_json::Value;
13use tiny_web_macro::fnv1a_64;
14use tokio::fs::create_dir_all;
15
16use super::{data::Data, dbs::adapter::DB, log::Log};
17
18#[derive(Debug, Clone, Serialize)]
26pub struct MailBodyFile {
27 pub name: String,
29 pub mime: Option<String>,
31 pub data: Vec<u8>,
33}
34
35#[derive(Debug, Clone, Serialize)]
43pub struct MailBodyHtml {
44 pub text: Option<String>,
46 pub html: String,
48 pub file: Vec<MailBodyFile>,
50}
51
52#[derive(Debug, Clone, Serialize)]
54pub enum MailBody {
55 Text(String),
57 Html(MailBodyHtml),
59 File(MailBodyFile),
61}
62
63#[derive(Debug, Clone, Serialize)]
65pub struct MailMessage {
66 pub to: Vec<String>,
68 pub cc: Option<Vec<String>>,
70 pub bcc: Option<Vec<String>>,
72 pub from: String,
74 pub reply_to: Option<String>,
76 pub subject: String,
78 pub body: Vec<MailBody>,
80}
81
82#[derive(Debug, Clone)]
84pub struct SmtpInfo {
85 server: String,
87 port: u16,
89 tls: Tls,
91 authentication: Vec<Mechanism>,
93 credentials: Option<Credentials>,
95}
96
97#[derive(Debug, Clone)]
99pub enum MailProvider {
100 None,
102 Sendmail(String),
104 SMTP(SmtpInfo),
106 File(String),
108}
109
110#[derive(Debug)]
112pub(crate) struct Mail {
113 pub provider: MailProvider,
115}
116
117impl Mail {
118 pub async fn new(db: Arc<DB>) -> Mail {
120 Mail { provider: Mail::get_provider(db).await }
121 }
122
123 async fn get_provider(db: Arc<DB>) -> MailProvider {
125 if !db.in_use() {
126 return MailProvider::None;
127 }
128 match db.query_prepare(fnv1a_64!("lib_get_setting"), &[&fnv1a_64!("mail:provider")], false).await {
129 Some(res) => {
130 if !res.is_empty() {
131 let row = if let Data::Vec(row) = unsafe { res.get_unchecked(0) } {
132 row
133 } else {
134 Log::warning(3011, Some("query:lib_get_setting:mail:provider".to_owned()));
135 return MailProvider::None;
136 };
137 if row.is_empty() {
138 Log::warning(3011, Some("query:lib_get_setting:mail:provider:empty".to_owned()));
139 return MailProvider::None;
140 }
141 let provider = if let Data::String(provider) = unsafe { row.get_unchecked(0) } {
142 provider.clone()
143 } else {
144 Log::warning(3011, Some("query:lib_get_setting:mail:provider:type".to_owned()));
145 return MailProvider::None;
146 };
147
148 match provider.as_ref() {
149 "Sendmail" => match db.query_prepare(fnv1a_64!("lib_get_setting"), &[&fnv1a_64!("mail:sendmail")], false).await {
150 Some(res) => {
151 if !res.is_empty() {
152 let row = if let Data::Vec(row) = unsafe { res.get_unchecked(0) } {
153 row
154 } else {
155 Log::warning(3011, Some("query:lib_get_setting:mail:sendmail".to_owned()));
156 return MailProvider::None;
157 };
158 if row.is_empty() {
159 Log::warning(3011, Some("query:lib_get_setting:mail:sendmail:empty".to_owned()));
160 return MailProvider::None;
161 }
162 let path = if let Data::String(path) = unsafe { row.get_unchecked(0) } {
163 path.clone()
164 } else {
165 Log::warning(3011, Some("query:lib_get_setting:mail:sendmail:type".to_owned()));
166 return MailProvider::None;
167 };
168 if !path.is_empty() {
169 MailProvider::File(path)
170 } else {
171 Log::warning(3011, Some("mail:sendmail".to_owned()));
172 MailProvider::None
173 }
174 } else {
175 Log::warning(3011, Some("mail:sendmail".to_owned()));
176 MailProvider::None
177 }
178 }
179 None => {
180 Log::warning(3011, Some("mail:sendmail".to_owned()));
181 MailProvider::None
182 }
183 },
184 "SMTP" => {
185 let server =
186 match db.query_prepare(fnv1a_64!("lib_get_setting"), &[&fnv1a_64!("mail:smtp:server")], false).await {
187 Some(res) => {
188 if !res.is_empty() {
189 let row = if let Data::Vec(row) = unsafe { res.get_unchecked(0) } {
190 row
191 } else {
192 Log::warning(3011, Some("query:lib_get_setting:mail:smtp:server".to_owned()));
193 return MailProvider::None;
194 };
195 if row.is_empty() {
196 Log::warning(3011, Some("query:lib_get_setting:mail:smtp:server:empty".to_owned()));
197 return MailProvider::None;
198 }
199 let server = if let Data::String(server) = unsafe { row.get_unchecked(0) } {
200 server.clone()
201 } else {
202 Log::warning(3011, Some("query:lib_get_setting:mail:smtp:server:type".to_owned()));
203 return MailProvider::None;
204 };
205 if !server.is_empty() {
206 server
207 } else {
208 Log::warning(3011, Some("mail:smtp:server".to_owned()));
209 return MailProvider::None;
210 }
211 } else {
212 Log::warning(3011, Some("mail:smtp:server".to_owned()));
213 return MailProvider::None;
214 }
215 }
216 None => {
217 Log::warning(3011, Some("mail:smtp:server".to_owned()));
218 return MailProvider::None;
219 }
220 };
221 let port = match db.query_prepare(fnv1a_64!("lib_get_setting"), &[&fnv1a_64!("mail:smtp:port")], false).await {
222 Some(res) => {
223 if !res.is_empty() {
224 let row = if let Data::Vec(row) = unsafe { res.get_unchecked(0) } {
225 row
226 } else {
227 Log::warning(3011, Some("query:lib_get_setting:mail:smtp:port".to_owned()));
228 return MailProvider::None;
229 };
230 if row.is_empty() {
231 Log::warning(3011, Some("query:lib_get_setting:mail:smtp:port:empty".to_owned()));
232 return MailProvider::None;
233 }
234 let port = if let Data::String(port) = unsafe { row.get_unchecked(0) } {
235 port.clone()
236 } else {
237 Log::warning(3011, Some("query:lib_get_setting:mail:smtp:port:type".to_owned()));
238 return MailProvider::None;
239 };
240 match port.parse::<u16>() {
241 Ok(port) => port,
242 Err(_) => {
243 Log::warning(3011, Some("mail:smtp:port".to_owned()));
244 return MailProvider::None;
245 }
246 }
247 } else {
248 Log::warning(3011, Some("mail:smtp:port".to_owned()));
249 return MailProvider::None;
250 }
251 }
252 None => {
253 Log::warning(3011, Some("mail:smtp:port".to_owned()));
254 return MailProvider::None;
255 }
256 };
257 let tls = match db.query_prepare(fnv1a_64!("lib_get_setting"), &[&fnv1a_64!("mail:smtp:tls")], false).await {
258 Some(res) => {
259 if !res.is_empty() {
260 let row = if let Data::Vec(row) = unsafe { res.get_unchecked(0) } {
261 row
262 } else {
263 Log::warning(3011, Some("query:lib_get_setting:mail:smtp:tls".to_owned()));
264 return MailProvider::None;
265 };
266 if row.is_empty() {
267 Log::warning(3011, Some("query:lib_get_setting:mail:smtp:tls:empty".to_owned()));
268 return MailProvider::None;
269 }
270 let tls = if let Data::String(tls) = unsafe { row.get_unchecked(0) } {
271 tls.clone()
272 } else {
273 Log::warning(3011, Some("query:lib_get_setting:mail:smtp:tls:type".to_owned()));
274 return MailProvider::None;
275 };
276 if !tls.is_empty() {
277 match tls.as_ref() {
278 "None" => Tls::None,
279 "STARTTLS" => {
280 let param = match TlsParametersBuilder::new(server.clone())
281 .dangerous_accept_invalid_certs(true)
282 .build()
283 {
284 Ok(param) => param,
285 Err(_) => {
286 Log::warning(3011, Some("mail:smtp:tls".to_owned()));
287 return MailProvider::None;
288 }
289 };
290 Tls::Required(param)
291 }
292 "SSL/TLS" => {
293 let param = match TlsParametersBuilder::new(server.clone())
294 .dangerous_accept_invalid_certs(true)
295 .build()
296 {
297 Ok(param) => param,
298 Err(_) => {
299 Log::warning(3011, Some("mail:smtp:tls".to_owned()));
300 return MailProvider::None;
301 }
302 };
303 Tls::Wrapper(param)
304 }
305 _ => {
306 Log::warning(3011, Some("mail:smtp:tls".to_owned()));
307 return MailProvider::None;
308 }
309 }
310 } else {
311 Log::warning(3011, Some("mail:smtp:tls".to_owned()));
312 return MailProvider::None;
313 }
314 } else {
315 Log::warning(3011, Some("mail:smtp:tls".to_owned()));
316 return MailProvider::None;
317 }
318 }
319 None => {
320 Log::warning(3011, Some("mail:smtp:tls".to_owned()));
321 return MailProvider::None;
322 }
323 };
324 let auth = match db.query_prepare(fnv1a_64!("lib_get_setting"), &[&fnv1a_64!("mail:smtp:auth")], false).await {
325 Some(res) => {
326 if !res.is_empty() {
327 let row = if let Data::Vec(row) = unsafe { res.get_unchecked(0) } {
328 row
329 } else {
330 Log::warning(3011, Some("query:lib_get_setting:mail:smtp:auth".to_owned()));
331 return MailProvider::None;
332 };
333 if row.is_empty() {
334 Log::warning(3011, Some("query:lib_get_setting:mail:smtp:auth:empty".to_owned()));
335 return MailProvider::None;
336 }
337 let auth = if let Data::String(auth) = unsafe { row.get_unchecked(0) } {
338 auth.clone()
339 } else {
340 Log::warning(3011, Some("query:lib_get_setting:mail:smtp:auth:type".to_owned()));
341 return MailProvider::None;
342 };
343 if !auth.is_empty() {
344 match auth.as_ref() {
345 "None" => Vec::new(),
346 "PLAIN" => vec![Mechanism::Plain],
347 "LOGIN" => vec![Mechanism::Login],
348 "XOAUTH2" => vec![Mechanism::Xoauth2],
349 _ => {
350 Log::warning(3011, Some("mail:smtp:auth".to_owned()));
351 return MailProvider::None;
352 }
353 }
354 } else {
355 Log::warning(3011, Some("mail:smtp:auth".to_owned()));
356 return MailProvider::None;
357 }
358 } else {
359 Log::warning(3011, Some("mail:smtp:auth".to_owned()));
360 return MailProvider::None;
361 }
362 }
363 None => {
364 Log::warning(3011, Some("mail:smtp:auth".to_owned()));
365 return MailProvider::None;
366 }
367 };
368 let user = match db.query_prepare(fnv1a_64!("lib_get_setting"), &[&fnv1a_64!("mail:smtp:user")], false).await {
369 Some(res) => {
370 if !res.is_empty() {
371 let row = if let Data::Vec(row) = unsafe { res.get_unchecked(0) } {
372 row
373 } else {
374 Log::warning(3011, Some("query:lib_get_setting:mail:smtp:user".to_owned()));
375 return MailProvider::None;
376 };
377 if row.is_empty() {
378 Log::warning(3011, Some("query:lib_get_setting:mail:smtp:user:empty".to_owned()));
379 return MailProvider::None;
380 }
381 let user = if let Data::String(user) = unsafe { row.get_unchecked(0) } {
382 user.clone()
383 } else {
384 Log::warning(3011, Some("query:lib_get_setting:mail:smtp:user:type".to_owned()));
385 return MailProvider::None;
386 };
387 user
388 } else {
389 String::new()
390 }
391 }
392 None => {
393 Log::warning(3011, Some("mail:smtp:user".to_owned()));
394 return MailProvider::None;
395 }
396 };
397 let pwd = match db.query_prepare(fnv1a_64!("lib_get_setting"), &[&fnv1a_64!("mail:smtp:pwd")], false).await {
398 Some(res) => {
399 if !res.is_empty() {
400 let row = if let Data::Vec(row) = unsafe { res.get_unchecked(0) } {
401 row
402 } else {
403 Log::warning(3011, Some("query:lib_get_setting:mail:smtp:pwd".to_owned()));
404 return MailProvider::None;
405 };
406 if row.is_empty() {
407 Log::warning(3011, Some("query:lib_get_setting:mail:smtp:pwd:empty".to_owned()));
408 return MailProvider::None;
409 }
410 let pwd = if let Data::String(pwd) = unsafe { row.get_unchecked(0) } {
411 pwd.clone()
412 } else {
413 Log::warning(3011, Some("query:lib_get_setting:mail:smtp:pwd:type".to_owned()));
414 return MailProvider::None;
415 };
416 pwd
417 } else {
418 String::new()
419 }
420 }
421 None => {
422 Log::warning(3011, Some("mail:smtp:pwd".to_owned()));
423 return MailProvider::None;
424 }
425 };
426 let cred = if !auth.is_empty() { Some(Credentials::new(user, pwd)) } else { None };
427 MailProvider::SMTP(SmtpInfo {
428 server,
429 port,
430 tls,
431 authentication: auth,
432 credentials: cred,
433 })
434 }
435 "File" => match db.query_prepare(fnv1a_64!("lib_get_setting"), &[&fnv1a_64!("mail:file")], false).await {
436 Some(res) => {
437 if !res.is_empty() {
438 let row = if let Data::Vec(row) = unsafe { res.get_unchecked(0) } {
439 row
440 } else {
441 Log::warning(3011, Some("query:lib_get_setting:mail:file".to_owned()));
442 return MailProvider::None;
443 };
444 if row.is_empty() {
445 Log::warning(3011, Some("query:lib_get_setting:mail:smtp:file".to_owned()));
446 return MailProvider::None;
447 }
448 let path = if let Data::String(path) = unsafe { row.get_unchecked(0) } {
449 path.clone()
450 } else {
451 Log::warning(3011, Some("query:lib_get_setting:mail:smtp:file".to_owned()));
452 return MailProvider::None;
453 };
454 if !path.is_empty() {
455 if !Path::new(&path).is_dir() {
456 if let Err(e) = create_dir_all(&path).await {
457 Log::warning(3015, Some(e.to_string()));
458 return MailProvider::None;
459 }
460 }
461 MailProvider::File(path)
462 } else {
463 Log::warning(3011, Some("mail:file".to_owned()));
464 MailProvider::None
465 }
466 } else {
467 Log::warning(3011, Some("mail:file".to_owned()));
468 MailProvider::None
469 }
470 }
471 None => {
472 Log::warning(3011, Some("mail:file".to_owned()));
473 MailProvider::None
474 }
475 },
476 "None" => MailProvider::None,
477 _ => {
478 Log::warning(3011, Some("mail:provider".to_owned()));
479 MailProvider::None
480 }
481 }
482 } else {
483 Log::warning(3011, Some("mail:provider".to_owned()));
484 MailProvider::None
485 }
486 }
487 None => {
488 Log::warning(3011, Some("mail:provider".to_owned()));
489 MailProvider::None
490 }
491 }
492 }
493
494 pub async fn send(provider: MailProvider, db: Arc<DB>, message: MailMessage, user_id: i64, host: String) -> bool {
496 if !db.in_use() {
497 return true;
498 }
499 let json = match serde_json::to_value(&message) {
500 Ok(json) => json,
501 Err(e) => {
502 Log::warning(3002, Some(format!("Error: {}\nMessage: {:?}. ", e, message)));
503 return false;
504 }
505 };
506 let mut id: i64 = 0;
507 match provider {
508 MailProvider::Sendmail(path) => {
509 match Mail::create_message(Arc::clone(&db), message, &json, user_id, host, &mut id, "Sendmail").await {
510 Ok(mes) => {
511 let sender = AsyncSendmailTransport::<Tokio1Executor>::new_with_command(path);
512 match sender.send(mes).await {
513 Ok(_) => {
514 db.execute_prepare(fnv1a_64!("lib_mail_ok"), &[&id]).await;
515 true
516 }
517 Err(e) => {
518 let e = Log::warning(3012, Some(format!("Error: {}", e)));
519 db.execute_prepare(fnv1a_64!("lib_mail_err"), &[&e, &id]).await;
520 false
521 }
522 }
523 }
524 Err(e) => {
525 if id > 0 {
526 db.execute_prepare(fnv1a_64!("lib_mail_err"), &[&e, &id]).await;
527 }
528 false
529 }
530 }
531 }
532 MailProvider::SMTP(smtp) => match Mail::create_message(Arc::clone(&db), message, &json, user_id, host, &mut id, "SMTP").await {
533 Ok(mes) => {
534 let mut sender = match &smtp.tls {
535 Tls::None => match AsyncSmtpTransport::<Tokio1Executor>::relay(&smtp.server) {
536 Ok(s) => s.port(smtp.port),
537 Err(e) => {
538 let e = Log::warning(3014, Some(format!("Error: {}", e)));
539 db.execute_prepare(fnv1a_64!("lib_mail_err"), &[&e, &id]).await;
540 return false;
541 }
542 },
543 Tls::Required(_) => match AsyncSmtpTransport::<Tokio1Executor>::starttls_relay(&smtp.server) {
544 Ok(s) => s.tls(smtp.tls).port(smtp.port),
545 Err(e) => {
546 let e = Log::warning(3014, Some(format!("Error: {}", e)));
547 db.execute_prepare(fnv1a_64!("lib_mail_err"), &[&e, &id]).await;
548 return false;
549 }
550 },
551 Tls::Wrapper(_) => match AsyncSmtpTransport::<Tokio1Executor>::relay(&smtp.server) {
552 Ok(s) => s.tls(smtp.tls).port(smtp.port),
553 Err(e) => {
554 let e = Log::warning(3014, Some(format!("Error: {}", e)));
555 db.execute_prepare(fnv1a_64!("lib_mail_err"), &[&e, &id]).await;
556 return false;
557 }
558 },
559 Tls::Opportunistic(_) => match AsyncSmtpTransport::<Tokio1Executor>::relay(&smtp.server) {
560 Ok(s) => s.port(smtp.port),
561 Err(e) => {
562 let e = Log::warning(3014, Some(format!("Error: {}", e)));
563 db.execute_prepare(fnv1a_64!("lib_mail_err"), &[&e, &id]).await;
564 return false;
565 }
566 },
567 };
568 if !smtp.authentication.is_empty() {
569 sender = sender.authentication(smtp.authentication);
570 }
571 if let Some(credentials) = smtp.credentials {
572 sender = sender.credentials(credentials);
573 }
574
575 match sender.build().send(mes).await {
576 Ok(_) => {
577 db.execute_prepare(fnv1a_64!("lib_mail_ok"), &[&id]).await;
578 true
579 }
580 Err(e) => {
581 let e = Log::warning(3014, Some(format!("Error: {}", e)));
582 db.execute_prepare(fnv1a_64!("lib_mail_err"), &[&e, &id]).await;
583 false
584 }
585 }
586 }
587 Err(e) => {
588 if id > 0 {
589 db.execute_prepare(fnv1a_64!("lib_mail_err"), &[&e, &id]).await;
590 }
591 false
592 }
593 },
594 MailProvider::File(path) => match Mail::create_message(Arc::clone(&db), message, &json, user_id, host, &mut id, "File").await {
595 Ok(mes) => {
596 let sender = AsyncFileTransport::<Tokio1Executor>::new(path);
597 match sender.send(mes).await {
598 Ok(_) => {
599 db.execute_prepare(fnv1a_64!("lib_mail_ok"), &[&id]).await;
600 true
601 }
602 Err(e) => {
603 let e = Log::warning(3013, Some(format!("Error: {}", e)));
604 db.execute_prepare(fnv1a_64!("lib_mail_err"), &[&e, &id]).await;
605 false
606 }
607 }
608 }
609 Err(e) => {
610 if id > 0 {
611 db.execute_prepare(fnv1a_64!("lib_mail_err"), &[&e, &id]).await;
612 }
613 false
614 }
615 },
616 MailProvider::None => db.execute_prepare(fnv1a_64!("lib_mail_add"), &[&user_id, &json.to_string()]).await.is_some(),
617 }
618 }
619
620 async fn create_message(
622 db: Arc<DB>,
623 message: MailMessage,
624 json: &Value,
625 user_id: i64,
626 host: String,
627 id: &mut i64,
628 transport: &str,
629 ) -> Result<Message, String> {
630 if !db.in_use() {
631 return Err(String::new());
632 }
633 let message_id = match db.query_prepare(fnv1a_64!("lib_mail_new"), &[&user_id, &json.to_string(), &transport], false).await {
634 Some(r) => {
635 if r.len() != 1 {
636 Log::warning(3003, Some(format!("Message: {:?}.", &json)));
637 return Err(String::new());
638 }
639 let row = if let Data::Vec(row) = unsafe { r.get_unchecked(0) } {
640 row
641 } else {
642 return Err(String::new());
643 };
644 if row.is_empty() {
645 return Err(String::new());
646 }
647 let new_id = if let Data::I64(new_id) = unsafe { row.get_unchecked(0) } {
648 *new_id
649 } else {
650 return Err(String::new());
651 };
652 *id = new_id;
653 format!("{}@{}", id, host)
654 }
655 None => return Err(String::new()),
656 };
657
658 let from = match message.from.parse::<Mailbox>() {
659 Ok(f) => f,
660 Err(e) => {
661 let res = Log::warning(3004, Some(format!("Message: {:?}. Error: {}.", &json, e)));
662 return Err(res);
663 }
664 };
665 let mut mes = Message::builder().message_id(Some(message_id)).from(from);
666 if let Some(rto) = message.reply_to {
667 match rto.parse::<Mailbox>() {
668 Ok(r) => mes = mes.reply_to(r),
669 Err(e) => {
670 let res = Log::warning(3005, Some(format!("Message: {:?}. Error: {}.", &json, e)));
671 return Err(res);
672 }
673 }
674 }
675 for to in message.to {
676 match to.parse::<Mailbox>() {
677 Ok(t) => mes = mes.to(t),
678 Err(e) => {
679 let res = Log::warning(3006, Some(format!("Message: {:?}. Error: {}.", &json, e)));
680 return Err(res);
681 }
682 }
683 }
684 if let Some(mail_cc) = message.cc {
685 for cc in mail_cc {
686 match cc.parse::<Mailbox>() {
687 Ok(c) => mes = mes.cc(c),
688 Err(e) => {
689 let res = Log::warning(3007, Some(format!("Message: {:?}. Error: {}.", &json, e)));
690 return Err(res);
691 }
692 }
693 }
694 }
695 if let Some(mail_cc) = message.bcc {
696 for cc in mail_cc {
697 match cc.parse::<Mailbox>() {
698 Ok(c) => mes = mes.bcc(c),
699 Err(e) => {
700 let res = Log::warning(3008, Some(format!("Message: {:?}. Error: {}.", &json, e)));
701 return Err(res);
702 }
703 }
704 }
705 }
706 mes = mes.subject(message.subject);
707 let mes = if !message.body.is_empty() {
708 let mut part = MultiPart::mixed().build();
709 for body in message.body {
710 match body {
711 MailBody::Text(s) => part = part.singlepart(SinglePart::plain(s)),
712 MailBody::Html(html) => {
713 if html.text.is_none() && html.file.is_empty() {
714 part = part.singlepart(SinglePart::html(html.html));
715 } else {
716 let mut mp = MultiPart::alternative().build();
717 if let Some(s) = html.text {
718 mp = mp.singlepart(SinglePart::plain(s));
719 }
720 if html.file.is_empty() {
721 mp = mp.singlepart(SinglePart::html(html.html));
722 } else {
723 let mut m = MultiPart::related().build();
724 m = m.singlepart(SinglePart::html(html.html));
725 for f in html.file {
726 let mime = match &f.mime {
727 Some(m) => m,
728 None => {
729 let (_, ext) = f.name.rsplit_once('.').unwrap_or(("", ""));
730 Mail::get_mime(ext)
731 }
732 };
733 let ct = match ContentType::parse(mime) {
734 Ok(ct) => ct,
735 Err(e) => {
736 let res = Log::warning(3010, Some(format!("Message: {:?}. Error: {}.", &json, e)));
737 return Err(res);
738 }
739 };
740 let a = Attachment::new_inline(f.name).body(f.data, ct);
741 m = m.singlepart(a);
742 }
743 mp = mp.multipart(m);
744 }
745 part = part.multipart(mp);
746 }
747 }
748 MailBody::File(file) => {
749 let mime = match &file.mime {
750 Some(m) => m,
751 None => {
752 let (_, ext) = file.name.rsplit_once('.').unwrap_or(("", ""));
753 Mail::get_mime(ext)
754 }
755 };
756 let ct = match ContentType::parse(mime) {
757 Ok(ct) => ct,
758 Err(e) => {
759 let res = Log::warning(3010, Some(format!("Message: {:?}. Error: {}.", &json, e)));
760 return Err(res);
761 }
762 };
763 let a = Attachment::new(file.name).body(file.data, ct);
764 part = part.singlepart(a);
765 }
766 }
767 }
768 match mes.multipart(part) {
769 Ok(mes) => mes,
770 Err(e) => {
771 let res = Log::warning(3009, Some(format!("Message: {:?}. Error: {}.", &json, e)));
772 return Err(res);
773 }
774 }
775 } else {
776 match mes.body("".to_string()) {
777 Ok(mes) => mes,
778 Err(e) => {
779 let res = Log::warning(3009, Some(format!("Message: {:?}. Error: {}.", &json, e)));
780 return Err(res);
781 }
782 }
783 };
784 Ok(mes)
785 }
786
787 fn get_mime(ext: &str) -> &'static str {
789 match ext {
790 "7z" => "application/x-7z-compressed",
791 "aac" => "audio/aac",
792 "abw" => "application/x-abiword",
793 "arc" => "application/x-freearc",
794 "avi" => "video/x-msvideo",
795 "avif" => "image/avif",
796 "azw" => "application/vnd.amazon.ebook",
797 "bin" => "application/octet-stream",
798 "bmp" => "image/bmp",
799 "bz" => "application/x-bzip",
800 "bz2" => "application/x-bzip2",
801 "cda" => "application/x-cdf",
802 "csh" => "application/x-csh",
803 "css" => "text/css",
804 "csv" => "text/csv",
805 "doc" => "application/msword",
806 "docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
807 "eot" => "application/vnd.ms-fontobject",
808 "epub" => "application/epub+zip",
809 "gif" => "image/gif",
810 "gz" => "application/gzip",
811 "htm" => "text/html",
812 "html" => "text/html",
813 "ico" => "image/vnd.microsoft.icon",
814 "ics" => "text/calendar",
815 "jar" => "application/java-archive",
816 "jpeg" => "image/jpeg",
817 "jpg" => "image/jpeg",
818 "js" => "text/javascript",
819 "json" => "application/json",
820 "jsonld" => "application/ld+json",
821 "mjs" => "text/javascript",
822 "mp3" => "audio/mpeg",
823 "mp4" => "video/mp4",
824 "mpeg" => "video/mpeg",
825 "mpkg" => "application/vnd.apple.installer+xml",
826 "odp" => "application/vnd.oasis.opendocument.presentation",
827 "ods" => "application/vnd.oasis.opendocument.spreadsheet",
828 "odt" => "application/vnd.oasis.opendocument.text",
829 "oga" => "audio/ogg",
830 "ogv" => "video/ogg",
831 "ogx" => "application/ogg",
832 "opus" => "audio/opus",
833 "otf" => "font/otf",
834 "pdf" => "application/pdf",
835 "php" => "application/x-httpd-php",
836 "png" => "image/png",
837 "ppt" => "application/vnd.ms-powerpoint",
838 "pptx" => "application/vnd.openxmlformats-officedocument.presentationml.presentation",
839 "rar" => "application/vnd.rar",
840 "rs" => "text/plain",
841 "rtf" => "application/rtf",
842 "sh" => "application/x-sh",
843 "svg" => "image/svg+xml",
844 "tar" => "application/x-tar",
845 "tif" => "image/tiff",
846 "tiff" => "image/tiff",
847 "ts" => "video/mp2t",
848 "ttf" => "font/ttf",
849 "txt" => "text/plain",
850 "vsd" => "application/vnd.visio",
851 "wav" => "audio/wav",
852 "weba" => "audio/webm",
853 "webm" => "video/webm",
854 "webp" => "image/webp",
855 "woff" => "font/woff",
856 "woff2" => "font/woff2",
857 "xhtml" => "application/xhtml+xml",
858 "xls" => "application/vnd.ms-excel",
859 "xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
860 "xml" => "application/xml",
861 "xul" => "application/vnd.mozilla.xul+xml",
862 "zip" => "application/zip",
863 _ => "application/octet-stream",
864 }
865 }
866}