diff --git a/melib/src/backends.rs b/melib/src/backends.rs index 3bd39001..1c76bc22 100644 --- a/melib/src/backends.rs +++ b/melib/src/backends.rs @@ -173,6 +173,7 @@ pub enum FolderOperation { Subscribe, Unsubscribe, Rename(NewFolderName), + SetPermissions(FolderPermissions), } type NewFolderName = String; @@ -309,6 +310,8 @@ pub trait BackendFolder: Debug { fn clone(&self) -> Folder; fn children(&self) -> &Vec; fn parent(&self) -> Option; + + fn permissions(&self) -> FolderPermissions; } #[derive(Debug)] @@ -342,6 +345,10 @@ impl BackendFolder for DummyFolder { fn parent(&self) -> Option { None } + + fn permissions(&self) -> FolderPermissions { + FolderPermissions::default() + } } pub fn folder_default() -> Folder { @@ -364,3 +371,30 @@ impl Default for Folder { folder_default() } } + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +pub struct FolderPermissions { + pub create_messages: bool, + pub remove_messages: bool, + pub set_flags: bool, + pub create_child: bool, + pub rename_messages: bool, + pub delete_messages: bool, + pub delete_mailbox: bool, + pub change_permissions: bool, +} + +impl Default for FolderPermissions { + fn default() -> Self { + FolderPermissions { + create_messages: false, + remove_messages: false, + set_flags: false, + create_child: false, + rename_messages: false, + delete_messages: false, + delete_mailbox: false, + change_permissions: false, + } + } +} diff --git a/melib/src/backends/imap.rs b/melib/src/backends/imap.rs index cce63117..d67764ca 100644 --- a/melib/src/backends/imap.rs +++ b/melib/src/backends/imap.rs @@ -123,7 +123,10 @@ impl MailBackend for ImapType { let uid_store = self.uid_store.clone(); let folder_path = folder.path().to_string(); let folder_hash = folder.hash(); - let folder_exists = self.folders.lock().unwrap()[&folder_hash].exists.clone(); + let (permissions, folder_exists) = { + let f = &self.folders.lock().unwrap()[&folder_hash]; + (f.permissions.clone(), f.exists.clone()) + }; let connection = self.connection.clone(); let closure = move |_work_context| { let connection = connection.clone(); @@ -134,13 +137,19 @@ impl MailBackend for ImapType { let mut conn = conn.unwrap(); debug!("locked for get {}", folder_path); + /* first SELECT the mailbox to get READ/WRITE permissions (because EXAMINE only + * returns READ-ONLY for both cases) */ exit_on_error!(&tx, - conn.send_command(format!("EXAMINE {}", folder_path).as_bytes()) + conn.send_command(format!("SELECT {}", folder_path).as_bytes()) conn.read_response(&mut response) ); let examine_response = protocol_parser::select_response(&response); exit_on_error!(&tx, examine_response); let examine_response = examine_response.unwrap(); + debug!( + "folder: {} examine_response: {:?}", + folder_path, examine_response + ); let mut exists: usize = examine_response.uidnext - 1; { let mut uidvalidities = uid_store.uidvalidity.lock().unwrap(); @@ -149,11 +158,22 @@ impl MailBackend for ImapType { .entry(folder_hash) .or_insert(examine_response.uidvalidity); *v = examine_response.uidvalidity; - } - { + + let mut permissions = permissions.lock().unwrap(); + permissions.create_messages = !examine_response.read_only; + permissions.remove_messages = !examine_response.read_only; + permissions.set_flags = !examine_response.read_only; + permissions.rename_messages = !examine_response.read_only; + permissions.delete_messages = !examine_response.read_only; + permissions.delete_messages = !examine_response.read_only; let mut folder_exists = folder_exists.lock().unwrap(); *folder_exists = exists; } + /* reselecting the same mailbox with EXAMINE prevents expunging it */ + exit_on_error!(&tx, + conn.send_command(format!("EXAMINE {}", folder_path).as_bytes()) + conn.read_response(&mut response) + ); while exists > 1 { let mut envelopes = vec![]; @@ -285,11 +305,23 @@ impl MailBackend for ImapType { let path = { let folders = self.folders.lock().unwrap(); - folders - .values() - .find(|v| v.name == folder) + let f_result = folders.values().find(|v| v.name == folder); + if f_result + .map(|f| !f.permissions.lock().unwrap().create_messages) + .unwrap_or(false) + { + return Err(MeliError::new(format!( + "You are not allowed to create messages in folder {}", + folder + ))); + } + + f_result .map(|v| v.path().to_string()) - .ok_or(MeliError::new(""))? + .ok_or(MeliError::new(format!( + "Folder with name {} not found.", + folder + )))? }; let mut response = String::with_capacity(8 * 1024); let mut conn = self.connection.lock().unwrap(); @@ -365,6 +397,9 @@ impl MailBackend for ImapType { conn.send_command(format!("UNSUBSCRIBE \"{}\"", path,).as_bytes())?; conn.read_response(&mut response)?; } + SetPermissions(_new_val) => { + unimplemented!(); + } } Ok(()) } diff --git a/melib/src/backends/imap/folder.rs b/melib/src/backends/imap/folder.rs index e1b27649..48d04ad9 100644 --- a/melib/src/backends/imap/folder.rs +++ b/melib/src/backends/imap/folder.rs @@ -18,7 +18,7 @@ * You should have received a copy of the GNU General Public License * along with meli. If not, see . */ -use crate::backends::{BackendFolder, Folder, FolderHash}; +use crate::backends::{BackendFolder, Folder, FolderHash, FolderPermissions}; use std::sync::{Arc, Mutex}; #[derive(Debug, Default, Clone)] @@ -29,6 +29,7 @@ pub struct ImapFolder { pub(super) parent: Option, pub(super) children: Vec, + pub permissions: Arc>, pub exists: Arc>, } @@ -60,6 +61,7 @@ impl BackendFolder for ImapFolder { name: self.name.clone(), parent: self.parent, children: self.children.clone(), + permissions: self.permissions.clone(), exists: self.exists.clone(), }) } @@ -67,4 +69,8 @@ impl BackendFolder for ImapFolder { fn parent(&self) -> Option { self.parent } + + fn permissions(&self) -> FolderPermissions { + *self.permissions.lock().unwrap() + } } diff --git a/melib/src/backends/imap/protocol_parser.rs b/melib/src/backends/imap/protocol_parser.rs index f2a4a997..57ba4ff3 100644 --- a/melib/src/backends/imap/protocol_parser.rs +++ b/melib/src/backends/imap/protocol_parser.rs @@ -287,6 +287,7 @@ pub struct SelectResponse { pub uidvalidity: usize, pub uidnext: usize, pub permanentflags: Flag, + pub read_only: bool, } /* @@ -337,6 +338,10 @@ pub fn select_response(input: &str) -> Result { flags(&l["* OK [PERMANENTFLAGS (".len()..l.find(')').unwrap()]) .to_full_result() .unwrap(); + } else if l.contains("OK [READ-WRITE]") { + ret.read_only = false; + } else if l.contains("OK [READ-ONLY]") { + ret.read_only = true; } else if !l.is_empty() { debug!("select response: {}", l); } diff --git a/melib/src/backends/maildir.rs b/melib/src/backends/maildir.rs index 6fbb8d10..9bab4399 100644 --- a/melib/src/backends/maildir.rs +++ b/melib/src/backends/maildir.rs @@ -182,6 +182,7 @@ pub struct MaildirFolder { path: PathBuf, parent: Option, children: Vec, + permissions: FolderPermissions, } impl MaildirFolder { @@ -229,6 +230,12 @@ impl MaildirFolder { None }; + let read_only = if let Ok(metadata) = std::fs::metadata(&pathbuf) { + metadata.permissions().readonly() + } else { + true + }; + let ret = MaildirFolder { hash: h.finish(), name: file_name, @@ -236,6 +243,16 @@ impl MaildirFolder { fs_path: pathbuf, parent, children, + permissions: FolderPermissions { + create_messages: !read_only, + remove_messages: !read_only, + set_flags: !read_only, + create_child: !read_only, + rename_messages: !read_only, + delete_messages: !read_only, + delete_mailbox: !read_only, + change_permissions: false, + }, }; ret.is_valid()?; Ok(ret) @@ -290,10 +307,15 @@ impl BackendFolder for MaildirFolder { path: self.path.clone(), children: self.children.clone(), parent: self.parent, + permissions: self.permissions, }) } fn parent(&self) -> Option { self.parent } + + fn permissions(&self) -> FolderPermissions { + self.permissions + } } diff --git a/melib/src/backends/mbox.rs b/melib/src/backends/mbox.rs index a1548f0b..cb969969 100644 --- a/melib/src/backends/mbox.rs +++ b/melib/src/backends/mbox.rs @@ -27,7 +27,8 @@ use crate::async_workers::{Async, AsyncBuilder, AsyncStatus, WorkContext}; use crate::backends::BackendOp; use crate::backends::FolderHash; use crate::backends::{ - BackendFolder, Folder, MailBackend, RefreshEvent, RefreshEventConsumer, RefreshEventKind, + BackendFolder, Folder, FolderPermissions, MailBackend, RefreshEvent, RefreshEventConsumer, + RefreshEventKind, }; use crate::conf::AccountSettings; use crate::email::parser::BytesExt; @@ -86,6 +87,7 @@ struct MboxFolder { content: Vec, children: Vec, parent: Option, + permissions: FolderPermissions, } impl BackendFolder for MboxFolder { @@ -114,6 +116,7 @@ impl BackendFolder for MboxFolder { content: self.content.clone(), children: self.children.clone(), parent: self.parent, + permissions: self.permissions, }) } @@ -124,6 +127,10 @@ impl BackendFolder for MboxFolder { fn parent(&self) -> Option { self.parent } + + fn permissions(&self) -> FolderPermissions { + self.permissions + } } /// `BackendOp` implementor for Mbox @@ -574,6 +581,13 @@ impl MboxType { .map(|f| f.to_string_lossy().into()) .unwrap_or(String::new()); let hash = get_path_hash!(&ret.path); + + let read_only = if let Ok(metadata) = std::fs::metadata(&ret.path) { + metadata.permissions().readonly() + } else { + true + }; + ret.folders.lock().unwrap().insert( hash, MboxFolder { @@ -583,6 +597,16 @@ impl MboxType { content: Vec::new(), children: Vec::new(), parent: None, + permissions: FolderPermissions { + create_messages: !read_only, + remove_messages: !read_only, + set_flags: !read_only, + create_child: !read_only, + rename_messages: !read_only, + delete_messages: !read_only, + delete_mailbox: !read_only, + change_permissions: false, + }, }, ); /*