You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

465 lines
15 KiB

1 year ago
  1. use super::*;
  2. use crate::backends::BackendOp;
  3. use crate::email::attachments::AttachmentBuilder;
  4. use chrono::{DateTime, Local};
  5. use data_encoding::BASE64_MIME;
  6. use std::ffi::OsStr;
  7. use std::io::Read;
  8. use std::path::Path;
  9. use std::str;
  10. pub mod mime;
  11. pub mod random;
  12. //use self::mime::*;
  13. use super::parser;
  14. use fnv::FnvHashMap;
  15. #[derive(Debug, PartialEq, Clone)]
  16. pub struct Draft {
  17. pub headers: FnvHashMap<String, String>,
  18. pub header_order: Vec<String>,
  19. pub body: String,
  20. pub attachments: Vec<AttachmentBuilder>,
  21. }
  22. impl Default for Draft {
  23. fn default() -> Self {
  24. let mut headers = FnvHashMap::with_capacity_and_hasher(8, Default::default());
  25. let mut header_order = Vec::with_capacity(8);
  26. headers.insert("From".into(), "".into());
  27. headers.insert("To".into(), "".into());
  28. headers.insert("Cc".into(), "".into());
  29. headers.insert("Bcc".into(), "".into());
  30. let now: DateTime<Local> = Local::now();
  31. headers.insert("Date".into(), now.to_rfc2822());
  32. headers.insert("Subject".into(), "".into());
  33. headers.insert(
  34. "User-Agent".into(),
  35. format!("meli {}", option_env!("CARGO_PKG_VERSION").unwrap_or("0.0")),
  36. );
  37. header_order.push("Date".into());
  38. header_order.push("From".into());
  39. header_order.push("To".into());
  40. header_order.push("Cc".into());
  41. header_order.push("Bcc".into());
  42. header_order.push("Subject".into());
  43. header_order.push("User-Agent".into());
  44. Draft {
  45. headers,
  46. header_order,
  47. body: String::new(),
  48. attachments: Vec::new(),
  49. }
  50. }
  51. }
  52. impl str::FromStr for Draft {
  53. type Err = MeliError;
  54. fn from_str(s: &str) -> Result<Self> {
  55. if s.is_empty() {
  56. return Err(MeliError::new("Empty input in Draft::from_str"));
  57. }
  58. let (headers, _) = parser::mail(s.as_bytes()).to_full_result()?;
  59. let mut ret = Draft::default();
  60. for (k, v) in headers {
  61. if ignore_header(k) {
  62. continue;
  63. }
  64. if ret
  65. .headers
  66. .insert(
  67. String::from_utf8(k.to_vec())?,
  68. String::from_utf8(v.to_vec())?,
  69. )
  70. .is_none()
  71. {
  72. ret.header_order.push(String::from_utf8(k.to_vec())?);
  73. }
  74. }
  75. if ret.headers.contains_key("From") && !ret.headers.contains_key("Message-ID") {
  76. if let super::parser::IResult::Done(_, addr) =
  77. super::parser::mailbox(ret.headers["From"].as_bytes())
  78. {
  79. if let Some(fqdn) = addr.get_fqdn() {
  80. if ret
  81. .headers
  82. .insert("Message-ID".into(), random::gen_message_id(&fqdn))
  83. .is_none()
  84. {
  85. let pos = ret
  86. .header_order
  87. .iter()
  88. .position(|h| h == "Subject")
  89. .unwrap();
  90. ret.header_order.insert(pos, "Message-ID".into());
  91. }
  92. }
  93. }
  94. }
  95. let body = Envelope::new(0).body_bytes(s.as_bytes());
  96. ret.body = String::from_utf8(decode(&body, None))?;
  97. Ok(ret)
  98. }
  99. }
  100. impl Draft {
  101. pub fn edit(envelope: &Envelope, mut op: Box<dyn BackendOp>) -> Result<Self> {
  102. let mut ret = Draft::default();
  103. //TODO: Inform user if error
  104. {
  105. let bytes = op.as_bytes().unwrap_or(&[]);
  106. for (k, v) in envelope.headers(bytes).unwrap_or_else(|_| Vec::new()) {
  107. if ignore_header(k.as_bytes()) {
  108. continue;
  109. }
  110. if ret.headers.insert(k.into(), v.into()).is_none() {
  111. ret.header_order.push(k.into());
  112. }
  113. }
  114. }
  115. ret.body = envelope.body(op)?.text();
  116. Ok(ret)
  117. }
  118. pub fn set_header(&mut self, header: &str, value: String) {
  119. if self.headers.insert(header.to_string(), value).is_none() {
  120. self.header_order.push(header.to_string());
  121. }
  122. }
  123. pub fn new_reply(envelope: &Envelope, bytes: &[u8]) -> Self {
  124. let mut ret = Draft::default();
  125. ret.headers_mut().insert(
  126. "References".into(),
  127. format!(
  128. "{} {}",
  129. envelope
  130. .references()
  131. .iter()
  132. .fold(String::new(), |mut acc, x| {
  133. if !acc.is_empty() {
  134. acc.push(' ');
  135. }
  136. acc.push_str(&x.to_string());
  137. acc
  138. }),
  139. envelope.message_id_display()
  140. ),
  141. );
  142. ret.header_order.push("References".into());
  143. ret.headers_mut()
  144. .insert("In-Reply-To".into(), envelope.message_id_display().into());
  145. ret.header_order.push("In-Reply-To".into());
  146. if let Some(reply_to) = envelope.other_headers().get("Reply-To") {
  147. ret.headers_mut().insert("To".into(), reply_to.to_string());
  148. } else {
  149. ret.headers_mut()
  150. .insert("To".into(), envelope.field_from_to_string());
  151. }
  152. ret.headers_mut()
  153. .insert("Cc".into(), envelope.field_cc_to_string());
  154. let body = envelope.body_bytes(bytes);
  155. ret.body = {
  156. let reply_body_bytes = decode_rec(&body, None);
  157. let reply_body = String::from_utf8_lossy(&reply_body_bytes);
  158. let lines: Vec<&str> = reply_body.lines().collect();
  159. let mut ret = String::with_capacity(reply_body.len() + lines.len());
  160. for l in lines {
  161. ret.push('>');
  162. ret.push(' ');
  163. ret.push_str(l.trim());
  164. ret.push('\n');
  165. }
  166. ret.pop();
  167. ret
  168. };
  169. ret
  170. }
  171. pub fn headers_mut(&mut self) -> &mut FnvHashMap<String, String> {
  172. &mut self.headers
  173. }
  174. pub fn headers(&self) -> &FnvHashMap<String, String> {
  175. &self.headers
  176. }
  177. pub fn attachments(&self) -> &Vec<AttachmentBuilder> {
  178. &self.attachments
  179. }
  180. pub fn attachments_mut(&mut self) -> &mut Vec<AttachmentBuilder> {
  181. &mut self.attachments
  182. }
  183. pub fn body(&self) -> &str {
  184. &self.body
  185. }
  186. pub fn set_body(&mut self, s: String) {
  187. self.body = s;
  188. }
  189. pub fn to_string(&self) -> Result<String> {
  190. let mut ret = String::new();
  191. for k in &self.header_order {
  192. let v = &self.headers[k];
  193. ret.extend(format!("{}: {}\n", k, v).chars());
  194. }
  195. ret.push('\n');
  196. ret.push_str(&self.body);
  197. Ok(ret)
  198. }
  199. pub fn finalise(mut self) -> Result<String> {
  200. let mut ret = String::new();
  201. if self.headers.contains_key("From") && !self.headers.contains_key("Message-ID") {
  202. if let super::parser::IResult::Done(_, addr) =
  203. super::parser::mailbox(self.headers["From"].as_bytes())
  204. {
  205. if let Some(fqdn) = addr.get_fqdn() {
  206. if self
  207. .headers
  208. .insert("Message-ID".into(), random::gen_message_id(&fqdn))
  209. .is_none()
  210. {
  211. let pos = self
  212. .header_order
  213. .iter()
  214. .position(|h| h == "Subject")
  215. .unwrap();
  216. self.header_order.insert(pos, "Message-ID".into());
  217. }
  218. }
  219. }
  220. }
  221. for k in &self.header_order {
  222. let v = &self.headers[k];
  223. if v.is_ascii() {
  224. ret.extend(format!("{}: {}\n", k, v).chars());
  225. } else {
  226. ret.extend(format!("{}: {}\n", k, mime::encode_header(v)).chars());
  227. }
  228. }
  229. ret.push_str("MIME-Version: 1.0\n");
  230. if self.attachments.is_empty() {
  231. let content_type: ContentType = Default::default();
  232. let content_transfer_encoding: ContentTransferEncoding = ContentTransferEncoding::_8Bit;
  233. ret.extend(format!("Content-Type: {}; charset=\"utf-8\"\n", content_type).chars());
  234. ret.extend(
  235. format!("Content-Transfer-Encoding: {}\n", content_transfer_encoding).chars(),
  236. );
  237. ret.push('\n');
  238. ret.push_str(&self.body);
  239. } else if self.attachments.len() == 1 && self.body.is_empty() {
  240. let attachment: Attachment = self.attachments.remove(0).into();
  241. ret.extend(attachment.into_raw().chars());
  242. } else {
  243. let mut parts = Vec::with_capacity(self.attachments.len() + 1);
  244. let attachments = std::mem::replace(&mut self.attachments, Vec::new());
  245. let mut body_attachment = AttachmentBuilder::default();
  246. body_attachment.set_raw(self.body.as_bytes().to_vec());
  247. parts.push(body_attachment);
  248. parts.extend(attachments.into_iter());
  249. build_multipart(&mut ret, MultipartType::Mixed, parts);
  250. }
  251. Ok(ret)
  252. }
  253. }
  254. fn ignore_header(header: &[u8]) -> bool {
  255. match header {
  256. b"From" => false,
  257. b"To" => false,
  258. b"Date" => false,
  259. b"Message-ID" => false,
  260. b"User-Agent" => false,
  261. b"Subject" => false,
  262. b"Reply-to" => false,
  263. b"Cc" => false,
  264. b"Bcc" => false,
  265. b"In-Reply-To" => false,
  266. b"References" => false,
  267. b"MIME-Version" => true,
  268. h if h.starts_with(b"X-") => false,
  269. _ => true,
  270. }
  271. }
  272. fn build_multipart(ret: &mut String, kind: MultipartType, parts: Vec<AttachmentBuilder>) {
  273. use ContentType::*;
  274. let boundary = ContentType::make_boundary(&parts);
  275. ret.extend(
  276. format!(
  277. "Content-Type: {}; charset=\"utf-8\"; boundary=\"{}\"\n",
  278. kind, boundary
  279. )
  280. .chars(),
  281. );
  282. ret.push('\n');
  283. /* rfc1341 */
  284. ret.extend("This is a MIME formatted message with attachments. Use a MIME-compliant client to view it properly.\n".chars());
  285. for sub in parts {
  286. ret.push_str("--");
  287. ret.extend(boundary.chars());
  288. ret.push('\n');
  289. match sub.content_type {
  290. ContentType::Text {
  291. kind: crate::email::attachment_types::Text::Plain,
  292. charset: Charset::UTF8,
  293. } => {
  294. ret.push('\n');
  295. ret.push_str(&String::from_utf8_lossy(sub.raw()));
  296. ret.push('\n');
  297. }
  298. Text {
  299. ref kind,
  300. charset: _,
  301. } => {
  302. ret.extend(format!("Content-Type: {}; charset=\"utf-8\"\n", kind).chars());
  303. ret.push('\n');
  304. ret.push_str(&String::from_utf8_lossy(sub.raw()));
  305. ret.push('\n');
  306. }
  307. Multipart {
  308. boundary: _boundary,
  309. kind,
  310. parts: subparts,
  311. } => {
  312. build_multipart(
  313. ret,
  314. kind,
  315. subparts
  316. .into_iter()
  317. .map(|s| s.into())
  318. .collect::<Vec<AttachmentBuilder>>(),
  319. );
  320. ret.push('\n');
  321. }
  322. MessageRfc822 | PGPSignature => {
  323. ret.extend(format!("Content-Type: {}; charset=\"utf-8\"\n", kind).chars());
  324. ret.push('\n');
  325. ret.push_str(&String::from_utf8_lossy(sub.raw()));
  326. ret.push('\n');
  327. }
  328. _ => {
  329. let content_transfer_encoding: ContentTransferEncoding =
  330. ContentTransferEncoding::Base64;
  331. if let Some(name) = sub.content_type().name() {
  332. ret.extend(
  333. format!(
  334. "Content-Type: {}; name=\"{}\"; charset=\"utf-8\"\n",
  335. sub.content_type(),
  336. name
  337. )
  338. .chars(),
  339. );
  340. } else {
  341. ret.extend(
  342. format!("Content-Type: {}; charset=\"utf-8\"\n", sub.content_type())
  343. .chars(),
  344. );
  345. }
  346. ret.extend(
  347. format!("Content-Transfer-Encoding: {}\n", content_transfer_encoding).chars(),
  348. );
  349. ret.push('\n');
  350. ret.push_str(&BASE64_MIME.encode(sub.raw()).trim());
  351. ret.push('\n');
  352. }
  353. }
  354. }
  355. ret.push_str("--");
  356. ret.extend(boundary.chars());
  357. ret.push_str("--\n");
  358. }
  359. #[cfg(test)]
  360. mod tests {
  361. use super::*;
  362. use std::io::Read;
  363. use std::str::FromStr;
  364. #[test]
  365. fn test_new() {
  366. let mut default = Draft::default();
  367. assert_eq!(
  368. Draft::from_str(&default.to_string().unwrap()).unwrap(),
  369. default
  370. );
  371. default.set_body("αδφαφσαφασ".to_string());
  372. assert_eq!(
  373. Draft::from_str(&default.to_string().unwrap()).unwrap(),
  374. default
  375. );
  376. default.set_body("ascii only".to_string());
  377. assert_eq!(
  378. Draft::from_str(&default.to_string().unwrap()).unwrap(),
  379. default
  380. );
  381. }
  382. #[test]
  383. fn test_attachments() {
  384. return;
  385. let mut default = Draft::default();
  386. default.set_body("αδφαφσαφασ".to_string());
  387. let mut file = std::fs::File::open("file path").unwrap();
  388. let mut contents = Vec::new();
  389. file.read_to_end(&mut contents).unwrap();
  390. let mut attachment = AttachmentBuilder::new(b"");
  391. attachment
  392. .set_raw(contents)
  393. .set_content_type(ContentType::Other {
  394. name: Some("images.jpeg".to_string()),
  395. tag: b"image/jpeg".to_vec(),
  396. })
  397. .set_content_transfer_encoding(ContentTransferEncoding::Base64);
  398. default.attachments_mut().push(attachment);
  399. println!("{}", default.finalise().unwrap());
  400. }
  401. }
  402. /// Reads file from given path, and returns an 'application/octet-stream' AttachmentBuilder object
  403. pub fn attachment_from_file<I>(path: &I) -> Result<AttachmentBuilder>
  404. where
  405. I: AsRef<OsStr>,
  406. {
  407. let path: &Path = Path::new(path);
  408. if !path.is_file() {
  409. return Err(MeliError::new(format!("{} is not a file", path.display())));
  410. }
  411. let mut file = std::fs::File::open(path)?;
  412. let mut contents = Vec::new();
  413. file.read_to_end(&mut contents)?;
  414. let mut attachment = AttachmentBuilder::new(b"");
  415. attachment
  416. .set_raw(contents)
  417. .set_content_type(ContentType::Other {
  418. name: path.file_name().map(|s| s.to_string_lossy().into()),
  419. tag: b"application/octet-stream".to_vec(),
  420. });
  421. Ok(attachment)
  422. }