diff --git a/src/chat.rs b/src/chat.rs index fdf7c3665..d51aa6d85 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -18,7 +18,7 @@ use crate::constants::{ Blocked, Chattype, DC_CHAT_ID_ALLDONE_HINT, DC_CHAT_ID_ARCHIVED_LINK, DC_CHAT_ID_LAST_SPECIAL, DC_CHAT_ID_TRASH, DC_GCM_ADDDAYMARKER, DC_GCM_INFO_ONLY, DC_RESEND_USER_AVATAR_DAYS, }; -use crate::contact::{addr_cmp, Contact, ContactId, Origin, VerifiedStatus}; +use crate::contact::{Contact, ContactId, Origin, VerifiedStatus}; use crate::context::Context; use crate::dc_receive_imf::ReceivedMsg; use crate::dc_tools::{ @@ -1243,7 +1243,7 @@ impl Chat { } } - let from = context.get_configured_addr().await?; + let from = context.get_primary_self_addr().await?; let new_rfc724_mid = { let grpid = match self.typ { Chattype::Group => Some(self.grpid.as_str()), @@ -2023,7 +2023,7 @@ async fn create_send_msg_job(context: &Context, msg_id: MsgId) -> Result Result { - self.get_config(Config::ConfiguredAddr) - .await? - .context("no address configured") - } - pub(crate) async fn should_watch_mvbox(&self) -> Result { Ok(self.get_config_bool(Config::MvboxMove).await? || self.get_config_bool(Config::OnlyFetchMvbox).await?) @@ -333,6 +333,70 @@ impl Context { } } +// Separate impl block for self address handling +impl Context { + /// determine whether the specified addr maps to the/a self addr + pub async fn is_self_addr(&self, addr: &str) -> Result { + let addr = addr_normalize(addr).to_lowercase(); + + // The addresses we get here are already normalized and lowercase TODO is this true? + Ok( + self.get_config(Config::ConfiguredAddr).await?.as_deref() == Some(&addr) + || self.get_secondary_self_addrs().await?.contains(&addr), + ) + } + + /// Sets `primary_new` as the new primary self address and saves the old + /// primary address (if exists) as a secondary address. + pub(crate) async fn set_primary_self_addr(&self, primary_new: &str) -> Result<()> { + let primary_new = addr_normalize(primary_new).to_lowercase(); // TODO check if this might make problems + + // Get all self addrs, including the old primary addr: + let mut secondary_new = self.get_all_self_addrs().await?; + // Remove the new primary addr in case it was a secondary addr before: + secondary_new.retain(|a| a != &primary_new); + self.set_config( + Config::SecondaryAddrs, + Some(secondary_new.join(" ").as_str()), + ) + .await?; + + self.set_config(Config::ConfiguredAddr, Some(&primary_new)) + .await?; + + Ok(()) + } + + /// Returns all primary and secondary self addresses. + pub async fn get_all_self_addrs(&self) -> Result> { + let mut ret = Vec::new(); + + ret.extend(self.get_config(Config::ConfiguredAddr).await?.into_iter()); + ret.extend(self.get_secondary_self_addrs().await?.into_iter()); + + Ok(ret) + } + + /// Returns all secondary self addresses. + pub async fn get_secondary_self_addrs(&self) -> Result> { + let secondary_addrs = self + .get_config(Config::SecondaryAddrs) + .await? + .unwrap_or_default(); + Ok(secondary_addrs + .split_ascii_whitespace() + .map(|s| s.to_string()) + .collect()) + } + + /// Returns the primary self address. + pub async fn get_primary_self_addr(&self) -> Result { + self.get_config(Config::ConfiguredAddr) + .await? + .context("No self addr configured") + } +} + /// Returns all available configuration keys concated together. fn get_config_keys_string() -> String { let keys = Config::iter().fold(String::new(), |mut acc, key| { diff --git a/src/contact.rs b/src/contact.rs index 93cf6a83d..97c03a9ee 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -296,7 +296,7 @@ impl Contact { .await?; if contact_id == ContactId::SELF { contact.name = stock_str::self_msg(context).await; - contact.addr = context.get_configured_addr().await.unwrap_or_default(); + contact.addr = context.get_primary_self_addr().await.unwrap_or_default(); contact.status = context .get_config(Config::Selfstatus) .await? @@ -398,6 +398,7 @@ impl Contact { if context.is_self_addr(addr_normalized).await? { return Ok(Some(ContactId::SELF)); } + let id = context .sql .query_get_value( @@ -448,10 +449,7 @@ impl Contact { let addr = addr_normalize(addr).to_string(); - // during configuration process some chats are added and addr - // might not be configured yet - let addr_self = context.get_configured_addr().await.unwrap_or_default(); - if addr_cmp(&addr, &addr_self) { + if context.is_self_addr(&addr).await? { return Ok((ContactId::SELF, sth_modified)); } @@ -686,7 +684,6 @@ impl Contact { listflags: u32, query: Option>, ) -> Result> { - let self_addr = context.get_configured_addr().await.unwrap_or_default(); let mut add_self = false; let mut ret = Vec::new(); let flag_verified_only = (listflags & DC_GCL_VERIFIED_ONLY) != 0; @@ -699,15 +696,13 @@ impl Contact { .query_map( "SELECT c.id FROM contacts c \ LEFT JOIN acpeerstates ps ON c.addr=ps.addr \ - WHERE c.addr!=?1 \ - AND c.id>?2 \ - AND c.origin>=?3 \ + WHERE c.id>?1 \ + AND c.origin>=?2 \ AND c.blocked=0 \ - AND (iif(c.name='',c.authname,c.name) LIKE ?4 OR c.addr LIKE ?5) \ - AND (1=?6 OR LENGTH(ps.verified_key_fingerprint)!=0) \ + AND (iif(c.name='',c.authname,c.name) LIKE ?3 OR c.addr LIKE ?4) \ + AND (1=?5 OR LENGTH(ps.verified_key_fingerprint)!=0) \ ORDER BY LOWER(iif(c.name='',c.authname,c.name)||c.addr),c.id;", paramsv![ - self_addr, ContactId::LAST_SPECIAL, Origin::IncomingReplyTo, s3str_like_cmd, @@ -724,13 +719,17 @@ impl Contact { ) .await?; - let self_name = context - .get_config(Config::Displayname) - .await? - .unwrap_or_default(); - let self_name2 = stock_str::self_msg(context); - if let Some(query) = query { + let self_addr = context + .get_config(Config::ConfiguredAddr) + .await? + .unwrap_or_default(); + let self_name = context + .get_config(Config::Displayname) + .await? + .unwrap_or_default(); + let self_name2 = stock_str::self_msg(context); + if self_addr.contains(query.as_ref()) || self_name.contains(query.as_ref()) || self_name2.await.contains(query.as_ref()) @@ -747,12 +746,11 @@ impl Contact { .sql .query_map( "SELECT id FROM contacts - WHERE addr!=?1 - AND id>?2 - AND origin>=?3 + WHERE id>?1 + AND origin>=?2 AND blocked=0 ORDER BY LOWER(iif(name='',authname,name)||addr),id;", - paramsv![self_addr, ContactId::LAST_SPECIAL, Origin::IncomingReplyTo], + paramsv![ContactId::LAST_SPECIAL, Origin::IncomingReplyTo], |row| row.get::<_, ContactId>(0), |ids| { for id in ids { @@ -1116,26 +1114,6 @@ impl Contact { Ok(VerifiedStatus::Unverified) } - pub async fn addr_equals_contact( - context: &Context, - addr: &str, - contact_id: ContactId, - ) -> Result { - if addr.is_empty() { - return Ok(false); - } - - let contact = Contact::load_from_db(context, contact_id).await?; - if !contact.addr.is_empty() { - let normalized_addr = addr_normalize(addr); - if contact.addr == normalized_addr { - return Ok(true); - } - } - - Ok(false) - } - pub async fn get_real_cnt(context: &Context) -> Result { if !context.sql.is_open().await { return Ok(0); @@ -1426,17 +1404,6 @@ fn cat_fingerprint( } } -impl Context { - /// determine whether the specified addr maps to the/a self addr - pub async fn is_self_addr(&self, addr: &str) -> Result { - if let Some(self_addr) = self.get_config(Config::ConfiguredAddr).await? { - Ok(addr_cmp(&self_addr, addr)) - } else { - Ok(false) - } - } -} - pub fn addr_cmp(addr1: &str, addr2: &str) -> bool { let norm1 = addr_normalize(addr1).to_lowercase(); let norm2 = addr_normalize(addr2).to_lowercase(); @@ -2163,4 +2130,49 @@ Hi."#; Ok(()) } + + #[async_std::test] + async fn test_self_addrs() -> Result<()> { + let alice = TestContext::new_alice().await; + + assert!(alice.is_self_addr("alice@example.org").await?); + assert_eq!(alice.get_all_self_addrs().await?, vec!["alice@example.org"]); + assert!(!alice.is_self_addr("alice@alice.com").await?); + + // Test adding a new (primary) self address + alice.set_primary_self_addr(" Alice@alice.com ").await?; + assert!(alice.is_self_addr("aliCe@example.org").await?); + assert!(alice.is_self_addr("alice@alice.com").await?); + assert_eq!( + alice.get_all_self_addrs().await?, + vec!["alice@alice.com", "alice@example.org"] + ); + + // Check that the entry is not duplicated + alice.set_primary_self_addr("alice@alice.com").await?; + assert_eq!( + alice.get_all_self_addrs().await?, + vec!["alice@alice.com", "alice@example.org"] + ); + + // Test switching back + alice.set_primary_self_addr("alice@example.org").await?; + assert_eq!( + alice.get_all_self_addrs().await?, + vec!["alice@example.org", "alice@alice.com"] + ); + + // Test setting a new primary self address, the previous self address + // should be kept as a secondary self address + alice.set_primary_self_addr("alice@alice.xyz").await?; + assert_eq!( + alice.get_all_self_addrs().await?, + vec!["alice@alice.xyz", "alice@example.org", "alice@alice.com"] + ); + assert!(alice.is_self_addr("alice@example.org").await?); + assert!(alice.is_self_addr("alice@alice.com").await?); + assert!(alice.is_self_addr("Alice@alice.xyz").await?); + + Ok(()) + } } diff --git a/src/context.rs b/src/context.rs index fa54395fb..93863e836 100644 --- a/src/context.rs +++ b/src/context.rs @@ -336,6 +336,7 @@ impl Context { let unset = "0"; let l = LoginParam::load_candidate_params(self).await?; let l2 = LoginParam::load_configured_params(self).await?; + let secondary_addrs = self.get_secondary_self_addrs().await?.join(", "); let displayname = self.get_config(Config::Displayname).await?; let chats = get_chat_cnt(self).await? as usize; let unblocked_msgs = message::get_unblocked_msg_cnt(self).await as usize; @@ -420,6 +421,7 @@ impl Context { res.insert("socks5_enabled", socks5_enabled.to_string()); res.insert("entered_account_settings", l.to_string()); res.insert("used_account_settings", l2.to_string()); + res.insert("secondary_addrs", secondary_addrs); res.insert( "fetch_existing_msgs", self.get_config_int(Config::FetchExistingMsgs) diff --git a/src/dc_receive_imf.rs b/src/dc_receive_imf.rs index e8f173a3c..0be5ff53e 100644 --- a/src/dc_receive_imf.rs +++ b/src/dc_receive_imf.rs @@ -16,7 +16,7 @@ use crate::config::Config; use crate::constants::{Blocked, Chattype, ShowEmails, DC_CHAT_ID_TRASH}; use crate::contact; use crate::contact::{ - addr_cmp, may_be_valid_addr, normalize_name, Contact, ContactId, Origin, VerifiedStatus, + may_be_valid_addr, normalize_name, Contact, ContactId, Origin, VerifiedStatus, }; use crate::context::Context; use crate::dc_tools::{dc_create_id, dc_extract_grpid_from_rfc724_mid, dc_smeared_time}; @@ -1415,7 +1415,17 @@ async fn create_or_lookup_group( ProtectionStatus::Unprotected }; - let self_addr = context.get_configured_addr().await?; + async fn self_explicitly_added( + context: &Context, + mime_parser: &&mut MimeMessage, + ) -> Result { + let ret = match mime_parser.get_header(HeaderDef::ChatGroupMemberAdded) { + Some(member_addr) => context.is_self_addr(member_addr).await?, + None => false, + }; + Ok(ret) + } + if chat_id.is_none() && !mime_parser.is_mailinglist_message() && !grpid.is_empty() @@ -1424,7 +1434,7 @@ async fn create_or_lookup_group( && mime_parser.get_header(HeaderDef::ChatGroupMemberRemoved).is_none() // re-create explicitly left groups only if ourself is re-added && (!chat::is_group_explicitly_left(context, &grpid).await? - || mime_parser.get_header(HeaderDef::ChatGroupMemberAdded).map_or(false, |member_addr| addr_cmp(&self_addr, member_addr))) + || self_explicitly_added(context, &mime_parser).await?) { // Group does not exist but should be created. if !allow_creation { @@ -1456,7 +1466,7 @@ async fn create_or_lookup_group( } for &to_id in to_ids.iter() { info!(context, "adding to={:?} to chat id={}", to_id, new_chat_id); - if !Contact::addr_equals_contact(context, &self_addr, to_id).await? + if to_id != ContactId::SELF && !chat::is_contact_in_chat(context, new_chat_id, to_id).await? { chat::add_to_chat_contacts_table(context, new_chat_id, to_id).await?; @@ -1511,7 +1521,6 @@ async fn apply_group_changes( return Ok(None); } - let self_addr = context.get_configured_addr().await?; let mut recreate_member_list = false; let mut send_event_chat_modified = false; @@ -1631,14 +1640,14 @@ async fn apply_group_changes( } } if !from_id.is_special() - && !Contact::addr_equals_contact(context, &self_addr, from_id).await? + && from_id != ContactId::SELF && !chat::is_contact_in_chat(context, chat_id, from_id).await? && removed_id != Some(from_id) { chat::add_to_chat_contacts_table(context, chat_id, from_id).await?; } for &to_id in to_ids.iter() { - if !Contact::addr_equals_contact(context, &self_addr, to_id).await? + if to_id != ContactId::SELF && !chat::is_contact_in_chat(context, chat_id, to_id).await? && removed_id != Some(to_id) { @@ -1955,7 +1964,7 @@ async fn create_adhoc_group( /// are hidden in BCC. This group ID is sent by DC in the messages sent to this chat, /// so having the same ID prevents group split. async fn create_adhoc_grp_id(context: &Context, member_ids: &[ContactId]) -> Result { - let member_cs = context.get_configured_addr().await?.to_lowercase(); + let member_cs = context.get_primary_self_addr().await?.to_lowercase(); let query = format!( "SELECT addr FROM contacts WHERE id IN({}) AND id!=?", sql::repeat_vars(member_ids.len())? diff --git a/src/e2ee.rs b/src/e2ee.rs index 219aff28a..55bfc162f 100644 --- a/src/e2ee.rs +++ b/src/e2ee.rs @@ -28,7 +28,7 @@ impl EncryptHelper { let prefer_encrypt = EncryptPreference::from_i32(context.get_config_int(Config::E2eeEnabled).await?) .unwrap_or_default(); - let addr = context.get_configured_addr().await?; + let addr = context.get_primary_self_addr().await?; let public_key = SignedPublicKey::load_self(context).await?; Ok(EncryptHelper { @@ -381,7 +381,7 @@ fn contains_report(mail: &ParsedMail<'_>) -> bool { /// [Config::ConfiguredAddr] is configured, this address is returned. // TODO, remove this once deltachat::key::Key no longer exists. pub async fn ensure_secret_key_exists(context: &Context) -> Result { - let self_addr = context.get_configured_addr().await?; + let self_addr = context.get_primary_self_addr().await?; SignedPublicKey::load_self(context).await?; Ok(self_addr) } diff --git a/src/imap.rs b/src/imap.rs index c6a72f2ba..323c0ec37 100644 --- a/src/imap.rs +++ b/src/imap.rs @@ -1206,10 +1206,26 @@ impl Imap { .session .as_mut() .context("IMAP No Connection established")?; - let self_addr = context.get_configured_addr().await?; - let search_command = format!("FROM \"{}\"", self_addr); + + // TODO probably requires a test if want to keep it as is: + let self_addrs = context + .get_all_self_addrs() + .await + .context("get_all_recipients() can't get self addrs")?; + + if self_addrs.is_empty() { + bail!("get_all_recipients(): No self addresses"); + } + + let from_part = self_addrs + .iter() + .map(|a| format!(" FROM {}", a)) + .collect::(); + let mut search_command = " OR".repeat(self_addrs.len() - 1); + search_command.push_str(&from_part); + let uids = session - .uid_search(search_command) + .uid_search(search_command.trim()) .await? .into_iter() .collect(); diff --git a/src/imex.rs b/src/imex.rs index 8bbc7b867..e973dbef4 100644 --- a/src/imex.rs +++ b/src/imex.rs @@ -351,7 +351,7 @@ async fn set_self_key( } }; - let self_addr = context.get_configured_addr().await?; + let self_addr = context.get_primary_self_addr().await?; let addr = EmailAddress::new(&self_addr)?; let keypair = pgp::KeyPair { addr, diff --git a/src/key.rs b/src/key.rs index 81050cee2..861ce4223 100644 --- a/src/key.rs +++ b/src/key.rs @@ -198,7 +198,7 @@ impl DcSecretKey for SignedSecretKey { } async fn generate_keypair(context: &Context) -> Result { - let addr = context.get_configured_addr().await?; + let addr = context.get_primary_self_addr().await?; let addr = EmailAddress::new(&addr)?; let _guard = context.generating_key_mutex.lock().await; diff --git a/src/location.rs b/src/location.rs index 0ed31dfe2..f74c6072d 100644 --- a/src/location.rs +++ b/src/location.rs @@ -423,7 +423,7 @@ pub async fn delete_all(context: &Context) -> Result<()> { pub async fn get_kml(context: &Context, chat_id: ChatId) -> Result<(String, u32)> { let mut last_added_location_id = 0; - let self_addr = context.get_configured_addr().await?; + let self_addr = context.get_primary_self_addr().await?; let (locations_send_begin, locations_send_until, locations_last_sent) = context.sql.query_row( "SELECT locations_send_begin, locations_send_until, locations_last_sent FROM chats WHERE id=?;", diff --git a/src/login_param.rs b/src/login_param.rs index 0debe2828..85c2e0e66 100644 --- a/src/login_param.rs +++ b/src/login_param.rs @@ -256,8 +256,7 @@ impl LoginParam { let prefix = "configured_"; let sql = &context.sql; - let key = format!("{}addr", prefix); - sql.set_raw_config(key, Some(&self.addr)).await?; + context.set_primary_self_addr(&self.addr).await?; let key = format!("{}mail_server", prefix); sql.set_raw_config(key, Some(&self.imap.server)).await?; diff --git a/src/mimefactory.rs b/src/mimefactory.rs index 7fed6cb73..2ee93f5ea 100644 --- a/src/mimefactory.rs +++ b/src/mimefactory.rs @@ -133,7 +133,7 @@ impl<'a> MimeFactory<'a> { ) -> Result> { let chat = Chat::load_from_db(context, msg.chat_id).await?; - let from_addr = context.get_configured_addr().await?; + let from_addr = context.get_primary_self_addr().await?; let config_displayname = context .get_config(Config::Displayname) .await? @@ -233,7 +233,7 @@ impl<'a> MimeFactory<'a> { ensure!(!msg.chat_id.is_special(), "Invalid chat id"); let contact = Contact::load_from_db(context, msg.from_id).await?; - let from_addr = context.get_configured_addr().await?; + let from_addr = context.get_primary_self_addr().await?; let from_displayname = context .get_config(Config::Displayname) .await? @@ -271,12 +271,13 @@ impl<'a> MimeFactory<'a> { &self, context: &Context, ) -> Result, &str)>> { - let self_addr = context.get_configured_addr().await?; + let self_addrs = context.get_all_self_addrs().await?; + let mut res = Vec::new(); for (_, addr) in self .recipients .iter() - .filter(|(_, addr)| addr != &self_addr) + .filter(|(_, addr)| !self_addrs.contains(addr)) { res.push((Peerstate::from_addr(context, addr).await?, addr.as_str())); } diff --git a/src/scheduler/connectivity.rs b/src/scheduler/connectivity.rs index 2bbf457d1..5ea221a62 100644 --- a/src/scheduler/connectivity.rs +++ b/src/scheduler/connectivity.rs @@ -449,7 +449,7 @@ impl Context { // [======67%===== ] // ============================================================================================= - let domain = dc_tools::EmailAddress::new(&self.get_configured_addr().await?)?.domain; + let domain = dc_tools::EmailAddress::new(&self.get_primary_self_addr().await?)?.domain; ret += &format!( "

{}

    ", stock_str::storage_on_domain(self, domain).await diff --git a/src/securejoin.rs b/src/securejoin.rs index 0f7795213..65e810ac0 100644 --- a/src/securejoin.rs +++ b/src/securejoin.rs @@ -66,7 +66,7 @@ pub async fn dc_get_securejoin_qr(context: &Context, group: Option) -> R .is_none(); let invitenumber = token::lookup_or_new(context, Namespace::InviteNumber, group).await; let auth = token::lookup_or_new(context, Namespace::Auth, group).await; - let self_addr = context.get_configured_addr().await?; + let self_addr = context.get_primary_self_addr().await?; let self_name = context .get_config(Config::Displayname) .await? diff --git a/src/sql/migrations.rs b/src/sql/migrations.rs index d2f02280b..7b5caf142 100644 --- a/src/sql/migrations.rs +++ b/src/sql/migrations.rs @@ -395,7 +395,7 @@ UPDATE chats SET protected=1, type=120 WHERE type=130;"#, if dbversion < 71 { info!(context, "[migration] v71"); - if let Ok(addr) = context.get_configured_addr().await { + if let Ok(addr) = context.get_primary_self_addr().await { if let Ok(domain) = addr.parse::().map(|email| email.domain) { context .set_config( diff --git a/src/test_utils.rs b/src/test_utils.rs index 01cc2f6a0..d55e91768 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -407,7 +407,7 @@ impl TestContext { .await .unwrap_or_default() .unwrap_or_default(); - let addr = other.ctx.get_configured_addr().await.unwrap(); + let addr = other.ctx.get_primary_self_addr().await.unwrap(); // MailinglistAddress is the lowest allowed origin, we'd prefer to not modify the // origin when creating this contact. let (contact_id, modified) =