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.

323 lines
9.3KB

  1. /*
  2. * meli - mailbox module.
  3. *
  4. * Copyright 2017 Manos Pitsidianakis
  5. *
  6. * This file is part of meli.
  7. *
  8. * meli is free software: you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation, either version 3 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * meli is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with meli. If not, see <http://www.gnu.org/licenses/>.
  20. */
  21. #[macro_use]
  22. mod backend;
  23. pub use self::backend::*;
  24. use crate::backends::*;
  25. use crate::email::parser;
  26. use crate::email::{Envelope, Flag};
  27. use crate::error::{MeliError, Result};
  28. use crate::shellexpand::ShellExpandTrait;
  29. use memmap::{Mmap, Protection};
  30. use std::collections::hash_map::DefaultHasher;
  31. use std::fs;
  32. use std::hash::{Hash, Hasher};
  33. use std::path::{Path, PathBuf};
  34. /// `BackendOp` implementor for Maildir
  35. #[derive(Debug)]
  36. pub struct MaildirOp {
  37. hash_index: HashIndexes,
  38. folder_hash: FolderHash,
  39. hash: EnvelopeHash,
  40. slice: Option<Mmap>,
  41. }
  42. impl Clone for MaildirOp {
  43. fn clone(&self) -> Self {
  44. MaildirOp {
  45. hash_index: self.hash_index.clone(),
  46. folder_hash: self.folder_hash,
  47. hash: self.hash,
  48. slice: None,
  49. }
  50. }
  51. }
  52. impl MaildirOp {
  53. pub fn new(hash: EnvelopeHash, hash_index: HashIndexes, folder_hash: FolderHash) -> Self {
  54. MaildirOp {
  55. hash_index,
  56. folder_hash,
  57. hash,
  58. slice: None,
  59. }
  60. }
  61. fn path(&self) -> PathBuf {
  62. let map = self.hash_index.lock().unwrap();
  63. let map = &map[&self.folder_hash];
  64. debug!("looking for {} in {} map", self.hash, self.folder_hash);
  65. if !map.contains_key(&self.hash) {
  66. debug!("doesn't contain it though len = {}\n{:#?}", map.len(), map);
  67. for e in map.iter() {
  68. debug!("{:#?}", e);
  69. }
  70. }
  71. if let Some(modif) = &map[&self.hash].modified {
  72. match modif {
  73. PathMod::Path(ref path) => path.clone(),
  74. PathMod::Hash(hash) => map[&hash].to_path_buf(),
  75. }
  76. } else {
  77. map.get(&self.hash).unwrap().to_path_buf()
  78. }
  79. }
  80. }
  81. impl<'a> BackendOp for MaildirOp {
  82. fn description(&self) -> String {
  83. format!("Path of file: {}", self.path().display())
  84. }
  85. fn as_bytes(&mut self) -> Result<&[u8]> {
  86. if self.slice.is_none() {
  87. self.slice = Some(Mmap::open_path(self.path(), Protection::Read)?);
  88. }
  89. /* Unwrap is safe since we use ? above. */
  90. Ok(unsafe { self.slice.as_ref().unwrap().as_slice() })
  91. }
  92. fn fetch_headers(&mut self) -> Result<&[u8]> {
  93. let raw = self.as_bytes()?;
  94. let result = parser::headers_raw(raw).to_full_result()?;
  95. Ok(result)
  96. }
  97. fn fetch_body(&mut self) -> Result<&[u8]> {
  98. let raw = self.as_bytes()?;
  99. let result = parser::body_raw(raw).to_full_result()?;
  100. Ok(result)
  101. }
  102. fn fetch_flags(&self) -> Flag {
  103. let mut flag = Flag::default();
  104. let path = self.path();
  105. let path = path.to_str().unwrap(); // Assume UTF-8 validity
  106. if !path.contains(":2,") {
  107. return flag;
  108. }
  109. for f in path.chars().rev() {
  110. match f {
  111. ',' => break,
  112. 'D' => flag |= Flag::DRAFT,
  113. 'F' => flag |= Flag::FLAGGED,
  114. 'P' => flag |= Flag::PASSED,
  115. 'R' => flag |= Flag::REPLIED,
  116. 'S' => flag |= Flag::SEEN,
  117. 'T' => flag |= Flag::TRASHED,
  118. _ => {
  119. debug!("DEBUG: in fetch_flags, path is {}", path);
  120. }
  121. }
  122. }
  123. flag
  124. }
  125. fn set_flag(&mut self, envelope: &mut Envelope, f: Flag, value: bool) -> Result<()> {
  126. let path = self.path();
  127. let path = path.to_str().unwrap(); // Assume UTF-8 validity
  128. let idx: usize = path
  129. .rfind(":2,")
  130. .ok_or_else(|| MeliError::new(format!("Invalid email filename: {:?}", self)))?
  131. + 3;
  132. let mut new_name: String = path[..idx].to_string();
  133. let mut flags = self.fetch_flags();
  134. flags.set(f, value);
  135. if !(flags & Flag::DRAFT).is_empty() {
  136. new_name.push('D');
  137. }
  138. if !(flags & Flag::FLAGGED).is_empty() {
  139. new_name.push('F');
  140. }
  141. if !(flags & Flag::PASSED).is_empty() {
  142. new_name.push('P');
  143. }
  144. if !(flags & Flag::REPLIED).is_empty() {
  145. new_name.push('R');
  146. }
  147. if !(flags & Flag::SEEN).is_empty() {
  148. new_name.push('S');
  149. }
  150. if !(flags & Flag::TRASHED).is_empty() {
  151. new_name.push('T');
  152. }
  153. let old_hash = envelope.hash();
  154. let new_name: PathBuf = new_name.into();
  155. let hash_index = self.hash_index.clone();
  156. let mut map = hash_index.lock().unwrap();
  157. let map = map.entry(self.folder_hash).or_default();
  158. map.entry(old_hash).or_default().modified = Some(PathMod::Path(new_name.clone()));
  159. debug!("renaming {:?} to {:?}", path, new_name);
  160. fs::rename(&path, &new_name)?;
  161. debug!("success in rename");
  162. Ok(())
  163. }
  164. }
  165. #[derive(Debug, Default)]
  166. pub struct MaildirFolder {
  167. hash: FolderHash,
  168. name: String,
  169. fs_path: PathBuf,
  170. path: PathBuf,
  171. parent: Option<FolderHash>,
  172. children: Vec<FolderHash>,
  173. permissions: FolderPermissions,
  174. }
  175. impl MaildirFolder {
  176. pub fn new(
  177. path: String,
  178. file_name: String,
  179. parent: Option<FolderHash>,
  180. children: Vec<FolderHash>,
  181. settings: &AccountSettings,
  182. ) -> Result<Self> {
  183. macro_rules! strip_slash {
  184. ($v:expr) => {
  185. if $v.ends_with("/") {
  186. &$v[..$v.len() - 1]
  187. } else {
  188. $v
  189. }
  190. };
  191. }
  192. let pathbuf = PathBuf::from(&path);
  193. let mut h = DefaultHasher::new();
  194. pathbuf.hash(&mut h);
  195. /* Check if folder path (Eg `INBOX/Lists/luddites`) is included in the subscribed
  196. * mailboxes in user configuration */
  197. let fname = if let Ok(fname) = pathbuf.strip_prefix(
  198. PathBuf::from(&settings.root_folder)
  199. .expand()
  200. .parent()
  201. .unwrap_or_else(|| &Path::new("/")),
  202. ) {
  203. if fname.components().count() != 0
  204. && !settings
  205. .subscribed_folders
  206. .iter()
  207. .any(|x| x == strip_slash!(fname.to_str().unwrap()))
  208. {
  209. return Err(MeliError::new(format!(
  210. "Folder with name `{}` is not included in configured subscribed mailboxes",
  211. fname.display()
  212. )));
  213. }
  214. Some(fname)
  215. } else {
  216. None
  217. };
  218. let read_only = if let Ok(metadata) = std::fs::metadata(&pathbuf) {
  219. metadata.permissions().readonly()
  220. } else {
  221. true
  222. };
  223. let ret = MaildirFolder {
  224. hash: h.finish(),
  225. name: file_name,
  226. path: fname.unwrap().to_path_buf(),
  227. fs_path: pathbuf,
  228. parent,
  229. children,
  230. permissions: FolderPermissions {
  231. create_messages: !read_only,
  232. remove_messages: !read_only,
  233. set_flags: !read_only,
  234. create_child: !read_only,
  235. rename_messages: !read_only,
  236. delete_messages: !read_only,
  237. delete_mailbox: !read_only,
  238. change_permissions: false,
  239. },
  240. };
  241. ret.is_valid()?;
  242. Ok(ret)
  243. }
  244. pub fn fs_path(&self) -> &Path {
  245. self.fs_path.as_path()
  246. }
  247. fn is_valid(&self) -> Result<()> {
  248. let path = self.fs_path();
  249. let mut p = PathBuf::from(path);
  250. for d in &["cur", "new", "tmp"] {
  251. p.push(d);
  252. if !p.is_dir() {
  253. return Err(MeliError::new(format!(
  254. "{} is not a valid maildir folder",
  255. path.display()
  256. )));
  257. }
  258. p.pop();
  259. }
  260. Ok(())
  261. }
  262. }
  263. impl BackendFolder for MaildirFolder {
  264. fn hash(&self) -> FolderHash {
  265. self.hash
  266. }
  267. fn name(&self) -> &str {
  268. &self.name
  269. }
  270. fn path(&self) -> &str {
  271. self.path.to_str().unwrap_or(self.name())
  272. }
  273. fn change_name(&mut self, s: &str) {
  274. self.name = s.to_string();
  275. }
  276. fn children(&self) -> &[FolderHash] {
  277. &self.children
  278. }
  279. fn clone(&self) -> Folder {
  280. Box::new(MaildirFolder {
  281. hash: self.hash,
  282. name: self.name.clone(),
  283. fs_path: self.fs_path.clone(),
  284. path: self.path.clone(),
  285. children: self.children.clone(),
  286. parent: self.parent,
  287. permissions: self.permissions,
  288. })
  289. }
  290. fn parent(&self) -> Option<FolderHash> {
  291. self.parent
  292. }
  293. fn permissions(&self) -> FolderPermissions {
  294. self.permissions
  295. }
  296. }