diff --git a/CHANGELOG.md b/CHANGELOG.md index 899d017a3..cecfd6723 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## 1.28.0 + +- new flag DC_GCL_FOR_FORWARDING for dc_get_chatlist() + that will sort the "saved messages" chat to the top of the chatlist #1336 +- mark mails as being deleted from server in dc_empty_server() #1333 +- fix interaction with servers that do not allow folder creation on root-level; + use path separator as defined by the email server #1359 +- fix group creation if group was created by non-delta clients #1357 +- fix showing replies from non-delta clients #1353 +- fix member list on rejoining left groups #1343 +- fix crash when using empty groups #1354 +- fix potential crash on special names #1350 + + ## 1.27.0 - handle keys reliably on armv7 #1327 diff --git a/Cargo.lock b/Cargo.lock index 85cc6be72..5a147a4c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -650,7 +650,7 @@ dependencies = [ [[package]] name = "deltachat" -version = "1.27.0" +version = "1.28.0" dependencies = [ "ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", "async-imap 0.2.0 (git+https://github.com/async-email/async-imap?branch=feat/send)", @@ -720,10 +720,10 @@ dependencies = [ [[package]] name = "deltachat_ffi" -version = "1.27.0" +version = "1.28.0" dependencies = [ "async-std 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "deltachat 1.27.0", + "deltachat 1.28.0", "failure 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "human-panic 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index f91aaa853..e25fa69e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "deltachat" -version = "1.27.0" +version = "1.28.0" authors = ["Delta Chat Developers (ML) "] edition = "2018" license = "MPL-2.0" diff --git a/deltachat-ffi/Cargo.toml b/deltachat-ffi/Cargo.toml index 4d3a77295..63c5bbd4c 100644 --- a/deltachat-ffi/Cargo.toml +++ b/deltachat-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "deltachat_ffi" -version = "1.27.0" +version = "1.28.0" description = "Deltachat FFI" authors = ["Delta Chat Developers (ML) "] edition = "2018" diff --git a/python/tests/test_account.py b/python/tests/test_account.py index e948fa8b7..4108a5a32 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -76,6 +76,20 @@ class TestOfflineAccountBasic: with pytest.raises(KeyError): ac1.get_config("123123") + def test_empty_group_bcc_self_enabled(self, acfactory): + ac1 = acfactory.get_configured_offline_account() + ac1.set_config("bcc_self", "1") + chat = ac1.create_group_chat(name="group1") + msg = chat.send_text("msg1") + assert msg in chat.get_messages() + + def test_empty_group_bcc_self_disabled(self, acfactory): + ac1 = acfactory.get_configured_offline_account() + ac1.set_config("bcc_self", "0") + chat = ac1.create_group_chat(name="group1") + msg = chat.send_text("msg1") + assert msg in chat.get_messages() + class TestOfflineContact: def test_contact_attr(self, acfactory): diff --git a/src/configure/mod.rs b/src/configure/mod.rs index 43bf1f96a..71e359192 100644 --- a/src/configure/mod.rs +++ b/src/configure/mod.rs @@ -412,9 +412,11 @@ async fn exec_step( } 16 => { progress!(ctx, 900); + let create_mvbox = ctx.get_config_bool(Config::MvboxWatch).await || ctx.get_config_bool(Config::MvboxMove).await; - if let Err(err) = imap.ensure_configured_folders(ctx, create_mvbox).await { + + if let Err(err) = imap.configure_folders(ctx, create_mvbox).await { bail!("configuring folders failed: {:?}", err); } diff --git a/src/constants.rs b/src/constants.rs index 6404b74f7..56d289f49 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -214,6 +214,9 @@ pub const DC_BOB_SUCCESS: i32 = 1; // max. width/height of an avatar pub const AVATAR_SIZE: u32 = 192; +// this value can be increased if the folder configuration is changed and must be redone on next program start +pub const DC_FOLDERS_CONFIGURED_VERSION: i32 = 3; + #[derive( Debug, Display, diff --git a/src/dc_receive_imf.rs b/src/dc_receive_imf.rs index 339b9696d..491978c26 100644 --- a/src/dc_receive_imf.rs +++ b/src/dc_receive_imf.rs @@ -402,7 +402,11 @@ async fn add_parts( let (new_chat_id, new_chat_id_blocked) = create_or_lookup_group( context, &mut mime_parser, - allow_creation, + if test_normal_chat_id.is_unset() { + allow_creation + } else { + true + }, create_blocked, from_id, to_ids, @@ -1715,8 +1719,9 @@ fn dc_create_incoming_rfc724_mid( #[cfg(test)] mod tests { use super::*; + use crate::chatlist::Chatlist; use crate::message::Message; - use crate::test_utils::dummy_context; + use crate::test_utils::{dummy_context, TestContext}; #[test] fn test_hex_hash() { @@ -1811,4 +1816,179 @@ mod tests { assert!(is_msgrmsg_rfc724_mid(&t.ctx, &msg.rfc724_mid).await); assert!(!is_msgrmsg_rfc724_mid(&t.ctx, "nonexistant@message.id").await); } + + async fn configured_offline_context() -> TestContext { + let t = dummy_context().await; + t.ctx + .set_config(Config::Addr, Some("alice@example.org")) + .await + .unwrap(); + t.ctx + .set_config(Config::ConfiguredAddr, Some("alice@example.org")) + .await + .unwrap(); + t.ctx + .set_config(Config::Configured, Some("1")) + .await + .unwrap(); + t + } + + static MSGRMSG: &[u8] = b"From: Bob \n\ + To: alice@example.org\n\ + Chat-Version: 1.0\n\ + Subject: Chat: hello\n\ + Message-ID: \n\ + Date: Sun, 22 Mar 2020 22:37:55 +0000\n\ + \n\ + hello\n"; + + static ONETOONE_NOREPLY_MAIL: &[u8] = b"From: Bob \n\ + To: alice@example.org\n\ + Subject: Chat: hello\n\ + Message-ID: <2222@example.org>\n\ + Date: Sun, 22 Mar 2020 22:37:56 +0000\n\ + \n\ + hello\n"; + + static GRP_MAIL: &[u8] = b"From: bob@example.org\n\ + To: alice@example.org, claire@example.org\n\ + Subject: group with Alice, Bob and Claire\n\ + Message-ID: <3333@example.org>\n\ + Date: Sun, 22 Mar 2020 22:37:57 +0000\n\ + \n\ + hello\n"; + + #[async_std::test] + async fn test_adhoc_group_show_chats_only() { + let t = configured_offline_context().await; + assert_eq!(t.ctx.get_config_int(Config::ShowEmails).await, 0); + + let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); + assert_eq!(chats.len(), 0); + + dc_receive_imf(&t.ctx, MSGRMSG, "INBOX", 1, false) + .await + .unwrap(); + let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); + assert_eq!(chats.len(), 1); + + dc_receive_imf(&t.ctx, ONETOONE_NOREPLY_MAIL, "INBOX", 1, false) + .await + .unwrap(); + let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); + assert_eq!(chats.len(), 1); + + dc_receive_imf(&t.ctx, GRP_MAIL, "INBOX", 1, false) + .await + .unwrap(); + let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); + assert_eq!(chats.len(), 1); + } + + #[async_std::test] + async fn test_adhoc_group_show_accepted_contact_unknown() { + let t = configured_offline_context().await; + t.ctx + .set_config(Config::ShowEmails, Some("1")) + .await + .unwrap(); + dc_receive_imf(&t.ctx, GRP_MAIL, "INBOX", 1, false) + .await + .unwrap(); + + // adhoc-group with unknown contacts with show_emails=accepted is ignored for unknown contacts + let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); + assert_eq!(chats.len(), 0); + } + + #[async_std::test] + async fn test_adhoc_group_show_accepted_contact_known() { + let t = configured_offline_context().await; + t.ctx + .set_config(Config::ShowEmails, Some("1")) + .await + .unwrap(); + Contact::create(&t.ctx, "Bob", "bob@example.org") + .await + .unwrap(); + dc_receive_imf(&t.ctx, GRP_MAIL, "INBOX", 1, false) + .await + .unwrap(); + + // adhoc-group with known contacts with show_emails=accepted is still ignored for known contacts + // (and existent chat is required) + let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); + assert_eq!(chats.len(), 0); + } + + #[async_std::test] + async fn test_adhoc_group_show_accepted_contact_accepted() { + let t = configured_offline_context().await; + t.ctx + .set_config(Config::ShowEmails, Some("1")) + .await + .unwrap(); + + // accept Bob by accepting a delta-message from Bob + dc_receive_imf(&t.ctx, MSGRMSG, "INBOX", 1, false) + .await + .unwrap(); + let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); + assert_eq!(chats.len(), 1); + assert!(chats.get_chat_id(0).is_deaddrop()); + let chat_id = chat::create_by_msg_id(&t.ctx, chats.get_msg_id(0).unwrap()) + .await + .unwrap(); + assert!(!chat_id.is_special()); + let chat = chat::Chat::load_from_db(&t.ctx, chat_id).await.unwrap(); + assert_eq!(chat.typ, Chattype::Single); + assert_eq!(chat.name, "Bob"); + assert_eq!(chat::get_chat_contacts(&t.ctx, chat_id).await.len(), 1); + assert_eq!(chat::get_chat_msgs(&t.ctx, chat_id, 0, None).await.len(), 1); + + // receive a non-delta-message from Bob, shows up because of the show_emails setting + dc_receive_imf(&t.ctx, ONETOONE_NOREPLY_MAIL, "INBOX", 2, false) + .await + .unwrap(); + assert_eq!(chat::get_chat_msgs(&t.ctx, chat_id, 0, None).await.len(), 2); + + // let Bob create an adhoc-group by a non-delta-message, shows up because of the show_emails setting + dc_receive_imf(&t.ctx, GRP_MAIL, "INBOX", 3, false) + .await + .unwrap(); + let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); + assert_eq!(chats.len(), 2); + let chat_id = chat::create_by_msg_id(&t.ctx, chats.get_msg_id(0).unwrap()) + .await + .unwrap(); + let chat = chat::Chat::load_from_db(&t.ctx, chat_id).await.unwrap(); + assert_eq!(chat.typ, Chattype::Group); + assert_eq!(chat.name, "group with Alice, Bob and Claire"); + assert_eq!(chat::get_chat_contacts(&t.ctx, chat_id).await.len(), 3); + } + + #[async_std::test] + async fn test_adhoc_group_show_all() { + let t = configured_offline_context().await; + t.ctx + .set_config(Config::ShowEmails, Some("2")) + .await + .unwrap(); + dc_receive_imf(&t.ctx, GRP_MAIL, "INBOX", 1, false) + .await + .unwrap(); + + // adhoc-group with unknown contacts with show_emails=all will show up in the deaddrop + let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); + assert_eq!(chats.len(), 1); + assert!(chats.get_chat_id(0).is_deaddrop()); + let chat_id = chat::create_by_msg_id(&t.ctx, chats.get_msg_id(0).unwrap()) + .await + .unwrap(); + let chat = chat::Chat::load_from_db(&t.ctx, chat_id).await.unwrap(); + assert_eq!(chat.typ, Chattype::Group); + assert_eq!(chat.name, "group with Alice, Bob and Claire"); + assert_eq!(chat::get_chat_contacts(&t.ctx, chat_id).await.len(), 3); + } } diff --git a/src/imap/mod.rs b/src/imap/mod.rs index 1082f95f3..86aadc6e9 100644 --- a/src/imap/mod.rs +++ b/src/imap/mod.rs @@ -187,7 +187,6 @@ struct ImapConfig { /// True if the server has MOVE capability as defined in /// https://tools.ietf.org/html/rfc6851 pub can_move: bool, - pub imap_delimiter: char, } impl Default for ImapConfig { @@ -205,7 +204,6 @@ impl Default for ImapConfig { selected_folder_needs_expunge: false, can_idle: false, can_move: false, - imap_delimiter: '.', } } } @@ -1111,17 +1109,17 @@ impl Imap { .sql .get_raw_config_int(context, "folders_configured") .await; - if folders_configured.unwrap_or_default() >= 3 { - info!(context, "IMAP-folders already configured"); - // the "3" here we increase if we have future updates to - // to folder configuration + if folders_configured.unwrap_or_default() >= DC_FOLDERS_CONFIGURED_VERSION { return Ok(()); } + self.configure_folders(context, create_mvbox).await + } + + pub async fn configure_folders(&mut self, context: &Context, create_mvbox: bool) -> Result<()> { if !self.is_connected() { return Err(Error::NoConnection); } - info!(context, "Configuring IMAP-folders."); if let Some(ref mut session) = &mut self.session { let mut folders = match session.list(Some(""), Some("*")).await { @@ -1133,7 +1131,17 @@ impl Imap { let mut sentbox_folder = None; let mut mvbox_folder = None; - let delimiter = self.config.imap_delimiter; + + let mut delimiter = ".".to_string(); + if let Some(folder) = folders.next().await { + let folder = folder.map_err(|err| Error::Other(err.to_string()))?; + if let Some(d) = folder.delimiter() { + if !d.is_empty() { + delimiter = d.to_string(); + } + } + } + info!(context, "Using \"{}\" as folder-delimiter.", delimiter); let fallback_folder = format!("INBOX{}DeltaChat", delimiter); while let Some(folder) = folders.next().await { @@ -1221,9 +1229,14 @@ impl Imap { } context .sql - .set_raw_config_int(context, "folders_configured", 3) + .set_raw_config_int(context, "folders_configured", DC_FOLDERS_CONFIGURED_VERSION) .await?; } + context + .sql + .set_raw_config_int(context, "folders_configured", 3) + .await?; + info!(context, "FINISHED configuring IMAP-folders."); Ok(()) } diff --git a/src/mimefactory.rs b/src/mimefactory.rs index b4f73f95f..2fa9ee77d 100644 --- a/src/mimefactory.rs +++ b/src/mimefactory.rs @@ -408,7 +408,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> { self.from_addr.clone(), ); - let mut to = Vec::with_capacity(self.recipients.len()); + let mut to = Vec::new(); for (name, addr) in self.recipients.iter() { if name.is_empty() { to.push(Address::new_mailbox(addr.clone())); @@ -420,6 +420,10 @@ impl<'a, 'b> MimeFactory<'a, 'b> { } } + if to.is_empty() { + to.push(from.clone()); + } + if !self.references.is_empty() { unprotected_headers.push(Header::new("References".into(), self.references.clone())); }