diff --git a/src/imap/command/authenticated.rs b/src/imap/command/authenticated.rs index 3fd132f..c5f2d00 100644 --- a/src/imap/command/authenticated.rs +++ b/src/imap/command/authenticated.rs @@ -235,11 +235,19 @@ impl<'a> AuthenticatedContext<'a> { .to_string() .try_into() .map_err(|_| anyhow!("invalid mailbox name"))?; - let mut items = vec![FlagNameAttribute::try_from(Atom::unvalidated( + let mut items = vec![FlagNameAttribute::from(Atom::unvalidated( "Subscribed", - ))?]; + ))]; if !*is_real { items.push(FlagNameAttribute::Noselect); + } else { + match *mb { + "Drafts" => items.push(Atom::unvalidated("Drafts").into()), + "Archive" => items.push(Atom::unvalidated("Archive").into()), + "Sent" => items.push(Atom::unvalidated("Sent").into()), + "Trash" => items.push(Atom::unvalidated("Trash").into()), + _ => (), + }; } if is_lsub { ret.push(Data::Lsub { diff --git a/src/mail/user.rs b/src/mail/user.rs index 5af86d0..4767a93 100644 --- a/src/mail/user.rs +++ b/src/mail/user.rs @@ -27,6 +27,19 @@ pub const MAILBOX_HIERARCHY_DELIMITER: char = '.'; /// INBOX), and we create a new empty mailbox for INBOX. pub const INBOX: &str = "INBOX"; +/// For convenience purpose, we also create some special mailbox +/// that are described in RFC6154 SPECIAL-USE +/// @FIXME maybe it should be a configuration parameter +/// @FIXME maybe we should have a per-mailbox flag mechanism, either an enum or a string, so we +/// track which mailbox is used for what. +/// @FIXME Junk could be useful but we don't have any antispam solution yet so... +/// @FIXME IMAP supports virtual mailbox. \All or \Flagged are intended to be virtual mailboxes. +/// \Trash might be one, or not one. I don't know what we should do there. +pub const DRAFTS: &str = "Drafts"; +pub const ARCHIVE: &str = "Archive"; +pub const SENT: &str = "Sent"; +pub const TRASH: &str = "Trash"; + const MAILBOX_LIST_PK: &str = "mailboxes"; const MAILBOX_LIST_SK: &str = "list"; @@ -124,7 +137,7 @@ impl User { let (mut list, ct) = self.load_mailbox_list().await?; if list.has_mailbox(name) { - // TODO: actually delete mailbox contents + //@TODO: actually delete mailbox contents list.set_mailbox(name, None); self.save_mailbox_list(&list, ct).await?; Ok(()) @@ -256,7 +269,16 @@ impl User { } }; - self.ensure_inbox_exists(&mut list, &row).await?; + let is_default_mbx_missing = [ DRAFTS, ARCHIVE, SENT, TRASH ] + .iter() + .map(|mbx| list.create_mailbox(mbx)) + .fold(false, |acc, r| acc || matches!(r, CreatedMailbox::Created(..))); + let is_inbox_missing = self.ensure_inbox_exists(&mut list, &row).await?; + if is_default_mbx_missing && !is_inbox_missing { + // It's the only case where we created some mailboxes and not saved them + // So we save them! + self.save_mailbox_list(&list, row.clone()).await?; + } Ok((list, row)) } diff --git a/tests/common/fragments.rs b/tests/common/fragments.rs index 7f7967a..11e0bb7 100644 --- a/tests/common/fragments.rs +++ b/tests/common/fragments.rs @@ -155,8 +155,8 @@ pub fn create_mailbox(imap: &mut TcpStream, mbx: Mailbox) -> Result<()> { let mbx_str = match mbx { Mailbox::Inbox => "INBOX", - Mailbox::Archive => "Archive", - Mailbox::Drafts => "Drafts", + Mailbox::Archive => "ArchiveCustom", + Mailbox::Drafts => "DraftsCustom", }; let cmd = format!("15 create {}\r\n", mbx_str); @@ -172,8 +172,8 @@ pub fn select(imap: &mut TcpStream, mbx: Mailbox, modifier: SelectMod) -> Result let mbx_str = match mbx { Mailbox::Inbox => "INBOX", - Mailbox::Archive => "Archive", - Mailbox::Drafts => "Drafts", + Mailbox::Archive => "ArchiveCustom", + Mailbox::Drafts => "DraftsCustom", }; let mod_str = match modifier { @@ -209,8 +209,8 @@ pub fn check(imap: &mut TcpStream) -> Result<()> { pub fn status(imap: &mut TcpStream, mbx: Mailbox, sk: StatusKind) -> Result { let mbx_str = match mbx { Mailbox::Inbox => "INBOX", - Mailbox::Archive => "Archive", - Mailbox::Drafts => "Drafts", + Mailbox::Archive => "ArchiveCustom", + Mailbox::Drafts => "DraftsCustom", }; let sk_str = match sk { StatusKind::UidNext => "(UIDNEXT)", @@ -325,7 +325,7 @@ pub fn copy(imap: &mut TcpStream, selection: Selection, to: Mailbox) -> Result<( assert!(matches!(selection, Selection::FirstId)); assert!(matches!(to, Mailbox::Archive)); - imap.write(&b"45 copy 1 Archive\r\n"[..])?; + imap.write(&b"45 copy 1 ArchiveCustom\r\n"[..])?; let read = read_lines(imap, &mut buffer, None)?; assert_eq!(&read[..5], &b"45 OK"[..]); @@ -421,7 +421,7 @@ pub fn rename_mailbox(imap: &mut TcpStream, from: Mailbox, to: Mailbox) -> Resul assert!(matches!(from, Mailbox::Archive)); assert!(matches!(to, Mailbox::Drafts)); - imap.write(&b"70 rename Archive Drafts\r\n"[..])?; + imap.write(&b"70 rename ArchiveCustom DraftsCustom\r\n"[..])?; let mut buffer: [u8; 1500] = [0; 1500]; let read = read_lines(imap, &mut buffer, None)?; assert_eq!(&read[..5], &b"70 OK"[..]); @@ -429,9 +429,9 @@ pub fn rename_mailbox(imap: &mut TcpStream, from: Mailbox, to: Mailbox) -> Resul imap.write(&b"71 list \"\" *\r\n"[..])?; let read = read_lines(imap, &mut buffer, Some(&b"71 OK LIST"[..]))?; let srv_msg = std::str::from_utf8(read)?; - assert!(!srv_msg.contains(" Archive\r\n")); + assert!(!srv_msg.contains(" ArchiveCustom\r\n")); assert!(srv_msg.contains(" INBOX\r\n")); - assert!(srv_msg.contains(" Drafts\r\n")); + assert!(srv_msg.contains(" DraftsCustom\r\n")); Ok(()) } @@ -439,8 +439,8 @@ pub fn rename_mailbox(imap: &mut TcpStream, from: Mailbox, to: Mailbox) -> Resul pub fn delete_mailbox(imap: &mut TcpStream, mbx: Mailbox) -> Result<()> { let mbx_str = match mbx { Mailbox::Inbox => "INBOX", - Mailbox::Archive => "Archive", - Mailbox::Drafts => "Drafts", + Mailbox::Archive => "ArchiveCustom", + Mailbox::Drafts => "DraftsCustom", }; let cmd = format!("80 delete {}\r\n", mbx_str); @@ -471,7 +471,7 @@ pub fn r#move(imap: &mut TcpStream, selection: Selection, to: Mailbox) -> Result assert!(matches!(to, Mailbox::Archive)); assert!(matches!(selection, Selection::FirstId)); - imap.write(&b"35 move 1 Archive\r\n"[..])?; + imap.write(&b"35 move 1 ArchiveCustom\r\n"[..])?; let read = read_lines(imap, &mut buffer, Some(&b"35 OK"[..]))?; let srv_msg = std::str::from_utf8(read)?; assert!(srv_msg.contains("* 1 EXPUNGE"));