Merge "Add ContactAddress type" (#3947)

This commit is contained in:
link2xt
2023-01-11 23:19:48 +00:00
11 changed files with 378 additions and 254 deletions

View File

@@ -3629,7 +3629,7 @@ mod tests {
use crate::chatlist::{get_archived_cnt, Chatlist};
use crate::constants::{DC_GCL_ARCHIVED_ONLY, DC_GCL_NO_SPECIALS};
use crate::contact::Contact;
use crate::contact::{Contact, ContactAddress};
use crate::receive_imf::receive_imf;
use crate::test_utils::TestContext;
@@ -4692,10 +4692,13 @@ mod tests {
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
assert!(!shall_attach_selfavatar(&t, chat_id).await?);
let (contact_id, _) =
Contact::add_or_lookup(&t, "", "foo@bar.org", Origin::IncomingUnknownTo)
.await?
.unwrap();
let (contact_id, _) = Contact::add_or_lookup(
&t,
"",
ContactAddress::new("foo@bar.org")?,
Origin::IncomingUnknownTo,
)
.await?;
add_contact_to_chat(&t, chat_id, contact_id).await?;
assert!(!shall_attach_selfavatar(&t, chat_id).await?);
t.set_config(Config::Selfavatar, None).await?; // setting to None also forces re-sending
@@ -4941,10 +4944,8 @@ mod tests {
alice.set_config(Config::ShowEmails, Some("2")).await?;
bob.set_config(Config::ShowEmails, Some("2")).await?;
let (contact_id, _) =
Contact::add_or_lookup(&alice, "", "bob@example.net", Origin::ManuallyCreated)
.await?
.unwrap();
let alice_bob_contact = alice.add_or_lookup_contact(&bob).await;
let contact_id = alice_bob_contact.id;
let alice_chat_id = create_group_chat(&alice, ProtectionStatus::Unprotected, "grp").await?;
let alice_chat = Chat::load_from_db(&alice, alice_chat_id).await?;
@@ -5654,10 +5655,13 @@ mod tests {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_create_for_contact_with_blocked() -> Result<()> {
let t = TestContext::new().await;
let (contact_id, _) =
Contact::add_or_lookup(&t, "", "foo@bar.org", Origin::ManuallyCreated)
.await?
.unwrap();
let (contact_id, _) = Contact::add_or_lookup(
&t,
"",
ContactAddress::new("foo@bar.org")?,
Origin::ManuallyCreated,
)
.await?;
// create a blocked chat
let chat_id_orig =

View File

@@ -4,6 +4,7 @@ use std::cmp::Reverse;
use std::collections::BinaryHeap;
use std::convert::{TryFrom, TryInto};
use std::fmt;
use std::ops::Deref;
use std::path::PathBuf;
use std::time::{SystemTime, UNIX_EPOCH};
@@ -36,6 +37,51 @@ use crate::{chat, stock_str};
/// Time during which a contact is considered as seen recently.
const SEEN_RECENTLY_SECONDS: i64 = 600;
/// Valid contact address.
#[derive(Debug, Clone, Copy)]
pub(crate) struct ContactAddress<'a>(&'a str);
impl Deref for ContactAddress<'_> {
type Target = str;
fn deref(&self) -> &Self::Target {
self.0
}
}
impl AsRef<str> for ContactAddress<'_> {
fn as_ref(&self) -> &str {
self.0
}
}
impl fmt::Display for ContactAddress<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl<'a> ContactAddress<'a> {
/// Constructs a new contact address from string,
/// normalizing and validating it.
pub fn new(s: &'a str) -> Result<Self> {
let addr = addr_normalize(s);
if !may_be_valid_addr(addr) {
bail!("invalid address {:?}", s);
}
Ok(Self(addr))
}
}
/// Allow converting [`ContactAddress`] to an SQLite type.
impl rusqlite::types::ToSql for ContactAddress<'_> {
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
let val = rusqlite::types::Value::Text(self.0.to_string());
let out = rusqlite::types::ToSqlOutput::Owned(val);
Ok(out)
}
}
/// Contact ID, including reserved IDs.
///
/// Some contact IDs are reserved to identify special contacts. This
@@ -378,12 +424,12 @@ impl Contact {
let name = improve_single_line_input(name);
let (name, addr) = sanitize_name_and_addr(&name, addr);
let addr = ContactAddress::new(&addr)?;
let (contact_id, sth_modified) =
Contact::add_or_lookup(context, &name, &addr, Origin::ManuallyCreated)
Contact::add_or_lookup(context, &name, addr, Origin::ManuallyCreated)
.await
.context("add_or_lookup")?
.with_context(|| format!("can't create a contact with address {:?}", addr))?;
.context("add_or_lookup")?;
let blocked = Contact::is_blocked_load(context, contact_id).await?;
match sth_modified {
Modifier::None => {}
@@ -473,28 +519,16 @@ impl Contact {
pub(crate) async fn add_or_lookup(
context: &Context,
name: &str,
addr: &str,
addr: ContactAddress<'_>,
mut origin: Origin,
) -> Result<Option<(ContactId, Modifier)>> {
) -> Result<(ContactId, Modifier)> {
let mut sth_modified = Modifier::None;
ensure!(!addr.is_empty(), "Can not add_or_lookup empty address");
ensure!(origin != Origin::Unknown, "Missing valid origin");
let addr = addr_normalize(addr).to_string();
if context.is_self_addr(&addr).await? {
return Ok(Some((ContactId::SELF, sth_modified)));
}
if !may_be_valid_addr(&addr) {
warn!(
context,
"Bad address \"{}\" for contact \"{}\".",
addr,
if !name.is_empty() { name } else { "<unset>" },
);
return Ok(None);
return Ok((ContactId::SELF, sth_modified));
}
let mut name = name;
@@ -555,7 +589,7 @@ impl Contact {
|| row_authname.is_empty());
row_id = u32::try_from(id)?;
if origin as i32 >= row_origin as i32 && addr != row_addr {
if origin >= row_origin && addr.as_ref() != row_addr {
update_addr = true;
}
if update_name || update_authname || update_addr || origin > row_origin {
@@ -657,7 +691,7 @@ impl Contact {
}
}
Ok(Some((ContactId::new(row_id), sth_modified)))
Ok((ContactId::new(row_id), sth_modified))
}
/// Add a number of contacts.
@@ -683,21 +717,25 @@ impl Contact {
for (name, addr) in split_address_book(addr_book).into_iter() {
let (name, addr) = sanitize_name_and_addr(name, addr);
let name = normalize_name(&name);
match Contact::add_or_lookup(context, &name, &addr, Origin::AddressBook).await {
Err(err) => {
warn!(
context,
"Failed to add address {} from address book: {}", addr, err
);
}
Ok(None) => {
warn!(context, "Cannot create contact with address {}.", addr);
}
Ok(Some((_, modified))) => {
if modified != Modifier::None {
modify_cnt += 1
match ContactAddress::new(&addr) {
Ok(addr) => {
match Contact::add_or_lookup(context, &name, addr, Origin::AddressBook).await {
Ok((_, modified)) => {
if modified != Modifier::None {
modify_cnt += 1
}
}
Err(err) => {
warn!(
context,
"Failed to add address {} from address book: {}", addr, err
);
}
}
}
Err(err) => {
warn!(context, "{:#}.", err);
}
}
}
if modify_cnt > 0 {
@@ -1701,11 +1739,10 @@ mod tests {
let (id, _modified) = Contact::add_or_lookup(
&context.ctx,
"bob",
"user@example.org",
ContactAddress::new("user@example.org")?,
Origin::IncomingReplyTo,
)
.await?
.unwrap();
.await?;
assert_ne!(id, ContactId::UNDEFINED);
let contact = Contact::load_from_db(&context.ctx, id).await.unwrap();
@@ -1730,11 +1767,10 @@ mod tests {
let (contact_bob_id, modified) = Contact::add_or_lookup(
&context.ctx,
"someone",
"user@example.org",
ContactAddress::new("user@example.org")?,
Origin::ManuallyCreated,
)
.await?
.unwrap();
.await?;
assert_eq!(contact_bob_id, id);
assert_eq!(modified, Modifier::Modified);
let contact = Contact::load_from_db(&context.ctx, id).await.unwrap();
@@ -1766,6 +1802,18 @@ mod tests {
Ok(())
}
#[test]
fn test_contact_address() -> Result<()> {
let alice_addr = "alice@example.org";
let contact_address = ContactAddress::new(alice_addr)?;
assert_eq!(contact_address.as_ref(), alice_addr);
let invalid_addr = "<> foobar";
assert!(ContactAddress::new(invalid_addr).is_err());
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_add_or_lookup() {
// add some contacts, this also tests add_address_book()
@@ -1781,11 +1829,14 @@ mod tests {
assert_eq!(Contact::add_address_book(&t, book).await.unwrap(), 4);
// check first added contact, this modifies authname because it is empty
let (contact_id, sth_modified) =
Contact::add_or_lookup(&t, "bla foo", "one@eins.org", Origin::IncomingUnknownTo)
.await
.unwrap()
.unwrap();
let (contact_id, sth_modified) = Contact::add_or_lookup(
&t,
"bla foo",
ContactAddress::new("one@eins.org").unwrap(),
Origin::IncomingUnknownTo,
)
.await
.unwrap();
assert!(!contact_id.is_special());
assert_eq!(sth_modified, Modifier::Modified);
let contact = Contact::load_from_db(&t, contact_id).await.unwrap();
@@ -1797,11 +1848,14 @@ mod tests {
assert_eq!(contact.get_name_n_addr(), "Name one (one@eins.org)");
// modify first added contact
let (contact_id_test, sth_modified) =
Contact::add_or_lookup(&t, "Real one", " one@eins.org ", Origin::ManuallyCreated)
.await
.unwrap()
.unwrap();
let (contact_id_test, sth_modified) = Contact::add_or_lookup(
&t,
"Real one",
ContactAddress::new(" one@eins.org ").unwrap(),
Origin::ManuallyCreated,
)
.await
.unwrap();
assert_eq!(contact_id, contact_id_test);
assert_eq!(sth_modified, Modifier::Modified);
let contact = Contact::load_from_db(&t, contact_id).await.unwrap();
@@ -1810,11 +1864,14 @@ mod tests {
assert!(!contact.is_blocked());
// check third added contact (contact without name)
let (contact_id, sth_modified) =
Contact::add_or_lookup(&t, "", "three@drei.sam", Origin::IncomingUnknownTo)
.await
.unwrap()
.unwrap();
let (contact_id, sth_modified) = Contact::add_or_lookup(
&t,
"",
ContactAddress::new("three@drei.sam").unwrap(),
Origin::IncomingUnknownTo,
)
.await
.unwrap();
assert!(!contact_id.is_special());
assert_eq!(sth_modified, Modifier::None);
let contact = Contact::load_from_db(&t, contact_id).await.unwrap();
@@ -1827,11 +1884,10 @@ mod tests {
let (contact_id_test, sth_modified) = Contact::add_or_lookup(
&t,
"m. serious",
"three@drei.sam",
ContactAddress::new("three@drei.sam").unwrap(),
Origin::IncomingUnknownFrom,
)
.await
.unwrap()
.unwrap();
assert_eq!(contact_id, contact_id_test);
assert_eq!(sth_modified, Modifier::Modified);
@@ -1840,11 +1896,14 @@ mod tests {
assert!(!contact.is_blocked());
// manually edit name of third contact (does not changed authorized name)
let (contact_id_test, sth_modified) =
Contact::add_or_lookup(&t, "schnucki", "three@drei.sam", Origin::ManuallyCreated)
.await
.unwrap()
.unwrap();
let (contact_id_test, sth_modified) = Contact::add_or_lookup(
&t,
"schnucki",
ContactAddress::new("three@drei.sam").unwrap(),
Origin::ManuallyCreated,
)
.await
.unwrap();
assert_eq!(contact_id, contact_id_test);
assert_eq!(sth_modified, Modifier::Modified);
let contact = Contact::load_from_db(&t, contact_id).await.unwrap();
@@ -1853,11 +1912,14 @@ mod tests {
assert!(!contact.is_blocked());
// Fourth contact:
let (contact_id, sth_modified) =
Contact::add_or_lookup(&t, "", "alice@w.de", Origin::IncomingUnknownTo)
.await
.unwrap()
.unwrap();
let (contact_id, sth_modified) = Contact::add_or_lookup(
&t,
"",
ContactAddress::new("alice@w.de").unwrap(),
Origin::IncomingUnknownTo,
)
.await
.unwrap();
assert!(!contact_id.is_special());
assert_eq!(sth_modified, Modifier::None);
let contact = Contact::load_from_db(&t, contact_id).await.unwrap();
@@ -1992,10 +2054,13 @@ mod tests {
assert!(Contact::delete(&alice, ContactId::SELF).await.is_err());
// Create Bob contact
let (contact_id, _) =
Contact::add_or_lookup(&alice, "Bob", "bob@example.net", Origin::ManuallyCreated)
.await?
.unwrap();
let (contact_id, _) = Contact::add_or_lookup(
&alice,
"Bob",
ContactAddress::new("bob@example.net")?,
Origin::ManuallyCreated,
)
.await?;
let chat = alice
.create_chat_with_contact("Bob", "bob@example.net")
.await;
@@ -2068,11 +2133,14 @@ mod tests {
let t = TestContext::new().await;
// incoming mail `From: bob1 <bob@example.org>` - this should init authname
let (contact_id, sth_modified) =
Contact::add_or_lookup(&t, "bob1", "bob@example.org", Origin::IncomingUnknownFrom)
.await
.unwrap()
.unwrap();
let (contact_id, sth_modified) = Contact::add_or_lookup(
&t,
"bob1",
ContactAddress::new("bob@example.org").unwrap(),
Origin::IncomingUnknownFrom,
)
.await
.unwrap();
assert!(!contact_id.is_special());
assert_eq!(sth_modified, Modifier::Created);
let contact = Contact::load_from_db(&t, contact_id).await.unwrap();
@@ -2081,11 +2149,14 @@ mod tests {
assert_eq!(contact.get_display_name(), "bob1");
// incoming mail `From: bob2 <bob@example.org>` - this should update authname
let (contact_id, sth_modified) =
Contact::add_or_lookup(&t, "bob2", "bob@example.org", Origin::IncomingUnknownFrom)
.await
.unwrap()
.unwrap();
let (contact_id, sth_modified) = Contact::add_or_lookup(
&t,
"bob2",
ContactAddress::new("bob@example.org").unwrap(),
Origin::IncomingUnknownFrom,
)
.await
.unwrap();
assert!(!contact_id.is_special());
assert_eq!(sth_modified, Modifier::Modified);
let contact = Contact::load_from_db(&t, contact_id).await.unwrap();
@@ -2104,11 +2175,14 @@ mod tests {
assert_eq!(contact.get_display_name(), "bob3");
// incoming mail `From: bob4 <bob@example.org>` - this should update authname, manually given name is still "bob3"
let (contact_id, sth_modified) =
Contact::add_or_lookup(&t, "bob4", "bob@example.org", Origin::IncomingUnknownFrom)
.await
.unwrap()
.unwrap();
let (contact_id, sth_modified) = Contact::add_or_lookup(
&t,
"bob4",
ContactAddress::new("bob@example.org").unwrap(),
Origin::IncomingUnknownFrom,
)
.await
.unwrap();
assert!(!contact_id.is_special());
assert_eq!(sth_modified, Modifier::Modified);
let contact = Contact::load_from_db(&t, contact_id).await.unwrap();
@@ -2133,11 +2207,10 @@ mod tests {
let (contact_id_same, sth_modified) = Contact::add_or_lookup(
&t,
"claire1",
"claire@example.org",
ContactAddress::new("claire@example.org").unwrap(),
Origin::IncomingUnknownFrom,
)
.await
.unwrap()
.unwrap();
assert_eq!(contact_id, contact_id_same);
assert_eq!(sth_modified, Modifier::Modified);
@@ -2150,11 +2223,10 @@ mod tests {
let (contact_id_same, sth_modified) = Contact::add_or_lookup(
&t,
"claire2",
"claire@example.org",
ContactAddress::new("claire@example.org").unwrap(),
Origin::IncomingUnknownFrom,
)
.await
.unwrap()
.unwrap();
assert_eq!(contact_id, contact_id_same);
assert_eq!(sth_modified, Modifier::Modified);
@@ -2173,29 +2245,38 @@ mod tests {
let t = TestContext::new().await;
// Incoming message from Bob.
let (contact_id, sth_modified) =
Contact::add_or_lookup(&t, "Bob", "bob@example.org", Origin::IncomingUnknownFrom)
.await?
.unwrap();
let (contact_id, sth_modified) = Contact::add_or_lookup(
&t,
"Bob",
ContactAddress::new("bob@example.org")?,
Origin::IncomingUnknownFrom,
)
.await?;
assert_eq!(sth_modified, Modifier::Created);
let contact = Contact::load_from_db(&t, contact_id).await?;
assert_eq!(contact.get_display_name(), "Bob");
// Incoming message from someone else with "Not Bob" <bob@example.org> in the "To:" field.
let (contact_id_same, sth_modified) =
Contact::add_or_lookup(&t, "Not Bob", "bob@example.org", Origin::IncomingUnknownTo)
.await?
.unwrap();
let (contact_id_same, sth_modified) = Contact::add_or_lookup(
&t,
"Not Bob",
ContactAddress::new("bob@example.org")?,
Origin::IncomingUnknownTo,
)
.await?;
assert_eq!(contact_id, contact_id_same);
assert_eq!(sth_modified, Modifier::Modified);
let contact = Contact::load_from_db(&t, contact_id).await?;
assert_eq!(contact.get_display_name(), "Not Bob");
// Incoming message from Bob, changing the name back.
let (contact_id_same, sth_modified) =
Contact::add_or_lookup(&t, "Bob", "bob@example.org", Origin::IncomingUnknownFrom)
.await?
.unwrap();
let (contact_id_same, sth_modified) = Contact::add_or_lookup(
&t,
"Bob",
ContactAddress::new("bob@example.org")?,
Origin::IncomingUnknownFrom,
)
.await?;
assert_eq!(contact_id, contact_id_same);
assert_eq!(sth_modified, Modifier::Modified); // This was None until the bugfix
let contact = Contact::load_from_db(&t, contact_id).await?;
@@ -2218,9 +2299,14 @@ mod tests {
assert_eq!(contact.get_display_name(), "dave1");
// incoming mail `From: dave2 <dave@example.org>` - this should update authname
Contact::add_or_lookup(&t, "dave2", "dave@example.org", Origin::IncomingUnknownFrom)
.await
.unwrap();
Contact::add_or_lookup(
&t,
"dave2",
ContactAddress::new("dave@example.org").unwrap(),
Origin::IncomingUnknownFrom,
)
.await
.unwrap();
let contact = Contact::load_from_db(&t, contact_id).await.unwrap();
assert_eq!(contact.get_authname(), "dave2");
assert_eq!(contact.get_name(), "dave1");
@@ -2334,10 +2420,13 @@ mod tests {
let encrinfo = Contact::get_encrinfo(&alice, ContactId::DEVICE).await;
assert!(encrinfo.is_err());
let (contact_bob_id, _modified) =
Contact::add_or_lookup(&alice, "Bob", "bob@example.net", Origin::ManuallyCreated)
.await?
.unwrap();
let (contact_bob_id, _modified) = Contact::add_or_lookup(
&alice,
"Bob",
ContactAddress::new("bob@example.net")?,
Origin::ManuallyCreated,
)
.await?;
let encrinfo = Contact::get_encrinfo(&alice, contact_bob_id).await?;
assert_eq!(encrinfo, "No encryption");
@@ -2494,10 +2583,13 @@ CCCB 5AA9 F6E1 141C 9431
async fn test_last_seen() -> Result<()> {
let alice = TestContext::new_alice().await;
let (contact_id, _) =
Contact::add_or_lookup(&alice, "Bob", "bob@example.net", Origin::ManuallyCreated)
.await?
.unwrap();
let (contact_id, _) = Contact::add_or_lookup(
&alice,
"Bob",
ContactAddress::new("bob@example.net")?,
Origin::ManuallyCreated,
)
.await?;
let contact = Contact::load_from_db(&alice, contact_id).await?;
assert_eq!(contact.last_seen(), 0);

View File

@@ -22,7 +22,7 @@ use crate::config::Config;
use crate::constants::{
Blocked, Chattype, ShowEmails, DC_FETCH_EXISTING_MSGS_COUNT, DC_FOLDERS_CONFIGURED_VERSION,
};
use crate::contact::{normalize_name, Contact, ContactId, Modifier, Origin};
use crate::contact::{normalize_name, Contact, ContactAddress, ContactId, Modifier, Origin};
use crate::context::Context;
use crate::events::EventType;
use crate::headerdef::{HeaderDef, HeaderDefMap};
@@ -2375,23 +2375,28 @@ async fn add_all_recipients_as_contacts(
.map(|s| normalize_name(s))
.unwrap_or_default();
match Contact::add_or_lookup(
let recipient_addr = match ContactAddress::new(&recipient.addr) {
Err(err) => {
warn!(
context,
"Could not add contact for recipient with address {:?}: {:#}",
recipient.addr,
err
);
continue;
}
Ok(recipient_addr) => recipient_addr,
};
let (_, modified) = Contact::add_or_lookup(
context,
&display_name_normalized,
&recipient.addr,
recipient_addr,
Origin::OutgoingTo,
)
.await?
{
Some((_, modified)) => {
if modified != Modifier::None {
any_modified = true;
}
}
None => warn!(
context,
"Could not add contact for recipient with address {:?}", recipient.addr
),
.await?;
if modified != Modifier::None {
any_modified = true;
}
}
if any_modified {

View File

@@ -1490,7 +1490,7 @@ mod tests {
ProtectionStatus,
};
use crate::chatlist::Chatlist;
use crate::contact::Origin;
use crate::contact::{ContactAddress, Origin};
use crate::mimeparser::MimeMessage;
use crate::receive_imf::receive_imf;
use crate::test_utils::{get_chat_msg, TestContext};
@@ -1817,12 +1817,15 @@ mod tests {
}
async fn first_subject_str(t: TestContext) -> String {
let contact_id =
Contact::add_or_lookup(&t, "Dave", "dave@example.com", Origin::ManuallyCreated)
.await
.unwrap()
.unwrap()
.0;
let contact_id = Contact::add_or_lookup(
&t,
"Dave",
ContactAddress::new("dave@example.com").unwrap(),
Origin::ManuallyCreated,
)
.await
.unwrap()
.0;
let chat_id = ChatId::create_for_contact(&t, contact_id).await.unwrap();

View File

@@ -9,7 +9,7 @@ use crate::aheader::{Aheader, EncryptPreference};
use crate::chat::{self, Chat};
use crate::chatlist::Chatlist;
use crate::constants::Chattype;
use crate::contact::{addr_cmp, Contact, Origin};
use crate::contact::{addr_cmp, Contact, ContactAddress, Origin};
use crate::context::Context;
use crate::events::EventType;
use crate::key::{DcKey, Fingerprint, SignedPublicKey};
@@ -542,16 +542,30 @@ impl Peerstate {
if (chat.typ == Chattype::Group && chat.is_protected())
|| chat.typ == Chattype::Broadcast
{
if let Some((new_contact_id, _)) =
Contact::add_or_lookup(context, "", new_addr, Origin::IncomingUnknownFrom)
.await?
{
chat::remove_from_chat_contacts_table(context, *chat_id, contact_id)
.await?;
chat::add_to_chat_contacts_table(context, *chat_id, &[new_contact_id])
match ContactAddress::new(new_addr) {
Ok(new_addr) => {
let (new_contact_id, _) = Contact::add_or_lookup(
context,
"",
new_addr,
Origin::IncomingUnknownFrom,
)
.await?;
chat::remove_from_chat_contacts_table(context, *chat_id, contact_id)
.await?;
chat::add_to_chat_contacts_table(context, *chat_id, &[new_contact_id])
.await?;
context.emit_event(EventType::ChatModified(*chat_id));
context.emit_event(EventType::ChatModified(*chat_id));
}
Err(err) => {
warn!(
context,
"New address {:?} is not vaild, not doing AEAP: {:#}.",
new_addr,
err
)
}
}
}
}

View File

@@ -14,7 +14,9 @@ use std::collections::BTreeMap;
use crate::chat::{self, get_chat_id_by_grpid, ChatIdBlocked};
use crate::config::Config;
use crate::constants::Blocked;
use crate::contact::{addr_normalize, may_be_valid_addr, Contact, ContactId, Origin};
use crate::contact::{
addr_normalize, may_be_valid_addr, Contact, ContactAddress, ContactId, Origin,
};
use crate::context::Context;
use crate::key::Fingerprint;
use crate::message::Message;
@@ -221,19 +223,14 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
.context("Can't load peerstate")?;
if let (Some(addr), Some(invitenumber), Some(authcode)) = (&addr, invitenumber, authcode) {
let addr = ContactAddress::new(addr)?;
let (contact_id, _) = Contact::add_or_lookup(context, &name, addr, Origin::UnhandledQrScan)
.await
.with_context(|| format!("failed to add or lookup contact for address {:?}", addr))?
.with_context(|| {
format!(
"do not want to lookup contact for invalid address {:?}",
addr
)
})?;
.with_context(|| format!("failed to add or lookup contact for address {:?}", addr))?;
if let (Some(grpid), Some(grpname)) = (grpid, grpname) {
if context
.is_self_addr(addr)
.is_self_addr(&addr)
.await
.with_context(|| format!("can't check if address {:?} is our address", addr))?
{
@@ -266,7 +263,7 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
authcode,
})
}
} else if context.is_self_addr(addr).await? {
} else if context.is_self_addr(&addr).await? {
if token::exists(context, token::Namespace::InviteNumber, &invitenumber).await {
Ok(Qr::WithdrawVerifyContact {
contact_id,
@@ -292,13 +289,11 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
}
} else if let Some(addr) = addr {
if let Some(peerstate) = peerstate {
let peerstate_addr = ContactAddress::new(&peerstate.addr)?;
let (contact_id, _) =
Contact::add_or_lookup(context, &name, &peerstate.addr, Origin::UnhandledQrScan)
Contact::add_or_lookup(context, &name, peerstate_addr, Origin::UnhandledQrScan)
.await
.context("add_or_lookup")?
.with_context(|| {
format!("Not looking up contact for address {:?}", &peerstate.addr)
})?;
.context("add_or_lookup")?;
let chat = ChatIdBlocked::get_for_contact(context, contact_id, Blocked::Request)
.await
.context("Failed to create (new) chat for contact")?;
@@ -538,11 +533,11 @@ async fn decode_mailto(context: &Context, qr: &str) -> Result<Qr> {
};
let addr = normalize_address(addr)?;
let name = "".to_string();
let name = "";
Qr::from_address(
context,
name,
addr,
&addr,
if draft.is_empty() { None } else { Some(draft) },
)
.await
@@ -562,8 +557,8 @@ async fn decode_smtp(context: &Context, qr: &str) -> Result<Qr> {
};
let addr = normalize_address(addr)?;
let name = "".to_string();
Qr::from_address(context, name, addr, None).await
let name = "";
Qr::from_address(context, name, &addr, None).await
}
/// Extract address for the matmsg scheme.
@@ -587,8 +582,8 @@ async fn decode_matmsg(context: &Context, qr: &str) -> Result<Qr> {
};
let addr = normalize_address(addr)?;
let name = "".to_string();
Qr::from_address(context, name, addr, None).await
let name = "";
Qr::from_address(context, name, &addr, None).await
}
static VCARD_NAME_RE: Lazy<regex::Regex> =
@@ -617,20 +612,19 @@ async fn decode_vcard(context: &Context, qr: &str) -> Result<Qr> {
bail!("Bad e-mail address");
};
Qr::from_address(context, name, addr, None).await
Qr::from_address(context, &name, &addr, None).await
}
impl Qr {
pub async fn from_address(
context: &Context,
name: String,
addr: String,
name: &str,
addr: &str,
draft: Option<String>,
) -> Result<Self> {
let addr = ContactAddress::new(addr)?;
let (contact_id, _) =
Contact::add_or_lookup(context, &name, &addr, Origin::UnhandledQrScan)
.await?
.context("QR code does not contain a valid address")?;
Contact::add_or_lookup(context, name, addr, Origin::UnhandledQrScan).await?;
Ok(Qr::Addr { contact_id, draft })
}
}

View File

@@ -286,11 +286,11 @@ pub async fn get_msg_reactions(context: &Context, msg_id: MsgId) -> Result<React
#[cfg(test)]
mod tests {
use super::*;
use crate::chat::get_chat_msgs;
use crate::chat::get_chat_msgs;
use crate::config::Config;
use crate::constants::DC_CHAT_ID_TRASH;
use crate::contact::{Contact, Origin};
use crate::contact::{Contact, ContactAddress, Origin};
use crate::download::DownloadState;
use crate::message::MessageState;
use crate::receive_imf::{receive_imf, receive_imf_inner};
@@ -366,10 +366,14 @@ Can we chat at 1pm pacific, today?"
let contacts = reactions.contacts();
assert_eq!(contacts.len(), 0);
let bob_id = Contact::add_or_lookup(&alice, "", "bob@example.net", Origin::ManuallyCreated)
.await?
.unwrap()
.0;
let bob_id = Contact::add_or_lookup(
&alice,
"",
ContactAddress::new("bob@example.net")?,
Origin::ManuallyCreated,
)
.await?
.0;
let bob_reaction = reactions.get(bob_id);
assert!(bob_reaction.is_empty()); // Bob has not reacted to message yet.

View File

@@ -16,7 +16,7 @@ use crate::chat::{self, Chat, ChatId, ChatIdBlocked, ProtectionStatus};
use crate::config::Config;
use crate::constants::{Blocked, Chattype, ShowEmails, DC_CHAT_ID_TRASH};
use crate::contact::{
may_be_valid_addr, normalize_name, Contact, ContactId, Origin, VerifiedStatus,
may_be_valid_addr, normalize_name, Contact, ContactAddress, ContactId, Origin, VerifiedStatus,
};
use crate::context::Context;
use crate::download::DownloadState;
@@ -378,6 +378,8 @@ pub(crate) async fn receive_imf_inner(
/// Also returns whether it is blocked or not and its origin.
///
/// * `prevent_rename`: passed through to `add_or_lookup_contacts_by_address_list()`
///
/// Returns `None` if From field does not contain a valid contact address.
pub async fn from_field_to_contact_id(
context: &Context,
from: &SingleInfo,
@@ -388,19 +390,24 @@ pub async fn from_field_to_contact_id(
} else {
from.display_name.as_deref()
};
let from_id = if let Some(from_id) = add_or_lookup_contact_by_addr(
let from_addr = match ContactAddress::new(&from.addr) {
Ok(from_addr) => from_addr,
Err(err) => {
warn!(
context,
"Cannot create a contact for the given From field: {:#}.", err
);
return Ok(None);
}
};
let from_id = add_or_lookup_contact_by_addr(
context,
display_name,
&from.addr,
from_addr,
Origin::IncomingUnknownFrom,
)
.await
.context("add_or_lookup_contact_by_addr")?
{
from_id
} else {
return Ok(None);
};
.await?;
if from_id == ContactId::SELF {
Ok(Some((ContactId::SELF, false, Origin::OutgoingBcc)))
@@ -1943,11 +1950,15 @@ async fn apply_mailinglist_changes(
}
let listid = &chat.grpid;
let contact_id =
match Contact::add_or_lookup(context, "", list_post, Origin::Hidden).await? {
Some((contact_id, _)) => contact_id,
None => return Ok(()),
};
let list_post = match ContactAddress::new(list_post) {
Ok(list_post) => list_post,
Err(err) => {
warn!(context, "Invalid List-Post: {:#}.", err);
return Ok(());
}
};
let (contact_id, _) =
Contact::add_or_lookup(context, "", list_post, Origin::Hidden).await?;
let mut contact = Contact::load_from_db(context, contact_id).await?;
if contact.param.get(Param::ListId) != Some(listid) {
contact.param.set(Param::ListId, listid);
@@ -1955,7 +1966,7 @@ async fn apply_mailinglist_changes(
}
if let Some(old_list_post) = chat.param.get(Param::ListPost) {
if list_post != old_list_post {
if list_post.as_ref() != old_list_post {
// Apparently the mailing list is using a different List-Post header in each message.
// Make the mailing list read-only because we would't know which message the user wants to reply to.
chat.param.remove(Param::ListPost);
@@ -2286,9 +2297,9 @@ async fn add_or_lookup_contacts_by_address_list(
continue;
}
let display_name = info.display_name.as_deref();
if let Some(contact_id) =
add_or_lookup_contact_by_addr(context, display_name, addr, origin).await?
{
if let Ok(addr) = ContactAddress::new(addr) {
let contact_id =
add_or_lookup_contact_by_addr(context, display_name, addr, origin).await?;
contact_ids.insert(contact_id);
} else {
warn!(context, "Contact with address {:?} cannot exist.", addr);
@@ -2299,26 +2310,20 @@ async fn add_or_lookup_contacts_by_address_list(
}
/// Add contacts to database on receiving messages.
///
/// Returns `None` if the address can't be a valid email address.
async fn add_or_lookup_contact_by_addr(
context: &Context,
display_name: Option<&str>,
addr: &str,
addr: ContactAddress<'_>,
origin: Origin,
) -> Result<Option<ContactId>> {
if context.is_self_addr(addr).await? {
return Ok(Some(ContactId::SELF));
) -> Result<ContactId> {
if context.is_self_addr(&addr).await? {
return Ok(ContactId::SELF);
}
let display_name_normalized = display_name.map(normalize_name).unwrap_or_default();
if let Some((contact_id, _modified)) =
Contact::add_or_lookup(context, &display_name_normalized, addr, origin).await?
{
Ok(Some(contact_id))
} else {
Ok(None)
}
let (contact_id, _modified) =
Contact::add_or_lookup(context, &display_name_normalized, addr, origin).await?;
Ok(contact_id)
}
#[cfg(test)]

View File

@@ -425,12 +425,15 @@ async fn test_escaped_recipients() {
.await
.unwrap();
let carl_contact_id =
Contact::add_or_lookup(&t, "Carl", "carl@host.tld", Origin::IncomingUnknownFrom)
.await
.unwrap()
.unwrap()
.0;
let carl_contact_id = Contact::add_or_lookup(
&t,
"Carl",
ContactAddress::new("carl@host.tld").unwrap(),
Origin::IncomingUnknownFrom,
)
.await
.unwrap()
.0;
receive_imf(
&t,
@@ -468,12 +471,15 @@ async fn test_cc_to_contact() {
.await
.unwrap();
let carl_contact_id =
Contact::add_or_lookup(&t, "garabage", "carl@host.tld", Origin::IncomingUnknownFrom)
.await
.unwrap()
.unwrap()
.0;
let carl_contact_id = Contact::add_or_lookup(
&t,
"garabage",
ContactAddress::new("carl@host.tld").unwrap(),
Origin::IncomingUnknownFrom,
)
.await
.unwrap()
.0;
receive_imf(
&t,
@@ -2056,11 +2062,10 @@ async fn test_duplicate_message() -> Result<()> {
let bob_contact_id = Contact::add_or_lookup(
&alice,
"Bob",
"bob@example.org",
ContactAddress::new("bob@example.org").unwrap(),
Origin::IncomingUnknownFrom,
)
.await?
.unwrap()
.0;
let first_message = b"Received: from [127.0.0.1]
@@ -2112,10 +2117,14 @@ Second signature";
async fn test_ignore_footer_status_from_mailinglist() -> Result<()> {
let t = TestContext::new_alice().await;
t.set_config(Config::ShowEmails, Some("2")).await?;
let bob_id = Contact::add_or_lookup(&t, "", "bob@example.net", Origin::IncomingUnknownCc)
.await?
.unwrap()
.0;
let bob_id = Contact::add_or_lookup(
&t,
"",
ContactAddress::new("bob@example.net").unwrap(),
Origin::IncomingUnknownCc,
)
.await?
.0;
let bob = Contact::load_from_db(&t, bob_id).await?;
assert_eq!(bob.get_status(), "");
assert_eq!(Chatlist::try_load(&t, 0, None, None).await?.len(), 0);
@@ -2527,14 +2536,8 @@ Second thread."#;
// Alice adds Fiona to both ad hoc groups.
let fiona = TestContext::new_fiona().await;
let (alice_fiona_contact_id, _) = Contact::add_or_lookup(
&alice,
"Fiona",
"fiona@example.net",
Origin::IncomingUnknownTo,
)
.await?
.unwrap();
let alice_fiona_contact = alice.add_or_lookup_contact(&fiona).await;
let alice_fiona_contact_id = alice_fiona_contact.id;
chat::add_contact_to_chat(&alice, alice_first_msg.chat_id, alice_fiona_contact_id).await?;
let alice_first_invite = alice.pop_sent_msg().await;

View File

@@ -710,6 +710,7 @@ mod tests {
use crate::chat::ProtectionStatus;
use crate::chatlist::Chatlist;
use crate::constants::{Chattype, DC_GCM_ADDDAYMARKER};
use crate::contact::ContactAddress;
use crate::peerstate::Peerstate;
use crate::receive_imf::receive_imf;
use crate::test_utils::{TestContext, TestContextManager};
@@ -1003,11 +1004,10 @@ mod tests {
let (contact_bob_id, _modified) = Contact::add_or_lookup(
&alice.ctx,
"Bob",
"bob@example.net",
ContactAddress::new("bob@example.net")?,
Origin::ManuallyCreated,
)
.await?
.unwrap();
.await?;
let contact_bob = Contact::load_from_db(&alice.ctx, contact_bob_id).await?;
assert_eq!(
contact_bob.is_verified(&alice.ctx).await?,

View File

@@ -23,7 +23,7 @@ use crate::chatlist::Chatlist;
use crate::config::Config;
use crate::constants::Chattype;
use crate::constants::{DC_GCL_NO_SPECIALS, DC_GCM_ADDDAYMARKER, DC_MSG_ID_DAYMARKER};
use crate::contact::{Contact, ContactId, Modifier, Origin};
use crate::contact::{Contact, ContactAddress, ContactId, Modifier, Origin};
use crate::context::Context;
use crate::events::{Event, EventType, Events};
use crate::key::{self, DcKey, KeyPair, KeyPairUse};
@@ -523,14 +523,14 @@ impl TestContext {
.await
.unwrap_or_default()
.unwrap_or_default();
let addr = other.ctx.get_primary_self_addr().await.unwrap();
let primary_self_addr = other.ctx.get_primary_self_addr().await.unwrap();
let addr = ContactAddress::new(&primary_self_addr).unwrap();
// MailinglistAddress is the lowest allowed origin, we'd prefer to not modify the
// origin when creating this contact.
let (contact_id, modified) =
Contact::add_or_lookup(self, &name, &addr, Origin::MailinglistAddress)
Contact::add_or_lookup(self, &name, addr, Origin::MailinglistAddress)
.await
.expect("add_or_lookup")
.unwrap_or_else(|| panic!("contact with address {:?} cannot be created", &addr));
.expect("add_or_lookup");
match modified {
Modifier::None => (),
Modifier::Modified => warn!(&self.ctx, "Contact {} modified by TestContext", &addr),