diff --git a/Cargo.lock b/Cargo.lock index d576a5f59..9a7559b4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -471,6 +471,7 @@ dependencies = [ "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "imap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "lettre 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", @@ -927,6 +928,14 @@ dependencies = [ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "itertools" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "itoa" version = "0.4.4" @@ -2762,6 +2771,7 @@ dependencies = [ "checksum imap-proto 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c4e77b1d61faf028893531b071cc5584cdd02b6186cebe7f7168ffd8d591339a" "checksum indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7e81a7c05f79578dbc15793d8b619db9ba32b4577003ef3af1a91c416798c58d" "checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" +"checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358" "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" "checksum keccak 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" diff --git a/Cargo.toml b/Cargo.toml index dfdfa7fe0..94ddf9337 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ strum_macros = "0.15.0" thread-local-object = "0.1.0" backtrace = "0.3.33" byteorder = "1.3.1" +itertools = "0.8.0" [dev-dependencies] tempfile = "3.0" diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 5652c450c..82098cb35 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -14,7 +14,8 @@ extern crate num_traits; use num_traits::{FromPrimitive, ToPrimitive}; use std::str::FromStr; -use deltachat::dc_tools::StrExt; +use deltachat::contact::Contact; +use deltachat::dc_tools::{as_str, StrExt}; use deltachat::*; // TODO: constants @@ -701,7 +702,7 @@ pub unsafe extern "C" fn dc_marknoticed_contact(context: *mut dc_context_t, cont assert!(!context.is_null()); let context = &*context; - dc_contact::dc_marknoticed_contact(context, contact_id) + Contact::mark_noticed(context, contact_id) } #[no_mangle] @@ -748,7 +749,7 @@ pub unsafe extern "C" fn dc_get_msg<'a>( #[no_mangle] pub unsafe extern "C" fn dc_may_be_valid_addr(addr: *mut libc::c_char) -> libc::c_int { assert!(!addr.is_null()); - dc_contact::dc_may_be_valid_addr(addr) as libc::c_int + contact::may_be_valid_addr(as_str(addr)) as libc::c_int } #[no_mangle] @@ -760,7 +761,7 @@ pub unsafe extern "C" fn dc_lookup_contact_id_by_addr( assert!(!addr.is_null()); let context = &*context; - dc_contact::dc_lookup_contact_id_by_addr(context, addr) + Contact::lookup_id_by_addr(context, as_str(addr)) } #[no_mangle] @@ -771,9 +772,15 @@ pub unsafe extern "C" fn dc_create_contact( ) -> u32 { assert!(!context.is_null()); assert!(!addr.is_null()); + let context = &*context; - dc_contact::dc_create_contact(context, name, addr) + let name = if name.is_null() { "" } else { as_str(name) }; + + match Contact::create(context, name, as_str(addr)) { + Ok(id) => id, + Err(_) => 0, + } } #[no_mangle] @@ -785,7 +792,10 @@ pub unsafe extern "C" fn dc_add_address_book( assert!(!addr_book.is_null()); let context = &*context; - dc_contact::dc_add_address_book(context, addr_book) + match Contact::add_address_book(context, as_str(addr_book)) { + Ok(cnt) => cnt as libc::c_int, + Err(_) => 0, + } } #[no_mangle] @@ -797,7 +807,16 @@ pub unsafe extern "C" fn dc_get_contacts( assert!(!context.is_null()); let context = &*context; - dc_contact::dc_get_contacts(context, flags, query) + let query = if query.is_null() { + None + } else { + Some(as_str(query)) + }; + + match Contact::get_all(context, flags, query) { + Ok(contacts) => contacts, + Err(_) => std::ptr::null_mut(), + } } #[no_mangle] @@ -805,7 +824,7 @@ pub unsafe extern "C" fn dc_get_blocked_cnt(context: *mut dc_context_t) -> libc: assert!(!context.is_null()); let context = &*context; - dc_contact::dc_get_blocked_cnt(context) + Contact::get_blocked_cnt(context) as libc::c_int } #[no_mangle] @@ -815,7 +834,7 @@ pub unsafe extern "C" fn dc_get_blocked_contacts( assert!(!context.is_null()); let context = &*context; - dc_contact::dc_get_blocked_contacts(context) + Contact::get_all_blocked(context) } #[no_mangle] @@ -827,7 +846,11 @@ pub unsafe extern "C" fn dc_block_contact( assert!(!context.is_null()); let context = &*context; - dc_contact::dc_block_contact(context, contact_id, block) + if block == 0 { + Contact::unblock(context, contact_id); + } else { + Contact::block(context, contact_id); + } } #[no_mangle] @@ -838,7 +861,7 @@ pub unsafe extern "C" fn dc_get_contact_encrinfo( assert!(!context.is_null()); let context = &*context; - dc_contact::dc_get_contact_encrinfo(context, contact_id) + Contact::get_encrinfo(context, contact_id).strdup() } #[no_mangle] @@ -849,18 +872,23 @@ pub unsafe extern "C" fn dc_delete_contact( assert!(!context.is_null()); let context = &*context; - dc_contact::dc_delete_contact(context, contact_id) as libc::c_int + match Contact::delete(context, contact_id) { + Ok(_) => 1, + Err(_) => 0, + } } #[no_mangle] pub unsafe extern "C" fn dc_get_contact<'a>( context: *mut dc_context_t, contact_id: u32, -) -> *mut dc_contact::dc_contact_t<'a> { +) -> *mut dc_contact_t<'a> { assert!(!context.is_null()); let context = &*context; - dc_contact::dc_get_contact(context, contact_id) + Contact::get_by_id(context, contact_id) + .map(|contact| Box::into_raw(Box::new(contact))) + .unwrap_or_else(|_| std::ptr::null_mut()) } #[no_mangle] @@ -1634,99 +1662,103 @@ pub unsafe extern "C" fn dc_msg_latefiling_mediasize( // dc_contact_t #[no_mangle] -pub type dc_contact_t<'a> = dc_contact::dc_contact_t<'a>; +pub type dc_contact_t<'a> = contact::Contact<'a>; #[no_mangle] -pub unsafe extern "C" fn dc_contact_unref(contact: *mut dc_contact::dc_contact_t) { +pub unsafe extern "C" fn dc_contact_unref(contact: *mut dc_contact_t) { assert!(!contact.is_null()); - - dc_contact::dc_contact_unref(contact) + Box::from_raw(contact); } #[no_mangle] -pub unsafe extern "C" fn dc_contact_get_id(contact: *mut dc_contact::dc_contact_t) -> u32 { +pub unsafe extern "C" fn dc_contact_get_id(contact: *mut dc_contact_t) -> u32 { assert!(!contact.is_null()); + let contact = &*contact; - dc_contact::dc_contact_get_id(contact) + contact.get_id() } #[no_mangle] -pub unsafe extern "C" fn dc_contact_get_addr( - contact: *mut dc_contact::dc_contact_t, -) -> *mut libc::c_char { +pub unsafe extern "C" fn dc_contact_get_addr(contact: *mut dc_contact_t) -> *mut libc::c_char { assert!(!contact.is_null()); + let contact = &*contact; - dc_contact::dc_contact_get_addr(contact) + contact.get_addr().strdup() } #[no_mangle] -pub unsafe extern "C" fn dc_contact_get_name( - contact: *mut dc_contact::dc_contact_t, -) -> *mut libc::c_char { +pub unsafe extern "C" fn dc_contact_get_name(contact: *mut dc_contact_t) -> *mut libc::c_char { assert!(!contact.is_null()); + let contact = &*contact; - dc_contact::dc_contact_get_name(contact) + contact.get_name().strdup() } #[no_mangle] pub unsafe extern "C" fn dc_contact_get_display_name( - contact: *mut dc_contact::dc_contact_t, + contact: *mut dc_contact_t, ) -> *mut libc::c_char { assert!(!contact.is_null()); + let contact = &*contact; - dc_contact::dc_contact_get_display_name(contact) + contact.get_display_name().strdup() } #[no_mangle] pub unsafe extern "C" fn dc_contact_get_name_n_addr( - contact: *mut dc_contact::dc_contact_t, + contact: *mut dc_contact_t, ) -> *mut libc::c_char { assert!(!contact.is_null()); + let contact = &*contact; - dc_contact::dc_contact_get_name_n_addr(contact) + contact.get_name_n_addr().strdup() } #[no_mangle] pub unsafe extern "C" fn dc_contact_get_first_name( - contact: *mut dc_contact::dc_contact_t, + contact: *mut dc_contact_t, ) -> *mut libc::c_char { assert!(!contact.is_null()); + let contact = &*contact; - dc_contact::dc_contact_get_first_name(contact) + contact.get_first_name().strdup() } #[no_mangle] pub unsafe extern "C" fn dc_contact_get_profile_image( - contact: *mut dc_contact::dc_contact_t, + contact: *mut dc_contact_t, ) -> *mut libc::c_char { assert!(!contact.is_null()); + let contact = &*contact; - dc_contact::dc_contact_get_profile_image(contact) + contact + .get_profile_image() + .map(|s| s.strdup()) + .unwrap_or_else(|| std::ptr::null_mut()) } #[no_mangle] -pub unsafe extern "C" fn dc_contact_get_color(contact: *mut dc_contact::dc_contact_t) -> u32 { +pub unsafe extern "C" fn dc_contact_get_color(contact: *mut dc_contact_t) -> u32 { assert!(!contact.is_null()); + let contact = &*contact; - dc_contact::dc_contact_get_color(contact) + contact.get_color() } #[no_mangle] -pub unsafe extern "C" fn dc_contact_is_blocked( - contact: *mut dc_contact::dc_contact_t, -) -> libc::c_int { +pub unsafe extern "C" fn dc_contact_is_blocked(contact: *mut dc_contact_t) -> libc::c_int { assert!(!contact.is_null()); + let contact = &*contact; - dc_contact::dc_contact_is_blocked(contact) + contact.is_blocked() as libc::c_int } #[no_mangle] -pub unsafe extern "C" fn dc_contact_is_verified( - contact: *mut dc_contact::dc_contact_t, -) -> libc::c_int { +pub unsafe extern "C" fn dc_contact_is_verified(contact: *mut dc_contact_t) -> libc::c_int { assert!(!contact.is_null()); + let contact = &*contact; - dc_contact::dc_contact_is_verified(contact) + contact.is_verified() as libc::c_int } // dc_lot_t diff --git a/examples/repl/cmdline.rs b/examples/repl/cmdline.rs index 30f27e67c..b30f1e763 100644 --- a/examples/repl/cmdline.rs +++ b/examples/repl/cmdline.rs @@ -4,11 +4,11 @@ use std::str::FromStr; use deltachat::chatlist::*; use deltachat::config; use deltachat::constants::*; +use deltachat::contact::*; use deltachat::context::*; use deltachat::dc_array::*; use deltachat::dc_chat::*; use deltachat::dc_configure::*; -use deltachat::dc_contact::*; use deltachat::dc_imex::*; use deltachat::dc_job::*; use deltachat::dc_location::*; @@ -218,9 +218,10 @@ unsafe fn poke_spec(context: &Context, spec: *const libc::c_char) -> libc::c_int } unsafe fn log_msg(context: &Context, prefix: impl AsRef, msg: *mut dc_msg_t) { - let contact: *mut dc_contact_t = dc_get_contact(context, dc_msg_get_from_id(msg)); - let contact_name: *mut libc::c_char = dc_contact_get_name(contact); - let contact_id: libc::c_int = dc_contact_get_id(contact) as libc::c_int; + let contact = Contact::get_by_id(context, dc_msg_get_from_id(msg)).expect("invalid contact"); + let contact_name = contact.get_name(); + let contact_id = contact.get_id(); + let statestr = match dc_msg_get_state(msg) { DC_STATE_OUT_PENDING => " o", DC_STATE_OUT_DELIVERED => " √", @@ -229,7 +230,7 @@ unsafe fn log_msg(context: &Context, prefix: impl AsRef, msg: *mut dc_msg_t _ => "", }; let temp2 = dc_timestamp_to_str(dc_msg_get_timestamp(msg)); - let msgtext: *mut libc::c_char = dc_msg_get_text(msg); + let msgtext = dc_msg_get_text(msg); info!( context, 0, @@ -242,7 +243,7 @@ unsafe fn log_msg(context: &Context, prefix: impl AsRef, msg: *mut dc_msg_t "" }, if dc_msg_has_location(msg) { "📍" } else { "" }, - as_str(contact_name), + &contact_name, contact_id, as_str(msgtext), if dc_msg_is_starred(msg) { "★" } else { "" }, @@ -264,8 +265,6 @@ unsafe fn log_msg(context: &Context, prefix: impl AsRef, msg: *mut dc_msg_t &temp2, ); free(msgtext as *mut libc::c_void); - free(contact_name as *mut libc::c_void); - dc_contact_unref(contact); } unsafe fn log_msglist(context: &Context, msglist: *mut dc_array_t) { @@ -303,7 +302,6 @@ unsafe fn log_msglist(context: &Context, msglist: *mut dc_array_t) { } unsafe fn log_contactlist(context: &Context, contacts: *mut dc_array_t) { - let mut contact: *mut dc_contact_t; if !dc_array_search_id(contacts, 1 as uint32_t, 0 as *mut size_t) { dc_array_add_id(contacts, 1 as uint32_t); } @@ -312,13 +310,12 @@ unsafe fn log_contactlist(context: &Context, contacts: *mut dc_array_t) { let contact_id = dc_array_get_id(contacts, i as size_t); let line; let mut line2 = "".to_string(); - contact = dc_get_contact(context, contact_id); - if !contact.is_null() { - let name: *mut libc::c_char = dc_contact_get_name(contact); - let addr: *mut libc::c_char = dc_contact_get_addr(contact); - let verified_state: libc::c_int = dc_contact_is_verified(contact); - let verified_str = if 0 != verified_state { - if verified_state == 2 { + if let Ok(contact) = Contact::get_by_id(context, contact_id) { + let name = contact.get_name(); + let addr = contact.get_addr(); + let verified_state = contact.is_verified(); + let verified_str = if VerifiedStatus::Unverified != verified_state { + if verified_state == VerifiedStatus::BidirectVerified { " √√" } else { " √" @@ -328,28 +325,26 @@ unsafe fn log_contactlist(context: &Context, contacts: *mut dc_array_t) { }; line = format!( "{}{} <{}>", - if !name.is_null() && 0 != *name.offset(0isize) as libc::c_int { - as_str(name) + if !name.is_empty() { + &name } else { "" }, verified_str, - if !addr.is_null() && 0 != *addr.offset(0isize) as libc::c_int { - as_str(addr) + if !addr.is_empty() { + &addr } else { "addr unset" } ); - let peerstate = Peerstate::from_addr(context, &context.sql, as_str(addr)); + let peerstate = Peerstate::from_addr(context, &context.sql, &addr); if peerstate.is_some() && contact_id != 1 as libc::c_uint { line2 = format!( ", prefer-encrypt={}", peerstate.as_ref().unwrap().prefer_encrypt ); } - dc_contact_unref(contact); - free(name as *mut libc::c_void); - free(addr as *mut libc::c_void); + info!(context, 0, "Contact#{}: {}{}", contact_id, line, line2); } } @@ -1060,15 +1055,15 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E dc_delete_msgs(context, ids.as_mut_ptr(), 1); } "listcontacts" | "contacts" | "listverified" => { - let contacts = dc_get_contacts( + let contacts = Contact::get_all( context, if arg0 == "listverified" { 0x1 | 0x2 } else { 0x2 }, - arg1_c, - ); + Some(arg1), + )?; if !contacts.is_null() { log_contactlist(context, contacts); println!("{} contacts.", dc_array_get_cnt(contacts) as libc::c_int,); @@ -1081,33 +1076,22 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E ensure!(!arg1.is_empty(), "Arguments [] expected."); if !arg2.is_empty() { - let book = dc_mprintf( - b"%s\n%s\x00" as *const u8 as *const libc::c_char, - arg1_c, - arg2_c, - ); - dc_add_address_book(context, book); - free(book as *mut libc::c_void); + let book = format!("{}\n{}", arg1, arg2); + Contact::add_address_book(context, book)?; } else { - if 0 == dc_create_contact(context, 0 as *const libc::c_char, arg1_c) { - bail!("Failed to create contact"); - } + Contact::create(context, "", arg1)?; } } "contactinfo" => { ensure!(!arg1.is_empty(), "Argument missing."); let contact_id = arg1.parse()?; - let contact = dc_get_contact(context, contact_id); - let name_n_addr = dc_contact_get_name_n_addr(contact); + let contact = Contact::get_by_id(context, contact_id)?; + let name_n_addr = contact.get_name_n_addr(); - let mut res = format!("Contact info for: {}:\n\n", as_str(name_n_addr),); - free(name_n_addr as *mut libc::c_void); - dc_contact_unref(contact); + let mut res = format!("Contact info for: {}:\n\n", name_n_addr); - let encrinfo = dc_get_contact_encrinfo(context, contact_id); - res += as_str(encrinfo); - free(encrinfo as *mut libc::c_void); + res += &Contact::get_encrinfo(context, contact_id); let chatlist = Chatlist::try_load(context, 0, None, Some(contact_id))?; let chatlist_cnt = chatlist.len(); @@ -1130,9 +1114,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E } "delcontact" => { ensure!(!arg1.is_empty(), "Argument missing."); - if !dc_delete_contact(context, arg1.parse()?) { - bail!("Failed to delete contact"); - } + Contact::delete(context, arg1.parse()?)?; } "checkqr" => { ensure!(!arg1.is_empty(), "Argument missing."); diff --git a/examples/simple.rs b/examples/simple.rs index 1ec8da0f1..628414c93 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -1,6 +1,6 @@ extern crate deltachat; -use std::ffi::{CStr, CString}; +use std::ffi::CStr; use std::sync::{Arc, RwLock}; use std::{thread, time}; use tempfile::tempdir; @@ -8,10 +8,10 @@ use tempfile::tempdir; use deltachat::chatlist::*; use deltachat::config; use deltachat::constants::Event; +use deltachat::contact::*; use deltachat::context::*; use deltachat::dc_chat::*; use deltachat::dc_configure::*; -use deltachat::dc_contact::*; use deltachat::dc_job::{ dc_perform_imap_fetch, dc_perform_imap_idle, dc_perform_imap_jobs, dc_perform_smtp_idle, dc_perform_smtp_jobs, @@ -93,9 +93,9 @@ fn main() { thread::sleep(duration); - let email = CString::new("dignifiedquire@gmail.com").unwrap(); println!("sending a message"); - let contact_id = dc_create_contact(&ctx, std::ptr::null(), email.as_ptr()); + let contact_id = + Contact::create(&ctx, "dignifiedquire", "dignifiedquire@gmail.com").unwrap(); let chat_id = dc_create_chat_by_contact_id(&ctx, contact_id); dc_send_text_msg(&ctx, chat_id, "Hi, here is my first message!".into()); diff --git a/src/aheader.rs b/src/aheader.rs index 35fcb25ca..20a2daa2c 100644 --- a/src/aheader.rs +++ b/src/aheader.rs @@ -6,7 +6,7 @@ use std::{fmt, str}; use mmime::mailimf_types::*; use crate::constants::*; -use crate::dc_contact::*; +use crate::contact::*; use crate::dc_tools::as_str; use crate::key::*; @@ -94,7 +94,7 @@ impl Aheader { match Self::from_str(value) { Ok(test) => { - if dc_addr_cmp(&test.addr, as_str(wanted_from)) { + if addr_cmp(&test.addr, as_str(wanted_from)) { if fine_header.is_none() { fine_header = Some(test); } else { diff --git a/src/chatlist.rs b/src/chatlist.rs index 6b93fe8fc..901e0b35b 100644 --- a/src/chatlist.rs +++ b/src/chatlist.rs @@ -1,7 +1,7 @@ use crate::constants::*; +use crate::contact::*; use crate::context::*; use crate::dc_chat::*; -use crate::dc_contact::*; use crate::dc_lot::*; use crate::dc_msg::*; use crate::dc_tools::*; @@ -261,7 +261,7 @@ impl<'a> Chatlist<'a> { } let lastmsg_id = self.ids[index].1; - let mut lastcontact = 0 as *mut dc_contact_t; + let mut lastcontact = None; if chat.is_null() { chat = dc_chat_new(self.context); @@ -282,8 +282,7 @@ impl<'a> Chatlist<'a> { && ((*chat).type_0 == DC_CHAT_TYPE_GROUP || (*chat).type_0 == DC_CHAT_TYPE_VERIFIED_GROUP) { - lastcontact = dc_contact_new(self.context); - dc_contact_load_from_db(lastcontact, &self.context.sql, (*lastmsg).from_id); + lastcontact = Contact::load_from_db(self.context, (*lastmsg).from_id).ok(); } lastmsg } else { @@ -295,11 +294,10 @@ impl<'a> Chatlist<'a> { } else if lastmsg.is_null() || (*lastmsg).from_id == DC_CONTACT_ID_SELF as u32 { (*ret).text2 = self.context.stock_str(StockMessage::NoMessages).strdup(); } else { - dc_lot_fill(ret, lastmsg, chat, lastcontact, self.context); + dc_lot_fill(ret, lastmsg, chat, lastcontact.as_ref(), self.context); } dc_msg_unref(lastmsg); - dc_contact_unref(lastcontact); ret } diff --git a/src/contact.rs b/src/contact.rs new file mode 100644 index 000000000..30d4ed42c --- /dev/null +++ b/src/contact.rs @@ -0,0 +1,1093 @@ +use itertools::Itertools; +use num_traits::{FromPrimitive, ToPrimitive}; +use rusqlite; +use rusqlite::types::*; + +use crate::aheader::EncryptPreference; +use crate::config::Config; +use crate::constants::*; +use crate::context::Context; +use crate::dc_array::*; +use crate::dc_e2ee::*; +use crate::dc_loginparam::*; +use crate::dc_tools::*; +use crate::error::Result; +use crate::key::*; +use crate::peerstate::*; +use crate::sql; +use crate::stock::StockMessage; +use crate::types::*; + +const DC_GCL_VERIFIED_ONLY: u32 = 0x01; + +/// Contacts with at least this origin value are shown in the contact list. +const DC_ORIGIN_MIN_CONTACT_LIST: i32 = 0x100; + +/// An object representing a single contact in memory. +/// The contact object is not updated. +/// If you want an update, you have to recreate the object. +/// +/// The library makes sure +/// only to use names _authorized_ by the contact in `To:` or `Cc:`. +/// _Given-names _as "Daddy" or "Honey" are not used there. +/// For this purpose, internally, two names are tracked - +/// authorized-name and given-name. +/// By default, these names are equal, but functions working with contact names +pub struct Contact<'a> { + context: &'a Context, + /// The contact ID. + /// + /// Special message IDs: + /// - DC_CONTACT_ID_SELF (1) - this is the owner of the account with the email-address set by + /// `dc_set_config` using "addr". + /// + /// Normal contact IDs are larger than these special ones (larger than DC_CONTACT_ID_LAST_SPECIAL). + pub id: u32, + /// Contact name. It is recommended to use `Contact::get_name`, + /// `Contact::get_display_name` or `Contact::get_name_n_addr` to access this field. + /// May be empty, initially set to `authname`. + name: String, + /// Name authorized by the contact himself. Only this name may be spread to others, + /// e.g. in To:-lists. May be empty. It is recommended to use `Contact::get_name`, + /// `Contact::get_display_name` or `Contact::get_name_n_addr` to access this field. + authname: String, + /// E-Mail-Address of the contact. It is recommended to use `Contact::get_addr`` to access this field. + addr: String, + /// Blocked state. Use dc_contact_is_blocked to access this field. + blocked: bool, + /// The origin/source of the contact. + pub origin: Origin, +} + +/// Possible origins of a contact. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromPrimitive, ToPrimitive)] +#[repr(i32)] +pub enum Origin { + Unknown = 0, + /// From: of incoming messages of unknown sender + IncomingUnknownFrom = 0x10, + /// Cc: of incoming messages of unknown sender + IncomingUnknownCc = 0x20, + /// To: of incoming messages of unknown sender + IncomingUnknownTo = 0x40, + /// address scanned but not verified + UnhandledQrScan = 0x80, + /// Reply-To: of incoming message of known sender + IncomingReplyTo = 0x100, + /// Cc: of incoming message of known sender + IncomingCc = 0x200, + /// additional To:'s of incoming message of known sender + IncomingTo = 0x400, + /// a chat was manually created for this user, but no message yet sent + CreateChat = 0x800, + /// message sent by us + OutgoingBcc = 0x1000, + /// message sent by us + OutgoingCc = 0x2000, + /// message sent by us + OutgoingTo = 0x4000, + /// internal use + Internal = 0x40000, + /// address is in our address book + AdressBook = 0x80000, + /// set on Alice's side for contacts like Bob that have scanned the QR code offered by her. Only means the contact has once been established using the "securejoin" procedure in the past, getting the current key verification status requires calling dc_contact_is_verified() ! + SecurejoinInvited = 0x1000000, + /// set on Bob's side for contacts scanned and verified from a QR code. Only means the contact has once been established using the "securejoin" procedure in the past, getting the current key verification status requires calling dc_contact_is_verified() ! + SecurejoinJoined = 0x2000000, + /// contact added mannually by dc_create_contact(), this should be the largets origin as otherwise the user cannot modify the names + ManuallyCreated = 0x4000000, +} + +impl ToSql for Origin { + fn to_sql(&self) -> rusqlite::Result { + let num: i64 = self + .to_i64() + .expect("impossible: Origin -> i64 conversion failed"); + + Ok(ToSqlOutput::Owned(Value::Integer(num))) + } +} + +impl FromSql for Origin { + fn column_result(col: ValueRef) -> FromSqlResult { + let inner = FromSql::column_result(col)?; + FromPrimitive::from_i64(inner).ok_or(FromSqlError::InvalidType) + } +} + +impl Origin { + /// Contacts that start a new "normal" chat, defaults to off. + pub fn is_start_new_chat(self) -> bool { + self as i32 >= 0x7FFFFFFF + } + + /// Contacts that are verified and known not to be spam. + pub fn is_verified(self) -> bool { + self as i32 >= 0x100 + } + + /// Contacts that are shown in the contact list. + pub fn include_in_contactlist(self) -> bool { + self as i32 >= DC_ORIGIN_MIN_CONTACT_LIST + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum Modifier { + None, + Modified, + Created, +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[repr(u8)] +pub enum VerifiedStatus { + /// Contact is not verified. + Unverified = 0, + // TODO: is this a thing? + Verified = 1, + /// SELF and contact have verified their fingerprints in both directions; in the UI typically checkmarks are shown. + BidirectVerified = 2, +} + +impl<'a> Contact<'a> { + pub fn load_from_db(context: &'a Context, contact_id: u32) -> Result { + if contact_id == DC_CONTACT_ID_SELF as u32 { + let contact = Contact { + context, + id: contact_id, + name: context.stock_str(StockMessage::SelfMsg).into(), + authname: "".into(), + addr: context + .get_config(Config::ConfiguredAddr) + .unwrap_or_default(), + blocked: false, + origin: Origin::Unknown, + }; + + return Ok(contact); + } + + context.sql.query_row( + "SELECT c.name, c.addr, c.origin, c.blocked, c.authname FROM contacts c WHERE c.id=?;", + params![contact_id as i32], + |row| { + let contact = Self { + context, + id: contact_id, + name: row.get::<_, String>(0)?, + authname: row.get::<_, String>(4)?, + addr: row.get::<_, String>(1)?, + blocked: row.get::<_, Option>(3)?.unwrap_or_default() != 0, + origin: row.get(2)?, + }; + Ok(contact) + } + ) + } + + /// Returns `true` if this contact is blocked. + pub fn is_blocked(&self) -> bool { + self.blocked + } + + /// Check if a contact is blocked. + pub fn is_blocked_load(context: &'a Context, id: u32) -> bool { + Self::load_from_db(context, id) + .map(|contact| contact.blocked) + .unwrap_or_default() + } + + /// Block the given contact. + pub fn block(context: &Context, id: u32) { + set_block_contact(context, id, true); + } + + /// Unblock the given contact. + pub fn unblock(context: &Context, id: u32) { + set_block_contact(context, id, false); + } + + /// Add a single contact as a result of an _explicit_ user action. + /// + /// We assume, the contact name, if any, is entered by the user and is used "as is" therefore, + /// normalize() is _not_ called for the name. If the contact is blocked, it is unblocked. + /// + /// To add a number of contacts, see `dc_add_address_book()` which is much faster for adding + /// a bunch of addresses. + /// + /// May result in a `#DC_EVENT_CONTACTS_CHANGED` event. + pub fn create(context: &Context, name: impl AsRef, addr: impl AsRef) -> Result { + ensure!( + !addr.as_ref().is_empty(), + "Cannot create contact with empty address" + ); + + let (contact_id, sth_modified) = + Contact::add_or_lookup(context, name, addr, Origin::ManuallyCreated)?; + let blocked = Contact::is_blocked_load(context, contact_id); + context.call_cb( + Event::CONTACTS_CHANGED, + (if sth_modified == Modifier::Created { + contact_id + } else { + 0 + }) as uintptr_t, + 0 as uintptr_t, + ); + if blocked { + Contact::unblock(context, contact_id); + } + + Ok(contact_id) + } + + /// Mark all messages sent by the given contact + /// as _noticed_. See also dc_marknoticed_chat() and dc_markseen_msgs() + /// + /// Calling this function usually results in the event `#DC_EVENT_MSGS_CHANGED`. + pub fn mark_noticed(context: &Context, id: u32) { + if sql::execute( + context, + &context.sql, + "UPDATE msgs SET state=? WHERE from_id=? AND state=?;", + params![DC_STATE_IN_NOTICED, id as i32, DC_STATE_IN_FRESH], + ) + .is_ok() + { + context.call_cb(Event::MSGS_CHANGED, 0, 0); + } + } + + /// Check if an e-mail address belongs to a known and unblocked contact. + /// Known and unblocked contacts will be returned by `dc_get_contacts()`. + /// + /// To validate an e-mail address independently of the contact database + /// use `dc_may_be_valid_addr()`. + pub fn lookup_id_by_addr(context: &Context, addr: impl AsRef) -> u32 { + if addr.as_ref().is_empty() { + return 0; + } + + let addr_normalized = addr_normalize(addr.as_ref()); + let addr_self = context + .get_config(Config::ConfiguredAddr) + .unwrap_or_default(); + + if addr_normalized == addr_self { + return 1; + } + + context.sql.query_row_col( + context, + "SELECT id FROM contacts WHERE addr=?1 COLLATE NOCASE AND id>?2 AND origin>=?3 AND blocked=0;", + params![ + addr_normalized, + DC_CONTACT_ID_LAST_SPECIAL as i32, + DC_ORIGIN_MIN_CONTACT_LIST, + ], + 0 + ).unwrap_or_default() + } + + /// Lookup a contact and create it if it does not exist yet. + /// + /// Returns the contact_id and a `Modifier` value indicating if a modification occured. + pub fn add_or_lookup( + context: &Context, + name: impl AsRef, + addr: impl AsRef, + origin: Origin, + ) -> Result<(u32, Modifier)> { + let mut sth_modified = Modifier::None; + + ensure!( + !addr.as_ref().is_empty(), + "Can not add_or_lookup empty address" + ); + ensure!(origin != Origin::Unknown, "Missing valid origin"); + + let addr = addr_normalize(addr.as_ref()); + let addr_self = context + .get_config(Config::ConfiguredAddr) + .unwrap_or_default(); + + if addr == addr_self { + return Ok((1, sth_modified)); + } + + if !may_be_valid_addr(&addr) { + warn!( + context, + 0, + "Bad address \"{}\" for contact \"{}\".", + addr, + if !name.as_ref().is_empty() { + name.as_ref() + } else { + "" + }, + ); + bail!("Bad address supplied"); + } + + let mut update_addr = false; + let mut update_name = false; + let mut update_authname = false; + let mut row_id = 0; + + if let Ok((id, row_name, row_addr, row_origin, row_authname)) = context.sql.query_row( + "SELECT id, name, addr, origin, authname FROM contacts WHERE addr=? COLLATE NOCASE;", + params![addr], + |row| { + let row_id = row.get(0)?; + let row_name: String = row.get(1)?; + let row_addr: String = row.get(2)?; + let row_origin = row.get(3)?; + let row_authname: String = row.get(4)?; + + if !name.as_ref().is_empty() && !row_name.is_empty() { + if origin >= row_origin && name.as_ref() != row_name { + update_name = true; + } + } else { + update_name = true; + } + if origin == Origin::IncomingUnknownFrom && name.as_ref() != row_authname { + update_authname = true; + } + Ok((row_id, row_name, row_addr, row_origin, row_authname)) + }, + ) { + row_id = id; + if origin as i32 >= row_origin as i32 && addr != row_addr { + update_addr = true; + } + if update_name || update_authname || update_addr || origin > row_origin { + sql::execute( + context, + &context.sql, + "UPDATE contacts SET name=?, addr=?, origin=?, authname=? WHERE id=?;", + params![ + if update_name { + name.as_ref() + } else { + &row_name + }, + if update_addr { addr } else { &row_addr }, + if origin > row_origin { + origin + } else { + row_origin + }, + if update_authname { + name.as_ref() + } else { + &row_authname + }, + row_id + ], + ) + .ok(); + + if update_name { + sql::execute( + context, + &context.sql, + "UPDATE chats SET name=? WHERE type=? AND id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?);", + params![name.as_ref(), 100, row_id] + ).ok(); + } + sth_modified = Modifier::Modified; + } + } else { + if sql::execute( + context, + &context.sql, + "INSERT INTO contacts (name, addr, origin) VALUES(?, ?, ?);", + params![name.as_ref(), addr, origin,], + ) + .is_ok() + { + row_id = sql::get_rowid(context, &context.sql, "contacts", "addr", addr); + sth_modified = Modifier::Created; + } else { + error!(context, 0, "Cannot add contact."); + } + } + + Ok((row_id, sth_modified)) + } + + /// Add a number of contacts. + /// + /// Typically used to add the whole address book from the OS. As names here are typically not + /// well formatted, we call `normalize()` for each name given. + /// + /// No email-address is added twice. + /// Trying to add email-addresses that are already in the contact list, + /// results in updating the name unless the name was changed manually by the user. + /// If any email-address or any name is really updated, + /// the event `DC_EVENT_CONTACTS_CHANGED` is sent. + /// + /// To add a single contact entered by the user, you should prefer `Contact::create`, + /// however, for adding a bunch of addresses, this function is _much_ faster. + /// + /// The `adr_book` is a multiline string in the format `Name one\nAddress one\nName two\nAddress two`. + /// + /// Returns the number of modified contacts. + pub fn add_address_book(context: &Context, adr_book: impl AsRef) -> Result { + let mut modify_cnt = 0; + + for chunk in &adr_book.as_ref().lines().chunks(2) { + let chunk = chunk.collect::>(); + if chunk.len() < 2 { + break; + } + let name = chunk[0]; + let addr = chunk[1]; + let name = normalize_name(name); + let (_, modified) = Contact::add_or_lookup(context, name, addr, Origin::AdressBook)?; + if modified != Modifier::None { + modify_cnt += 1 + } + } + if modify_cnt > 0 { + context.call_cb(Event::CONTACTS_CHANGED, 0 as uintptr_t, 0 as uintptr_t); + } + + Ok(modify_cnt) + } + + /// Returns known and unblocked contacts. + /// + /// To get information about a single contact, see dc_get_contact(). + /// + /// `listflags` is a combination of flags: + /// - if the flag DC_GCL_ADD_SELF is set, SELF is added to the list unless filtered by other parameters + /// - if the flag DC_GCL_VERIFIED_ONLY is set, only verified contacts are returned. + /// if DC_GCL_VERIFIED_ONLY is not set, verified and unverified contacts are returned. + /// `query` is a string to filter the list. + pub fn get_all( + context: &Context, + listflags: u32, + query: Option>, + ) -> Result<*mut dc_array_t> { + let self_addr = context + .get_config(Config::ConfiguredAddr) + .unwrap_or_default(); + + let mut add_self = false; + let mut ret = dc_array_t::new(100); + + if (listflags & DC_GCL_VERIFIED_ONLY) > 0 || query.is_some() { + let s3str_like_cmd = format!( + "%{}%", + query + .as_ref() + .map(|s| s.as_ref().to_string()) + .unwrap_or_default() + ); + context.sql.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 \ + AND c.blocked=0 \ + AND (c.name LIKE ?4 OR c.addr LIKE ?5) \ + AND (1=?6 OR LENGTH(ps.verified_key_fingerprint)!=0) \ + ORDER BY LOWER(c.name||c.addr),c.id;", + params![ + self_addr, + DC_CONTACT_ID_LAST_SPECIAL as i32, + 0x100, + &s3str_like_cmd, + &s3str_like_cmd, + if 0 != listflags & 0x1 { 0 } else { 1 }, + ], + |row| row.get::<_, i32>(0), + |ids| { + for id in ids { + ret.add_id(id? as u32); + } + Ok(()) + }, + )?; + + let self_name = context.get_config(Config::Displayname).unwrap_or_default(); + let self_name2 = context.stock_str(StockMessage::SelfMsg); + + if let Some(query) = query { + if self_addr.contains(query.as_ref()) + || self_name.contains(query.as_ref()) + || self_name2.contains(query.as_ref()) + { + add_self = true; + } + } else { + add_self = true; + } + } else { + add_self = true; + + context.sql.query_map( + "SELECT id FROM contacts WHERE addr!=?1 AND id>?2 AND origin>=?3 AND blocked=0 ORDER BY LOWER(name||addr),id;", + params![self_addr, DC_CONTACT_ID_LAST_SPECIAL as i32, 0x100], + |row| row.get::<_, i32>(0), + |ids| { + for id in ids { + ret.add_id(id? as u32); + } + Ok(()) + } + )?; + } + + if 0 != listflags & DC_GCL_ADD_SELF as u32 && add_self { + ret.add_id(DC_CONTACT_ID_SELF as u32); + } + + Ok(ret.into_raw()) + } + + pub fn get_blocked_cnt(context: &Context) -> usize { + context + .sql + .query_row_col::<_, isize>( + context, + "SELECT COUNT(*) FROM contacts WHERE id>? AND blocked!=0", + params![DC_CONTACT_ID_LAST_SPECIAL as i32], + 0, + ) + .unwrap_or_default() as usize + } + + /// Get blocked contacts. + pub fn get_all_blocked(context: &Context) -> *mut dc_array_t { + context + .sql + .query_map( + "SELECT id FROM contacts WHERE id>? AND blocked!=0 ORDER BY LOWER(name||addr),id;", + params![DC_CONTACT_ID_LAST_SPECIAL as i32], + |row| row.get::<_, i32>(0), + |ids| { + let mut ret = dc_array_t::new(100); + + for id in ids { + ret.add_id(id? as u32); + } + + Ok(ret.into_raw()) + }, + ) + .unwrap_or_else(|_| std::ptr::null_mut()) + } + + pub fn get_encrinfo(context: &Context, contact_id: u32) -> String { + let mut ret = String::new(); + + if let Ok(contact) = Contact::load_from_db(context, contact_id) { + let peerstate = Peerstate::from_addr(context, &context.sql, &contact.addr); + let loginparam = dc_loginparam_read(context, &context.sql, "configured_"); + + let mut self_key = Key::from_self_public(context, &loginparam.addr, &context.sql); + + if peerstate.is_some() && peerstate.as_ref().and_then(|p| p.peek_key(0)).is_some() { + let peerstate = peerstate.as_ref().unwrap(); + let p = + context.stock_str(if peerstate.prefer_encrypt == EncryptPreference::Mutual { + StockMessage::E2ePreferred + } else { + StockMessage::E2eAvailable + }); + ret += &p; + if self_key.is_none() { + unsafe { dc_ensure_secret_key_exists(context) }; + self_key = Key::from_self_public(context, &loginparam.addr, &context.sql); + } + let p = context.stock_str(StockMessage::FingerPrints); + ret += &format!(" {}:", p); + + let fingerprint_self = self_key + .map(|k| k.formatted_fingerprint()) + .unwrap_or_default(); + let fingerprint_other_verified = peerstate + .peek_key(2) + .map(|k| k.formatted_fingerprint()) + .unwrap_or_default(); + let fingerprint_other_unverified = peerstate + .peek_key(0) + .map(|k| k.formatted_fingerprint()) + .unwrap_or_default(); + if peerstate.addr.is_some() && &loginparam.addr < peerstate.addr.as_ref().unwrap() { + cat_fingerprint(&mut ret, &loginparam.addr, &fingerprint_self, ""); + cat_fingerprint( + &mut ret, + peerstate.addr.as_ref().unwrap(), + &fingerprint_other_verified, + &fingerprint_other_unverified, + ); + } else { + cat_fingerprint( + &mut ret, + peerstate.addr.as_ref().unwrap(), + &fingerprint_other_verified, + &fingerprint_other_unverified, + ); + cat_fingerprint(&mut ret, &loginparam.addr, &fingerprint_self, ""); + } + } else if 0 == loginparam.server_flags & DC_LP_IMAP_SOCKET_PLAIN as i32 + && 0 == loginparam.server_flags & DC_LP_SMTP_SOCKET_PLAIN as i32 + { + ret += &context.stock_str(StockMessage::EncrTransp); + } else { + ret += &context.stock_str(StockMessage::EncrNone); + } + } + + ret + } + + /// Delete a contact. The contact is deleted from the local device. It may happen that this is not + /// possible as the contact is in use. In this case, the contact can be blocked. + /// + /// May result in a `#DC_EVENT_CONTACTS_CHANGED` event. + pub fn delete(context: &Context, contact_id: u32) -> Result<()> { + ensure!( + contact_id > DC_CONTACT_ID_LAST_SPECIAL as u32, + "Can not delete special contact" + ); + + let count_contacts: i32 = context + .sql + .query_row_col( + context, + "SELECT COUNT(*) FROM chats_contacts WHERE contact_id=?;", + params![contact_id as i32], + 0, + ) + .unwrap_or_default(); + + let count_msgs: i32 = if count_contacts > 0 { + context + .sql + .query_row_col( + context, + "SELECT COUNT(*) FROM msgs WHERE from_id=? OR to_id=?;", + params![contact_id as i32, contact_id as i32], + 0, + ) + .unwrap_or_default() + } else { + 0 + }; + + if count_msgs == 0 { + match sql::execute( + context, + &context.sql, + "DELETE FROM contacts WHERE id=?;", + params![contact_id as i32], + ) { + Ok(_) => { + context.call_cb(Event::CONTACTS_CHANGED, 0, 0); + return Ok(()); + } + Err(err) => { + error!(context, 0, "delete_contact {} failed ({})", contact_id, err); + return Err(err); + } + } + } + + info!( + context, + 0, "could not delete contact {}, there are {} messages with it", contact_id, count_msgs + ); + bail!("Could not delete contact with messages in it"); + } + + /// Get a single contact object. For a list, see eg. dc_get_contacts(). + /// + /// For contact DC_CONTACT_ID_SELF (1), the function returns sth. + /// like "Me" in the selected language and the email address + /// defined by dc_set_config(). + pub fn get_by_id(context: &Context, contact_id: u32) -> Result { + Contact::load_from_db(context, contact_id) + } + + /// Get the ID of the contact. + pub fn get_id(&self) -> u32 { + self.id + } + + /// Get email address. The email address is always set for a contact. + pub fn get_addr(&self) -> &str { + &self.addr + } + + pub fn get_authname(&self) -> &str { + &self.authname + } + + /// Get the contact name. This is the name as defined by the contact himself or + /// modified by the user. May be an empty string. + /// + /// This name is typically used in a form where the user can edit the name of a contact. + /// To get a fine name to display in lists etc., use `Contact::get_display_name` or `Contact::get_name_n_addr`. + pub fn get_name(&self) -> &str { + &self.name + } + + /// Get display name. This is the name as defined by the contact himself, + /// modified by the user or, if both are unset, the email address. + /// + /// This name is typically used in lists. + /// To get the name editable in a formular, use `Contact::get_name`. + pub fn get_display_name(&self) -> &str { + if !self.name.is_empty() { + return &self.name; + } + &self.addr + } + + /// Get a summary of name and address. + /// + /// The returned string is either "Name (email@domain.com)" or just + /// "email@domain.com" if the name is unset. + /// + /// The summary is typically used when asking the user something about the contact. + /// The attached email address makes the question unique, eg. "Chat with Alan Miller (am@uniquedomain.com)?" + pub fn get_name_n_addr(&self) -> String { + if !self.name.is_empty() { + return format!("{} ({})", self.name, self.addr); + } + (&self.addr).into() + } + + /// Get the part of the name before the first space. In most languages, this seems to be + /// the prename. If there is no space, the full display name is returned. + /// If the display name is not set, the e-mail address is returned. + pub fn get_first_name(&self) -> &str { + if !self.name.is_empty() { + return get_first_name(&self.name); + } + &self.addr + } + + /// Get the contact's profile image. + /// This is the image set by each remote user on their own + /// using dc_set_config(context, "selfavatar", image). + pub fn get_profile_image(&self) -> Option { + if self.id == DC_CONTACT_ID_SELF as u32 { + return self.context.get_config(Config::Selfavatar); + } + // TODO: else get image_abs from contact param + None + } + + /// Get a color for the contact. + /// The color is calculated from the contact's email address + /// and can be used for an fallback avatar with white initials + /// as well as for headlines in bubbles of group chats. + pub fn get_color(&self) -> u32 { + dc_str_to_color_safe(&self.addr) + } + + /// Check if a contact was verified. E.g. by a secure-join QR code scan + /// and if the key has not changed since this verification. + /// + /// The UI may draw a checkbox or something like that beside verified contacts. + /// + pub fn is_verified(&self) -> VerifiedStatus { + self.is_verified_ex(None) + } + + /// Same as `Contact::is_verified` but allows speeding up things + /// by adding the peerstate belonging to the contact. + /// If you do not have the peerstate available, it is loaded automatically. + pub fn is_verified_ex(&self, peerstate: Option<&Peerstate<'a>>) -> VerifiedStatus { + // We're always sort of secured-verified as we could verify the key on this device any time with the key + // on this device + if self.id == DC_CONTACT_ID_SELF as u32 { + return VerifiedStatus::BidirectVerified; + } + + if let Some(peerstate) = peerstate { + if peerstate.verified_key().is_some() { + return VerifiedStatus::BidirectVerified; + } + } + + let peerstate = Peerstate::from_addr(self.context, &self.context.sql, &self.addr); + if let Some(ps) = peerstate { + if ps.verified_key().is_some() { + return VerifiedStatus::BidirectVerified; + } + } + + VerifiedStatus::Unverified + } + + pub fn addr_equals_contact(context: &Context, addr: impl AsRef, contact_id: u32) -> bool { + if addr.as_ref().is_empty() { + return false; + } + + if let Ok(contact) = Contact::load_from_db(context, contact_id) { + if !contact.addr.is_empty() { + let normalized_addr = addr_normalize(addr.as_ref()); + if &contact.addr == &normalized_addr { + return true; + } + } + } + + false + } + + pub fn get_real_cnt(context: &Context) -> usize { + if !context.sql.is_open() { + return 0; + } + + context + .sql + .query_row_col::<_, isize>( + context, + "SELECT COUNT(*) FROM contacts WHERE id>?;", + params![DC_CONTACT_ID_LAST_SPECIAL as i32], + 0, + ) + .unwrap_or_default() as usize + } + + pub fn get_origin_by_id(context: &Context, contact_id: u32, ret_blocked: &mut i32) -> Origin { + let mut ret = Origin::Unknown; + *ret_blocked = 0; + + if let Ok(contact) = Contact::load_from_db(context, contact_id) { + /* we could optimize this by loading only the needed fields */ + if contact.blocked { + *ret_blocked = 1; + } else { + ret = contact.origin; + } + } + + ret + } + + pub fn real_exists_by_id(context: &Context, contact_id: u32) -> bool { + if !context.sql.is_open() || contact_id <= 9 { + return false; + } + + context + .sql + .exists( + "SELECT id FROM contacts WHERE id=?;", + params![contact_id as i32], + ) + .unwrap_or_default() + } + + pub fn scaleup_origin_by_id(context: &Context, contact_id: u32, origin: Origin) -> bool { + context + .sql + .execute( + "UPDATE contacts SET origin=? WHERE id=? AND origin(full_name: &'a str) -> &'a str { + full_name.splitn(2, ' ').next().unwrap_or_default() +} + +/// Returns false if addr is an invalid address, otherwise true. +pub fn may_be_valid_addr(addr: &str) -> bool { + if addr.is_empty() { + return false; + } + + let at = addr.find('@').unwrap_or_default(); + if at < 1 { + return false; + } + let dot = addr.find('.').unwrap_or_default(); + if dot < 1 || dot > addr.len() - 3 || dot < at + 2 { + return false; + } + + true +} + +pub fn addr_normalize(addr: &str) -> &str { + let norm = addr.trim(); + + if norm.starts_with("mailto:") { + return &norm[7..]; + } + + norm +} + +fn set_block_contact(context: &Context, contact_id: u32, new_blocking: bool) { + if contact_id <= 9 { + return; + } + + if let Ok(contact) = Contact::load_from_db(context, contact_id) { + if contact.blocked != new_blocking { + if sql::execute( + context, + &context.sql, + "UPDATE contacts SET blocked=? WHERE id=?;", + params![new_blocking as i32, contact_id as i32], + ) + .is_ok() + { + // also (un)block all chats with _only_ this contact - we do not delete them to allow a + // non-destructive blocking->unblocking. + // (Maybe, beside normal chats (type=100) we should also block group chats with only this user. + // However, I'm not sure about this point; it may be confusing if the user wants to add other people; + // this would result in recreating the same group...) + if sql::execute( + context, + &context.sql, + "UPDATE chats SET blocked=? WHERE type=? AND id IN (SELECT chat_id FROM chats_contacts WHERE contact_id=?);", + params![new_blocking, 100, contact_id as i32], + ).is_ok() { + Contact::mark_noticed(context, contact_id); + context.call_cb( + Event::CONTACTS_CHANGED, + 0, + 0, + ); + } + } + } + } +} + +/// Normalize a name. +/// +/// - Remove quotes (come from some bad MUA implementations) +/// - Convert names as "Petersen, Björn" to "Björn Petersen" +/// - Trims the resulting string +/// +/// Typically, this function is not needed as it is called implicitly by `Contact::add_address_book`. +pub fn normalize_name(full_name: impl AsRef) -> String { + let mut full_name = full_name.as_ref().trim(); + if full_name.is_empty() { + return full_name.into(); + } + + let len = full_name.len(); + if len > 0 { + let firstchar = full_name.as_bytes()[0]; + let lastchar = full_name.as_bytes()[len - 1]; + if firstchar == '\'' as u8 && lastchar == '\'' as u8 + || firstchar == '\"' as u8 && lastchar == '\"' as u8 + || firstchar == '<' as u8 && lastchar == '>' as u8 + { + full_name = &full_name[1..len - 1]; + } + } + + if let Some(p1) = full_name.find(',') { + let (last_name, first_name) = full_name.split_at(p1); + + let last_name = last_name.trim(); + let first_name = (&first_name[1..]).trim(); + + return format!("{} {}", first_name, last_name); + } + + full_name.trim().into() +} + +fn cat_fingerprint( + ret: &mut String, + addr: impl AsRef, + fingerprint_verified: impl AsRef, + fingerprint_unverified: impl AsRef, +) { + *ret += &format!( + "\n\n{}:\n{}", + addr.as_ref(), + if !fingerprint_verified.as_ref().is_empty() { + fingerprint_verified.as_ref() + } else { + fingerprint_unverified.as_ref() + }, + ); + if !fingerprint_verified.as_ref().is_empty() + && !fingerprint_unverified.as_ref().is_empty() + && fingerprint_verified.as_ref() != fingerprint_unverified.as_ref() + { + *ret += &format!( + "\n\n{} (alternative):\n{}", + addr.as_ref(), + fingerprint_unverified.as_ref() + ); + } +} + +pub fn addr_cmp(addr1: impl AsRef, addr2: impl AsRef) -> bool { + let norm1 = addr_normalize(addr1.as_ref()); + let norm2 = addr_normalize(addr2.as_ref()); + + norm1 == norm2 +} + +pub fn addr_equals_self(context: &Context, addr: impl AsRef) -> bool { + if !addr.as_ref().is_empty() { + let normalized_addr = addr_normalize(addr.as_ref()); + if let Some(self_addr) = context.get_config(Config::ConfiguredAddr) { + return normalized_addr == self_addr; + } + } + false +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_may_be_valid_addr() { + assert_eq!(may_be_valid_addr(""), false); + assert_eq!(may_be_valid_addr("user@domain.tld"), true); + assert_eq!(may_be_valid_addr("uuu"), false); + assert_eq!(may_be_valid_addr("dd.tt"), false); + assert_eq!(may_be_valid_addr("tt.dd@uu"), false); + assert_eq!(may_be_valid_addr("u@d"), false); + assert_eq!(may_be_valid_addr("u@d."), false); + assert_eq!(may_be_valid_addr("u@d.t"), false); + assert_eq!(may_be_valid_addr("u@d.tt"), true); + assert_eq!(may_be_valid_addr("u@.tt"), false); + assert_eq!(may_be_valid_addr("@d.tt"), false); + } + + #[test] + fn test_normalize_name() { + assert_eq!(&normalize_name("Doe, John"), "John Doe"); + assert_eq!(&normalize_name(" hello world "), "hello world"); + } + + #[test] + fn test_normalize_addr() { + assert_eq!(addr_normalize("mailto:john@doe.com"), "john@doe.com"); + assert_eq!(addr_normalize(" hello@world.com "), "hello@world.com"); + } + + #[test] + fn test_get_first_name() { + assert_eq!(get_first_name("John Doe"), "John"); + } +} diff --git a/src/context.rs b/src/context.rs index 1480c8181..013f13f5d 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,9 +1,9 @@ use std::sync::{Arc, Condvar, Mutex, RwLock}; use crate::constants::*; +use crate::contact::*; use crate::dc_array::*; use crate::dc_chat::*; -use crate::dc_contact::*; use crate::dc_job::*; use crate::dc_jobthread::*; use crate::dc_loginparam::*; @@ -346,7 +346,7 @@ pub unsafe fn dc_get_info(context: &Context) -> *mut libc::c_char { let chats = dc_get_chat_cnt(context) as usize; let real_msgs = dc_get_real_msg_cnt(context) as usize; let deaddrop_msgs = dc_get_deaddrop_msg_cnt(context) as usize; - let contacts = dc_get_real_contact_cnt(context) as usize; + let contacts = Contact::get_real_cnt(context) as usize; let is_configured = context .sql .get_config_int(context, "configured") diff --git a/src/dc_chat.rs b/src/dc_chat.rs index ec38aeb80..5497ad927 100644 --- a/src/dc_chat.rs +++ b/src/dc_chat.rs @@ -2,9 +2,9 @@ use std::ffi::CString; use crate::chatlist::*; use crate::constants::*; +use crate::contact::*; use crate::context::Context; use crate::dc_array::*; -use crate::dc_contact::*; use crate::dc_job::*; use crate::dc_msg::*; use crate::dc_tools::*; @@ -52,7 +52,7 @@ pub unsafe fn dc_create_chat_by_msg_id(context: &Context, msg_id: uint32_t) -> u dc_unblock_chat(context, (*chat).id); send_event = 1i32 } - dc_scaleup_contact_origin(context, (*msg).from_id, 0x800i32); + Contact::scaleup_origin_by_id(context, (*msg).from_id, Origin::CreateChat); } dc_msg_unref(msg); @@ -206,7 +206,9 @@ pub unsafe fn dc_create_chat_by_contact_id(context: &Context, contact_id: uint32 dc_unblock_chat(context, chat_id); send_event = 1i32 } - } else if !dc_real_contact_exists(context, contact_id) && contact_id != 1i32 as libc::c_uint { + } else if !Contact::real_exists_by_id(context, contact_id) + && contact_id != DC_CONTACT_ID_SELF as u32 + { warn!( context, 0, "Cannot create chat, contact {} does not exist.", contact_id as libc::c_int, @@ -222,7 +224,7 @@ pub unsafe fn dc_create_chat_by_contact_id(context: &Context, contact_id: uint32 if 0 != chat_id { send_event = 1; } - dc_scaleup_contact_origin(context, contact_id, 0x800i32); + Contact::scaleup_origin_by_id(context, contact_id, Origin::CreateChat); } if 0 != send_event { context.call_cb(Event::MSGS_CHANGED, 0i32 as uintptr_t, 0i32 as uintptr_t); @@ -239,8 +241,6 @@ pub unsafe fn dc_create_or_lookup_nchat_by_contact_id( ) { let mut chat_id = 0; let mut chat_blocked = 0; - let contact: *mut dc_contact_t; - let chat_name: *mut libc::c_char; if !ret_chat_id.is_null() { *ret_chat_id = 0; @@ -264,14 +264,8 @@ pub unsafe fn dc_create_or_lookup_nchat_by_contact_id( } return; } - contact = dc_contact_new(context); - if dc_contact_load_from_db(contact, &context.sql, contact_id) { - chat_name = - if !(*contact).name.is_null() && 0 != *(*contact).name.offset(0isize) as libc::c_int { - (*contact).name - } else { - (*contact).addr - }; + if let Ok(contact) = Contact::load_from_db(context, contact_id) { + let chat_name = contact.get_display_name(); if sql::execute( context, @@ -279,10 +273,10 @@ pub unsafe fn dc_create_or_lookup_nchat_by_contact_id( format!( "INSERT INTO chats (type, name, param, blocked, grpid) VALUES({}, '{}', '{}', {}, '{}')", 100, - as_str(chat_name), - if contact_id == 1 { "K=1" } else { "" }, + chat_name, + if contact_id == DC_CONTACT_ID_SELF as u32 { "K=1" } else { "" }, create_blocked, - as_str((*contact).addr), + contact.get_addr(), ), params![], ).is_ok() { @@ -291,7 +285,7 @@ pub unsafe fn dc_create_or_lookup_nchat_by_contact_id( &context.sql, "chats", "grpid", - as_str((*contact).addr), + contact.get_addr(), ); sql::execute( @@ -303,7 +297,6 @@ pub unsafe fn dc_create_or_lookup_nchat_by_contact_id( } } - dc_contact_unref(contact); if !ret_chat_id.is_null() { *ret_chat_id = chat_id } @@ -1542,15 +1535,18 @@ pub unsafe fn dc_add_contact_to_chat_ex( ) -> libc::c_int { let mut OK_TO_CONTINUE = true; let mut success: libc::c_int = 0; - let contact: *mut dc_contact_t = dc_get_contact(context, contact_id); + let contact = Contact::get_by_id(context, contact_id); let chat: *mut Chat = dc_chat_new(context); let mut msg: *mut dc_msg_t = dc_msg_new_untyped(context); - if !(contact.is_null() || chat_id <= 9 as libc::c_uint) { + if !(contact.is_err() || chat_id <= 9 as libc::c_uint) { dc_reset_gossiped_timestamp(context, chat_id); + let contact = contact.unwrap(); + /*this also makes sure, not contacts are added to special or normal chats*/ if !(0 == real_group_exists(context, chat_id) - || !dc_real_contact_exists(context, contact_id) && contact_id != 1 as libc::c_uint + || !Contact::real_exists_by_id(context, contact_id) + && contact_id != DC_CONTACT_ID_SELF as u32 || !dc_chat_load_from_db(chat, chat_id)) { if !(dc_is_contact_in_chat(context, chat_id, 1 as uint32_t) == 1) { @@ -1572,7 +1568,7 @@ pub unsafe fn dc_add_contact_to_chat_ex( .sql .get_config(context, "configured_addr") .unwrap_or_default(); - if as_str((*contact).addr) != &self_addr { + if contact.get_addr() != &self_addr { // ourself is added using DC_CONTACT_ID_SELF, do not add it explicitly. // if SELF is not in the group, members cannot be added at all. @@ -1584,7 +1580,7 @@ pub unsafe fn dc_add_contact_to_chat_ex( } else { // else continue and send status mail if (*chat).type_0 == 130 { - if dc_contact_is_verified(contact) != 2 { + if contact.is_verified() != VerifiedStatus::BidirectVerified { error!( context, 0, "Only bidirectional verified contacts can be added to verified groups." @@ -1603,14 +1599,12 @@ pub unsafe fn dc_add_contact_to_chat_ex( (*msg).type_0 = Viewtype::Text; (*msg).text = Some(context.stock_system_msg( StockMessage::MsgAddMember, - as_str((*contact).addr), + contact.get_addr(), "", DC_CONTACT_ID_SELF as uint32_t, )); (*msg).param.set_int(Param::Cmd, 4); - if !(*contact).addr.is_null() { - (*msg).param.set(Param::Arg, as_str((*contact).addr)); - } + (*msg).param.set(Param::Arg, contact.get_addr()); (*msg).param.set_int(Param::Arg2, flags); (*msg).id = dc_send_msg(context, chat_id, msg); context.call_cb( @@ -1627,7 +1621,6 @@ pub unsafe fn dc_add_contact_to_chat_ex( } } dc_chat_unref(chat); - dc_contact_unref(contact); dc_msg_unref(msg); success @@ -1689,13 +1682,12 @@ pub unsafe fn dc_remove_contact_from_chat( chat_id: u32, contact_id: u32, ) -> libc::c_int { - let mut success: libc::c_int = 0; - let contact: *mut dc_contact_t = dc_get_contact(context, contact_id); + let mut success = 0; let chat: *mut Chat = dc_chat_new(context); let mut msg: *mut dc_msg_t = dc_msg_new_untyped(context); if !(chat_id <= 9 as libc::c_uint - || contact_id <= 9 as libc::c_uint && contact_id != 1 as libc::c_uint) + || contact_id <= 9 as libc::c_uint && contact_id != DC_CONTACT_ID_SELF as u32) { /* we do not check if "contact_id" exists but just delete all records with the id from chats_contacts */ /* this allows to delete pending references to deleted contacts. Of course, this should _not_ happen. */ @@ -1709,10 +1701,10 @@ pub unsafe fn dc_remove_contact_from_chat( ); } else { /* we should respect this - whatever we send to the group, it gets discarded anyway! */ - if !contact.is_null() { + if let Ok(contact) = Contact::get_by_id(context, contact_id) { if (*chat).param.get_int(Param::Unpromoted).unwrap_or_default() == 0 { (*msg).type_0 = Viewtype::Text; - if (*contact).id == 1 as libc::c_uint { + if contact.id == DC_CONTACT_ID_SELF as u32 { dc_set_group_explicitly_left(context, (*chat).grpid); (*msg).text = Some(context.stock_system_msg( StockMessage::MsgGroupLeft, @@ -1723,15 +1715,13 @@ pub unsafe fn dc_remove_contact_from_chat( } else { (*msg).text = Some(context.stock_system_msg( StockMessage::MsgDelMember, - as_str((*contact).addr), + contact.get_addr(), "", DC_CONTACT_ID_SELF as u32, )); } (*msg).param.set_int(Param::Cmd, 5); - if !(*contact).addr.is_null() { - (*msg).param.set(Param::Arg, as_str((*contact).addr)); - } + (*msg).param.set(Param::Arg, contact.get_addr()); (*msg).id = dc_send_msg(context, chat_id, msg); context.call_cb( Event::MSGS_CHANGED, @@ -1756,7 +1746,6 @@ pub unsafe fn dc_remove_contact_from_chat( } dc_chat_unref(chat); - dc_contact_unref(contact); dc_msg_unref(msg); success @@ -1947,7 +1936,6 @@ pub unsafe fn dc_forward_msgs( let msg = dc_msg_new_untyped(context); let chat = dc_chat_new(context); - let contact = dc_contact_new(context); let mut created_db_entries = Vec::new(); let mut curr_timestamp: i64; @@ -1981,7 +1969,7 @@ pub unsafe fn dc_forward_msgs( break; } let original_param = (*msg).param.clone(); - if (*msg).from_id != 1 { + if (*msg).from_id != DC_CONTACT_ID_SELF as u32 { (*msg).param.set_int(Param::Forwarded, 1); } (*msg).param.remove(Param::GuranteeE2ee); @@ -2027,7 +2015,6 @@ pub unsafe fn dc_forward_msgs( created_db_entries[i + 1] as uintptr_t, ); } - dc_contact_unref(contact); dc_msg_unref(msg); dc_chat_unref(chat); } @@ -2113,7 +2100,7 @@ pub unsafe fn dc_chat_get_profile_image(chat: *const Chat) -> *mut libc::c_char let mut image_rel: *mut libc::c_char = 0 as *mut libc::c_char; let mut image_abs: *mut libc::c_char = 0 as *mut libc::c_char; let mut contacts: *mut dc_array_t = 0 as *mut dc_array_t; - let mut contact: *mut dc_contact_t = 0 as *mut dc_contact_t; + if !(chat.is_null() || (*chat).magic != 0xc4a7c4a7u32) { image_rel = (*chat) .param @@ -2125,15 +2112,17 @@ pub unsafe fn dc_chat_get_profile_image(chat: *const Chat) -> *mut libc::c_char } else if (*chat).type_0 == 100i32 { contacts = dc_get_chat_contacts((*chat).context, (*chat).id); if !(*contacts).is_empty() { - contact = dc_get_contact((*chat).context, (*contacts).get_id(0)); - image_abs = dc_contact_get_profile_image(contact) + if let Ok(contact) = Contact::get_by_id((*chat).context, (*contacts).get_id(0)) { + if let Some(img) = contact.get_profile_image() { + image_abs = img.strdup(); + } + } } } } free(image_rel as *mut libc::c_void); dc_array_unref(contacts); - dc_contact_unref(contact); image_abs } @@ -2141,13 +2130,14 @@ pub unsafe fn dc_chat_get_profile_image(chat: *const Chat) -> *mut libc::c_char pub unsafe fn dc_chat_get_color(chat: *const Chat) -> uint32_t { let mut color: uint32_t = 0i32 as uint32_t; let mut contacts: *mut dc_array_t = 0 as *mut dc_array_t; - let mut contact: *mut dc_contact_t = 0 as *mut dc_contact_t; + if !(chat.is_null() || (*chat).magic != 0xc4a7c4a7u32) { if (*chat).type_0 == 100i32 { contacts = dc_get_chat_contacts((*chat).context, (*chat).id); if !(*contacts).is_empty() { - contact = dc_get_contact((*chat).context, (*contacts).get_id(0)); - color = dc_str_to_color((*contact).addr) as uint32_t + if let Ok(contact) = Contact::get_by_id((*chat).context, (*contacts).get_id(0)) { + color = contact.get_color(); + } } } else { color = dc_str_to_color((*chat).name) as uint32_t @@ -2155,7 +2145,6 @@ pub unsafe fn dc_chat_get_color(chat: *const Chat) -> uint32_t { } dc_array_unref(contacts); - dc_contact_unref(contact); color } diff --git a/src/dc_contact.rs b/src/dc_contact.rs deleted file mode 100644 index 8e58af568..000000000 --- a/src/dc_contact.rs +++ /dev/null @@ -1,1137 +0,0 @@ -use std::ffi::CString; - -use crate::aheader::EncryptPreference; -use crate::config; -use crate::constants::*; -use crate::context::Context; -use crate::dc_array::*; -use crate::dc_e2ee::*; -use crate::dc_loginparam::*; -use crate::dc_tools::*; -use crate::key::*; -use crate::peerstate::*; -use crate::sql::{self, Sql}; -use crate::stock::StockMessage; -use crate::types::*; -use crate::x::*; - -const DC_GCL_VERIFIED_ONLY: u32 = 0x01; - -#[derive(Copy, Clone)] -#[repr(C)] -pub struct dc_contact_t<'a> { - pub magic: uint32_t, - pub context: &'a Context, - pub id: uint32_t, - pub name: *mut libc::c_char, - pub authname: *mut libc::c_char, - pub addr: *mut libc::c_char, - pub blocked: libc::c_int, - pub origin: libc::c_int, -} - -pub fn dc_marknoticed_contact(context: &Context, contact_id: u32) { - if sql::execute( - context, - &context.sql, - "UPDATE msgs SET state=? WHERE from_id=? AND state=?;", - params![DC_STATE_IN_NOTICED, contact_id as i32, DC_STATE_IN_FRESH], - ) - .is_ok() - { - context.call_cb(Event::MSGS_CHANGED, 0, 0); - } -} - -/// Returns false if addr is an invalid address, otherwise true. -pub unsafe fn dc_may_be_valid_addr(addr: *const libc::c_char) -> bool { - if addr.is_null() { - return false; - } - let at: *const libc::c_char = strchr(addr, '@' as i32); - if at.is_null() || at.wrapping_offset_from(addr) < 1 { - return false; - } - let dot: *const libc::c_char = strchr(at, '.' as i32); - if dot.is_null() - || dot.wrapping_offset_from(at) < 2 - || *dot.offset(1isize) as libc::c_int == 0i32 - || *dot.offset(2isize) as libc::c_int == 0i32 - { - return false; - } - - true -} - -pub unsafe fn dc_lookup_contact_id_by_addr( - context: &Context, - addr: *const libc::c_char, -) -> uint32_t { - if addr.is_null() || *addr.offset(0) as libc::c_int == 0 { - return 0; - } - - let addr_normalized_c = dc_addr_normalize(addr); - let addr_normalized = as_str(addr_normalized_c); - let addr_self = context - .sql - .get_config(context, "configured_addr") - .unwrap_or_default(); - - let contact_id = if addr_normalized == addr_self { - 1 - } else { - context.sql.query_row_col( - context, - "SELECT id FROM contacts WHERE addr=?1 COLLATE NOCASE AND id>?2 AND origin>=?3 AND blocked=0;", - params![addr_normalized, 9, 0x100], - 0 - ).unwrap_or_default() - }; - free(addr_normalized_c as *mut libc::c_void); - - contact_id -} - -pub unsafe fn dc_addr_normalize(addr: *const libc::c_char) -> *mut libc::c_char { - let mut addr_normalized: *mut libc::c_char = dc_strdup(addr); - dc_trim(addr_normalized); - if strncmp( - addr_normalized, - b"mailto:\x00" as *const u8 as *const libc::c_char, - 7, - ) == 0i32 - { - let old: *mut libc::c_char = addr_normalized; - addr_normalized = dc_strdup(&mut *old.offset(7isize)); - free(old as *mut libc::c_void); - dc_trim(addr_normalized); - } - - addr_normalized -} - -pub fn dc_addr_normalize_safe(addr: &str) -> &str { - let norm = addr.trim(); - - if norm.starts_with("mailto:") { - return &norm[7..]; - } - - norm -} - -pub unsafe fn dc_create_contact( - context: &Context, - name: *const libc::c_char, - addr: *const libc::c_char, -) -> uint32_t { - let mut contact_id: uint32_t = 0i32 as uint32_t; - let mut sth_modified: libc::c_int = 0i32; - let blocked: bool; - if !(addr.is_null() || *addr.offset(0isize) as libc::c_int == 0i32) { - contact_id = dc_add_or_lookup_contact(context, name, addr, 0x4000000i32, &mut sth_modified); - blocked = dc_is_contact_blocked(context, contact_id); - context.call_cb( - Event::CONTACTS_CHANGED, - (if sth_modified == 2i32 { - contact_id - } else { - 0i32 as libc::c_uint - }) as uintptr_t, - 0i32 as uintptr_t, - ); - if blocked { - dc_block_contact(context, contact_id, 0i32); - } - } - - contact_id -} - -pub unsafe fn dc_block_contact(context: &Context, contact_id: uint32_t, new_blocking: libc::c_int) { - if contact_id <= 9 { - return; - } - - let contact = dc_contact_new(context); - - if dc_contact_load_from_db(contact, &context.sql, contact_id) - && (*contact).blocked != new_blocking - { - if sql::execute( - context, - &context.sql, - "UPDATE contacts SET blocked=? WHERE id=?;", - params![new_blocking, contact_id as i32], - ) - .is_ok() - { - // also (un)block all chats with _only_ this contact - we do not delete them to allow a - // non-destructive blocking->unblocking. - // (Maybe, beside normal chats (type=100) we should also block group chats with only this user. - // However, I'm not sure about this point; it may be confusing if the user wants to add other people; - // this would result in recreating the same group...) - if sql::execute( - context, - &context.sql, - "UPDATE chats SET blocked=? WHERE type=? AND id IN (SELECT chat_id FROM chats_contacts WHERE contact_id=?);", - params![new_blocking, 100, contact_id as i32], - ).is_ok() { - dc_marknoticed_contact(context, contact_id); - context.call_cb( - Event::CONTACTS_CHANGED, - 0, - 0, - ); - } - } - } - - dc_contact_unref(contact); -} - -/** - * @class dc_contact_t - * - * An object representing a single contact in memory. - * The contact object is not updated. - * If you want an update, you have to recreate the object. - * - * The library makes sure - * only to use names _authorized_ by the contact in `To:` or `Cc:`. - * _Given-names _as "Daddy" or "Honey" are not used there. - * For this purpose, internally, two names are tracked - - * authorized-name and given-name. - * By default, these names are equal, - * but functions working with contact names - * (eg. dc_contact_get_name(), dc_contact_get_display_name(), - * dc_contact_get_name_n_addr(), dc_contact_get_first_name(), - * dc_create_contact() or dc_add_address_book()) - * only affect the given-name. - */ -pub unsafe fn dc_contact_new<'a>(context: &'a Context) -> *mut dc_contact_t<'a> { - let mut contact: *mut dc_contact_t; - contact = calloc(1, ::std::mem::size_of::()) as *mut dc_contact_t; - assert!(!contact.is_null()); - - (*contact).magic = 0xc047ac7i32 as uint32_t; - (*contact).context = context; - - contact -} - -pub unsafe fn dc_contact_unref(contact: *mut dc_contact_t) { - if contact.is_null() || (*contact).magic != 0xc047ac7i32 as libc::c_uint { - return; - } - dc_contact_empty(contact); - (*contact).magic = 0i32 as uint32_t; - free(contact as *mut libc::c_void); -} - -pub unsafe fn dc_contact_empty(mut contact: *mut dc_contact_t) { - if contact.is_null() || (*contact).magic != 0xc047ac7i32 as libc::c_uint { - return; - } - (*contact).id = 0i32 as uint32_t; - free((*contact).name as *mut libc::c_void); - (*contact).name = 0 as *mut libc::c_char; - free((*contact).authname as *mut libc::c_void); - (*contact).authname = 0 as *mut libc::c_char; - free((*contact).addr as *mut libc::c_void); - (*contact).addr = 0 as *mut libc::c_char; - (*contact).origin = 0i32; - (*contact).blocked = 0i32; -} - -/* From: of incoming messages of unknown sender */ -/* Cc: of incoming messages of unknown sender */ -/* To: of incoming messages of unknown sender */ -/* address scanned but not verified */ -/* Reply-To: of incoming message of known sender */ -/* Cc: of incoming message of known sender */ -/* additional To:'s of incoming message of known sender */ -/* a chat was manually created for this user, but no message yet sent */ -/* message sent by us */ -/* message sent by us */ -/* message sent by us */ -/* internal use */ -/* address is in our address book */ -/* set on Alice's side for contacts like Bob that have scanned the QR code offered by her. Only means the contact has once been established using the "securejoin" procedure in the past, getting the current key verification status requires calling dc_contact_is_verified() ! */ -/* set on Bob's side for contacts scanned and verified from a QR code. Only means the contact has once been established using the "securejoin" procedure in the past, getting the current key verification status requires calling dc_contact_is_verified() ! */ -/* contact added manually by dc_create_contact(), this should be the largets origin as otherwise the user cannot modify the names */ -/* contacts with at least this origin value are shown in the contact list */ -/* contacts with at least this origin value are verified and known not to be spam */ -/* contacts with at least this origin value start a new "normal" chat, defaults to off */ -pub unsafe fn dc_contact_load_from_db( - contact: *mut dc_contact_t, - sql: &Sql, - contact_id: u32, -) -> bool { - if contact.is_null() || (*contact).magic != 0xc047ac7i32 as libc::c_uint { - return false; - } - - dc_contact_empty(contact); - - if contact_id == 1 as libc::c_uint { - (*contact).id = contact_id; - (*contact).name = (*contact).context.stock_str(StockMessage::SelfMsg).strdup(); - (*contact).addr = (*contact) - .context - .sql - .get_config((*contact).context, "configured_addr") - .unwrap_or_default() - .strdup(); - true - } else { - sql.query_row( - "SELECT c.name, c.addr, c.origin, c.blocked, c.authname FROM contacts c WHERE c.id=?;", - params![contact_id as i32], - |row| { - (*contact).id = contact_id; - (*contact).name = row.get::<_, String>(0)?.strdup(); - (*contact).addr = row.get::<_, String>(1)?.strdup(); - (*contact).origin = row.get(2)?; - (*contact).blocked = row.get::<_, Option>(3)?.unwrap_or_default(); - (*contact).authname = row.get::<_, String>(4)?.strdup(); - Ok(()) - } - ).is_ok() - } -} - -pub unsafe fn dc_is_contact_blocked(context: &Context, contact_id: uint32_t) -> bool { - let mut is_blocked = false; - let contact: *mut dc_contact_t = dc_contact_new(context); - if dc_contact_load_from_db(contact, &context.sql, contact_id) { - if 0 != (*contact).blocked { - is_blocked = true - } - } - dc_contact_unref(contact); - - is_blocked -} - -/*can be NULL*/ -pub fn dc_add_or_lookup_contact( - context: &Context, - name: *const libc::c_char, - addr__: *const libc::c_char, - origin: libc::c_int, - mut sth_modified: *mut libc::c_int, -) -> uint32_t { - let mut dummy = 0; - - if sth_modified.is_null() { - sth_modified = &mut dummy; - } - unsafe { *sth_modified = 0 }; - - if addr__.is_null() || origin <= 0 { - return 0; - } - - let addr_c = unsafe { dc_addr_normalize(addr__) }; - let addr = as_str(addr_c); - let addr_self = context - .sql - .get_config(context, "configured_addr") - .unwrap_or_default(); - - if addr == addr_self { - return 1; - } - - if !unsafe { dc_may_be_valid_addr(addr_c) } { - warn!( - context, - 0, - "Bad address \"{}\" for contact \"{}\".", - addr, - if !name.is_null() { - as_str(name) - } else { - "" - }, - ); - return 0; - } - - let mut update_addr = false; - let mut update_name = false; - let mut update_authname = false; - let mut row_id = 0; - - if let Ok((id, row_name, row_addr, row_origin, row_authname)) = context.sql.query_row( - "SELECT id, name, addr, origin, authname FROM contacts WHERE addr=? COLLATE NOCASE;", - params![addr], - |row| { - let row_id = row.get(0)?; - let row_name: String = row.get(1)?; - let row_addr: String = row.get(2)?; - let row_origin = row.get(3)?; - let row_authname: String = row.get(4)?; - - if !name.is_null() && 0 != unsafe { *name.offset(0) as libc::c_int } { - if !row_name.is_empty() { - if origin >= row_origin && as_str(name) != row_name { - update_name = true; - } - } - } else { - update_name = true; - } - if origin == 0x10 && !name.is_null() && as_str(name) != row_authname { - update_authname = true; - } - Ok((row_id, row_name, row_addr, row_origin, row_authname)) - }, - ) { - row_id = id; - if origin >= row_origin && addr != row_addr { - update_addr = true; - } - if update_name || update_authname || update_addr || origin > row_origin { - sql::execute( - context, - &context.sql, - "UPDATE contacts SET name=?, addr=?, origin=?, authname=? WHERE id=?;", - params![ - if update_name { - to_string(name) - } else { - row_name - }, - if update_addr { addr } else { &row_addr }, - if origin > row_origin { - origin - } else { - row_origin - }, - if update_authname { - to_string(name) - } else { - row_authname - }, - row_id - ], - ) - .ok(); - - if update_name { - sql::execute( - context, - &context.sql, - "UPDATE chats SET name=? WHERE type=? AND id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?);", - params![to_string(name), 100, row_id] - ).ok(); - } - unsafe { *sth_modified = 1 }; - } - } else { - if sql::execute( - context, - &context.sql, - "INSERT INTO contacts (name, addr, origin) VALUES(?, ?, ?);", - params![to_string(name), addr, origin,], - ) - .is_ok() - { - row_id = sql::get_rowid(context, &context.sql, "contacts", "addr", addr); - unsafe { *sth_modified = 2 }; - } else { - error!(context, 0, "Cannot add contact."); - } - } - - unsafe { free(addr_c as *mut libc::c_void) }; - - row_id -} - -#[allow(non_snake_case)] -pub unsafe fn dc_add_address_book(context: &Context, adr_book: *const libc::c_char) -> libc::c_int { - let mut sth_modified: libc::c_int = 0i32; - let mut modify_cnt: libc::c_int = 0i32; - if !(adr_book.is_null()) { - let lines = dc_split_into_lines(adr_book); - - for chunk in lines.chunks(2) { - let name: *mut libc::c_char = chunk[0]; - let addr: *mut libc::c_char = chunk[1]; - dc_normalize_name(name); - dc_add_or_lookup_contact(context, name, addr, 0x80000i32, &mut sth_modified); - if 0 != sth_modified { - modify_cnt += 1 - } - } - if 0 != modify_cnt { - context.call_cb( - Event::CONTACTS_CHANGED, - 0i32 as uintptr_t, - 0i32 as uintptr_t, - ); - } - dc_free_splitted_lines(lines); - } - - modify_cnt -} - -// Working with names -pub unsafe fn dc_normalize_name(full_name: *mut libc::c_char) { - if full_name.is_null() { - return; - } - dc_trim(full_name); - let len: libc::c_int = strlen(full_name) as libc::c_int; - if len > 0i32 { - let firstchar: libc::c_char = *full_name.offset(0isize); - let lastchar: libc::c_char = *full_name.offset((len - 1i32) as isize); - if firstchar as libc::c_int == '\'' as i32 && lastchar as libc::c_int == '\'' as i32 - || firstchar as libc::c_int == '\"' as i32 && lastchar as libc::c_int == '\"' as i32 - || firstchar as libc::c_int == '<' as i32 && lastchar as libc::c_int == '>' as i32 - { - *full_name.offset(0isize) = ' ' as i32 as libc::c_char; - *full_name.offset((len - 1i32) as isize) = ' ' as i32 as libc::c_char - } - } - let p1: *mut libc::c_char = strchr(full_name, ',' as i32); - if !p1.is_null() { - *p1 = 0i32 as libc::c_char; - let last_name: *mut libc::c_char = dc_strdup(full_name); - let first_name: *mut libc::c_char = dc_strdup(p1.offset(1isize)); - dc_trim(last_name); - dc_trim(first_name); - strcpy(full_name, first_name); - strcat(full_name, b" \x00" as *const u8 as *const libc::c_char); - strcat(full_name, last_name); - free(last_name as *mut libc::c_void); - free(first_name as *mut libc::c_void); - } else { - dc_trim(full_name); - }; -} - -#[allow(non_snake_case)] -pub fn dc_get_contacts( - context: &Context, - listflags: u32, - query: *const libc::c_char, -) -> *mut dc_array_t { - let self_addr = context - .sql - .get_config(context, "configured_addr") - .unwrap_or_default(); - - let mut add_self = false; - let mut ret = dc_array_t::new(100); - - if (listflags & DC_GCL_VERIFIED_ONLY) > 0 || !query.is_null() { - let s3strLikeCmd = format!("%{}%", if !query.is_null() { as_str(query) } else { "" }); - eprintln!("query '{}'", &s3strLikeCmd); - context - .sql - .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 \ - AND c.blocked=0 \ - AND (c.name LIKE ?4 OR c.addr LIKE ?5) \ - AND (1=?6 OR LENGTH(ps.verified_key_fingerprint)!=0) \ - ORDER BY LOWER(c.name||c.addr),c.id;", - params![ - self_addr, - 9, - 0x100, - &s3strLikeCmd, - &s3strLikeCmd, - if 0 != listflags & 0x1 { 0 } else { 1 }, - ], - |row| row.get::<_, i32>(0), - |ids| { - for id in ids { - ret.add_id(id? as u32); - } - Ok(()) - }, - ) - .unwrap(); // TODO: Better error handling - - let self_name = context - .sql - .get_config(context, "displayname") - .unwrap_or_default(); - - let self_name2 = CString::new(context.stock_str(StockMessage::SelfMsg).as_ref()).unwrap(); - - if query.is_null() - || self_addr.contains(as_str(query)) - || self_name.contains(as_str(query)) - || 0 != unsafe { dc_str_contains(self_name2.as_ptr(), query) } - { - add_self = true; - } - } else { - add_self = true; - - context.sql.query_map( - "SELECT id FROM contacts WHERE addr!=?1 AND id>?2 AND origin>=?3 AND blocked=0 ORDER BY LOWER(name||addr),id;", - params![self_addr, 9, 0x100], - |row| row.get::<_, i32>(0), - |ids| { - for id in ids { - ret.add_id(id? as u32); - } - Ok(()) - } - ).unwrap(); // TODO: better error handling - } - - if 0 != listflags & 0x2 && add_self { - ret.add_id(1); - } - - ret.into_raw() -} - -pub fn dc_get_blocked_cnt(context: &Context) -> libc::c_int { - context - .sql - .query_row_col( - context, - "SELECT COUNT(*) FROM contacts WHERE id>? AND blocked!=0", - params![9], - 0, - ) - .unwrap_or_default() -} - -pub fn dc_get_blocked_contacts(context: &Context) -> *mut dc_array_t { - context - .sql - .query_map( - "SELECT id FROM contacts WHERE id>? AND blocked!=0 ORDER BY LOWER(name||addr),id;", - params![9], - |row| row.get::<_, i32>(0), - |ids| { - let mut ret = dc_array_t::new(100); - - for id in ids { - ret.add_id(id? as u32); - } - - Ok(ret.into_raw()) - }, - ) - .unwrap_or_else(|_| std::ptr::null_mut()) -} - -pub unsafe fn dc_get_contact_encrinfo( - context: &Context, - contact_id: uint32_t, -) -> *mut libc::c_char { - let mut ret = String::new(); - let contact = dc_contact_new(context); - - let mut fingerprint_self = 0 as *mut libc::c_char; - let mut fingerprint_other_verified = 0 as *mut libc::c_char; - let mut fingerprint_other_unverified = 0 as *mut libc::c_char; - - if !(!dc_contact_load_from_db(contact, &context.sql, contact_id)) { - let peerstate = Peerstate::from_addr(context, &context.sql, as_str((*contact).addr)); - let loginparam = dc_loginparam_read(context, &context.sql, "configured_"); - - let mut self_key = Key::from_self_public(context, &loginparam.addr, &context.sql); - - if peerstate.is_some() && peerstate.as_ref().and_then(|p| p.peek_key(0)).is_some() { - let peerstate = peerstate.as_ref().unwrap(); - let p = context.stock_str(if peerstate.prefer_encrypt == EncryptPreference::Mutual { - StockMessage::E2ePreferred - } else { - StockMessage::E2eAvailable - }); - ret += &p; - if self_key.is_none() { - dc_ensure_secret_key_exists(context); - self_key = Key::from_self_public(context, &loginparam.addr, &context.sql); - } - let p = context.stock_str(StockMessage::FingerPrints); - ret += &format!(" {}:", p); - - fingerprint_self = self_key - .map(|k| k.formatted_fingerprint_c()) - .unwrap_or(std::ptr::null_mut()); - fingerprint_other_verified = peerstate - .peek_key(2) - .map(|k| k.formatted_fingerprint_c()) - .unwrap_or(std::ptr::null_mut()); - fingerprint_other_unverified = peerstate - .peek_key(0) - .map(|k| k.formatted_fingerprint_c()) - .unwrap_or(std::ptr::null_mut()); - if peerstate.addr.is_some() && &loginparam.addr < peerstate.addr.as_ref().unwrap() { - cat_fingerprint( - &mut ret, - &loginparam.addr, - fingerprint_self, - 0 as *const libc::c_char, - ); - cat_fingerprint( - &mut ret, - peerstate.addr.as_ref().unwrap(), - fingerprint_other_verified, - fingerprint_other_unverified, - ); - } else { - cat_fingerprint( - &mut ret, - peerstate.addr.as_ref().unwrap(), - fingerprint_other_verified, - fingerprint_other_unverified, - ); - cat_fingerprint( - &mut ret, - &loginparam.addr, - fingerprint_self, - 0 as *const libc::c_char, - ); - } - } else if 0 == loginparam.server_flags & 0x400 && 0 == loginparam.server_flags & 0x40000 { - ret += &context.stock_str(StockMessage::EncrTransp); - } else { - ret += &context.stock_str(StockMessage::EncrNone); - } - } - - dc_contact_unref(contact); - - free(fingerprint_self as *mut libc::c_void); - free(fingerprint_other_verified as *mut libc::c_void); - free(fingerprint_other_unverified as *mut libc::c_void); - - ret.strdup() -} - -unsafe fn cat_fingerprint( - ret: &mut String, - addr: impl AsRef, - fingerprint_verified: *const libc::c_char, - fingerprint_unverified: *const libc::c_char, -) { - *ret += &format!( - "\n\n{}:\n{}", - addr.as_ref(), - if !fingerprint_verified.is_null() - && 0 != *fingerprint_verified.offset(0isize) as libc::c_int - { - as_str(fingerprint_verified) - } else { - as_str(fingerprint_unverified) - }, - ); - if !fingerprint_verified.is_null() - && 0 != *fingerprint_verified.offset(0isize) as libc::c_int - && !fingerprint_unverified.is_null() - && 0 != *fingerprint_unverified.offset(0isize) as libc::c_int - && strcmp(fingerprint_verified, fingerprint_unverified) != 0 - { - *ret += &format!( - "\n\n{} (alternative):\n{}", - addr.as_ref(), - as_str(fingerprint_unverified) - ); - } -} - -pub fn dc_delete_contact(context: &Context, contact_id: u32) -> bool { - if contact_id <= 9 { - return false; - } - - let count_contacts: i32 = context - .sql - .query_row_col( - context, - "SELECT COUNT(*) FROM chats_contacts WHERE contact_id=?;", - params![contact_id as i32], - 0, - ) - .unwrap_or_default(); - - let count_msgs: i32 = if count_contacts > 0 { - context - .sql - .query_row_col( - context, - "SELECT COUNT(*) FROM msgs WHERE from_id=? OR to_id=?;", - params![contact_id as i32, contact_id as i32], - 0, - ) - .unwrap_or_default() - } else { - 0 - }; - - if count_msgs == 0 { - if sql::execute( - context, - &context.sql, - "DELETE FROM contacts WHERE id=?;", - params![contact_id as i32], - ) - .is_ok() - { - context.call_cb(Event::CONTACTS_CHANGED, 0, 0); - true - } else { - error!(context, 0, "delete_contact {} failed", contact_id); - false - } - } else { - info!( - context, - 0, "could not delete contact {}, there are {} messages with it", contact_id, count_msgs - ); - false - } -} - -pub unsafe fn dc_get_contact(context: &Context, contact_id: uint32_t) -> *mut dc_contact_t { - let mut ret: *mut dc_contact_t = dc_contact_new(context); - if !dc_contact_load_from_db(ret, &context.sql, contact_id) { - dc_contact_unref(ret); - ret = 0 as *mut dc_contact_t - } - ret -} - -pub unsafe fn dc_contact_get_id(contact: *const dc_contact_t) -> uint32_t { - if contact.is_null() || (*contact).magic != 0xc047ac7i32 as libc::c_uint { - return 0i32 as uint32_t; - } - (*contact).id -} - -pub unsafe fn dc_contact_get_addr(contact: *const dc_contact_t) -> *mut libc::c_char { - if contact.is_null() || (*contact).magic != 0xc047ac7i32 as libc::c_uint { - return dc_strdup(0 as *const libc::c_char); - } - dc_strdup((*contact).addr) -} - -pub unsafe fn dc_contact_get_name(contact: *const dc_contact_t) -> *mut libc::c_char { - if contact.is_null() || (*contact).magic != 0xc047ac7i32 as libc::c_uint { - return dc_strdup(0 as *const libc::c_char); - } - dc_strdup((*contact).name) -} - -pub unsafe fn dc_contact_get_display_name(contact: *const dc_contact_t) -> *mut libc::c_char { - if contact.is_null() || (*contact).magic != 0xc047ac7i32 as libc::c_uint { - return dc_strdup(0 as *const libc::c_char); - } - if !(*contact).name.is_null() && 0 != *(*contact).name.offset(0isize) as libc::c_int { - return dc_strdup((*contact).name); - } - dc_strdup((*contact).addr) -} - -pub unsafe fn dc_contact_get_name_n_addr(contact: *const dc_contact_t) -> *mut libc::c_char { - if contact.is_null() || (*contact).magic != 0xc047ac7i32 as libc::c_uint { - return dc_strdup(0 as *const libc::c_char); - } - if !(*contact).name.is_null() && 0 != *(*contact).name.offset(0isize) as libc::c_int { - return dc_mprintf( - b"%s (%s)\x00" as *const u8 as *const libc::c_char, - (*contact).name, - (*contact).addr, - ); - } - dc_strdup((*contact).addr) -} - -pub unsafe fn dc_contact_get_first_name(contact: *const dc_contact_t) -> *mut libc::c_char { - if contact.is_null() || (*contact).magic != 0xc047ac7i32 as libc::c_uint { - return dc_strdup(0 as *const libc::c_char); - } - if !(*contact).name.is_null() && 0 != *(*contact).name.offset(0isize) as libc::c_int { - return dc_get_first_name((*contact).name); - } - dc_strdup((*contact).addr) -} - -pub unsafe fn dc_get_first_name(full_name: *const libc::c_char) -> *mut libc::c_char { - let mut first_name: *mut libc::c_char = dc_strdup(full_name); - let p1: *mut libc::c_char = strchr(first_name, ' ' as i32); - if !p1.is_null() { - *p1 = 0i32 as libc::c_char; - dc_rtrim(first_name); - if *first_name.offset(0isize) as libc::c_int == 0i32 { - free(first_name as *mut libc::c_void); - first_name = dc_strdup(full_name) - } - } - first_name -} - -pub fn dc_contact_get_profile_image(contact: *const dc_contact_t) -> *mut libc::c_char { - let mut image_abs = 0 as *mut libc::c_char; - - if contact.is_null() || unsafe { (*contact).magic != 0xc047ac7 } { - return image_abs; - } - - if unsafe { (*contact).id } == 1 { - let context = unsafe { (*contact) }.context; - if let Some(avatar) = context.get_config(config::Config::Selfavatar) { - image_abs = unsafe { avatar.strdup() }; - } - } - // TODO: else get image_abs from contact param - image_abs -} - -pub unsafe fn dc_contact_get_color(contact: *const dc_contact_t) -> uint32_t { - if contact.is_null() || (*contact).magic != 0xc047ac7i32 as libc::c_uint { - return 0i32 as uint32_t; - } - dc_str_to_color((*contact).addr) as uint32_t -} - -pub unsafe fn dc_contact_is_blocked(contact: *const dc_contact_t) -> libc::c_int { - if contact.is_null() || (*contact).magic != 0xc047ac7i32 as libc::c_uint { - return 0i32; - } - (*contact).blocked -} - -/// Check if a contact was verified. E.g. by a secure-join QR code scan -/// and if the key has not changed since this verification. -/// -/// The UI may draw a checkbox or something like that beside verified contacts. -/// -/// Returns -/// - 0: contact is not verified. -/// - 2: SELF and contact have verified their fingerprints in both directions; in the UI typically checkmarks are shown. -pub unsafe fn dc_contact_is_verified(contact: *mut dc_contact_t) -> libc::c_int { - dc_contact_is_verified_ex(contact, None) -} - -/// Same as dc_contact_is_verified() but allows speeding up things -/// by adding the peerstate belonging to the contact. -/// If you do not have the peerstate available, it is loaded automatically. -pub unsafe fn dc_contact_is_verified_ex<'a>( - contact: *mut dc_contact_t<'a>, - peerstate: Option<&Peerstate<'a>>, -) -> libc::c_int { - if contact.is_null() || (*contact).magic != 0xc047ac7i32 as libc::c_uint { - return 0; - } - - // we're always sort of secured-verified as we could verify the key on this device any time with the key - // on this device - if (*contact).id == 1 as libc::c_uint { - return 2; - } - - if let Some(peerstate) = peerstate { - if peerstate.verified_key().is_some() { - 2 - } else { - 0 - } - } else { - let peerstate = Peerstate::from_addr( - (*contact).context, - &(*contact).context.sql, - as_str((*contact).addr), - ); - - let res = if let Some(ps) = peerstate { - if ps.verified_key().is_some() { - 2 - } else { - 0 - } - } else { - 0 - }; - - res - } -} - -// Working with e-mail-addresses -pub fn dc_addr_cmp(addr1: impl AsRef, addr2: impl AsRef) -> bool { - let norm1 = dc_addr_normalize_safe(addr1.as_ref()); - let norm2 = dc_addr_normalize_safe(addr2.as_ref()); - - norm1 == norm2 -} - -pub fn dc_addr_equals_self(context: &Context, addr: *const libc::c_char) -> libc::c_int { - let mut ret = 0; - - if !addr.is_null() { - let normalized_addr = unsafe { dc_addr_normalize(addr) }; - if let Some(self_addr) = context.sql.get_config(context, "configured_addr") { - ret = (as_str(normalized_addr) == self_addr) as libc::c_int; - } - unsafe { free(normalized_addr as *mut libc::c_void) }; - } - - ret -} - -pub unsafe fn dc_addr_equals_contact( - context: &Context, - addr: impl AsRef, - contact_id: u32, -) -> bool { - if addr.as_ref().is_empty() { - return false; - } - - let contact = dc_contact_new(context); - let mut addr_are_equal = false; - - if dc_contact_load_from_db(contact, &context.sql, contact_id) { - if !(*contact).addr.is_null() { - let normalized_addr = dc_addr_normalize_safe(addr.as_ref()); - if as_str((*contact).addr) == normalized_addr { - addr_are_equal = true; - } - } - dc_contact_unref(contact); - } - - addr_are_equal -} - -// Context functions to work with contacts -pub fn dc_get_real_contact_cnt(context: &Context) -> usize { - if !context.sql.is_open() { - return 0; - } - - context - .sql - .query_row_col::<_, isize>( - context, - "SELECT COUNT(*) FROM contacts WHERE id>?;", - params![9], - 0, - ) - .unwrap_or_default() as usize -} - -pub unsafe fn dc_get_contact_origin( - context: &Context, - contact_id: uint32_t, - mut ret_blocked: *mut libc::c_int, -) -> libc::c_int { - let mut ret: libc::c_int = 0i32; - let mut dummy: libc::c_int = 0i32; - if ret_blocked.is_null() { - ret_blocked = &mut dummy - } - let contact: *mut dc_contact_t = dc_contact_new(context); - *ret_blocked = 0i32; - if dc_contact_load_from_db(contact, &context.sql, contact_id) { - /* we could optimize this by loading only the needed fields */ - if 0 != (*contact).blocked { - *ret_blocked = 1i32 - } else { - ret = (*contact).origin - } - } - dc_contact_unref(contact); - ret -} - -pub fn dc_real_contact_exists(context: &Context, contact_id: u32) -> bool { - if !context.sql.is_open() || contact_id <= 9 { - return false; - } - - context - .sql - .exists( - "SELECT id FROM contacts WHERE id=?;", - params![contact_id as i32], - ) - .unwrap_or_default() -} - -pub fn dc_scaleup_contact_origin(context: &Context, contact_id: u32, origin: libc::c_int) -> bool { - context - .sql - .execute( - "UPDATE contacts SET origin=? WHERE id=? AND origin, context: &Context, ) { if lot.is_null() || (*lot).magic != 0x107107i32 as libc::c_uint || msg.is_null() { @@ -150,16 +150,24 @@ pub unsafe fn dc_lot_fill( (*lot).text1 = 0 as *mut libc::c_char; (*lot).text1_meaning = 0i32 } else if (*chat).type_0 == 120i32 || (*chat).type_0 == 130i32 { - if 0 != dc_msg_is_info(msg) || contact.is_null() { + if 0 != dc_msg_is_info(msg) || contact.is_none() { (*lot).text1 = 0 as *mut libc::c_char; (*lot).text1_meaning = 0i32 } else { if !chat.is_null() && (*chat).id == 1i32 as libc::c_uint { - (*lot).text1 = dc_contact_get_display_name(contact) + if let Some(contact) = contact { + (*lot).text1 = contact.get_display_name().strdup(); + } else { + (*lot).text1 = std::ptr::null_mut(); + } } else { - (*lot).text1 = dc_contact_get_first_name(contact) + if let Some(contact) = contact { + (*lot).text1 = contact.get_first_name().strdup(); + } else { + (*lot).text1 = std::ptr::null_mut(); + } } - (*lot).text1_meaning = 2i32 + (*lot).text1_meaning = 2i32; } } diff --git a/src/dc_mimefactory.rs b/src/dc_mimefactory.rs index 5d0d9d3ad..a42d7ae70 100644 --- a/src/dc_mimefactory.rs +++ b/src/dc_mimefactory.rs @@ -12,9 +12,9 @@ use mmime::other::*; use std::ptr; use crate::constants::*; +use crate::contact::*; use crate::context::Context; use crate::dc_chat::*; -use crate::dc_contact::*; use crate::dc_e2ee::*; use crate::dc_location::*; use crate::dc_msg::*; @@ -294,7 +294,6 @@ pub unsafe fn dc_mimefactory_load_mdn( } let mut success = 0; - let mut contact = 0 as *mut dc_contact_t; (*factory).recipients_names = clist_new(); (*factory).recipients_addr = clist_new(); @@ -306,24 +305,19 @@ pub unsafe fn dc_mimefactory_load_mdn( .unwrap_or_else(|| 1) { // MDNs not enabled - check this is late, in the job. the use may have changed its choice while offline ... - contact = dc_contact_new((*factory).context); - if !(!dc_msg_load_from_db((*factory).msg, (*factory).context, msg_id) - || !dc_contact_load_from_db( - contact, - &(*factory).context.sql, - (*(*factory).msg).from_id, - )) - { - if !(0 != (*contact).blocked || (*(*factory).msg).chat_id <= 9 as libc::c_uint) { + if !dc_msg_load_from_db((*factory).msg, (*factory).context, msg_id) { + return success; + } + + if let Ok(contact) = Contact::load_from_db((*factory).context, (*(*factory).msg).from_id) { + if !(contact.is_blocked() || (*(*factory).msg).chat_id <= 9 as libc::c_uint) { // Do not send MDNs trash etc.; chats.blocked is already checked by the caller in dc_markseen_msgs() if !((*(*factory).msg).from_id <= 9 as libc::c_uint) { clist_insert_after( (*factory).recipients_names, (*(*factory).recipients_names).last, - (if !(*contact).authname.is_null() - && 0 != *(*contact).authname.offset(0isize) as libc::c_int - { - dc_strdup((*contact).authname) + (if !contact.get_authname().is_empty() { + contact.get_authname().strdup() } else { 0 as *mut libc::c_char }) as *mut libc::c_void, @@ -331,7 +325,7 @@ pub unsafe fn dc_mimefactory_load_mdn( clist_insert_after( (*factory).recipients_addr, (*(*factory).recipients_addr).last, - dc_strdup((*contact).addr) as *mut libc::c_void, + contact.get_addr().strdup() as *mut libc::c_void, ); load_from(factory); (*factory).timestamp = dc_create_smeared_timestamp((*factory).context); @@ -346,8 +340,6 @@ pub unsafe fn dc_mimefactory_load_mdn( } } - dc_contact_unref(contact); - success } diff --git a/src/dc_mimeparser.rs b/src/dc_mimeparser.rs index 89522d047..a9c9476c1 100644 --- a/src/dc_mimeparser.rs +++ b/src/dc_mimeparser.rs @@ -11,8 +11,8 @@ use mmime::mailmime_types::*; use mmime::mmapstring::*; use mmime::other::*; +use crate::contact::*; use crate::context::Context; -use crate::dc_contact::*; use crate::dc_e2ee::*; use crate::dc_location::*; use crate::dc_simplify::*; @@ -422,7 +422,7 @@ pub unsafe fn mailimf_find_first_addr(mb_list: *const mailimf_mailbox_list) -> * 0 as *mut libc::c_void }) as *mut mailimf_mailbox; if !mb.is_null() && !(*mb).mb_addr_spec.is_null() { - return dc_addr_normalize((*mb).mb_addr_spec); + return addr_normalize(as_str((*mb).mb_addr_spec)).strdup(); } cur = if !cur.is_null() { (*cur).next @@ -1565,7 +1565,7 @@ pub unsafe fn dc_mimeparser_sender_equals_recipient(mimeparser: &dc_mimeparser_t let fld: *const mailimf_field; let mut fld_from: *const mailimf_from = 0 as *const mailimf_from; let mb: *mut mailimf_mailbox; - let mut from_addr_norm: *mut libc::c_char = 0 as *mut libc::c_char; + if !(*mimeparser).header_root.is_null() { /* get From: and check there is exactly one sender */ fld = mailimf_find_field(mimeparser.header_root, MAILIMF_FIELD_FROM as libc::c_int); @@ -1584,17 +1584,16 @@ pub unsafe fn dc_mimeparser_sender_equals_recipient(mimeparser: &dc_mimeparser_t 0 as *mut libc::c_void }) as *mut mailimf_mailbox; if !mb.is_null() { - from_addr_norm = dc_addr_normalize((*mb).mb_addr_spec); + let from_addr_norm = addr_normalize(as_str((*mb).mb_addr_spec)); let recipients = mailimf_get_recipients(mimeparser.header_root); if recipients.len() == 1 { - if recipients.contains(as_str(from_addr_norm)) { + if recipients.contains(from_addr_norm) { sender_equals_recipient = 1i32; } } } } } - free(from_addr_norm as *mut libc::c_void); sender_equals_recipient } @@ -1691,15 +1690,15 @@ pub unsafe fn mailimf_get_recipients(imffields: *mut mailimf_fields) -> HashSet< /* ****************************************************************************** * low-level-tools for getting a list of all recipients ******************************************************************************/ + #[allow(non_snake_case)] unsafe fn mailimf_get_recipients__add_addr( recipients: &mut HashSet, mb: *mut mailimf_mailbox, ) { if !mb.is_null() { - let addr_norm: *mut libc::c_char = dc_addr_normalize((*mb).mb_addr_spec); - recipients.insert(to_string(addr_norm)); - free(addr_norm as *mut libc::c_void); + let addr_norm = addr_normalize(as_str((*mb).mb_addr_spec)); + recipients.insert(addr_norm.into()); }; } diff --git a/src/dc_msg.rs b/src/dc_msg.rs index 109d5ebae..963cb6ed4 100644 --- a/src/dc_msg.rs +++ b/src/dc_msg.rs @@ -1,9 +1,9 @@ use std::ffi::CString; use crate::constants::*; +use crate::contact::*; use crate::context::*; use crate::dc_chat::*; -use crate::dc_contact::*; use crate::dc_job::*; use crate::dc_lot::dc_lot_t; use crate::dc_lot::*; @@ -48,12 +48,10 @@ pub struct dc_msg_t<'a> { // handle messages pub unsafe fn dc_get_msg_info(context: &Context, msg_id: u32) -> *mut libc::c_char { let msg = dc_msg_new_untyped(context); - let contact_from = dc_contact_new(context); let mut p: *mut libc::c_char; let mut ret = String::new(); dc_msg_load_from_db(msg, context, msg_id); - dc_contact_load_from_db(contact_from, &context.sql, (*msg).from_id); let rawtxt: Option = context.sql.query_row_col( context, @@ -65,7 +63,6 @@ pub unsafe fn dc_get_msg_info(context: &Context, msg_id: u32) -> *mut libc::c_ch if rawtxt.is_none() { ret += &format!("Cannot load message #{}.", msg_id as usize); dc_msg_unref(msg); - dc_contact_unref(contact_from); return ret.strdup(); } let rawtxt = rawtxt.unwrap(); @@ -74,12 +71,14 @@ pub unsafe fn dc_get_msg_info(context: &Context, msg_id: u32) -> *mut libc::c_ch let fts = dc_timestamp_to_str(dc_msg_get_timestamp(msg)); ret += &format!("Sent: {}", fts); - p = dc_contact_get_name_n_addr(contact_from); - ret += &format!(" by {}", to_string(p)); - free(p as *mut libc::c_void); + let name = Contact::load_from_db(context, (*msg).from_id) + .map(|contact| contact.get_name_n_addr()) + .unwrap_or_default(); + + ret += &format!(" by {}", name); ret += "\n"; - if (*msg).from_id != 1 as libc::c_uint { + if (*msg).from_id != DC_CONTACT_ID_SELF as libc::c_uint { let s = dc_timestamp_to_str(if 0 != (*msg).timestamp_rcvd { (*msg).timestamp_rcvd } else { @@ -92,7 +91,6 @@ pub unsafe fn dc_get_msg_info(context: &Context, msg_id: u32) -> *mut libc::c_ch if (*msg).from_id == 2 || (*msg).to_id == 2 { // device-internal message, no further details needed dc_msg_unref(msg); - dc_contact_unref(contact_from); return ret.strdup(); } @@ -112,14 +110,11 @@ pub unsafe fn dc_get_msg_info(context: &Context, msg_id: u32) -> *mut libc::c_ch let fts = dc_timestamp_to_str(ts); ret += &format!("Read: {}", fts); - let contact = dc_contact_new(context); - dc_contact_load_from_db(contact, &context.sql, contact_id as u32); - - p = dc_contact_get_name_n_addr(contact); - ret += &format!(" by {}", as_str(p)); - free(p as *mut libc::c_void); - dc_contact_unref(contact); + let name = Contact::load_from_db(context, contact_id as u32) + .map(|contact| contact.get_name_n_addr()) + .unwrap_or_default(); + ret += &format!(" by {}", name); ret += "\n"; } Ok(()) @@ -210,7 +205,6 @@ pub unsafe fn dc_get_msg_info(context: &Context, msg_id: u32) -> *mut libc::c_ch } dc_msg_unref(msg); - dc_contact_unref(contact_from); ret.strdup() } @@ -794,8 +788,8 @@ pub unsafe fn dc_msg_get_summary<'a>( ) -> *mut dc_lot_t { let current_block: u64; let ret: *mut dc_lot_t = dc_lot_new(); - let mut contact: *mut dc_contact_t = 0 as *mut dc_contact_t; let mut chat_to_delete: *mut Chat = 0 as *mut Chat; + if !(msg.is_null() || (*msg).magic != 0x11561156 as libc::c_uint) { if chat.is_null() { chat_to_delete = dc_get_chat((*msg).context, (*msg).chat_id); @@ -811,16 +805,19 @@ pub unsafe fn dc_msg_get_summary<'a>( match current_block { 15204159476013091401 => {} _ => { - if (*msg).from_id != 1 as libc::c_uint + let contact = if (*msg).from_id != DC_CONTACT_ID_SELF as libc::c_uint && ((*chat).type_0 == 120 || (*chat).type_0 == 130) { - contact = dc_get_contact((*chat).context, (*msg).from_id) - } - dc_lot_fill(ret, msg, chat, contact, (*msg).context); + Contact::get_by_id((*chat).context, (*msg).from_id).ok() + } else { + None + }; + + dc_lot_fill(ret, msg, chat, contact.as_ref(), (*msg).context); } } } - dc_contact_unref(contact); + dc_chat_unref(chat_to_delete); ret @@ -1572,12 +1569,8 @@ mod tests { let d = test::dummy_context(); let ctx = &d.ctx; - let contact = dc_create_contact( - ctx, - b"\x00".as_ptr().cast(), - b"dest@example.com\x00".as_ptr().cast(), - ); - assert!(contact != 0); + let contact = + Contact::create(ctx, "", "dest@example.com").expect("failed to create contact"); let res = ctx.set_config(Config::ConfiguredAddr, Some("self@example.com")); assert!(res.is_ok()); diff --git a/src/dc_qr.rs b/src/dc_qr.rs index b216822a4..251b3bd2a 100644 --- a/src/dc_qr.rs +++ b/src/dc_qr.rs @@ -1,8 +1,8 @@ use percent_encoding::percent_decode_str; +use crate::contact::*; use crate::context::Context; use crate::dc_chat::*; -use crate::dc_contact::*; use crate::dc_lot::*; use crate::dc_strencode::*; use crate::dc_tools::*; @@ -66,8 +66,7 @@ pub unsafe fn dc_check_qr(context: &Context, qr: *const libc::c_char) -> *mut dc let name_r = percent_decode_str(name_enc) .decode_utf8() .expect("invalid name"); - name = name_r.strdup(); - dc_normalize_name(name); + name = normalize_name(name_r).strdup(); } invitenumber = param .get(Param::ProfileImage) @@ -186,7 +185,7 @@ pub unsafe fn dc_check_qr(context: &Context, qr: *const libc::c_char) -> *mut dc b";\x00" as *const u8 as *const libc::c_char, b",\x00" as *const u8 as *const libc::c_char, ); - dc_normalize_name(name); + name = normalize_name(as_str(name)).strdup(); } } } @@ -204,10 +203,10 @@ pub unsafe fn dc_check_qr(context: &Context, qr: *const libc::c_char) -> *mut dc let mut temp: *mut libc::c_char = dc_urldecode(addr); free(addr as *mut libc::c_void); addr = temp; - temp = dc_addr_normalize(addr); + temp = addr_normalize(as_str(addr)).strdup(); free(addr as *mut libc::c_void); addr = temp; - if !dc_may_be_valid_addr(addr) { + if !may_be_valid_addr(as_str(addr)) { (*qr_parsed).state = 400i32; (*qr_parsed).text1 = dc_strdup( b"Bad e-mail address.\x00" as *const u8 as *const libc::c_char, @@ -248,19 +247,19 @@ pub unsafe fn dc_check_qr(context: &Context, qr: *const libc::c_char) -> *mut dc if addr.is_null() || invitenumber.is_null() || auth.is_null() { if let Some(peerstate) = peerstate { (*qr_parsed).state = 210i32; - let addr_ptr = if let Some(ref addr) = peerstate.addr { - addr.strdup() - } else { - std::ptr::null() - }; - (*qr_parsed).id = dc_add_or_lookup_contact( + let addr = peerstate + .addr + .as_ref() + .map(|s| s.as_str()) + .unwrap_or_else(|| ""); + (*qr_parsed).id = Contact::add_or_lookup( context, - 0 as *const libc::c_char, - addr_ptr, - 0x80i32, - 0 as *mut libc::c_int, - ); - free(addr_ptr as *mut _); + "", + addr, + Origin::UnhandledQrScan, + ) + .map(|(id, _)| id) + .unwrap_or_default(); dc_create_or_lookup_nchat_by_contact_id( context, (*qr_parsed).id, @@ -286,26 +285,28 @@ pub unsafe fn dc_check_qr(context: &Context, qr: *const libc::c_char) -> *mut dc } else { (*qr_parsed).state = 200i32 } - (*qr_parsed).id = dc_add_or_lookup_contact( + (*qr_parsed).id = Contact::add_or_lookup( context, - name, - addr, - 0x80i32, - 0 as *mut libc::c_int, - ); + as_str(name), + as_str(addr), + Origin::UnhandledQrScan, + ) + .map(|(id, _)| id) + .unwrap_or_default(); (*qr_parsed).fingerprint = dc_strdup(fingerprint); (*qr_parsed).invitenumber = dc_strdup(invitenumber); (*qr_parsed).auth = dc_strdup(auth) } } else if !addr.is_null() { (*qr_parsed).state = 320i32; - (*qr_parsed).id = dc_add_or_lookup_contact( + (*qr_parsed).id = Contact::add_or_lookup( context, - name, - addr, - 0x80i32, - 0 as *mut libc::c_int, + as_str(name), + as_str(addr), + Origin::UnhandledQrScan, ) + .map(|(id, _)| id) + .unwrap_or_default(); } else if strstr( qr, b"http://\x00" as *const u8 as *const libc::c_char, diff --git a/src/dc_receive_imf.rs b/src/dc_receive_imf.rs index 21010d955..77e6b452c 100644 --- a/src/dc_receive_imf.rs +++ b/src/dc_receive_imf.rs @@ -8,10 +8,10 @@ use mmime::other::*; use sha2::{Digest, Sha256}; use crate::constants::*; +use crate::contact::*; use crate::context::Context; use crate::dc_array::*; use crate::dc_chat::*; -use crate::dc_contact::*; use crate::dc_job::*; use crate::dc_location::*; use crate::dc_mimeparser::*; @@ -38,7 +38,7 @@ pub unsafe fn dc_receive_imf( let mut current_block: u64; /* the function returns the number of created messages in the database */ let mut incoming: libc::c_int = 1; - let mut incoming_origin: libc::c_int = 0; + let mut incoming_origin = Origin::Unknown; let mut to_self: libc::c_int = 0; let mut from_id: uint32_t = 0 as uint32_t; let mut from_id_blocked: libc::c_int = 0; @@ -103,7 +103,7 @@ pub unsafe fn dc_receive_imf( dc_add_or_lookup_contacts_by_mailbox_list( context, (*fld_from).frm_mb_list, - 0x10, + Origin::IncomingUnknownFrom, from_list, &mut check_self, ); @@ -114,7 +114,8 @@ pub unsafe fn dc_receive_imf( } } else if dc_array_get_cnt(from_list) >= 1 { from_id = dc_array_get_id(from_list, 0 as size_t); - incoming_origin = dc_get_contact_origin(context, from_id, &mut from_id_blocked) + incoming_origin = + Contact::get_origin_by_id(context, from_id, &mut from_id_blocked) } dc_array_unref(from_list); } @@ -127,11 +128,11 @@ pub unsafe fn dc_receive_imf( context, (*fld_to).to_addr_list, if 0 == incoming { - 0x4000 - } else if incoming_origin >= 0x100 { - 0x400 + Origin::OutgoingTo + } else if incoming_origin.is_verified() { + Origin::IncomingTo } else { - 0x40 + Origin::IncomingUnknownTo }, to_ids, &mut to_self, @@ -147,11 +148,11 @@ pub unsafe fn dc_receive_imf( context, (*fld_cc).cc_addr_list, if 0 == incoming { - 0x2000 - } else if incoming_origin >= 0x100 { - 0x200 + Origin::OutgoingCc + } else if incoming_origin.is_verified() { + Origin::IncomingCc } else { - 0x20 + Origin::IncomingUnknownCc }, to_ids, 0 as *mut libc::c_int, @@ -253,7 +254,7 @@ pub unsafe fn dc_receive_imf( if chat_id == 0 as libc::c_uint { let create_blocked: libc::c_int = if 0 != test_normal_chat_id && test_normal_chat_id_blocked == 0 - || incoming_origin >= 0x7fffffff + || incoming_origin.is_start_new_chat() { 0 } else { @@ -285,7 +286,7 @@ pub unsafe fn dc_receive_imf( } if chat_id == 0 as libc::c_uint { let create_blocked_0: libc::c_int = - if incoming_origin >= 0x7fffffff || from_id == to_id { + if incoming_origin.is_start_new_chat() || from_id == to_id { 0 } else { 2 @@ -309,16 +310,20 @@ pub unsafe fn dc_receive_imf( } else if 0 != dc_is_reply_to_known_message(context, &mime_parser) { - dc_scaleup_contact_origin(context, from_id, 0x100); + Contact::scaleup_origin_by_id( + context, + from_id, + Origin::IncomingReplyTo, + ); info!( context, 0, "Message is a reply to a known message, mark sender as known.", ); - incoming_origin = if incoming_origin > 0x100 { + incoming_origin = if incoming_origin.is_verified() { incoming_origin } else { - 0x100 + Origin::IncomingReplyTo } } } @@ -327,8 +332,8 @@ pub unsafe fn dc_receive_imf( chat_id = 3 as uint32_t } if 0 != chat_id_blocked && state == 10 { - if incoming_origin < 0x100 && msgrmsg == 0 { - state = 13 + if !incoming_origin.is_verified() && msgrmsg == 0 { + state = 13; } } } else { @@ -353,12 +358,13 @@ pub unsafe fn dc_receive_imf( } } if chat_id == 0 as libc::c_uint && 0 != allow_creation { - let create_blocked_1: libc::c_int = - if 0 != msgrmsg && !dc_is_contact_blocked(context, to_id) { - 0 - } else { - 2 - }; + let create_blocked_1: libc::c_int = if 0 != msgrmsg + && !Contact::is_blocked_load(context, to_id) + { + 0 + } else { + 2 + }; dc_create_or_lookup_nchat_by_contact_id( context, to_id, @@ -777,28 +783,34 @@ pub unsafe fn dc_receive_imf( if !mime_parser.location_kml.is_none() && chat_id > DC_CHAT_ID_LAST_SPECIAL as libc::c_uint { - let contact = dc_get_contact(context, from_id); - if !mime_parser.location_kml.as_ref().unwrap().addr.is_null() - && !contact.is_null() - && !(*contact).addr.is_null() - && strcasecmp( - (*contact).addr, - mime_parser.location_kml.as_ref().unwrap().addr, - ) == 0 - { - let newest_location_id = dc_save_locations( - context, - chat_id, - from_id, - &mime_parser.location_kml.as_ref().unwrap().locations, - 0, - ); - if newest_location_id != 0 && hidden == 0 && !location_id_written { - dc_set_msg_location_id(context, insert_msg_id, newest_location_id); + if !mime_parser.location_kml.as_ref().unwrap().addr.is_null() { + if let Ok(contact) = Contact::get_by_id(context, from_id) { + if !contact.get_addr().is_empty() + && contact.get_addr().to_lowercase() + == as_str(mime_parser.location_kml.as_ref().unwrap().addr) + .to_lowercase() + { + let newest_location_id = dc_save_locations( + context, + chat_id, + from_id, + &mime_parser.location_kml.as_ref().unwrap().locations, + 0, + ); + if newest_location_id != 0 + && hidden == 0 + && !location_id_written + { + dc_set_msg_location_id( + context, + insert_msg_id, + newest_location_id, + ); + } + send_event = true; + } } - send_event = true; } - dc_contact_unref(contact); } if send_event { context.call_cb( @@ -1014,9 +1026,8 @@ unsafe fn create_or_lookup_group( if !optional_field.is_null() { X_MrRemoveFromGrp = (*optional_field).fld_value; mime_parser.is_system_message = 5; - let left_group: libc::c_int = - (dc_lookup_contact_id_by_addr(context, X_MrRemoveFromGrp) - == from_id as libc::c_uint) as libc::c_int; + let left_group = (Contact::lookup_id_by_addr(context, as_str(X_MrRemoveFromGrp)) + == from_id as u32) as libc::c_int; better_msg = context.stock_system_msg( if 0 != left_group { StockMessage::MsgGroupLeft @@ -1125,7 +1136,7 @@ unsafe fn create_or_lookup_group( && !grpname.is_null() && X_MrRemoveFromGrp.is_null() && (0 == group_explicitly_left - || !X_MrAddToGrp.is_null() && dc_addr_cmp(&self_addr, as_str(X_MrAddToGrp))) + || !X_MrAddToGrp.is_null() && addr_cmp(&self_addr, as_str(X_MrAddToGrp))) { /*otherwise, a pending "quit" message may pop up*/ /*re-create explicitly left groups only if ourself is re-added*/ @@ -1255,17 +1266,20 @@ unsafe fn create_or_lookup_group( params![chat_id as i32], ) .ok(); - if skip.is_null() || !dc_addr_cmp(&self_addr, as_str(skip)) { + if skip.is_null() || !addr_cmp(&self_addr, as_str(skip)) { dc_add_to_chat_contacts_table(context, chat_id, 1); } if from_id > 9 { - if !dc_addr_equals_contact(context, &self_addr, from_id as u32) - && (skip.is_null() - || !dc_addr_equals_contact( - context, - to_string(skip), - from_id as u32, - )) + if !Contact::addr_equals_contact( + context, + &self_addr, + from_id as u32, + ) && (skip.is_null() + || !Contact::addr_equals_contact( + context, + to_string(skip), + from_id as u32, + )) { dc_add_to_chat_contacts_table( context, @@ -1277,9 +1291,13 @@ unsafe fn create_or_lookup_group( i = 0; while i < to_ids_cnt { let to_id = dc_array_get_id(to_ids, i as size_t); - if !dc_addr_equals_contact(context, &self_addr, to_id) + if !Contact::addr_equals_contact(context, &self_addr, to_id) && (skip.is_null() - || !dc_addr_equals_contact(context, to_string(skip), to_id)) + || !Contact::addr_equals_contact( + context, + to_string(skip), + to_id, + )) { dc_add_to_chat_contacts_table(context, chat_id, to_id); } @@ -1604,26 +1622,21 @@ unsafe fn check_verified_properties( to_ids: *const dc_array_t, failure_reason: *mut *mut libc::c_char, ) -> libc::c_int { - let contact = dc_contact_new(context); - let verify_fail = |reason: String| { *failure_reason = format!("{}. See \"Info\" for details.", reason).strdup(); warn!(context, 0, "{}", reason); }; - let cleanup = || { - dc_contact_unref(contact); + let contact = match Contact::load_from_db(context, from_id) { + Ok(contact) => contact, + Err(_err) => { + verify_fail("Internal Error; cannot load contact".into()); + return 0; + } }; - if !dc_contact_load_from_db(contact, &context.sql, from_id) { - verify_fail("Internal Error; cannot load contact".into()); - cleanup(); - return 0; - } - if 0 == mimeparser.e2ee_helper.encrypted { verify_fail("This message is not encrypted".into()); - cleanup(); return 0; } @@ -1632,18 +1645,18 @@ unsafe fn check_verified_properties( // this check is skipped for SELF as there is no proper SELF-peerstate // and results in group-splits otherwise. if from_id != 1 { - let peerstate = Peerstate::from_addr(context, &context.sql, as_str((*contact).addr)); + let peerstate = Peerstate::from_addr(context, &context.sql, contact.get_addr()); - if peerstate.is_none() || dc_contact_is_verified_ex(contact, peerstate.as_ref()) != 2 { + if peerstate.is_none() + || contact.is_verified_ex(peerstate.as_ref()) != VerifiedStatus::BidirectVerified + { verify_fail("The sender of this message is not verified.".into()); - cleanup(); return 0; } if let Some(peerstate) = peerstate { if !peerstate.has_verified_key(&mimeparser.e2ee_helper.signatures) { verify_fail("The message was sent with non-verified encryption.".into()); - cleanup(); return 0; } } @@ -1665,7 +1678,6 @@ unsafe fn check_verified_properties( ); if rows.is_err() { - cleanup(); return 0; } for (to_addr, mut is_verified) in rows.unwrap().into_iter() { @@ -1686,7 +1698,7 @@ unsafe fn check_verified_properties( context, 0, "{} has verfied {}.", - as_str((*contact).addr), + contact.get_addr(), to_addr, ); let fp = peerstate.gossip_key_fingerprint.clone(); @@ -1702,7 +1714,6 @@ unsafe fn check_verified_properties( "{} is not a member of this verified group", to_addr )); - cleanup(); return 0; } } @@ -1883,7 +1894,7 @@ fn is_msgrmsg_rfc724_mid(context: &Context, rfc724_mid: *const libc::c_char) -> unsafe fn dc_add_or_lookup_contacts_by_address_list( context: &Context, adr_list: *const mailimf_address_list, - origin: libc::c_int, + origin: Origin, ids: *mut dc_array_t, check_self: *mut libc::c_int, ) { @@ -1933,7 +1944,7 @@ unsafe fn dc_add_or_lookup_contacts_by_address_list( unsafe fn dc_add_or_lookup_contacts_by_mailbox_list( context: &Context, mb_list: *const mailimf_mailbox_list, - origin: libc::c_int, + origin: Origin, ids: *mut dc_array_t, check_self: *mut libc::c_int, ) { @@ -1971,7 +1982,7 @@ unsafe fn add_or_lookup_contact_by_addr( context: &Context, display_name_enc: *const libc::c_char, addr_spec: *const libc::c_char, - origin: libc::c_int, + origin: Origin, ids: *mut dc_array_t, mut check_self: *mut libc::c_int, ) { @@ -1989,7 +2000,7 @@ unsafe fn add_or_lookup_contact_by_addr( .get_config(context, "configured_addr") .unwrap_or_default(); - if dc_addr_cmp(self_addr, as_str(addr_spec)) { + if addr_cmp(self_addr, as_str(addr_spec)) { *check_self = 1; } @@ -1997,20 +2008,15 @@ unsafe fn add_or_lookup_contact_by_addr( return; } /* add addr_spec if missing, update otherwise */ - let mut display_name_dec = 0 as *mut libc::c_char; + let mut display_name_dec = "".to_string(); if !display_name_enc.is_null() { - display_name_dec = dc_decode_header_words(display_name_enc); - dc_normalize_name(display_name_dec); + let tmp = as_str(dc_decode_header_words(display_name_enc)); + display_name_dec = normalize_name(&tmp); } /*can be NULL*/ - let row_id = dc_add_or_lookup_contact( - context, - display_name_dec, - addr_spec, - origin, - 0 as *mut libc::c_int, - ); - free(display_name_dec as *mut libc::c_void); + let row_id = Contact::add_or_lookup(context, display_name_dec, as_str(addr_spec), origin) + .map(|(id, _)| id) + .unwrap_or_default(); if 0 != row_id { if !dc_array_search_id(ids, row_id, 0 as *mut size_t) { dc_array_add_id(ids, row_id); diff --git a/src/dc_securejoin.rs b/src/dc_securejoin.rs index edcdacffa..45d970d96 100644 --- a/src/dc_securejoin.rs +++ b/src/dc_securejoin.rs @@ -5,11 +5,11 @@ use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; use crate::aheader::EncryptPreference; use crate::constants::*; +use crate::contact::*; use crate::context::Context; use crate::dc_array::*; use crate::dc_chat::*; use crate::dc_configure::*; -use crate::dc_contact::*; use crate::dc_e2ee::*; use crate::dc_lot::*; use crate::dc_mimeparser::*; @@ -315,30 +315,23 @@ unsafe fn fingerprint_equals_sender( return 0; } let mut fingerprint_equal: libc::c_int = 0i32; - let contacts: *mut dc_array_t = dc_get_chat_contacts(context, contact_chat_id); - let contact: *mut dc_contact_t = dc_contact_new(context); + let contacts = dc_get_chat_contacts(context, contact_chat_id); if !(dc_array_get_cnt(contacts) != 1) { - if !dc_contact_load_from_db( - contact, - &context.sql, - dc_array_get_id(contacts, 0i32 as size_t), - ) { + if let Ok(contact) = Contact::load_from_db(context, dc_array_get_id(contacts, 0)) { + if let Some(peerstate) = Peerstate::from_addr(context, &context.sql, contact.get_addr()) + { + let fingerprint_normalized = dc_normalize_fingerprint(as_str(fingerprint)); + if peerstate.public_key_fingerprint.is_some() + && &fingerprint_normalized == peerstate.public_key_fingerprint.as_ref().unwrap() + { + fingerprint_equal = 1; + } + } + } else { return 0; } - - if let Some(peerstate) = - Peerstate::from_addr(context, &context.sql, as_str((*contact).addr)) - { - let fingerprint_normalized = dc_normalize_fingerprint(as_str(fingerprint)); - if peerstate.public_key_fingerprint.is_some() - && &fingerprint_normalized == peerstate.public_key_fingerprint.as_ref().unwrap() - { - fingerprint_equal = 1; - } - } } - dc_contact_unref(contact); dc_array_unref(contacts); fingerprint_equal @@ -360,7 +353,7 @@ pub unsafe fn dc_handle_securejoin_handshake( let mut contact_chat_id_blocked: libc::c_int = 0i32; let mut grpid: *mut libc::c_char = 0 as *mut libc::c_char; let mut ret: libc::c_int = 0i32; - let mut contact: *mut dc_contact_t = 0 as *mut dc_contact_t; + if !(contact_id <= 9i32 as libc::c_uint) { step = lookup_field(mimeparser, "Secure-Join"); if !step.is_null() { @@ -572,7 +565,11 @@ pub unsafe fn dc_handle_securejoin_handshake( ); current_block = 4378276786830486580; } else { - dc_scaleup_contact_origin(context, contact_id, 0x1000000i32); + Contact::scaleup_origin_by_id( + context, + contact_id, + Origin::SecurejoinInvited, + ); info!(context, 0, "Auth verified.",); secure_connection_established(context, contact_chat_id); context.call_cb( @@ -699,16 +696,23 @@ pub unsafe fn dc_handle_securejoin_handshake( ); current_block = 4378276786830486580; } else { - dc_scaleup_contact_origin(context, contact_id, 0x2000000i32); + Contact::scaleup_origin_by_id( + context, + contact_id, + Origin::SecurejoinJoined, + ); context.call_cb( Event::CONTACTS_CHANGED, 0i32 as uintptr_t, 0i32 as uintptr_t, ); if 0 != join_vg { - if 0 == dc_addr_equals_self( + if !addr_equals_self( context, - lookup_field(mimeparser, "Chat-Group-Member-Added"), + as_str(lookup_field( + mimeparser, + "Chat-Group-Member-Added", + )), ) { info!( context, @@ -756,22 +760,26 @@ pub unsafe fn dc_handle_securejoin_handshake( ==== Alice - the inviter side ==== ==== Step 8 in "Out-of-band verified groups" protocol ==== ============================================================ */ - contact = dc_get_contact(context, contact_id); - if contact.is_null() || 0 == dc_contact_is_verified(contact) { + if let Ok(contact) = Contact::get_by_id(context, contact_id) { + if contact.is_verified() == VerifiedStatus::Unverified { + warn!(context, 0, "vg-member-added-received invalid.",); + current_block = 4378276786830486580; + } else { + context.call_cb( + Event::SECUREJOIN_INVITER_PROGRESS, + contact_id as uintptr_t, + 800i32 as uintptr_t, + ); + context.call_cb( + Event::SECUREJOIN_INVITER_PROGRESS, + contact_id as uintptr_t, + 1000i32 as uintptr_t, + ); + current_block = 10256747982273457880; + } + } else { warn!(context, 0, "vg-member-added-received invalid.",); current_block = 4378276786830486580; - } else { - context.call_cb( - Event::SECUREJOIN_INVITER_PROGRESS, - contact_id as uintptr_t, - 800i32 as uintptr_t, - ); - context.call_cb( - Event::SECUREJOIN_INVITER_PROGRESS, - contact_id as uintptr_t, - 1000i32 as uintptr_t, - ); - current_block = 10256747982273457880; } } else { current_block = 10256747982273457880; @@ -786,7 +794,7 @@ pub unsafe fn dc_handle_securejoin_handshake( } } } - dc_contact_unref(contact); + free(scanned_fingerprint_of_alice as *mut libc::c_void); free(auth as *mut libc::c_void); free(own_fingerprint as *mut libc::c_void); @@ -802,23 +810,20 @@ unsafe fn end_bobs_joining(context: &Context, status: libc::c_int) { unsafe fn secure_connection_established(context: &Context, contact_chat_id: uint32_t) { let contact_id: uint32_t = chat_id_2_contact_id(context, contact_chat_id); - let contact: *mut dc_contact_t = dc_get_contact(context, contact_id); - let msg = CString::new(context.stock_string_repl_str( - StockMessage::ContactVerified, - if !contact.is_null() { - as_str((*contact).addr) - } else { - "?" - }, - )) - .unwrap(); + let contact = Contact::get_by_id(context, contact_id); + let addr = if let Ok(ref contact) = contact { + contact.get_addr() + } else { + "?" + }; + let msg = + CString::new(context.stock_string_repl_str(StockMessage::ContactVerified, addr)).unwrap(); dc_add_device_msg(context, contact_chat_id, msg.as_ptr()); context.call_cb( Event::CHAT_MODIFIED, contact_chat_id as uintptr_t, 0i32 as uintptr_t, ); - dc_contact_unref(contact); } unsafe fn lookup_field(mimeparser: &dc_mimeparser_t, key: &str) -> *const libc::c_char { @@ -844,11 +849,11 @@ unsafe fn could_not_establish_secure_connection( details: *const libc::c_char, ) { let contact_id: uint32_t = chat_id_2_contact_id(context, contact_chat_id); - let contact = dc_get_contact(context, contact_id); + let contact = Contact::get_by_id(context, contact_id); let msg = context.stock_string_repl_str( StockMessage::ContactNotVerified, - if !contact.is_null() { - as_str((*contact).addr) + if let Ok(ref contact) = contact { + contact.get_addr() } else { "?" }, @@ -856,7 +861,6 @@ unsafe fn could_not_establish_secure_connection( let msg_c = CString::new(msg.as_str()).unwrap(); dc_add_device_msg(context, contact_chat_id, msg_c.as_ptr()); error!(context, 0, "{} ({})", msg, as_str(details)); - dc_contact_unref(contact); } unsafe fn mark_peer_as_verified( diff --git a/src/dc_tools.rs b/src/dc_tools.rs index ae63098df..814ddd1ab 100644 --- a/src/dc_tools.rs +++ b/src/dc_tools.rs @@ -539,32 +539,32 @@ pub unsafe fn dc_str_to_clist( list } +/* the colors must fulfill some criterions as: +- contrast to black and to white +- work as a text-color +- being noticeable on a typical map +- harmonize together while being different enough +(therefore, we cannot just use random rgb colors :) */ +const COLORS: [u32; 16] = [ + 0xe56555, 0xf28c48, 0x8e85ee, 0x76c84d, 0x5bb6cc, 0x549cdd, 0xd25c99, 0xb37800, 0xf23030, + 0x39b249, 0xbb243b, 0x964078, 0x66874f, 0x308ab9, 0x127ed0, 0xbe450c, +]; + +pub fn dc_str_to_color_safe(s: impl AsRef) -> u32 { + let str_lower = s.as_ref().to_lowercase(); + let mut checksum = 0; + let bytes = str_lower.as_bytes(); + for i in 0..str_lower.len() { + checksum += (i + 1) * bytes[i] as usize; + checksum %= 0xffffff; + } + let color_index = checksum % COLORS.len(); + + COLORS[color_index] +} + pub unsafe fn dc_str_to_color(str: *const libc::c_char) -> libc::c_int { let str_lower: *mut libc::c_char = dc_strlower(str); - /* the colors must fulfill some criterions as: - - contrast to black and to white - - work as a text-color - - being noticeable on a typical map - - harmonize together while being different enough - (therefore, we cannot just use random rgb colors :) */ - static mut COLORS: [uint32_t; 16] = [ - 0xe56555i32 as uint32_t, - 0xf28c48i32 as uint32_t, - 0x8e85eei32 as uint32_t, - 0x76c84di32 as uint32_t, - 0x5bb6cci32 as uint32_t, - 0x549cddi32 as uint32_t, - 0xd25c99i32 as uint32_t, - 0xb37800i32 as uint32_t, - 0xf23030i32 as uint32_t, - 0x39b249i32 as uint32_t, - 0xbb243bi32 as uint32_t, - 0x964078i32 as uint32_t, - 0x66874fi32 as uint32_t, - 0x308ab9i32 as uint32_t, - 0x127ed0i32 as uint32_t, - 0xbe450ci32 as uint32_t, - ]; let mut checksum: libc::c_int = 0i32; let str_len: libc::c_int = strlen(str_lower) as libc::c_int; let mut i: libc::c_int = 0i32; diff --git a/src/lib.rs b/src/lib.rs index f735de035..395d2068f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,6 +21,7 @@ pub mod aheader; pub mod chatlist; pub mod config; pub mod constants; +pub mod contact; pub mod context; pub mod imap; pub mod key; @@ -39,7 +40,6 @@ pub mod x; pub mod dc_array; pub mod dc_chat; pub mod dc_configure; -pub mod dc_contact; pub mod dc_dehtml; pub mod dc_e2ee; pub mod dc_imex; diff --git a/src/stock.rs b/src/stock.rs index 3416c31ea..20231712b 100644 --- a/src/stock.rs +++ b/src/stock.rs @@ -1,12 +1,11 @@ use std::borrow::Cow; -use std::ffi::CString; use strum::EnumProperty; use strum_macros::EnumProperty; use crate::constants::Event; +use crate::contact::*; use crate::context::Context; -use crate::dc_contact::*; use crate::dc_tools::*; use libc::free; @@ -200,40 +199,30 @@ impl Context { from_id: u32, ) -> String { let insert1 = if id == StockMessage::MsgAddMember || id == StockMessage::MsgDelMember { - unsafe { - let param1_c = CString::new(param1.as_ref()).unwrap(); - let contact_id = dc_lookup_contact_id_by_addr(self, param1_c.as_ptr()); - if contact_id != 0 { - let contact = dc_get_contact(self, contact_id); - let displayname = dc_contact_get_name_n_addr(contact); - let ret = to_string(displayname); - free(contact as *mut libc::c_void); - free(displayname as *mut libc::c_void); - ret - } else { - param1.as_ref().to_string() - } + let contact_id = Contact::lookup_id_by_addr(self, param1.as_ref()); + if contact_id != 0 { + Contact::get_by_id(self, contact_id) + .map(|contact| contact.get_name_n_addr()) + .unwrap_or_default() + } else { + param1.as_ref().to_string() } } else { param1.as_ref().to_string() }; + let action = self.stock_string_repl_str2(id, insert1, param2.as_ref().to_string()); let action1 = action.trim_end_matches('.'); match from_id { 0 => action, 1 => self.stock_string_repl_str(StockMessage::MsgActionByMe, action1), // DC_CONTACT_ID_SELF - _ => unsafe { - let contact = dc_get_contact(self, from_id); - let displayname = dc_contact_get_display_name(contact); - let ret = self.stock_string_repl_str2( - StockMessage::MsgActionByUser, - action1, - as_str(displayname), - ); - free(contact as *mut libc::c_void); - free(displayname as *mut libc::c_void); - ret - }, + _ => { + let displayname = Contact::get_by_id(self, from_id) + .map(|contact| contact.get_name_n_addr()) + .unwrap_or_default(); + + self.stock_string_repl_str2(StockMessage::MsgActionByUser, action1, &displayname) + } } } } @@ -343,11 +332,7 @@ mod tests { #[test] fn test_stock_system_msg_add_member_by_me_with_displayname() { let t = dummy_context(); - unsafe { - let name = CString::new("Alice").unwrap(); - let addr = CString::new("alice@example.com").unwrap(); - assert!(dc_create_contact(&t.ctx, name.as_ptr(), addr.as_ptr()) > 0); - } + Contact::create(&t.ctx, "Alice", "alice@example.com").expect("failed to create contact"); assert_eq!( t.ctx.stock_system_msg( StockMessage::MsgAddMember, @@ -356,23 +341,17 @@ mod tests { DC_CONTACT_ID_SELF as u32 ), "Member Alice (alice@example.com) added by me." - ) + ); } #[test] fn test_stock_system_msg_add_member_by_other_with_displayname() { let t = dummy_context(); - let contact_id = unsafe { - let name = CString::new("Alice").unwrap(); - let addr = CString::new("alice@example.com").unwrap(); - assert!( - dc_create_contact(&t.ctx, name.as_ptr(), addr.as_ptr()) > 0, - "Failed to create contact Alice" - ); - let name = CString::new("Bob").unwrap(); - let addr = CString::new("bob@example.com").unwrap(); - let id = dc_create_contact(&t.ctx, name.as_ptr(), addr.as_ptr()); - assert!(id > 0, "Failed to create contact Bob"); + let contact_id = { + Contact::create(&t.ctx, "Alice", "alice@example.com") + .expect("Failed to create contact Alice"); + let id = + Contact::create(&t.ctx, "Bob", "bob@example.com").expect("failed to create bob"); id }; assert_eq!( @@ -382,8 +361,8 @@ mod tests { "", contact_id, ), - "Member Alice (alice@example.com) added by Bob." - ) + "Member Alice (alice@example.com) added by Bob (bob@example.com)." + ); } #[test] @@ -403,21 +382,13 @@ mod tests { #[test] fn test_stock_system_msg_grp_name_other() { let t = dummy_context(); - let contact_id = unsafe { - let name = CString::new("Alice").unwrap(); - let addr = CString::new("alice@example.com").unwrap(); - let id = dc_create_contact(&t.ctx, name.as_ptr(), addr.as_ptr()); - assert!(id > 0, "Failed to create contact Alice"); - id - }; + let id = Contact::create(&t.ctx, "Alice", "alice@example.com") + .expect("failed to create contact"); + assert_eq!( - t.ctx.stock_system_msg( - StockMessage::MsgGrpName, - "Some chat", - "Other chat", - contact_id - ), - "Group name changed from \"Some chat\" to \"Other chat\" by Alice." + t.ctx + .stock_system_msg(StockMessage::MsgGrpName, "Some chat", "Other chat", id,), + "Group name changed from \"Some chat\" to \"Other chat\" by Alice (alice@example.com)." ) } } diff --git a/tests/stress.rs b/tests/stress.rs index 1230dda85..46176a418 100644 --- a/tests/stress.rs +++ b/tests/stress.rs @@ -8,11 +8,11 @@ use tempfile::{tempdir, TempDir}; use deltachat::config; use deltachat::constants::*; +use deltachat::contact::*; use deltachat::context::*; use deltachat::dc_array::*; use deltachat::dc_chat::*; use deltachat::dc_configure::*; -use deltachat::dc_contact::*; use deltachat::dc_imex::*; use deltachat::dc_location::*; use deltachat::dc_lot::*; @@ -890,22 +890,18 @@ fn test_stress_tests() { fn test_get_contacts() { unsafe { let context = create_test_context(); - let name = CString::yolo("some2"); - let contacts = dc_get_contacts(&context.ctx, 0, name.as_ptr()); + let contacts = Contact::get_all(&context.ctx, 0, Some("some2")).unwrap(); assert_eq!(dc_array_get_cnt(contacts), 0); dc_array_unref(contacts); - let name = CString::yolo("bob"); - let email = CString::yolo("bob@mail.de"); - let id = dc_create_contact(&context.ctx, name.as_ptr(), email.as_ptr()); + let id = Contact::create(&context.ctx, "bob", "bob@mail.de").unwrap(); assert_ne!(id, 0); - let contacts = dc_get_contacts(&context.ctx, 0, name.as_ptr()); + let contacts = Contact::get_all(&context.ctx, 0, Some("bob")).unwrap(); assert_eq!(dc_array_get_cnt(contacts), 1); dc_array_unref(contacts); - let name2 = CString::yolo("alice"); - let contacts = dc_get_contacts(&context.ctx, 0, name2.as_ptr()); + let contacts = Contact::get_all(&context.ctx, 0, Some("alice")).unwrap(); assert_eq!(dc_array_get_cnt(contacts), 0); dc_array_unref(contacts); } @@ -915,10 +911,7 @@ fn test_get_contacts() { fn test_chat() { unsafe { let context = create_test_context(); - let name = CString::yolo("bob"); - let email = CString::yolo("bob@mail.de"); - - let contact1 = dc_create_contact(&context.ctx, name.as_ptr(), email.as_ptr()); + let contact1 = Contact::create(&context.ctx, "bob", "bob@mail.de").unwrap(); assert_ne!(contact1, 0); let chat_id = dc_create_chat_by_contact_id(&context.ctx, contact1);