Resultification

This commit is contained in:
link2xt
2021-09-18 09:58:28 +00:00
parent 7bb7748b6b
commit 47bf67e658
19 changed files with 459 additions and 547 deletions

View File

@@ -1319,8 +1319,13 @@ pub unsafe extern "C" fn dc_is_contact_in_chat(
} }
let ctx = &*context; let ctx = &*context;
block_on(async move { chat::is_contact_in_chat(ctx, ChatId::new(chat_id), contact_id).await }) block_on(chat::is_contact_in_chat(
.into() ctx,
ChatId::new(chat_id),
contact_id,
))
.log_err(ctx, "is_contact_in_chat failed")
.unwrap_or_default() as libc::c_int
} }
#[no_mangle] #[no_mangle]
@@ -1335,9 +1340,13 @@ pub unsafe extern "C" fn dc_add_contact_to_chat(
} }
let ctx = &*context; let ctx = &*context;
block_on(async move { block_on(chat::add_contact_to_chat(
chat::add_contact_to_chat(ctx, ChatId::new(chat_id), contact_id).await as libc::c_int ctx,
}) ChatId::new(chat_id),
contact_id,
))
.log_err(ctx, "Failed to add contact")
.is_ok() as libc::c_int
} }
#[no_mangle] #[no_mangle]
@@ -1352,12 +1361,13 @@ pub unsafe extern "C" fn dc_remove_contact_from_chat(
} }
let ctx = &*context; let ctx = &*context;
block_on(async move { block_on(chat::remove_contact_from_chat(
chat::remove_contact_from_chat(ctx, ChatId::new(chat_id), contact_id) ctx,
.await ChatId::new(chat_id),
.map(|_| 1) contact_id,
.unwrap_or_log_default(ctx, "Failed to remove contact") ))
}) .log_err(ctx, "Failed to remove contact")
.is_ok() as libc::c_int
} }
#[no_mangle] #[no_mangle]
@@ -2019,12 +2029,9 @@ pub unsafe extern "C" fn dc_get_securejoin_qr(
Some(ChatId::new(chat_id)) Some(ChatId::new(chat_id))
}; };
block_on(async move { block_on(securejoin::dc_get_securejoin_qr(ctx, chat_id))
securejoin::dc_get_securejoin_qr(ctx, chat_id) .unwrap_or_else(|_| "".to_string())
.await .strdup()
.unwrap_or_else(|| "".to_string())
.strdup()
})
} }
#[no_mangle] #[no_mangle]
@@ -2615,8 +2622,10 @@ pub unsafe extern "C" fn dc_chat_can_send(chat: *mut dc_chat_t) -> libc::c_int {
return 0; return 0;
} }
let ffi_chat = &*chat; let ffi_chat = &*chat;
let cxt = &*ffi_chat.context; let ctx = &*ffi_chat.context;
block_on(ffi_chat.chat.can_send(cxt)) as libc::c_int block_on(ffi_chat.chat.can_send(ctx))
.log_err(ctx, "can_send failed")
.unwrap_or_default() as libc::c_int
} }
#[no_mangle] #[no_mangle]
@@ -3502,7 +3511,9 @@ pub unsafe extern "C" fn dc_contact_is_verified(contact: *mut dc_contact_t) -> l
let ffi_contact = &*contact; let ffi_contact = &*contact;
let ctx = &*ffi_contact.context; let ctx = &*ffi_contact.context;
block_on(async move { ffi_contact.contact.is_verified(ctx).await as libc::c_int }) block_on(ffi_contact.contact.is_verified(ctx))
.log_err(ctx, "is_verified failed")
.unwrap_or_default() as libc::c_int
} }
// dc_lot_t // dc_lot_t

View File

@@ -2,7 +2,7 @@ extern crate dirs;
use std::str::FromStr; use std::str::FromStr;
use anyhow::{bail, ensure, Error}; use anyhow::{bail, ensure, Result};
use async_std::path::Path; use async_std::path::Path;
use deltachat::chat::{ use deltachat::chat::{
self, Chat, ChatId, ChatItem, ChatVisibility, MuteDuration, ProtectionStatus, self, Chat, ChatId, ChatItem, ChatVisibility, MuteDuration, ProtectionStatus,
@@ -98,7 +98,7 @@ async fn reset_tables(context: &Context, bits: i32) {
}); });
} }
async fn poke_eml_file(context: &Context, filename: impl AsRef<Path>) -> Result<(), anyhow::Error> { async fn poke_eml_file(context: &Context, filename: impl AsRef<Path>) -> Result<()> {
let data = dc_read_file(context, filename).await?; let data = dc_read_file(context, filename).await?;
if let Err(err) = dc_receive_imf(context, &data, "import", 0, false).await { if let Err(err) = dc_receive_imf(context, &data, "import", 0, false).await {
@@ -239,7 +239,7 @@ async fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
); );
} }
async fn log_msglist(context: &Context, msglist: &[MsgId]) -> Result<(), Error> { async fn log_msglist(context: &Context, msglist: &[MsgId]) -> Result<()> {
let mut lines_out = 0; let mut lines_out = 0;
for &msg_id in msglist { for &msg_id in msglist {
if msg_id == MsgId::new(DC_MSG_ID_DAYMARKER) { if msg_id == MsgId::new(DC_MSG_ID_DAYMARKER) {
@@ -267,59 +267,59 @@ async fn log_msglist(context: &Context, msglist: &[MsgId]) -> Result<(), Error>
Ok(()) Ok(())
} }
async fn log_contactlist(context: &Context, contacts: &[u32]) { async fn log_contactlist(context: &Context, contacts: &[u32]) -> Result<()> {
for contact_id in contacts { for contact_id in contacts {
let line; let line;
let mut line2 = "".to_string(); let mut line2 = "".to_string();
if let Ok(contact) = Contact::get_by_id(context, *contact_id).await { let contact = Contact::get_by_id(context, *contact_id).await?;
let name = contact.get_display_name(); let name = contact.get_display_name();
let addr = contact.get_addr(); let addr = contact.get_addr();
let verified_state = contact.is_verified(context).await; let verified_state = contact.is_verified(context).await?;
let verified_str = if VerifiedStatus::Unverified != verified_state { let verified_str = if VerifiedStatus::Unverified != verified_state {
if verified_state == VerifiedStatus::BidirectVerified { if verified_state == VerifiedStatus::BidirectVerified {
" √√" " √√"
} else {
""
}
} else { } else {
"" ""
};
line = format!(
"{}{} <{}>",
if !name.is_empty() {
&name
} else {
"<name unset>"
},
verified_str,
if !addr.is_empty() {
&addr
} else {
"addr unset"
}
);
let peerstate = Peerstate::from_addr(context, addr)
.await
.expect("peerstate error");
if peerstate.is_some() && *contact_id != 1 {
line2 = format!(
", prefer-encrypt={}",
peerstate.as_ref().unwrap().prefer_encrypt
);
} }
} else {
println!("Contact#{}: {}{}", *contact_id, line, line2); ""
};
line = format!(
"{}{} <{}>",
if !name.is_empty() {
&name
} else {
"<name unset>"
},
verified_str,
if !addr.is_empty() {
&addr
} else {
"addr unset"
}
);
let peerstate = Peerstate::from_addr(context, &addr)
.await
.expect("peerstate error");
if peerstate.is_some() && *contact_id != 1 {
line2 = format!(
", prefer-encrypt={}",
peerstate.as_ref().unwrap().prefer_encrypt
);
} }
println!("Contact#{}: {}{}", *contact_id, line, line2);
} }
Ok(())
} }
fn chat_prefix(chat: &Chat) -> &'static str { fn chat_prefix(chat: &Chat) -> &'static str {
chat.typ.into() chat.typ.into()
} }
pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Result<(), Error> { pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Result<()> {
let mut sel_chat = if !chat_id.is_unset() { let mut sel_chat = if !chat_id.is_unset() {
Chat::load_from_db(&context, *chat_id).await.ok() Some(Chat::load_from_db(&context, *chat_id).await?)
} else { } else {
None None
}; };
@@ -726,17 +726,9 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
ensure!(!arg1.is_empty(), "Argument <contact-id> missing."); ensure!(!arg1.is_empty(), "Argument <contact-id> missing.");
let contact_id_0: u32 = arg1.parse()?; let contact_id_0: u32 = arg1.parse()?;
if chat::add_contact_to_chat( chat::add_contact_to_chat(&context, sel_chat.as_ref().unwrap().get_id(), contact_id_0)
&context, .await?;
sel_chat.as_ref().unwrap().get_id(), println!("Contact added to chat.");
contact_id_0,
)
.await
{
println!("Contact added to chat.");
} else {
bail!("Cannot add contact to chat.");
}
} }
"removemember" => { "removemember" => {
ensure!(sel_chat.is_some(), "No chat selected."); ensure!(sel_chat.is_some(), "No chat selected.");
@@ -774,7 +766,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
chat::get_chat_contacts(&context, sel_chat.as_ref().unwrap().get_id()).await?; chat::get_chat_contacts(&context, sel_chat.as_ref().unwrap().get_id()).await?;
println!("Memberlist:"); println!("Memberlist:");
log_contactlist(&context, &contacts).await; log_contactlist(&context, &contacts).await?;
println!( println!(
"{} contacts\nLocation streaming: {}", "{} contacts\nLocation streaming: {}",
contacts.len(), contacts.len(),
@@ -1090,7 +1082,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
Some(arg1), Some(arg1),
) )
.await?; .await?;
log_contactlist(&context, &contacts).await; log_contactlist(&context, &contacts).await?;
println!("{} contacts.", contacts.len()); println!("{} contacts.", contacts.len());
} }
"addcontact" => { "addcontact" => {
@@ -1155,7 +1147,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
} }
"listblocked" => { "listblocked" => {
let contacts = Contact::get_all_blocked(&context).await?; let contacts = Contact::get_all_blocked(&context).await?;
log_contactlist(&context, &contacts).await; log_contactlist(&context, &contacts).await?;
println!("{} blocked contacts.", contacts.len()); println!("{} blocked contacts.", contacts.len());
} }
"checkqr" => { "checkqr" => {

View File

@@ -409,20 +409,19 @@ async fn handle_cmd(
} }
"getqr" | "getbadqr" => { "getqr" | "getbadqr" => {
ctx.start_io().await; ctx.start_io().await;
let group = arg1.parse::<u32>().ok().map(ChatId::new); let group = arg1.parse::<u32>().ok().map(|id| ChatId::new(id));
if let Some(mut qr) = dc_get_securejoin_qr(&ctx, group).await { let mut qr = dc_get_securejoin_qr(&ctx, group).await?;
if !qr.is_empty() { if !qr.is_empty() {
if arg0 == "getbadqr" && qr.len() > 40 { if arg0 == "getbadqr" && qr.len() > 40 {
qr.replace_range(12..22, "0000000000") qr.replace_range(12..22, "0000000000")
}
println!("{}", qr);
let output = Command::new("qrencode")
.args(&["-t", "ansiutf8", qr.as_str(), "-o", "-"])
.output()
.expect("failed to execute process");
io::stdout().write_all(&output.stdout).unwrap();
io::stderr().write_all(&output.stderr).unwrap();
} }
println!("{}", qr);
let output = Command::new("qrencode")
.args(&["-t", "ansiutf8", qr.as_str(), "-o", "-"])
.output()
.expect("failed to execute process");
io::stdout().write_all(&output.stdout).unwrap();
io::stderr().write_all(&output.stderr).unwrap();
} }
} }
"joinqr" => { "joinqr" => {

View File

@@ -2,12 +2,12 @@
//! //!
//! Parse and create [Autocrypt-headers](https://autocrypt.org/en/latest/level1.html#the-autocrypt-header). //! Parse and create [Autocrypt-headers](https://autocrypt.org/en/latest/level1.html#the-autocrypt-header).
use anyhow::{bail, format_err, Error, Result};
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::str::FromStr; use std::str::FromStr;
use std::{fmt, str}; use std::{fmt, str};
use crate::contact::addr_cmp; use crate::contact::addr_cmp;
use crate::context::Context;
use crate::headerdef::{HeaderDef, HeaderDefMap}; use crate::headerdef::{HeaderDef, HeaderDefMap};
use crate::key::{DcKey, SignedPublicKey}; use crate::key::{DcKey, SignedPublicKey};
@@ -37,13 +37,13 @@ impl fmt::Display for EncryptPreference {
} }
impl str::FromStr for EncryptPreference { impl str::FromStr for EncryptPreference {
type Err = (); type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self> {
match s { match s {
"mutual" => Ok(EncryptPreference::Mutual), "mutual" => Ok(EncryptPreference::Mutual),
"nopreference" => Ok(EncryptPreference::NoPreference), "nopreference" => Ok(EncryptPreference::NoPreference),
_ => Err(()), _ => bail!("Cannot parse encryption preference {}", s),
} }
} }
} }
@@ -70,28 +70,27 @@ impl Aheader {
} }
} }
/// Tries to parse Autocrypt header.
///
/// If there is none, returns None. If the header is present but cannot be parsed, returns an
/// error.
pub fn from_headers( pub fn from_headers(
context: &Context,
wanted_from: &str, wanted_from: &str,
headers: &[mailparse::MailHeader<'_>], headers: &[mailparse::MailHeader<'_>],
) -> Option<Self> { ) -> Result<Option<Self>> {
if let Some(value) = headers.get_header_value(HeaderDef::Autocrypt) { if let Some(value) = headers.get_header_value(HeaderDef::Autocrypt) {
match Self::from_str(&value) { let header = Self::from_str(&value)?;
Ok(header) => { if !addr_cmp(&header.addr, wanted_from) {
if addr_cmp(&header.addr, wanted_from) { bail!(
return Some(header); "Autocrypt header address {:?} is not {:?}",
} header.addr,
} wanted_from
Err(err) => { );
warn!(
context,
"found invalid autocrypt header {}: {:?}", value, err
);
}
} }
Ok(Some(header))
} else {
Ok(None)
} }
None
} }
} }
@@ -120,9 +119,9 @@ impl fmt::Display for Aheader {
} }
impl str::FromStr for Aheader { impl str::FromStr for Aheader {
type Err = (); type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self> {
let mut attributes: BTreeMap<String, String> = s let mut attributes: BTreeMap<String, String> = s
.split(';') .split(';')
.filter_map(|a| { .filter_map(|a| {
@@ -136,15 +135,20 @@ impl str::FromStr for Aheader {
let addr = match attributes.remove("addr") { let addr = match attributes.remove("addr") {
Some(addr) => addr, Some(addr) => addr,
None => { None => bail!("Autocrypt header has no addr"),
return Err(());
}
}; };
let public_key: SignedPublicKey = attributes let public_key: SignedPublicKey = attributes
.remove("keydata") .remove("keydata")
.ok_or(()) .ok_or_else(|| format_err!("keydata attribute is not found"))
.and_then(|raw| SignedPublicKey::from_base64(&raw).or(Err(()))) .and_then(|raw| {
.and_then(|key| key.verify().and(Ok(key)).or(Err(())))?; SignedPublicKey::from_base64(&raw)
.map_err(|_| format_err!("Autocrypt key cannot be decoded"))
})
.and_then(|key| {
key.verify()
.and(Ok(key))
.map_err(|_| format_err!("Autocrypt key cannot be verified"))
})?;
let prefer_encrypt = attributes let prefer_encrypt = attributes
.remove("prefer-encrypt") .remove("prefer-encrypt")
@@ -154,7 +158,7 @@ impl str::FromStr for Aheader {
// Autocrypt-Level0: unknown attributes starting with an underscore can be safely ignored // Autocrypt-Level0: unknown attributes starting with an underscore can be safely ignored
// Autocrypt-Level0: unknown attribute, treat the header as invalid // Autocrypt-Level0: unknown attribute, treat the header as invalid
if attributes.keys().any(|k| !k.starts_with('_')) { if attributes.keys().any(|k| !k.starts_with('_')) {
return Err(()); bail!("Unknown Autocrypt attribute found");
} }
Ok(Aheader { Ok(Aheader {
@@ -172,38 +176,40 @@ mod tests {
const RAWKEY: &str = "xsBNBFzG3j0BCAC6iNhT8zydvCXi8LI/gFnkadMbfmSE/rTJskRRra/utGbLyDta/yTrJgWL7O3y/g4HdDW/dN2z26Y6W13IMzx9gLInn1KQZChtqWAcr/ReUucXcymwcfg1mdkBGk3TSLeLihN6CJx8Wsv8ig+kgAzte4f5rqEEAJVQ9WZHuti7UiYs6oRzqTo06CRe9owVXxzdMf0VDQtf7ZFm9dpzKKbhH7Lu8880iiotQ9/yRCkDGp9fNThsrLdZiK6OIAcIBAqi2rI89aS1dAmnRbktQieCx5izzyYkR1KvVL3gTTllHOzfKVEC2asmtWu2e4se/+O4WMIS1eGrn7GeWVb0Vwc5ABEBAAHNETxhQEBiLmV4YW1wbGUuZGU+wsCJBBABCAAzAhkBBQJcxt5FAhsDBAsJCAcGFQgJCgsCAxYCARYhBI4xxYKBgH3ANh5cufaKrc9mtiMLAAoJEPaKrc9mtiML938H/18F+3Wf9/JaAy/8hCO1v4S2PVBhxaKCokaNFtkfaMRne2l087LscCFPiFNyb4mv6Z3YeK8Xpxlp2sI0ecvdiqLUOGfnxS6tQrj+83EjtIrZ/hXOk1h121QFWH9Zg2VNHtODXjAgdLDC0NWUrclR0ZOqEDQHeo0ibTILdokVfXFN25wakPmGaYJP2y729cb1ve7RzvIvwn+Dddfxo3ao72rBfLi7l4NQ4S0KsY4cw+/6l5bRCKYCP77wZtvCwUvfVVosLdT43agtSiBI49+ayqvZ8OCvSJa61i+v81brTiEy9GBod4eAp45Ibsuemkw+gon4ZOvUXHTjwFB+h63MrozOwE0EXMbePQEIAL/vauf1zK8JgCu3V+G+SOX0iWw5xUlCPX+ERpBbWfwu3uAqn4wYXD3JDE/fVAF668xiV4eTPtlSUd5h0mn+G7uXMMOtkb+20SoEt50f8zw8TrL9t+ZsV11GKZWJpCar5AhXWsn6EEi8I2hLL5vn55ZZmHuGgN4jjmkRl3ToKCLhaXwTBjCJem7N5EH7F75wErEITa55v4Lb4Nfca7vnvtYrI1OA446xa8gHra0SINelTD09/JM/Fw4sWVPBaRZmJK/Tnu79N23No9XBUubmFPv1pNexZsQclicnTpt/BEWhiun7d6lfGB63K1aoHRTR1pcrWvBuALuuz0gqar2zlI0AEQEAAcLAdgQYAQgAIAUCXMbeRQIbDBYhBI4xxYKBgH3ANh5cufaKrc9mtiMLAAoJEPaKrc9mtiMLKSEIAIyLCRO2OyZ0IYRvRPpMn4p7E+7Pfcz/0mSkOy+1hshgJnqivXurm8zwGrwdMqeV4eslKR9H1RUdWGUQJNbtwmmjrt5DHpIhYHl5t3FpCBaGbV20Omo00Q38lBl9MtrmZkZw+ktEk6X+0xCKssMF+2MADkSOIufbR5HrDVB89VZOHCO9DeXvCUUAw2hyJiL/LHmLzJ40zYoTmb+F//f0k0j+tRdbkefyRoCmwG7YGiT+2hnCdgcezswnzah5J3ZKlrg7jOGo1LxtbvNUzxNBbC6S/aNgwm6qxo7xegRhmEl5uZ16zwyj4qz+xkjGy25Of5mWfUDoNw7OT7sjUbHOOMc="; const RAWKEY: &str = "xsBNBFzG3j0BCAC6iNhT8zydvCXi8LI/gFnkadMbfmSE/rTJskRRra/utGbLyDta/yTrJgWL7O3y/g4HdDW/dN2z26Y6W13IMzx9gLInn1KQZChtqWAcr/ReUucXcymwcfg1mdkBGk3TSLeLihN6CJx8Wsv8ig+kgAzte4f5rqEEAJVQ9WZHuti7UiYs6oRzqTo06CRe9owVXxzdMf0VDQtf7ZFm9dpzKKbhH7Lu8880iiotQ9/yRCkDGp9fNThsrLdZiK6OIAcIBAqi2rI89aS1dAmnRbktQieCx5izzyYkR1KvVL3gTTllHOzfKVEC2asmtWu2e4se/+O4WMIS1eGrn7GeWVb0Vwc5ABEBAAHNETxhQEBiLmV4YW1wbGUuZGU+wsCJBBABCAAzAhkBBQJcxt5FAhsDBAsJCAcGFQgJCgsCAxYCARYhBI4xxYKBgH3ANh5cufaKrc9mtiMLAAoJEPaKrc9mtiML938H/18F+3Wf9/JaAy/8hCO1v4S2PVBhxaKCokaNFtkfaMRne2l087LscCFPiFNyb4mv6Z3YeK8Xpxlp2sI0ecvdiqLUOGfnxS6tQrj+83EjtIrZ/hXOk1h121QFWH9Zg2VNHtODXjAgdLDC0NWUrclR0ZOqEDQHeo0ibTILdokVfXFN25wakPmGaYJP2y729cb1ve7RzvIvwn+Dddfxo3ao72rBfLi7l4NQ4S0KsY4cw+/6l5bRCKYCP77wZtvCwUvfVVosLdT43agtSiBI49+ayqvZ8OCvSJa61i+v81brTiEy9GBod4eAp45Ibsuemkw+gon4ZOvUXHTjwFB+h63MrozOwE0EXMbePQEIAL/vauf1zK8JgCu3V+G+SOX0iWw5xUlCPX+ERpBbWfwu3uAqn4wYXD3JDE/fVAF668xiV4eTPtlSUd5h0mn+G7uXMMOtkb+20SoEt50f8zw8TrL9t+ZsV11GKZWJpCar5AhXWsn6EEi8I2hLL5vn55ZZmHuGgN4jjmkRl3ToKCLhaXwTBjCJem7N5EH7F75wErEITa55v4Lb4Nfca7vnvtYrI1OA446xa8gHra0SINelTD09/JM/Fw4sWVPBaRZmJK/Tnu79N23No9XBUubmFPv1pNexZsQclicnTpt/BEWhiun7d6lfGB63K1aoHRTR1pcrWvBuALuuz0gqar2zlI0AEQEAAcLAdgQYAQgAIAUCXMbeRQIbDBYhBI4xxYKBgH3ANh5cufaKrc9mtiMLAAoJEPaKrc9mtiMLKSEIAIyLCRO2OyZ0IYRvRPpMn4p7E+7Pfcz/0mSkOy+1hshgJnqivXurm8zwGrwdMqeV4eslKR9H1RUdWGUQJNbtwmmjrt5DHpIhYHl5t3FpCBaGbV20Omo00Q38lBl9MtrmZkZw+ktEk6X+0xCKssMF+2MADkSOIufbR5HrDVB89VZOHCO9DeXvCUUAw2hyJiL/LHmLzJ40zYoTmb+F//f0k0j+tRdbkefyRoCmwG7YGiT+2hnCdgcezswnzah5J3ZKlrg7jOGo1LxtbvNUzxNBbC6S/aNgwm6qxo7xegRhmEl5uZ16zwyj4qz+xkjGy25Of5mWfUDoNw7OT7sjUbHOOMc=";
#[test] #[test]
fn test_from_str() { fn test_from_str() -> Result<()> {
let h: Aheader = format!( let h: Aheader = format!(
"addr=me@mail.com; prefer-encrypt=mutual; keydata={}", "addr=me@mail.com; prefer-encrypt=mutual; keydata={}",
RAWKEY RAWKEY
) )
.parse() .parse()?;
.expect("failed to parse");
assert_eq!(h.addr, "me@mail.com"); assert_eq!(h.addr, "me@mail.com");
assert_eq!(h.prefer_encrypt, EncryptPreference::Mutual); assert_eq!(h.prefer_encrypt, EncryptPreference::Mutual);
Ok(())
} }
// EncryptPreference::Reset is an internal value, parser should never return it // EncryptPreference::Reset is an internal value, parser should never return it
#[test] #[test]
fn test_from_str_reset() { fn test_from_str_reset() -> Result<()> {
let raw = format!( let raw = format!(
"addr=reset@example.com; prefer-encrypt=reset; keydata={}", "addr=reset@example.com; prefer-encrypt=reset; keydata={}",
RAWKEY RAWKEY
); );
let h: Aheader = raw.parse().expect("failed to parse"); let h: Aheader = raw.parse()?;
assert_eq!(h.addr, "reset@example.com"); assert_eq!(h.addr, "reset@example.com");
assert_eq!(h.prefer_encrypt, EncryptPreference::NoPreference); assert_eq!(h.prefer_encrypt, EncryptPreference::NoPreference);
Ok(())
} }
#[test] #[test]
fn test_from_str_non_critical() { fn test_from_str_non_critical() -> Result<()> {
let raw = format!("addr=me@mail.com; _foo=one; _bar=two; keydata={}", RAWKEY); let raw = format!("addr=me@mail.com; _foo=one; _bar=two; keydata={}", RAWKEY);
let h: Aheader = raw.parse().expect("failed to parse"); let h: Aheader = raw.parse()?;
assert_eq!(h.addr, "me@mail.com"); assert_eq!(h.addr, "me@mail.com");
assert_eq!(h.prefer_encrypt, EncryptPreference::NoPreference); assert_eq!(h.prefer_encrypt, EncryptPreference::NoPreference);
Ok(())
} }
#[test] #[test]
@@ -216,7 +222,7 @@ mod tests {
} }
#[test] #[test]
fn test_good_headers() { fn test_good_headers() -> Result<()> {
let fixed_header = concat!( let fixed_header = concat!(
"addr=a@b.example.org; prefer-encrypt=mutual; ", "addr=a@b.example.org; prefer-encrypt=mutual; ",
"keydata=xsBNBFzG3j0BCAC6iNhT8zydvCXi8LI/gFnkadMbfmSE/rTJskRRra/utGbLyDta/yTrJg", "keydata=xsBNBFzG3j0BCAC6iNhT8zydvCXi8LI/gFnkadMbfmSE/rTJskRRra/utGbLyDta/yTrJg",
@@ -242,7 +248,7 @@ mod tests {
" wm6qxo7xegRhmEl5uZ16zwyj4qz+xkjGy25Of5mWfUDoNw7OT7sjUbHOOMc=" " wm6qxo7xegRhmEl5uZ16zwyj4qz+xkjGy25Of5mWfUDoNw7OT7sjUbHOOMc="
); );
let ah = Aheader::from_str(fixed_header).expect("failed to parse"); let ah = Aheader::from_str(fixed_header)?;
assert_eq!(ah.addr, "a@b.example.org"); assert_eq!(ah.addr, "a@b.example.org");
assert_eq!(ah.prefer_encrypt, EncryptPreference::Mutual); assert_eq!(ah.prefer_encrypt, EncryptPreference::Mutual);
assert_eq!(format!("{}", ah), fixed_header); assert_eq!(format!("{}", ah), fixed_header);
@@ -250,18 +256,17 @@ mod tests {
let rendered = ah.to_string(); let rendered = ah.to_string();
assert_eq!(rendered, fixed_header); assert_eq!(rendered, fixed_header);
let ah = Aheader::from_str(&format!(" _foo; __FOO=BAR ;;; addr = a@b.example.org ;\r\n prefer-encrypt = mutual ; keydata = {}", RAWKEY)).expect("failed to parse"); let ah = Aheader::from_str(&format!(" _foo; __FOO=BAR ;;; addr = a@b.example.org ;\r\n prefer-encrypt = mutual ; keydata = {}", RAWKEY))?;
assert_eq!(ah.addr, "a@b.example.org"); assert_eq!(ah.addr, "a@b.example.org");
assert_eq!(ah.prefer_encrypt, EncryptPreference::Mutual); assert_eq!(ah.prefer_encrypt, EncryptPreference::Mutual);
Aheader::from_str(&format!( Aheader::from_str(&format!(
"addr=a@b.example.org; prefer-encrypt=ignoreUnknownValues; keydata={}", "addr=a@b.example.org; prefer-encrypt=ignoreUnknownValues; keydata={}",
RAWKEY RAWKEY
)) ))?;
.expect("failed to parse");
Aheader::from_str(&format!("addr=a@b.example.org; keydata={}", RAWKEY)) Aheader::from_str(&format!("addr=a@b.example.org; keydata={}", RAWKEY))?;
.expect("failed to parse"); Ok(())
} }
#[test] #[test]

View File

@@ -179,7 +179,7 @@ impl ChatId {
chat.id chat.id
} }
None => { None => {
if Contact::real_exists_by_id(context, contact_id).await if Contact::real_exists_by_id(context, contact_id).await?
|| contact_id == DC_CONTACT_ID_SELF || contact_id == DC_CONTACT_ID_SELF
{ {
let chat_id = ChatId::get_for_contact(context, contact_id).await?; let chat_id = ChatId::get_for_contact(context, contact_id).await?;
@@ -324,7 +324,7 @@ impl ChatId {
let contact_ids = get_chat_contacts(context, self).await?; let contact_ids = get_chat_contacts(context, self).await?;
for contact_id in contact_ids.into_iter() { for contact_id in contact_ids.into_iter() {
let contact = Contact::get_by_id(context, contact_id).await?; let contact = Contact::get_by_id(context, contact_id).await?;
if contact.is_verified(context).await != VerifiedStatus::BidirectVerified { if contact.is_verified(context).await? != VerifiedStatus::BidirectVerified {
bail!("{} is not verified.", contact.get_display_name()); bail!("{} is not verified.", contact.get_display_name());
} }
} }
@@ -499,7 +499,7 @@ impl ChatId {
job::kill_action(context, Action::Housekeeping).await?; job::kill_action(context, Action::Housekeeping).await?;
let j = job::Job::new(Action::Housekeeping, 0, Params::new(), 10); let j = job::Job::new(Action::Housekeeping, 0, Params::new(), 10);
job::add(context, j).await; job::add(context, j).await?;
if chat.is_self_talk() { if chat.is_self_talk() {
let mut msg = Message::new(Viewtype::Text); let mut msg = Message::new(Viewtype::Text);
@@ -605,7 +605,7 @@ impl ChatId {
} }
let chat = Chat::load_from_db(context, self).await?; let chat = Chat::load_from_db(context, self).await?;
if !chat.can_send(context).await { if !chat.can_send(context).await? {
bail!("Can't set a draft: Can't send"); bail!("Can't set a draft: Can't send");
} }
@@ -940,13 +940,13 @@ impl Chat {
} }
/// Returns true if user can send messages to this chat. /// Returns true if user can send messages to this chat.
pub async fn can_send(&self, context: &Context) -> bool { pub async fn can_send(&self, context: &Context) -> Result<bool> {
!self.id.is_special() Ok(!self.id.is_special()
&& !self.is_device_talk() && !self.is_device_talk()
&& !self.is_mailing_list() && !self.is_mailing_list()
&& !self.is_contact_request() && !self.is_contact_request()
&& (self.typ == Chattype::Single && (self.typ == Chattype::Single
|| is_contact_in_chat(context, self.id, DC_CONTACT_ID_SELF).await) || is_contact_in_chat(context, self.id, DC_CONTACT_ID_SELF).await?))
} }
pub async fn update_param(&mut self, context: &Context) -> Result<()> { pub async fn update_param(&mut self, context: &Context) -> Result<()> {
@@ -1097,7 +1097,7 @@ impl Chat {
} }
if self.typ == Chattype::Group if self.typ == Chattype::Group
&& !is_contact_in_chat(context, self.id, DC_CONTACT_ID_SELF).await && !is_contact_in_chat(context, self.id, DC_CONTACT_ID_SELF).await?
{ {
context.emit_event(EventType::ErrorSelfNotInGroup( context.emit_event(EventType::ErrorSelfNotInGroup(
"Cannot send message; self not in group.".into(), "Cannot send message; self not in group.".into(),
@@ -1667,7 +1667,7 @@ async fn prepare_msg_common(
chat_id.unarchive(context).await?; chat_id.unarchive(context).await?;
let mut chat = Chat::load_from_db(context, chat_id).await?; let mut chat = Chat::load_from_db(context, chat_id).await?;
ensure!(chat.can_send(context).await, "cannot send to {}", chat_id); ensure!(chat.can_send(context).await?, "cannot send to {}", chat_id);
// The OutPreparing state is set by dc_prepare_msg() before it // The OutPreparing state is set by dc_prepare_msg() before it
// calls this function and the message is left in the OutPreparing // calls this function and the message is left in the OutPreparing
@@ -1686,20 +1686,24 @@ async fn prepare_msg_common(
} }
/// Returns whether a contact is in a chat or not. /// Returns whether a contact is in a chat or not.
pub async fn is_contact_in_chat(context: &Context, chat_id: ChatId, contact_id: u32) -> bool { pub async fn is_contact_in_chat(
context: &Context,
chat_id: ChatId,
contact_id: u32,
) -> Result<bool> {
// this function works for group and for normal chats, however, it is more useful // this function works for group and for normal chats, however, it is more useful
// for group chats. // for group chats.
// DC_CONTACT_ID_SELF may be used to check, if the user itself is in a group // DC_CONTACT_ID_SELF may be used to check, if the user itself is in a group
// chat (DC_CONTACT_ID_SELF is not added to normal chats) // chat (DC_CONTACT_ID_SELF is not added to normal chats)
context let exists = context
.sql .sql
.exists( .exists(
"SELECT COUNT(*) FROM chats_contacts WHERE chat_id=? AND contact_id=?;", "SELECT COUNT(*) FROM chats_contacts WHERE chat_id=? AND contact_id=?;",
paramsv![chat_id, contact_id as i32], paramsv![chat_id, contact_id as i32],
) )
.await .await?;
.unwrap_or_default() Ok(exists)
} }
/// Send a message defined by a dc_msg_t object to a chat. /// Send a message defined by a dc_msg_t object to a chat.
@@ -1766,7 +1770,7 @@ pub async fn send_msg_sync(context: &Context, chat_id: ChatId, msg: &mut Message
async fn send_msg_inner(context: &Context, chat_id: ChatId, msg: &mut Message) -> Result<MsgId> { async fn send_msg_inner(context: &Context, chat_id: ChatId, msg: &mut Message) -> Result<MsgId> {
if let Some(send_job) = prepare_send_msg(context, chat_id, msg).await? { if let Some(send_job) = prepare_send_msg(context, chat_id, msg).await? {
job::add(context, send_job).await; job::add(context, send_job).await?;
context.emit_event(EventType::MsgsChanged { context.emit_event(EventType::MsgsChanged {
chat_id: msg.chat_id, chat_id: msg.chat_id,
@@ -2161,7 +2165,8 @@ pub async fn create_group_chat(
.await?; .await?;
let chat_id = ChatId::new(u32::try_from(row_id)?); let chat_id = ChatId::new(u32::try_from(row_id)?);
if add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF).await { if !is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF).await? {
add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF).await?;
let mut draft_msg = Message::new(Viewtype::Text); let mut draft_msg = Message::new(Viewtype::Text);
draft_msg.set_text(Some(draft_txt)); draft_msg.set_text(Some(draft_txt));
chat_id.set_draft_raw(context, &mut draft_msg).await?; chat_id.set_draft_raw(context, &mut draft_msg).await?;
@@ -2186,25 +2191,15 @@ pub(crate) async fn add_to_chat_contacts_table(
context: &Context, context: &Context,
chat_id: ChatId, chat_id: ChatId,
contact_id: u32, contact_id: u32,
) -> bool { ) -> Result<()> {
match context context
.sql .sql
.execute( .execute(
"INSERT INTO chats_contacts (chat_id, contact_id) VALUES(?, ?)", "INSERT INTO chats_contacts (chat_id, contact_id) VALUES(?, ?)",
paramsv![chat_id, contact_id as i32], paramsv![chat_id, contact_id as i32],
) )
.await .await?;
{ Ok(())
Ok(_) => true,
Err(err) => {
error!(
context,
"could not add {} to chat {} table: {}", contact_id, chat_id, err
);
false
}
}
} }
/// remove a contact from the chats_contact table /// remove a contact from the chats_contact table
@@ -2212,36 +2207,25 @@ pub(crate) async fn remove_from_chat_contacts_table(
context: &Context, context: &Context,
chat_id: ChatId, chat_id: ChatId,
contact_id: u32, contact_id: u32,
) -> bool { ) -> Result<()> {
match context context
.sql .sql
.execute( .execute(
"DELETE FROM chats_contacts WHERE chat_id=? AND contact_id=?", "DELETE FROM chats_contacts WHERE chat_id=? AND contact_id=?",
paramsv![chat_id, contact_id as i32], paramsv![chat_id, contact_id as i32],
) )
.await .await?;
{ Ok(())
Ok(_) => true,
Err(_) => {
warn!(
context,
"could not remove contact {:?} from chat {:?}", contact_id, chat_id
);
false
}
}
} }
/// Adds a contact to the chat. /// Adds a contact to the chat.
pub async fn add_contact_to_chat(context: &Context, chat_id: ChatId, contact_id: u32) -> bool { pub async fn add_contact_to_chat(
match add_contact_to_chat_ex(context, chat_id, contact_id, false).await { context: &Context,
Ok(res) => res, chat_id: ChatId,
Err(err) => { contact_id: u32,
error!(context, "failed to add contact: {}", err); ) -> Result<()> {
false add_contact_to_chat_ex(context, chat_id, contact_id, false).await?;
} Ok(())
}
} }
pub(crate) async fn add_contact_to_chat_ex( pub(crate) async fn add_contact_to_chat_ex(
@@ -2264,13 +2248,13 @@ pub(crate) async fn add_contact_to_chat_ex(
chat_id chat_id
); );
ensure!( ensure!(
Contact::real_exists_by_id(context, contact_id).await || contact_id == DC_CONTACT_ID_SELF, Contact::real_exists_by_id(context, contact_id).await? || contact_id == DC_CONTACT_ID_SELF,
"invalid contact_id {} for adding to group", "invalid contact_id {} for adding to group",
contact_id contact_id
); );
ensure!(!chat.is_mailing_list(), "Mailing lists can't be changed"); ensure!(!chat.is_mailing_list(), "Mailing lists can't be changed");
if !is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF).await { if !is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF).await? {
/* we should respect this - whatever we send to the group, it gets discarded anyway! */ /* we should respect this - whatever we send to the group, it gets discarded anyway! */
context.emit_event(EventType::ErrorSelfNotInGroup( context.emit_event(EventType::ErrorSelfNotInGroup(
"Cannot add contact to group; self not in group.".into(), "Cannot add contact to group; self not in group.".into(),
@@ -2295,14 +2279,14 @@ pub(crate) async fn add_contact_to_chat_ex(
return Ok(false); return Ok(false);
} }
if is_contact_in_chat(context, chat_id, contact_id).await { if is_contact_in_chat(context, chat_id, contact_id).await? {
if !from_handshake { if !from_handshake {
return Ok(true); return Ok(true);
} }
} else { } else {
// else continue and send status mail // else continue and send status mail
if chat.is_protected() if chat.is_protected()
&& contact.is_verified(context).await != VerifiedStatus::BidirectVerified && contact.is_verified(context).await? != VerifiedStatus::BidirectVerified
{ {
error!( error!(
context, context,
@@ -2310,9 +2294,10 @@ pub(crate) async fn add_contact_to_chat_ex(
); );
return Ok(false); return Ok(false);
} }
if !add_to_chat_contacts_table(context, chat_id, contact_id).await { if is_contact_in_chat(context, chat_id, contact_id).await? {
return Ok(false); return Ok(false);
} }
add_to_chat_contacts_table(context, chat_id, contact_id).await?;
} }
if chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 0 { if chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 0 {
msg.viewtype = Viewtype::Text; msg.viewtype = Viewtype::Text;
@@ -2480,7 +2465,7 @@ pub async fn remove_contact_from_chat(
/* this allows to delete pending references to deleted contacts. Of course, this should _not_ happen. */ /* this allows to delete pending references to deleted contacts. Of course, this should _not_ happen. */
if let Ok(chat) = Chat::load_from_db(context, chat_id).await { if let Ok(chat) = Chat::load_from_db(context, chat_id).await {
if chat.typ == Chattype::Group { if chat.typ == Chattype::Group {
if !is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF).await { if !is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF).await? {
context.emit_event(EventType::ErrorSelfNotInGroup( context.emit_event(EventType::ErrorSelfNotInGroup(
"Cannot remove contact from chat; self not in group.".into(), "Cannot remove contact from chat; self not in group.".into(),
)); ));
@@ -2518,7 +2503,9 @@ pub async fn remove_contact_from_chat(
// in order to correctly determine encryption so if we // in order to correctly determine encryption so if we
// removed it first, it would complicate the // removed it first, it would complicate the
// check/encryption logic. // check/encryption logic.
success = remove_from_chat_contacts_table(context, chat_id, contact_id).await; success = remove_from_chat_contacts_table(context, chat_id, contact_id)
.await
.is_ok();
context.emit_event(EventType::ChatModified(chat_id)); context.emit_event(EventType::ChatModified(chat_id));
} }
} }
@@ -2574,7 +2561,7 @@ pub async fn set_chat_name(context: &Context, chat_id: ChatId, new_name: &str) -
if chat.typ == Chattype::Group || chat.typ == Chattype::Mailinglist { if chat.typ == Chattype::Group || chat.typ == Chattype::Mailinglist {
if chat.name == new_name { if chat.name == new_name {
success = true; success = true;
} else if !is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF).await { } else if !is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF).await? {
context.emit_event(EventType::ErrorSelfNotInGroup( context.emit_event(EventType::ErrorSelfNotInGroup(
"Cannot set chat name; self not in group".into(), "Cannot set chat name; self not in group".into(),
)); ));
@@ -2640,7 +2627,7 @@ pub async fn set_chat_profile_image(
"Failed to set profile image; group does not exist" "Failed to set profile image; group does not exist"
); );
/* we should respect this - whatever we send to the group, it gets discarded anyway! */ /* we should respect this - whatever we send to the group, it gets discarded anyway! */
if !is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF).await { if !is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF).await? {
context.emit_event(EventType::ErrorSelfNotInGroup( context.emit_event(EventType::ErrorSelfNotInGroup(
"Cannot set chat profile image; self not in group.".into(), "Cannot set chat profile image; self not in group.".into(),
)); ));
@@ -2690,7 +2677,7 @@ pub async fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: ChatId)
chat_id.unarchive(context).await?; chat_id.unarchive(context).await?;
if let Ok(mut chat) = Chat::load_from_db(context, chat_id).await { if let Ok(mut chat) = Chat::load_from_db(context, chat_id).await {
ensure!(chat.can_send(context).await, "cannot send to {}", chat_id); ensure!(chat.can_send(context).await?, "cannot send to {}", chat_id);
curr_timestamp = dc_create_smeared_timestamps(context, msg_ids.len()).await; curr_timestamp = dc_create_smeared_timestamps(context, msg_ids.len()).await;
let ids = context let ids = context
.sql .sql
@@ -2755,7 +2742,7 @@ pub async fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: ChatId)
curr_timestamp += 1; curr_timestamp += 1;
new_msg_id = chat.prepare_msg_raw(context, &mut msg, fresh10).await?; new_msg_id = chat.prepare_msg_raw(context, &mut msg, fresh10).await?;
if let Some(send_job) = job::send_msg_job(context, new_msg_id).await? { if let Some(send_job) = job::send_msg_job(context, new_msg_id).await? {
job::add(context, send_job).await; job::add(context, send_job).await?;
} }
} }
created_chats.push(chat_id); created_chats.push(chat_id);
@@ -3014,12 +3001,8 @@ pub(crate) async fn add_info_msg(
chat_id: ChatId, chat_id: ChatId,
text: impl AsRef<str>, text: impl AsRef<str>,
timestamp: i64, timestamp: i64,
) { ) -> Result<MsgId> {
if let Err(e) = add_info_msg_with_cmd(context, chat_id, text, SystemMessage::Unknown, timestamp).await
add_info_msg_with_cmd(context, chat_id, text, SystemMessage::Unknown, timestamp).await
{
warn!(context, "Could not add info msg: {}", e);
}
} }
#[cfg(test)] #[cfg(test)]
@@ -3141,7 +3124,7 @@ mod tests {
} }
#[async_std::test] #[async_std::test]
async fn test_self_talk() { async fn test_self_talk() -> Result<()> {
let t = TestContext::new().await; let t = TestContext::new().await;
let chat = &t.get_self_chat().await; let chat = &t.get_self_chat().await;
assert_eq!(DC_CONTACT_ID_SELF, 1); assert_eq!(DC_CONTACT_ID_SELF, 1);
@@ -3149,9 +3132,10 @@ mod tests {
assert!(chat.is_self_talk()); assert!(chat.is_self_talk());
assert!(chat.visibility == ChatVisibility::Normal); assert!(chat.visibility == ChatVisibility::Normal);
assert!(!chat.is_device_talk()); assert!(!chat.is_device_talk());
assert!(chat.can_send(&t).await); assert!(chat.can_send(&t).await?);
assert_eq!(chat.name, stock_str::saved_messages(&t).await); assert_eq!(chat.name, stock_str::saved_messages(&t).await);
assert!(chat.get_profile_image(&t).await.unwrap().is_some()); assert!(chat.get_profile_image(&t).await?.is_some());
Ok(())
} }
#[async_std::test] #[async_std::test]
@@ -3190,7 +3174,7 @@ mod tests {
} }
#[async_std::test] #[async_std::test]
async fn test_add_device_msg_labelled() { async fn test_add_device_msg_labelled() -> Result<()> {
let t = TestContext::new().await; let t = TestContext::new().await;
// add two device-messages with the same label (second attempt is not added) // add two device-messages with the same label (second attempt is not added)
@@ -3207,9 +3191,7 @@ mod tests {
assert!(msg2_id.as_ref().unwrap().is_unset()); assert!(msg2_id.as_ref().unwrap().is_unset());
// check added message // check added message
let msg1 = message::Message::load_from_db(&t, *msg1_id.as_ref().unwrap()).await; let msg1 = message::Message::load_from_db(&t, *msg1_id.as_ref().unwrap()).await?;
assert!(msg1.is_ok());
let msg1 = msg1.unwrap();
assert_eq!(msg1_id.as_ref().unwrap(), &msg1.id); assert_eq!(msg1_id.as_ref().unwrap(), &msg1.id);
assert_eq!(msg1.text.as_ref().unwrap(), "first message"); assert_eq!(msg1.text.as_ref().unwrap(), "first message");
assert_eq!(msg1.from_id, DC_CONTACT_ID_DEVICE); assert_eq!(msg1.from_id, DC_CONTACT_ID_DEVICE);
@@ -3220,28 +3202,25 @@ mod tests {
// check device chat // check device chat
let chat_id = msg1.chat_id; let chat_id = msg1.chat_id;
assert_eq!(chat_id.get_msg_cnt(&t).await.unwrap(), 1); assert_eq!(chat_id.get_msg_cnt(&t).await?, 1);
assert!(!chat_id.is_special()); assert!(!chat_id.is_special());
let chat = Chat::load_from_db(&t, chat_id).await; let chat = Chat::load_from_db(&t, chat_id).await?;
assert!(chat.is_ok());
let chat = chat.unwrap();
assert_eq!(chat.get_type(), Chattype::Single); assert_eq!(chat.get_type(), Chattype::Single);
assert!(chat.is_device_talk()); assert!(chat.is_device_talk());
assert!(!chat.is_self_talk()); assert!(!chat.is_self_talk());
assert!(!chat.can_send(&t).await); assert!(!chat.can_send(&t).await?);
assert_eq!(chat.name, stock_str::device_messages(&t).await); assert_eq!(chat.name, stock_str::device_messages(&t).await);
assert!(chat.get_profile_image(&t).await.unwrap().is_some()); assert!(chat.get_profile_image(&t).await?.is_some());
// delete device message, make sure it is not added again // delete device message, make sure it is not added again
message::delete_msgs(&t, &[*msg1_id.as_ref().unwrap()]) message::delete_msgs(&t, &[*msg1_id.as_ref().unwrap()]).await?;
.await
.unwrap();
let msg1 = message::Message::load_from_db(&t, *msg1_id.as_ref().unwrap()).await; let msg1 = message::Message::load_from_db(&t, *msg1_id.as_ref().unwrap()).await;
assert!(msg1.is_err() || msg1.unwrap().chat_id.is_trash()); assert!(msg1.is_err() || msg1.unwrap().chat_id.is_trash());
let msg3_id = add_device_msg(&t, Some("any-label"), Some(&mut msg2)).await; let msg3_id = add_device_msg(&t, Some("any-label"), Some(&mut msg2)).await;
assert!(msg3_id.is_ok()); assert!(msg3_id.is_ok());
assert!(msg2_id.as_ref().unwrap().is_unset()); assert!(msg2_id.as_ref().unwrap().is_unset());
Ok(())
} }
#[async_std::test] #[async_std::test]
@@ -3566,24 +3545,21 @@ mod tests {
} }
#[async_std::test] #[async_std::test]
async fn test_shall_attach_selfavatar() { async fn test_shall_attach_selfavatar() -> Result<()> {
let t = TestContext::new().await; let t = TestContext::new().await;
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo") let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
.await assert!(!shall_attach_selfavatar(&t, chat_id).await?);
.unwrap();
assert!(!shall_attach_selfavatar(&t, chat_id).await.unwrap());
let (contact_id, _) = let (contact_id, _) =
Contact::add_or_lookup(&t, "", "foo@bar.org", Origin::IncomingUnknownTo) Contact::add_or_lookup(&t, "", "foo@bar.org", Origin::IncomingUnknownTo).await?;
.await add_contact_to_chat(&t, chat_id, contact_id).await?;
.unwrap(); assert!(!shall_attach_selfavatar(&t, chat_id).await?);
add_contact_to_chat(&t, chat_id, contact_id).await; t.set_config(Config::Selfavatar, None).await?; // setting to None also forces re-sending
assert!(!shall_attach_selfavatar(&t, chat_id).await.unwrap()); assert!(shall_attach_selfavatar(&t, chat_id).await?);
t.set_config(Config::Selfavatar, None).await.unwrap(); // setting to None also forces re-sending
assert!(shall_attach_selfavatar(&t, chat_id).await.unwrap());
assert!(chat_id.set_selfavatar_timestamp(&t, time()).await.is_ok()); chat_id.set_selfavatar_timestamp(&t, time()).await?;
assert!(!shall_attach_selfavatar(&t, chat_id).await.unwrap()); assert!(!shall_attach_selfavatar(&t, chat_id).await?);
Ok(())
} }
#[async_std::test] #[async_std::test]
@@ -3638,12 +3614,10 @@ mod tests {
} }
#[async_std::test] #[async_std::test]
async fn test_add_info_msg() { async fn test_add_info_msg() -> Result<()> {
let t = TestContext::new().await; let t = TestContext::new().await;
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo") let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
.await add_info_msg(&t, chat_id, "foo info", 200000).await?;
.unwrap();
add_info_msg(&t, chat_id, "foo info", 200000).await;
let msg = t.get_last_msg_in(chat_id).await; let msg = t.get_last_msg_in(chat_id).await;
assert_eq!(msg.get_chat_id(), chat_id); assert_eq!(msg.get_chat_id(), chat_id);
@@ -3651,6 +3625,7 @@ mod tests {
assert_eq!(msg.get_text().unwrap(), "foo info"); assert_eq!(msg.get_text().unwrap(), "foo info");
assert!(msg.is_info()); assert!(msg.is_info());
assert_eq!(msg.get_info_type(), SystemMessage::Unknown); assert_eq!(msg.get_info_type(), SystemMessage::Unknown);
Ok(())
} }
#[async_std::test] #[async_std::test]
@@ -3807,42 +3782,24 @@ mod tests {
} }
#[async_std::test] #[async_std::test]
async fn test_group_with_removed_message_id() { async fn test_group_with_removed_message_id() -> Result<()> {
// Alice creates a group with Bob, sends a message to bob // Alice creates a group with Bob, sends a message to bob
let alice = TestContext::new_alice().await; let alice = TestContext::new_alice().await;
let bob = TestContext::new_bob().await; let bob = TestContext::new_bob().await;
alice alice.set_config(Config::ShowEmails, Some("2")).await?;
.set_config(Config::ShowEmails, Some("2")) bob.set_config(Config::ShowEmails, Some("2")).await?;
.await
.unwrap();
bob.set_config(Config::ShowEmails, Some("2")).await.unwrap();
let (contact_id, _) = let (contact_id, _) =
Contact::add_or_lookup(&alice, "", "bob@example.net", Origin::ManuallyCreated) Contact::add_or_lookup(&alice, "", "bob@example.net", Origin::ManuallyCreated).await?;
.await let alice_chat_id = create_group_chat(&alice, ProtectionStatus::Unprotected, "grp").await?;
.unwrap(); let alice_chat = Chat::load_from_db(&alice, alice_chat_id).await?;
let alice_chat_id = create_group_chat(&alice, ProtectionStatus::Unprotected, "grp")
.await
.unwrap();
let alice_chat = Chat::load_from_db(&alice, alice_chat_id).await.unwrap();
add_contact_to_chat(&alice, alice_chat_id, contact_id).await; add_contact_to_chat(&alice, alice_chat_id, contact_id).await?;
assert_eq!(get_chat_contacts(&alice, alice_chat_id).await?.len(), 2);
send_text_msg(&alice, alice_chat_id, "hi!".to_string()).await?;
assert_eq!( assert_eq!(
get_chat_contacts(&alice, alice_chat_id) get_chat_msgs(&alice, alice_chat_id, 0, None).await?.len(),
.await
.unwrap()
.len(),
2
);
send_text_msg(&alice, alice_chat_id, "hi!".to_string())
.await
.unwrap();
assert_eq!(
get_chat_msgs(&alice, alice_chat_id, 0, None)
.await
.unwrap()
.len(),
1 1
); );
@@ -3858,17 +3815,15 @@ mod tests {
.unwrap(); .unwrap();
let msg = bob.get_last_msg().await; let msg = bob.get_last_msg().await;
let bob_chat = Chat::load_from_db(&bob, msg.chat_id).await.unwrap(); let bob_chat = Chat::load_from_db(&bob, msg.chat_id).await?;
assert_eq!(bob_chat.grpid, alice_chat.grpid); assert_eq!(bob_chat.grpid, alice_chat.grpid);
// Bob accepts contact request. // Bob accepts contact request.
bob_chat.id.unblock(&bob).await.unwrap(); bob_chat.id.unblock(&bob).await?;
// Bob answers - simulate a normal MUA by not setting `Chat-*`-headers; // Bob answers - simulate a normal MUA by not setting `Chat-*`-headers;
// moreover, Bob's SMTP-server also replaces the `Message-ID:`-header // moreover, Bob's SMTP-server also replaces the `Message-ID:`-header
send_text_msg(&bob, bob_chat.id, "ho!".to_string()) send_text_msg(&bob, bob_chat.id, "ho!".to_string()).await?;
.await
.unwrap();
let msg = bob.pop_sent_msg().await.payload(); let msg = bob.pop_sent_msg().await.payload();
let msg = msg.replace("Message-ID: <Gr.", "Message-ID: <XXX"); let msg = msg.replace("Message-ID: <Gr.", "Message-ID: <XXX");
let msg = msg.replace("Chat-", "XXXX-"); let msg = msg.replace("Chat-", "XXXX-");
@@ -3882,12 +3837,10 @@ mod tests {
assert_eq!(msg.chat_id, alice_chat_id); assert_eq!(msg.chat_id, alice_chat_id);
assert_eq!(msg.text, Some("ho!".to_string())); assert_eq!(msg.text, Some("ho!".to_string()));
assert_eq!( assert_eq!(
get_chat_msgs(&alice, alice_chat_id, 0, None) get_chat_msgs(&alice, alice_chat_id, 0, None).await?.len(),
.await
.unwrap()
.len(),
2 2
); );
Ok(())
} }
#[async_std::test] #[async_std::test]
@@ -4202,13 +4155,13 @@ mod tests {
let bob = Contact::create(&alice, "", "bob@f.br").await?; let bob = Contact::create(&alice, "", "bob@f.br").await?;
let chat_id = ChatId::create_for_contact(&alice, bob).await?; let chat_id = ChatId::create_for_contact(&alice, bob).await?;
let chat = Chat::load_from_db(&alice, chat_id).await?; let chat = Chat::load_from_db(&alice, chat_id).await?;
assert!(chat.can_send(&alice).await); assert!(chat.can_send(&alice).await?);
let chat_id = create_group_chat(&alice, ProtectionStatus::Unprotected, "foo").await?; let chat_id = create_group_chat(&alice, ProtectionStatus::Unprotected, "foo").await?;
assert_eq!( assert_eq!(
Chat::load_from_db(&alice, chat_id) Chat::load_from_db(&alice, chat_id)
.await? .await?
.can_send(&alice) .can_send(&alice)
.await, .await?,
true true
); );
remove_contact_from_chat(&alice, chat_id, DC_CONTACT_ID_SELF).await?; remove_contact_from_chat(&alice, chat_id, DC_CONTACT_ID_SELF).await?;
@@ -4216,7 +4169,7 @@ mod tests {
Chat::load_from_db(&alice, chat_id) Chat::load_from_db(&alice, chat_id)
.await? .await?
.can_send(&alice) .can_send(&alice)
.await, .await?,
false false
); );
Ok(()) Ok(())

View File

@@ -444,7 +444,7 @@ async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> {
ctx, ctx,
job::Job::new(Action::FetchExistingMsgs, 0, Params::new(), 0), job::Job::new(Action::FetchExistingMsgs, 0, Params::new(), 0),
) )
.await; .await?;
progress!(ctx, 940); progress!(ctx, 940);
update_device_chats_handle.await?; update_device_chats_handle.await?;

View File

@@ -2,7 +2,7 @@
use std::convert::{TryFrom, TryInto}; use std::convert::{TryFrom, TryInto};
use anyhow::{bail, ensure, format_err, Result}; use anyhow::{bail, ensure, format_err, Context as _, Result};
use async_std::path::PathBuf; use async_std::path::PathBuf;
use deltachat_derive::{FromSql, ToSql}; use deltachat_derive::{FromSql, ToSql};
use itertools::Itertools; use itertools::Itertools;
@@ -174,6 +174,12 @@ pub enum VerifiedStatus {
BidirectVerified = 2, BidirectVerified = 2,
} }
impl Default for VerifiedStatus {
fn default() -> Self {
Self::Unverified
}
}
impl Contact { impl Contact {
pub async fn load_from_db(context: &Context, contact_id: u32) -> Result<Self> { pub async fn load_from_db(context: &Context, contact_id: u32) -> Result<Self> {
let mut contact = context let mut contact = context
@@ -229,11 +235,9 @@ impl Contact {
} }
/// Check if a contact is blocked. /// Check if a contact is blocked.
pub async fn is_blocked_load(context: &Context, id: u32) -> bool { pub async fn is_blocked_load(context: &Context, id: u32) -> Result<bool> {
Self::load_from_db(context, id) let blocked = Self::load_from_db(context, id).await?.blocked;
.await Ok(blocked)
.map(|contact| contact.blocked)
.unwrap_or_default()
} }
/// Block the given contact. /// Block the given contact.
@@ -263,7 +267,7 @@ impl Contact {
let (contact_id, sth_modified) = let (contact_id, sth_modified) =
Contact::add_or_lookup(context, &name, &addr, Origin::ManuallyCreated).await?; Contact::add_or_lookup(context, &name, &addr, Origin::ManuallyCreated).await?;
let blocked = Contact::is_blocked_load(context, contact_id).await; let blocked = Contact::is_blocked_load(context, contact_id).await?;
match sth_modified { match sth_modified {
Modifier::None => {} Modifier::None => {}
Modifier::Modified | Modifier::Created => { Modifier::Modified | Modifier::Created => {
@@ -754,12 +758,9 @@ impl Contact {
/// Get blocked contacts. /// Get blocked contacts.
pub async fn get_all_blocked(context: &Context) -> Result<Vec<u32>> { pub async fn get_all_blocked(context: &Context) -> Result<Vec<u32>> {
if let Err(e) = Contact::update_blocked_mailinglist_contacts(context).await { Contact::update_blocked_mailinglist_contacts(context)
warn!( .await
context, .context("cannot update blocked mailinglist contacts")?;
"Cannot update blocked mailinglist contacts: {:?}", e
);
}
let list = context let list = context
.sql .sql
@@ -1018,7 +1019,7 @@ impl Contact {
/// ///
/// The UI may draw a checkbox or something like that beside verified contacts. /// The UI may draw a checkbox or something like that beside verified contacts.
/// ///
pub async fn is_verified(&self, context: &Context) -> VerifiedStatus { pub async fn is_verified(&self, context: &Context) -> Result<VerifiedStatus> {
self.is_verified_ex(context, None).await self.is_verified_ex(context, None).await
} }
@@ -1029,54 +1030,46 @@ impl Contact {
&self, &self,
context: &Context, context: &Context,
peerstate: Option<&Peerstate>, peerstate: Option<&Peerstate>,
) -> VerifiedStatus { ) -> Result<VerifiedStatus> {
// We're always sort of secured-verified as we could verify the key on this device any time with the key // We're always sort of secured-verified as we could verify the key on this device any time with the key
// on this device // on this device
if self.id == DC_CONTACT_ID_SELF { if self.id == DC_CONTACT_ID_SELF {
return VerifiedStatus::BidirectVerified; return Ok(VerifiedStatus::BidirectVerified);
} }
if let Some(peerstate) = peerstate { if let Some(peerstate) = peerstate {
if peerstate.verified_key.is_some() { if peerstate.verified_key.is_some() {
return VerifiedStatus::BidirectVerified; return Ok(VerifiedStatus::BidirectVerified);
} }
} }
let peerstate = match Peerstate::from_addr(context, &self.addr).await { if let Some(peerstate) = Peerstate::from_addr(context, &self.addr).await? {
Ok(peerstate) => peerstate, if peerstate.verified_key.is_some() {
Err(err) => { return Ok(VerifiedStatus::BidirectVerified);
warn!(
context,
"Failed to load peerstate for address {}: {}", self.addr, err
);
return VerifiedStatus::Unverified;
}
};
if let Some(ps) = peerstate {
if ps.verified_key.is_some() {
return VerifiedStatus::BidirectVerified;
} }
} }
VerifiedStatus::Unverified Ok(VerifiedStatus::Unverified)
} }
pub async fn addr_equals_contact(context: &Context, addr: &str, contact_id: u32) -> bool { pub async fn addr_equals_contact(
context: &Context,
addr: &str,
contact_id: u32,
) -> Result<bool> {
if addr.is_empty() { if addr.is_empty() {
return false; return Ok(false);
} }
if let Ok(contact) = Contact::load_from_db(context, contact_id).await { let contact = Contact::load_from_db(context, contact_id).await?;
if !contact.addr.is_empty() { if !contact.addr.is_empty() {
let normalized_addr = addr_normalize(addr); let normalized_addr = addr_normalize(addr);
if contact.addr == normalized_addr { if contact.addr == normalized_addr {
return true; return Ok(true);
}
} }
} }
false Ok(false)
} }
pub async fn get_real_cnt(context: &Context) -> Result<usize> { pub async fn get_real_cnt(context: &Context) -> Result<usize> {
@@ -1094,19 +1087,19 @@ impl Contact {
Ok(count) Ok(count)
} }
pub async fn real_exists_by_id(context: &Context, contact_id: u32) -> bool { pub async fn real_exists_by_id(context: &Context, contact_id: u32) -> Result<bool> {
if !context.sql.is_open().await || contact_id <= DC_CONTACT_ID_LAST_SPECIAL { if contact_id <= DC_CONTACT_ID_LAST_SPECIAL {
return false; return Ok(false);
} }
context let exists = context
.sql .sql
.exists( .exists(
"SELECT COUNT(*) FROM contacts WHERE id=?;", "SELECT COUNT(*) FROM contacts WHERE id=?;",
paramsv![contact_id as i32], paramsv![contact_id as i32],
) )
.await .await?;
.unwrap_or_default() Ok(exists)
} }
pub async fn scaleup_origin_by_id( pub async fn scaleup_origin_by_id(

View File

@@ -298,7 +298,7 @@ pub(crate) async fn dc_receive_imf_inner(
0, 0,
), ),
) )
.await; .await?;
} }
} else if insert_msg_id } else if insert_msg_id
.needs_move(context, server_folder.as_ref()) .needs_move(context, server_folder.as_ref())
@@ -311,7 +311,7 @@ pub(crate) async fn dc_receive_imf_inner(
context, context,
job::Job::new(Action::MoveMsg, insert_msg_id.to_u32(), Params::new(), 0), job::Job::new(Action::MoveMsg, insert_msg_id.to_u32(), Params::new(), 0),
) )
.await; .await?;
} else if !mime_parser.mdn_reports.is_empty() && mime_parser.has_chat_version() { } else if !mime_parser.mdn_reports.is_empty() && mime_parser.has_chat_version() {
// This is a Delta Chat MDN. Mark as read. // This is a Delta Chat MDN. Mark as read.
job::add( job::add(
@@ -323,7 +323,7 @@ pub(crate) async fn dc_receive_imf_inner(
0, 0,
), ),
) )
.await; .await?;
} }
} }
@@ -550,7 +550,7 @@ async fn add_parts(
// In lookup_chat_by_reply() and create_or_lookup_group(), it can happen that the message is put into a chat // In lookup_chat_by_reply() and create_or_lookup_group(), it can happen that the message is put into a chat
// but the From-address is not a member of this chat. // but the From-address is not a member of this chat.
if let Some(chat_id) = chat_id { if let Some(chat_id) = chat_id {
if !chat::is_contact_in_chat(context, chat_id, from_id as u32).await { if !chat::is_contact_in_chat(context, chat_id, from_id as u32).await? {
let chat = Chat::load_from_db(context, chat_id).await?; let chat = Chat::load_from_db(context, chat_id).await?;
if chat.is_protected() { if chat.is_protected() {
let s = stock_str::unknown_sender_for_chat(context).await; let s = stock_str::unknown_sender_for_chat(context).await;
@@ -774,7 +774,7 @@ async fn add_parts(
} }
} }
if chat_id.is_none() && allow_creation { if chat_id.is_none() && allow_creation {
let create_blocked = if !Contact::is_blocked_load(context, to_id).await { let create_blocked = if !Contact::is_blocked_load(context, to_id).await? {
Blocked::Not Blocked::Not
} else { } else {
Blocked::Request Blocked::Request
@@ -915,7 +915,7 @@ async fn add_parts(
stock_ephemeral_timer_changed(context, ephemeral_timer, from_id).await, stock_ephemeral_timer_changed(context, ephemeral_timer, from_id).await,
sort_timestamp, sort_timestamp,
) )
.await; .await?;
} }
} }
} else { } else {
@@ -974,7 +974,7 @@ async fn add_parts(
format!("Cannot set protection: {}", e), format!("Cannot set protection: {}", e),
sort_timestamp, sort_timestamp,
) )
.await; .await?;
return Ok(chat_id); // do not return an error as this would result in retrying the message return Ok(chat_id); // do not return an error as this would result in retrying the message
} }
} }
@@ -1680,7 +1680,7 @@ async fn create_or_lookup_group(
.update_timestamp(context, Param::MemberListTimestamp, sent_timestamp) .update_timestamp(context, Param::MemberListTimestamp, sent_timestamp)
.await? .await?
{ {
if !chat::is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF).await { if !chat::is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF).await? {
// Members could have been removed while we were // Members could have been removed while we were
// absent. We can't use existing member list and need to // absent. We can't use existing member list and need to
// start from scratch. // start from scratch.
@@ -1693,20 +1693,20 @@ async fn create_or_lookup_group(
.await .await
.ok(); .ok();
chat::add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF).await; chat::add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF).await?;
} }
if from_id > DC_CONTACT_ID_LAST_SPECIAL if from_id > DC_CONTACT_ID_LAST_SPECIAL
&& !Contact::addr_equals_contact(context, &self_addr, from_id).await && !Contact::addr_equals_contact(context, &self_addr, from_id).await?
&& !chat::is_contact_in_chat(context, chat_id, from_id).await && !chat::is_contact_in_chat(context, chat_id, from_id).await?
{ {
chat::add_to_chat_contacts_table(context, chat_id, from_id).await; chat::add_to_chat_contacts_table(context, chat_id, from_id).await?;
} }
for &to_id in to_ids.iter() { for &to_id in to_ids.iter() {
info!(context, "adding to={:?} to chat id={}", to_id, chat_id); info!(context, "adding to={:?} to chat id={}", to_id, chat_id);
if !Contact::addr_equals_contact(context, &self_addr, to_id).await if !Contact::addr_equals_contact(context, &self_addr, to_id).await?
&& !chat::is_contact_in_chat(context, chat_id, to_id).await && !chat::is_contact_in_chat(context, chat_id, to_id).await?
{ {
chat::add_to_chat_contacts_table(context, chat_id, to_id).await; chat::add_to_chat_contacts_table(context, chat_id, to_id).await?;
} }
} }
send_EVENT_CHAT_MODIFIED = true; send_EVENT_CHAT_MODIFIED = true;
@@ -1716,7 +1716,7 @@ async fn create_or_lookup_group(
.update_timestamp(context, Param::MemberListTimestamp, sent_timestamp) .update_timestamp(context, Param::MemberListTimestamp, sent_timestamp)
.await? .await?
{ {
chat::remove_from_chat_contacts_table(context, chat_id, contact_id).await; chat::remove_from_chat_contacts_table(context, chat_id, contact_id).await?;
send_EVENT_CHAT_MODIFIED = true; send_EVENT_CHAT_MODIFIED = true;
} }
} }
@@ -1832,7 +1832,7 @@ async fn create_or_lookup_mailinglist(
)) ))
})?; })?;
chat::add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF).await; chat::add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF).await?;
Ok(Some((chat_id, Blocked::Request))) Ok(Some((chat_id, Blocked::Request)))
} else { } else {
info!(context, "creating list forbidden by caller"); info!(context, "creating list forbidden by caller");
@@ -1926,7 +1926,7 @@ async fn create_adhoc_group(
) )
.await?; .await?;
for &member_id in member_ids.iter() { for &member_id in member_ids.iter() {
chat::add_to_chat_contacts_table(context, new_chat_id, member_id).await; chat::add_to_chat_contacts_table(context, new_chat_id, member_id).await?;
} }
context.emit_event(EventType::ChatModified(new_chat_id)); context.emit_event(EventType::ChatModified(new_chat_id));
@@ -2047,7 +2047,7 @@ async fn check_verified_properties(
let peerstate = Peerstate::from_addr(context, contact.get_addr()).await?; let peerstate = Peerstate::from_addr(context, contact.get_addr()).await?;
if peerstate.is_none() if peerstate.is_none()
|| contact.is_verified_ex(context, peerstate.as_ref()).await || contact.is_verified_ex(context, peerstate.as_ref()).await?
!= VerifiedStatus::BidirectVerified != VerifiedStatus::BidirectVerified
{ {
bail!( bail!(
@@ -2511,24 +2511,22 @@ mod tests {
} }
#[async_std::test] #[async_std::test]
async fn test_read_receipt_and_unarchive() { async fn test_read_receipt_and_unarchive() -> Result<()> {
// create alice's account // create alice's account
let t = TestContext::new_alice().await; let t = TestContext::new_alice().await;
let bob_id = Contact::create(&t, "bob", "bob@example.com").await.unwrap(); let bob_id = Contact::create(&t, "bob", "bob@example.com").await?;
let one2one_id = ChatId::create_for_contact(&t, bob_id).await.unwrap(); let one2one_id = ChatId::create_for_contact(&t, bob_id).await?;
one2one_id one2one_id
.set_visibility(&t, ChatVisibility::Archived) .set_visibility(&t, ChatVisibility::Archived)
.await .await
.unwrap(); .unwrap();
let one2one = Chat::load_from_db(&t, one2one_id).await.unwrap(); let one2one = Chat::load_from_db(&t, one2one_id).await?;
assert!(one2one.get_visibility() == ChatVisibility::Archived); assert!(one2one.get_visibility() == ChatVisibility::Archived);
// create a group with bob, archive group // create a group with bob, archive group
let group_id = chat::create_group_chat(&t, ProtectionStatus::Unprotected, "foo") let group_id = chat::create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
.await chat::add_contact_to_chat(&t, group_id, bob_id).await?;
.unwrap();
chat::add_contact_to_chat(&t, group_id, bob_id).await;
assert_eq!( assert_eq!(
chat::get_chat_msgs(&t, group_id, 0, None) chat::get_chat_msgs(&t, group_id, 0, None)
.await .await
@@ -2538,16 +2536,14 @@ mod tests {
); );
group_id group_id
.set_visibility(&t, ChatVisibility::Archived) .set_visibility(&t, ChatVisibility::Archived)
.await .await?;
.unwrap(); let group = Chat::load_from_db(&t, group_id).await?;
let group = Chat::load_from_db(&t, group_id).await.unwrap();
assert!(group.get_visibility() == ChatVisibility::Archived); assert!(group.get_visibility() == ChatVisibility::Archived);
// everything archived, chatlist should be empty // everything archived, chatlist should be empty
assert_eq!( assert_eq!(
Chatlist::try_load(&t, DC_GCL_NO_SPECIALS, None, None) Chatlist::try_load(&t, DC_GCL_NO_SPECIALS, None, None)
.await .await?
.unwrap()
.len(), .len(),
0 0
); );
@@ -2575,13 +2571,12 @@ mod tests {
1, 1,
false, false,
) )
.await .await?;
.unwrap();
let msg = get_chat_msg(&t, group_id, 0, 1).await; let msg = get_chat_msg(&t, group_id, 0, 1).await;
assert_eq!(msg.is_dc_message, MessengerMessage::Yes); assert_eq!(msg.is_dc_message, MessengerMessage::Yes);
assert_eq!(msg.text.unwrap(), "hello"); assert_eq!(msg.text.unwrap(), "hello");
assert_eq!(msg.state, MessageState::OutDelivered); assert_eq!(msg.state, MessageState::OutDelivered);
let group = Chat::load_from_db(&t, group_id).await.unwrap(); let group = Chat::load_from_db(&t, group_id).await?;
assert!(group.get_visibility() == ChatVisibility::Normal); assert!(group.get_visibility() == ChatVisibility::Normal);
// bob sends a read receipt to the group // bob sends a read receipt to the group
@@ -2622,27 +2617,21 @@ mod tests {
1, 1,
false, false,
) )
.await.unwrap(); .await?;
assert_eq!( assert_eq!(chat::get_chat_msgs(&t, group_id, 0, None).await?.len(), 1);
chat::get_chat_msgs(&t, group_id, 0, None) let msg = message::Message::load_from_db(&t, msg.id).await?;
.await
.unwrap()
.len(),
1
);
let msg = message::Message::load_from_db(&t, msg.id).await.unwrap();
assert_eq!(msg.state, MessageState::OutMdnRcvd); assert_eq!(msg.state, MessageState::OutMdnRcvd);
// check, the read-receipt has not unarchived the one2one // check, the read-receipt has not unarchived the one2one
assert_eq!( assert_eq!(
Chatlist::try_load(&t, DC_GCL_NO_SPECIALS, None, None) Chatlist::try_load(&t, DC_GCL_NO_SPECIALS, None, None)
.await .await?
.unwrap()
.len(), .len(),
1 1
); );
let one2one = Chat::load_from_db(&t, one2one_id).await.unwrap(); let one2one = Chat::load_from_db(&t, one2one_id).await?;
assert!(one2one.get_visibility() == ChatVisibility::Archived); assert!(one2one.get_visibility() == ChatVisibility::Archived);
Ok(())
} }
#[async_std::test] #[async_std::test]
@@ -2937,7 +2926,7 @@ mod tests {
} }
#[async_std::test] #[async_std::test]
async fn test_parse_ndn_group_msg() { async fn test_parse_ndn_group_msg() -> Result<()> {
let t = TestContext::new().await; let t = TestContext::new().await;
t.configure_addr("alice@gmail.com").await; t.configure_addr("alice@gmail.com").await;
@@ -2959,32 +2948,32 @@ mod tests {
1, 1,
false, false,
) )
.await .await?;
.unwrap();
let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap(); let chats = Chatlist::try_load(&t, 0, None, None).await?;
let msg_id = chats.get_msg_id(0).unwrap().unwrap(); let msg_id = chats.get_msg_id(0)?.unwrap();
let raw = include_bytes!("../test-data/message/gmail_ndn_group.eml"); let raw = include_bytes!("../test-data/message/gmail_ndn_group.eml");
dc_receive_imf(&t, raw, "INBOX", 1, false).await.unwrap(); dc_receive_imf(&t, raw, "INBOX", 1, false).await?;
let msg = Message::load_from_db(&t, msg_id).await.unwrap(); let msg = Message::load_from_db(&t, msg_id).await?;
assert_eq!(msg.state, MessageState::OutFailed); assert_eq!(msg.state, MessageState::OutFailed);
let msgs = chat::get_chat_msgs(&t, msg.chat_id, 0, None).await.unwrap(); let msgs = chat::get_chat_msgs(&t, msg.chat_id, 0, None).await?;
let msg_id = if let ChatItem::Message { msg_id } = msgs.last().unwrap() { let msg_id = if let ChatItem::Message { msg_id } = msgs.last().unwrap() {
msg_id msg_id
} else { } else {
panic!("Wrong item type"); panic!("Wrong item type");
}; };
let last_msg = Message::load_from_db(&t, *msg_id).await.unwrap(); let last_msg = Message::load_from_db(&t, *msg_id).await?;
assert_eq!( assert_eq!(
last_msg.text, last_msg.text,
Some(stock_str::failed_sending_to(&t, "assidhfaaspocwaeofi@gmail.com").await,) Some(stock_str::failed_sending_to(&t, "assidhfaaspocwaeofi@gmail.com").await,)
); );
assert_eq!(last_msg.from_id, DC_CONTACT_ID_INFO); assert_eq!(last_msg.from_id, DC_CONTACT_ID_INFO);
Ok(())
} }
async fn load_imf_email(context: &Context, imf_raw: &[u8]) -> Message { async fn load_imf_email(context: &Context, imf_raw: &[u8]) -> Message {
@@ -3032,57 +3021,43 @@ mod tests {
hello back\n"; hello back\n";
#[async_std::test] #[async_std::test]
async fn test_github_mailing_list() { async fn test_github_mailing_list() -> Result<()> {
let t = TestContext::new_alice().await; let t = TestContext::new_alice().await;
t.ctx t.ctx.set_config(Config::ShowEmails, Some("2")).await?;
.set_config(Config::ShowEmails, Some("2"))
.await
.unwrap();
dc_receive_imf(&t.ctx, GH_MAILINGLIST, "INBOX", 1, false) dc_receive_imf(&t.ctx, GH_MAILINGLIST, "INBOX", 1, false).await?;
.await
.unwrap();
let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); let chats = Chatlist::try_load(&t.ctx, 0, None, None).await?;
assert_eq!(chats.len(), 1); assert_eq!(chats.len(), 1);
let chat_id = chats.get_chat_id(0); let chat_id = chats.get_chat_id(0);
chat_id.accept(&t).await.unwrap(); chat_id.accept(&t).await.unwrap();
let chat = chat::Chat::load_from_db(&t.ctx, chat_id).await.unwrap(); let chat = chat::Chat::load_from_db(&t.ctx, chat_id).await?;
assert!(chat.is_mailing_list()); assert!(chat.is_mailing_list());
assert_eq!(chat.can_send(&t.ctx).await, false); assert_eq!(chat.can_send(&t.ctx).await?, false);
assert_eq!(chat.name, "deltachat/deltachat-core-rust"); assert_eq!(chat.name, "deltachat/deltachat-core-rust");
assert_eq!( assert_eq!(chat::get_chat_contacts(&t.ctx, chat_id).await?.len(), 1);
chat::get_chat_contacts(&t.ctx, chat_id)
.await
.unwrap()
.len(),
1
);
dc_receive_imf(&t.ctx, GH_MAILINGLIST2, "INBOX", 1, false) dc_receive_imf(&t.ctx, GH_MAILINGLIST2, "INBOX", 1, false).await?;
.await
.unwrap();
let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); let chats = Chatlist::try_load(&t.ctx, 0, None, None).await?;
assert_eq!(chats.len(), 1); assert_eq!(chats.len(), 1);
let contacts = Contact::get_all(&t.ctx, 0, None as Option<String>) let contacts = Contact::get_all(&t.ctx, 0, None as Option<String>).await?;
.await
.unwrap();
assert_eq!(contacts.len(), 0); // mailing list recipients and senders do not count as "known contacts" assert_eq!(contacts.len(), 0); // mailing list recipients and senders do not count as "known contacts"
let msg1 = get_chat_msg(&t, chat_id, 0, 2).await; let msg1 = get_chat_msg(&t, chat_id, 0, 2).await;
let contact1 = Contact::load_from_db(&t.ctx, msg1.from_id).await.unwrap(); let contact1 = Contact::load_from_db(&t.ctx, msg1.from_id).await?;
assert_eq!(contact1.get_addr(), "notifications@github.com"); assert_eq!(contact1.get_addr(), "notifications@github.com");
assert_eq!(contact1.get_display_name(), "notifications@github.com"); // Make sure this is not "Max Mustermann" or somethinng assert_eq!(contact1.get_display_name(), "notifications@github.com"); // Make sure this is not "Max Mustermann" or somethinng
let msg2 = get_chat_msg(&t, chat_id, 1, 2).await; let msg2 = get_chat_msg(&t, chat_id, 1, 2).await;
let contact2 = Contact::load_from_db(&t.ctx, msg2.from_id).await.unwrap(); let contact2 = Contact::load_from_db(&t.ctx, msg2.from_id).await?;
assert_eq!(contact2.get_addr(), "notifications@github.com"); assert_eq!(contact2.get_addr(), "notifications@github.com");
assert_eq!(msg1.get_override_sender_name().unwrap(), "Max Mustermann"); assert_eq!(msg1.get_override_sender_name().unwrap(), "Max Mustermann");
assert_eq!(msg2.get_override_sender_name().unwrap(), "Github"); assert_eq!(msg2.get_override_sender_name().unwrap(), "Github");
Ok(())
} }
static DC_MAILINGLIST: &[u8] = b"Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n\ static DC_MAILINGLIST: &[u8] = b"Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n\

View File

@@ -85,7 +85,7 @@ impl MsgId {
context, context,
Job::new(Action::DownloadMsg, self.to_u32(), Params::new(), 0), Job::new(Action::DownloadMsg, self.to_u32(), Params::new(), 0),
) )
.await; .await?;
} }
} }
Ok(()) Ok(())

View File

@@ -161,15 +161,19 @@ pub async fn try_decrypt(
let mut peerstate = Peerstate::from_addr(context, &from).await?; let mut peerstate = Peerstate::from_addr(context, &from).await?;
// Apply Autocrypt header // Apply Autocrypt header
if let Some(ref header) = Aheader::from_headers(context, &from, &mail.headers) { match Aheader::from_headers(&from, &mail.headers) {
if let Some(ref mut peerstate) = peerstate { Ok(Some(ref header)) => {
peerstate.apply_header(header, message_time); if let Some(ref mut peerstate) = peerstate {
peerstate.save_to_db(&context.sql, false).await?; peerstate.apply_header(header, message_time);
} else { peerstate.save_to_db(&context.sql, false).await?;
let p = Peerstate::from_header(header, message_time); } else {
p.save_to_db(&context.sql, true).await?; let p = Peerstate::from_header(header, message_time);
peerstate = Some(p); p.save_to_db(&context.sql, true).await?;
peerstate = Some(p);
}
} }
Ok(None) => {}
Err(err) => warn!(context, "Failed to parse Autocrypt header: {}", err),
} }
// Possibly perform decryption // Possibly perform decryption

View File

@@ -1614,13 +1614,13 @@ async fn precheck_imf(
context, context,
job::Job::new(Action::MoveMsg, msg_id.to_u32(), Params::new(), 0), job::Job::new(Action::MoveMsg, msg_id.to_u32(), Params::new(), 0),
) )
.await; .await?;
} else { } else {
job::add( job::add(
context, context,
job::Job::new(Action::MarkseenMsgOnImap, msg_id.to_u32(), Params::new(), 0), job::Job::new(Action::MarkseenMsgOnImap, msg_id.to_u32(), Params::new(), 0),
) )
.await; .await?;
} }
} }
} else if old_server_folder != server_folder { } else if old_server_folder != server_folder {
@@ -1663,7 +1663,7 @@ async fn precheck_imf(
context, context,
job::Job::new(Action::MarkseenMsgOnImap, msg_id.to_u32(), Params::new(), 0), job::Job::new(Action::MarkseenMsgOnImap, msg_id.to_u32(), Params::new(), 0),
) )
.await; .await?;
} }
} }
} }

View File

@@ -842,15 +842,15 @@ async fn kill_ids(context: &Context, job_ids: &[u32]) -> Result<()> {
Ok(()) Ok(())
} }
pub async fn action_exists(context: &Context, action: Action) -> bool { pub async fn action_exists(context: &Context, action: Action) -> Result<bool> {
context let exists = context
.sql .sql
.exists( .exists(
"SELECT COUNT(*) FROM jobs WHERE action=?;", "SELECT COUNT(*) FROM jobs WHERE action=?;",
paramsv![action], paramsv![action],
) )
.await .await?;
.unwrap_or_default() Ok(exists)
} }
async fn set_delivered(context: &Context, msg_id: MsgId) -> Result<()> { async fn set_delivered(context: &Context, msg_id: MsgId) -> Result<()> {
@@ -1185,7 +1185,7 @@ async fn send_mdn(context: &Context, msg: &Message) -> Result<()> {
let mut param = Params::new(); let mut param = Params::new();
param.set(Param::MsgId, msg.id.to_u32().to_string()); param.set(Param::MsgId, msg.id.to_u32().to_string());
add(context, Job::new(Action::SendMdn, msg.from_id, param, 0)).await; add(context, Job::new(Action::SendMdn, msg.from_id, param, 0)).await?;
Ok(()) Ok(())
} }
@@ -1196,7 +1196,7 @@ pub(crate) async fn schedule_resync(context: &Context) -> Result<()> {
context, context,
Job::new(Action::ResyncFolders, 0, Params::new(), 0), Job::new(Action::ResyncFolders, 0, Params::new(), 0),
) )
.await; .await?;
Ok(()) Ok(())
} }
@@ -1211,12 +1211,10 @@ pub fn create(action: Action, foreign_id: u32, param: Params, delay_seconds: i64
} }
/// Adds a job to the database, scheduling it. /// Adds a job to the database, scheduling it.
pub async fn add(context: &Context, job: Job) { pub async fn add(context: &Context, job: Job) -> Result<()> {
let action = job.action; let action = job.action;
let delay_seconds = job.delay_seconds(); let delay_seconds = job.delay_seconds();
job.save(context).await.unwrap_or_else(|err| { job.save(context).await.context("failed to save job")?;
error!(context, "failed to save job: {}", err);
});
if delay_seconds == 0 { if delay_seconds == 0 {
match action { match action {
@@ -1245,6 +1243,7 @@ pub async fn add(context: &Context, job: Job) {
} }
} }
} }
Ok(())
} }
async fn load_housekeeping_job(context: &Context) -> Result<Option<Job>> { async fn load_housekeeping_job(context: &Context) -> Result<Option<Job>> {

View File

@@ -223,11 +223,11 @@ pub async fn send_locations_to_chat(
.unwrap_or_default(); .unwrap_or_default();
} else if 0 == seconds && is_sending_locations_before { } else if 0 == seconds && is_sending_locations_before {
let stock_str = stock_str::msg_location_disabled(context).await; let stock_str = stock_str::msg_location_disabled(context).await;
chat::add_info_msg(context, chat_id, stock_str, now).await; chat::add_info_msg(context, chat_id, stock_str, now).await?;
} }
context.emit_event(EventType::ChatModified(chat_id)); context.emit_event(EventType::ChatModified(chat_id));
if 0 != seconds { if 0 != seconds {
schedule_maybe_send_locations(context, false).await; schedule_maybe_send_locations(context, false).await?;
job::add( job::add(
context, context,
job::Job::new( job::Job::new(
@@ -237,19 +237,20 @@ pub async fn send_locations_to_chat(
seconds + 1, seconds + 1,
), ),
) )
.await; .await?;
} }
Ok(()) Ok(())
} }
async fn schedule_maybe_send_locations(context: &Context, force_schedule: bool) { async fn schedule_maybe_send_locations(context: &Context, force_schedule: bool) -> Result<()> {
if force_schedule || !job::action_exists(context, job::Action::MaybeSendLocations).await { if force_schedule || !job::action_exists(context, job::Action::MaybeSendLocations).await? {
job::add( job::add(
context, context,
job::Job::new(job::Action::MaybeSendLocations, 0, Params::new(), 60), job::Job::new(job::Action::MaybeSendLocations, 0, Params::new(), 60),
) )
.await; .await?;
}; };
Ok(())
} }
/// Returns whether `chat_id` or any chat is sending locations. /// Returns whether `chat_id` or any chat is sending locations.
@@ -324,7 +325,7 @@ pub async fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64
if continue_streaming { if continue_streaming {
context.emit_event(EventType::LocationChanged(Some(DC_CONTACT_ID_SELF))); context.emit_event(EventType::LocationChanged(Some(DC_CONTACT_ID_SELF)));
}; };
schedule_maybe_send_locations(context, false).await; schedule_maybe_send_locations(context, false).await.ok();
} }
continue_streaming continue_streaming
@@ -697,7 +698,7 @@ pub(crate) async fn job_maybe_send_locations(context: &Context, _job: &Job) -> j
} }
if continue_streaming { if continue_streaming {
schedule_maybe_send_locations(context, true).await; job_try!(schedule_maybe_send_locations(context, true).await);
} }
job::Status::Finished(Ok(())) job::Status::Finished(Ok(()))
} }
@@ -743,7 +744,7 @@ pub(crate) async fn job_maybe_send_locations_ended(
); );
let stock_str = stock_str::msg_location_disabled(context).await; let stock_str = stock_str::msg_location_disabled(context).await;
chat::add_info_msg(context, chat_id, stock_str, now).await; job_try!(chat::add_info_msg(context, chat_id, stock_str, now).await);
context.emit_event(EventType::ChatModified(chat_id)); context.emit_event(EventType::ChatModified(chat_id));
} }
} }

View File

@@ -1295,7 +1295,7 @@ pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
context, context,
job::Job::new(Action::DeleteMsgOnImap, msg_id.to_u32(), Params::new(), 0), job::Job::new(Action::DeleteMsgOnImap, msg_id.to_u32(), Params::new(), 0),
) )
.await; .await?;
} }
if !msg_ids.is_empty() { if !msg_ids.is_empty() {
@@ -1308,7 +1308,7 @@ pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
context, context,
job::Job::new(Action::Housekeeping, 0, Params::new(), 10), job::Job::new(Action::Housekeeping, 0, Params::new(), 10),
) )
.await; .await?;
} }
Ok(()) Ok(())
} }
@@ -1383,7 +1383,7 @@ pub async fn markseen_msgs(context: &Context, msg_ids: Vec<MsgId>) -> Result<()>
context, context,
job::Job::new(Action::MarkseenMsgOnImap, id.to_u32(), Params::new(), 0), job::Job::new(Action::MarkseenMsgOnImap, id.to_u32(), Params::new(), 0),
) )
.await; .await?;
updated_chat_ids.insert(curr_chat_id, true); updated_chat_ids.insert(curr_chat_id, true);
} }
} }
@@ -1654,7 +1654,7 @@ async fn ndn_maybe_add_info_msg(
text, text,
dc_create_smeared_timestamp(context).await, dc_create_smeared_timestamp(context).await,
) )
.await; .await?;
context.emit_event(EventType::ChatModified(chat_id)); context.emit_event(EventType::ChatModified(chat_id));
} }
} }

View File

@@ -1605,27 +1605,30 @@ mod tests {
} }
#[async_std::test] #[async_std::test]
async fn test_subject_in_group() { async fn test_subject_in_group() -> Result<()> {
async fn send_msg_get_subject( async fn send_msg_get_subject(
t: &TestContext, t: &TestContext,
group_id: ChatId, group_id: ChatId,
quote: Option<&Message>, quote: Option<&Message>,
) -> String { ) -> Result<String> {
let mut new_msg = Message::new(Viewtype::Text); let mut new_msg = Message::new(Viewtype::Text);
new_msg.set_text(Some("Hi".to_string())); new_msg.set_text(Some("Hi".to_string()));
if let Some(q) = quote { if let Some(q) = quote {
new_msg.set_quote(t, q).await.unwrap(); new_msg.set_quote(t, q).await?;
} }
let sent = t.send_msg(group_id, &mut new_msg).await; let sent = t.send_msg(group_id, &mut new_msg).await;
get_subject(t, sent).await get_subject(t, sent).await
} }
async fn get_subject(t: &TestContext, sent: crate::test_utils::SentMessage) -> String { async fn get_subject(
t: &TestContext,
sent: crate::test_utils::SentMessage,
) -> Result<String> {
let parsed_subject = t.parse_msg(&sent).await.get_subject().unwrap(); let parsed_subject = t.parse_msg(&sent).await.get_subject().unwrap();
let sent_msg = Message::load_from_db(t, sent.sender_msg_id).await.unwrap(); let sent_msg = Message::load_from_db(t, sent.sender_msg_id).await?;
assert_eq!(parsed_subject, sent_msg.subject); assert_eq!(parsed_subject, sent_msg.subject);
parsed_subject Ok(parsed_subject)
} }
// 6. Test that in a group, replies also take the quoted message's subject, while non-replies use the group title as subject // 6. Test that in a group, replies also take the quoted message's subject, while non-replies use the group title as subject
@@ -1634,13 +1637,13 @@ mod tests {
chat::create_group_chat(&t, chat::ProtectionStatus::Unprotected, "groupname") // TODO encodings, ä chat::create_group_chat(&t, chat::ProtectionStatus::Unprotected, "groupname") // TODO encodings, ä
.await .await
.unwrap(); .unwrap();
let bob = Contact::create(&t, "", "bob@example.org").await.unwrap(); let bob = Contact::create(&t, "", "bob@example.org").await?;
chat::add_contact_to_chat(&t, group_id, bob).await; chat::add_contact_to_chat(&t, group_id, bob).await?;
let subject = send_msg_get_subject(&t, group_id, None).await; let subject = send_msg_get_subject(&t, group_id, None).await?;
assert_eq!(subject, "groupname"); assert_eq!(subject, "groupname");
let subject = send_msg_get_subject(&t, group_id, None).await; let subject = send_msg_get_subject(&t, group_id, None).await?;
assert_eq!(subject, "Re: groupname"); assert_eq!(subject, "Re: groupname");
dc_receive_imf( dc_receive_imf(
@@ -1662,28 +1665,26 @@ mod tests {
5, 5,
false, false,
) )
.await .await?;
.unwrap();
let message_from_bob = t.get_last_msg().await; let message_from_bob = t.get_last_msg().await;
let subject = send_msg_get_subject(&t, group_id, None).await; let subject = send_msg_get_subject(&t, group_id, None).await?;
assert_eq!(subject, "Re: groupname"); assert_eq!(subject, "Re: groupname");
let subject = send_msg_get_subject(&t, group_id, Some(&message_from_bob)).await; let subject = send_msg_get_subject(&t, group_id, Some(&message_from_bob)).await?;
let outgoing_quoting_msg = t.get_last_msg().await; let outgoing_quoting_msg = t.get_last_msg().await;
assert_eq!(subject, "Re: Different subject"); assert_eq!(subject, "Re: Different subject");
let subject = send_msg_get_subject(&t, group_id, None).await; let subject = send_msg_get_subject(&t, group_id, None).await?;
assert_eq!(subject, "Re: groupname"); assert_eq!(subject, "Re: groupname");
let subject = send_msg_get_subject(&t, group_id, Some(&outgoing_quoting_msg)).await; let subject = send_msg_get_subject(&t, group_id, Some(&outgoing_quoting_msg)).await?;
assert_eq!(subject, "Re: Different subject"); assert_eq!(subject, "Re: Different subject");
chat::forward_msgs(&t, &[message_from_bob.id], group_id) chat::forward_msgs(&t, &[message_from_bob.id], group_id).await?;
.await let subject = get_subject(&t, t.pop_sent_msg().await).await?;
.unwrap();
let subject = get_subject(&t, t.pop_sent_msg().await).await;
assert_eq!(subject, "Fwd: Different subject"); assert_eq!(subject, "Fwd: Different subject");
Ok(())
} }
async fn first_subject_str(t: TestContext) -> String { async fn first_subject_str(t: TestContext) -> String {

View File

@@ -277,7 +277,7 @@ impl Peerstate {
let msg = stock_str::contact_setup_changed(context, self.addr.clone()).await; let msg = stock_str::contact_setup_changed(context, self.addr.clone()).await;
chat::add_info_msg(context, chat_id, msg, timestamp).await; chat::add_info_msg(context, chat_id, msg, timestamp).await?;
context.emit_event(EventType::ChatModified(chat_id)); context.emit_event(EventType::ChatModified(chat_id));
} else { } else {
bail!("contact with peerstate.addr {:?} not found", &self.addr); bail!("contact with peerstate.addr {:?} not found", &self.addr);

View File

@@ -167,7 +167,8 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Lot {
format!("{} verified.", peerstate.addr), format!("{} verified.", peerstate.addr),
time(), time(),
) )
.await; .await
.ok();
} }
} else if let Some(addr) = addr { } else if let Some(addr) = addr {
lot.state = LotState::QrFprMismatch; lot.state = LotState::QrFprMismatch;

View File

@@ -104,7 +104,7 @@ impl Context {
self, self,
job::Job::new(Action::UpdateRecentQuota, 0, Params::new(), 0), job::Job::new(Action::UpdateRecentQuota, 0, Params::new(), 0),
) )
.await; .await?;
Ok(()) Ok(())
} }

View File

@@ -160,7 +160,7 @@ impl Bob {
/// ///
/// With `group` set to `None` this generates a setup-contact QR code, with `group` set to a /// With `group` set to `None` this generates a setup-contact QR code, with `group` set to a
/// [`ChatId`] generates a join-group QR code for the given chat. /// [`ChatId`] generates a join-group QR code for the given chat.
pub async fn dc_get_securejoin_qr(context: &Context, group: Option<ChatId>) -> Option<String> { pub async fn dc_get_securejoin_qr(context: &Context, group: Option<ChatId>) -> Result<String> {
/*======================================================= /*=======================================================
==== Alice - the inviter side ==== ==== Alice - the inviter side ====
==== Step 1 in "Setup verified contact" protocol ==== ==== Step 1 in "Setup verified contact" protocol ====
@@ -175,28 +175,25 @@ pub async fn dc_get_securejoin_qr(context: &Context, group: Option<ChatId>) -> O
let self_addr = match context.get_config(Config::ConfiguredAddr).await { let self_addr = match context.get_config(Config::ConfiguredAddr).await {
Ok(Some(addr)) => addr, Ok(Some(addr)) => addr,
Ok(None) => { Ok(None) => {
error!(context, "Not configured, cannot generate QR code."); bail!("Not configured, cannot generate QR code.");
return None;
} }
Err(err) => { Err(err) => {
error!( bail!(
context, "Unable to retrieve configuration, cannot generate QR code: {:?}",
"Unable to retrieve configuration, cannot generate QR code: {:?}", err err
); );
return None;
} }
}; };
let self_name = context let self_name = context
.get_config(Config::Displayname) .get_config(Config::Displayname)
.await .await?
.ok()?
.unwrap_or_default(); .unwrap_or_default();
let fingerprint: Fingerprint = match get_self_fingerprint(context).await { let fingerprint: Fingerprint = match get_self_fingerprint(context).await {
Some(fp) => fp, Some(fp) => fp,
None => { None => {
return None; bail!("No fingerprint, cannot generate QR code.");
} }
}; };
@@ -207,39 +204,34 @@ pub async fn dc_get_securejoin_qr(context: &Context, group: Option<ChatId>) -> O
let qr = if let Some(group) = group { let qr = if let Some(group) = group {
// parameters used: a=g=x=i=s= // parameters used: a=g=x=i=s=
if let Ok(chat) = Chat::load_from_db(context, group).await { let chat = Chat::load_from_db(context, group).await?;
let group_name = chat.get_name(); let group_name = chat.get_name();
let group_name_urlencoded = let group_name_urlencoded = utf8_percent_encode(group_name, NON_ALPHANUMERIC).to_string();
utf8_percent_encode(group_name, NON_ALPHANUMERIC).to_string();
Some(format!( format!(
"OPENPGP4FPR:{}#a={}&g={}&x={}&i={}&s={}", "OPENPGP4FPR:{}#a={}&g={}&x={}&i={}&s={}",
fingerprint.hex(), fingerprint.hex(),
self_addr_urlencoded, self_addr_urlencoded,
&group_name_urlencoded, &group_name_urlencoded,
&chat.grpid, &chat.grpid,
&invitenumber, &invitenumber,
&auth, &auth,
)) )
} else {
error!(context, "Cannot get QR-code for chat-id {}", group,);
return None;
}
} else { } else {
// parameters used: a=n=i=s= // parameters used: a=n=i=s=
Some(format!( format!(
"OPENPGP4FPR:{}#a={}&n={}&i={}&s={}", "OPENPGP4FPR:{}#a={}&n={}&i={}&s={}",
fingerprint.hex(), fingerprint.hex(),
self_addr_urlencoded, self_addr_urlencoded,
self_name_urlencoded, self_name_urlencoded,
&invitenumber, &invitenumber,
&auth, &auth,
)) )
}; };
info!(context, "Generated QR code: {}", qr.as_ref().unwrap()); info!(context, "Generated QR code: {}", qr);
qr Ok(qr)
} }
async fn get_self_fingerprint(context: &Context) -> Option<Fingerprint> { async fn get_self_fingerprint(context: &Context) -> Option<Fingerprint> {
@@ -720,7 +712,7 @@ pub(crate) async fn handle_securejoin_handshake(
==========================================================*/ ==========================================================*/
if let Ok(contact) = Contact::get_by_id(context, contact_id).await { if let Ok(contact) = Contact::get_by_id(context, contact_id).await {
if contact.is_verified(context).await == VerifiedStatus::Unverified { if contact.is_verified(context).await? == VerifiedStatus::Unverified {
warn!(context, "{} invalid.", step); warn!(context, "{} invalid.", step);
return Ok(HandshakeMessage::Ignore); return Ok(HandshakeMessage::Ignore);
} }
@@ -860,7 +852,7 @@ async fn secure_connection_established(
"?" "?"
}; };
let msg = stock_str::contact_verified(context, addr).await; let msg = stock_str::contact_verified(context, addr).await;
chat::add_info_msg(context, contact_chat_id, msg, time()).await; chat::add_info_msg(context, contact_chat_id, msg, time()).await?;
context.emit_event(EventType::ChatModified(contact_chat_id)); context.emit_event(EventType::ChatModified(contact_chat_id));
info!(context, "StockMessage::ContactVerified posted to 1:1 chat"); info!(context, "StockMessage::ContactVerified posted to 1:1 chat");
@@ -884,7 +876,7 @@ async fn could_not_establish_secure_connection(
) )
.await; .await;
chat::add_info_msg(context, contact_chat_id, &msg, time()).await; chat::add_info_msg(context, contact_chat_id, &msg, time()).await?;
error!( error!(
context, context,
"StockMessage::ContactNotVerified posted to 1:1 chat ({})", details "StockMessage::ContactNotVerified posted to 1:1 chat ({})", details
@@ -958,7 +950,7 @@ mod tests {
use crate::test_utils::TestContext; use crate::test_utils::TestContext;
#[async_std::test] #[async_std::test]
async fn test_setup_contact() { async fn test_setup_contact() -> Result<()> {
let alice = TestContext::new_alice().await; let alice = TestContext::new_alice().await;
let bob = TestContext::new_bob().await; let bob = TestContext::new_bob().await;
@@ -975,10 +967,10 @@ mod tests {
.await; .await;
// Step 1: Generate QR-code, ChatId(0) indicates setup-contact // Step 1: Generate QR-code, ChatId(0) indicates setup-contact
let qr = dc_get_securejoin_qr(&alice.ctx, None).await.unwrap(); let qr = dc_get_securejoin_qr(&alice.ctx, None).await?;
// Step 2: Bob scans QR-code, sends vc-request // Step 2: Bob scans QR-code, sends vc-request
dc_join_securejoin(&bob.ctx, &qr).await.unwrap(); dc_join_securejoin(&bob.ctx, &qr).await?;
let sent = bob.pop_sent_msg().await; let sent = bob.pop_sent_msg().await;
assert!(!bob.ctx.has_ongoing().await); assert!(!bob.ctx.has_ongoing().await);
@@ -1055,14 +1047,14 @@ mod tests {
.await .await
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
contact_bob.is_verified(&alice.ctx).await, contact_bob.is_verified(&alice.ctx).await?,
VerifiedStatus::Unverified VerifiedStatus::Unverified
); );
// Step 5+6: Alice receives vc-request-with-auth, sends vc-contact-confirm // Step 5+6: Alice receives vc-request-with-auth, sends vc-contact-confirm
alice.recv_msg(&sent).await; alice.recv_msg(&sent).await;
assert_eq!( assert_eq!(
contact_bob.is_verified(&alice.ctx).await, contact_bob.is_verified(&alice.ctx).await?,
VerifiedStatus::BidirectVerified VerifiedStatus::BidirectVerified
); );
@@ -1104,14 +1096,14 @@ mod tests {
.await .await
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
contact_bob.is_verified(&bob.ctx).await, contact_bob.is_verified(&bob.ctx).await?,
VerifiedStatus::Unverified VerifiedStatus::Unverified
); );
// Step 7: Bob receives vc-contact-confirm, sends vc-contact-confirm-received // Step 7: Bob receives vc-contact-confirm, sends vc-contact-confirm-received
bob.recv_msg(&sent).await; bob.recv_msg(&sent).await;
assert_eq!( assert_eq!(
contact_alice.is_verified(&bob.ctx).await, contact_alice.is_verified(&bob.ctx).await?,
VerifiedStatus::BidirectVerified VerifiedStatus::BidirectVerified
); );
@@ -1142,6 +1134,7 @@ mod tests {
msg.get_header(HeaderDef::SecureJoin).unwrap(), msg.get_header(HeaderDef::SecureJoin).unwrap(),
"vc-contact-confirm-received" "vc-contact-confirm-received"
); );
Ok(())
} }
#[async_std::test] #[async_std::test]
@@ -1152,7 +1145,7 @@ mod tests {
} }
#[async_std::test] #[async_std::test]
async fn test_setup_contact_bob_knows_alice() { async fn test_setup_contact_bob_knows_alice() -> Result<()> {
let alice = TestContext::new_alice().await; let alice = TestContext::new_alice().await;
let bob = TestContext::new_bob().await; let bob = TestContext::new_bob().await;
@@ -1169,7 +1162,7 @@ mod tests {
.await; .await;
// Ensure Bob knows Alice_FP // Ensure Bob knows Alice_FP
let alice_pubkey = SignedPublicKey::load_self(&alice.ctx).await.unwrap(); let alice_pubkey = SignedPublicKey::load_self(&alice.ctx).await?;
let peerstate = Peerstate { let peerstate = Peerstate {
addr: "alice@example.com".into(), addr: "alice@example.com".into(),
last_seen: 10, last_seen: 10,
@@ -1185,10 +1178,10 @@ mod tests {
to_save: Some(ToSave::All), to_save: Some(ToSave::All),
fingerprint_changed: false, fingerprint_changed: false,
}; };
peerstate.save_to_db(&bob.ctx.sql, true).await.unwrap(); peerstate.save_to_db(&bob.ctx.sql, true).await?;
// Step 1: Generate QR-code, ChatId(0) indicates setup-contact // Step 1: Generate QR-code, ChatId(0) indicates setup-contact
let qr = dc_get_securejoin_qr(&alice.ctx, None).await.unwrap(); let qr = dc_get_securejoin_qr(&alice.ctx, None).await?;
// Step 2+4: Bob scans QR-code, sends vc-request-with-auth, skipping vc-request // Step 2+4: Bob scans QR-code, sends vc-request-with-auth, skipping vc-request
dc_join_securejoin(&bob.ctx, &qr).await.unwrap(); dc_join_securejoin(&bob.ctx, &qr).await.unwrap();
@@ -1228,10 +1221,7 @@ mod tests {
"vc-request-with-auth" "vc-request-with-auth"
); );
assert!(msg.get_header(HeaderDef::SecureJoinAuth).is_some()); assert!(msg.get_header(HeaderDef::SecureJoinAuth).is_some());
let bob_fp = SignedPublicKey::load_self(&bob.ctx) let bob_fp = SignedPublicKey::load_self(&bob.ctx).await?.fingerprint();
.await
.unwrap()
.fingerprint();
assert_eq!( assert_eq!(
*msg.get_header(HeaderDef::SecureJoinFingerprint).unwrap(), *msg.get_header(HeaderDef::SecureJoinFingerprint).unwrap(),
bob_fp.hex() bob_fp.hex()
@@ -1244,20 +1234,17 @@ mod tests {
"bob@example.net", "bob@example.net",
Origin::ManuallyCreated, Origin::ManuallyCreated,
) )
.await .await?;
.unwrap(); let contact_bob = Contact::load_from_db(&alice.ctx, contact_bob_id).await?;
let contact_bob = Contact::load_from_db(&alice.ctx, contact_bob_id)
.await
.unwrap();
assert_eq!( assert_eq!(
contact_bob.is_verified(&alice.ctx).await, contact_bob.is_verified(&alice.ctx).await?,
VerifiedStatus::Unverified VerifiedStatus::Unverified
); );
// Step 5+6: Alice receives vc-request-with-auth, sends vc-contact-confirm // Step 5+6: Alice receives vc-request-with-auth, sends vc-contact-confirm
alice.recv_msg(&sent).await; alice.recv_msg(&sent).await;
assert_eq!( assert_eq!(
contact_bob.is_verified(&alice.ctx).await, contact_bob.is_verified(&alice.ctx).await?,
VerifiedStatus::BidirectVerified VerifiedStatus::BidirectVerified
); );
@@ -1275,18 +1262,16 @@ mod tests {
.await .await
.expect("Error looking up contact") .expect("Error looking up contact")
.expect("Contact not found"); .expect("Contact not found");
let contact_alice = Contact::load_from_db(&bob.ctx, contact_alice_id) let contact_alice = Contact::load_from_db(&bob.ctx, contact_alice_id).await?;
.await
.unwrap();
assert_eq!( assert_eq!(
contact_bob.is_verified(&bob.ctx).await, contact_bob.is_verified(&bob.ctx).await?,
VerifiedStatus::Unverified VerifiedStatus::Unverified
); );
// Step 7: Bob receives vc-contact-confirm, sends vc-contact-confirm-received // Step 7: Bob receives vc-contact-confirm, sends vc-contact-confirm-received
bob.recv_msg(&sent).await; bob.recv_msg(&sent).await;
assert_eq!( assert_eq!(
contact_alice.is_verified(&bob.ctx).await, contact_alice.is_verified(&bob.ctx).await?,
VerifiedStatus::BidirectVerified VerifiedStatus::BidirectVerified
); );
@@ -1297,10 +1282,11 @@ mod tests {
msg.get_header(HeaderDef::SecureJoin).unwrap(), msg.get_header(HeaderDef::SecureJoin).unwrap(),
"vc-contact-confirm-received" "vc-contact-confirm-received"
); );
Ok(())
} }
#[async_std::test] #[async_std::test]
async fn test_secure_join() { async fn test_secure_join() -> Result<()> {
let alice = TestContext::new_alice().await; let alice = TestContext::new_alice().await;
let bob = TestContext::new_bob().await; let bob = TestContext::new_bob().await;
@@ -1316,9 +1302,8 @@ mod tests {
}) })
.await; .await;
let chatid = chat::create_group_chat(&alice.ctx, ProtectionStatus::Protected, "the chat") let chatid =
.await chat::create_group_chat(&alice.ctx, ProtectionStatus::Protected, "the chat").await?;
.unwrap();
// Step 1: Generate QR-code, secure-join implied by chatid // Step 1: Generate QR-code, secure-join implied by chatid
let qr = dc_get_securejoin_qr(&alice.ctx, Some(chatid)) let qr = dc_get_securejoin_qr(&alice.ctx, Some(chatid))
@@ -1388,10 +1373,7 @@ mod tests {
"vg-request-with-auth" "vg-request-with-auth"
); );
assert!(msg.get_header(HeaderDef::SecureJoinAuth).is_some()); assert!(msg.get_header(HeaderDef::SecureJoinAuth).is_some());
let bob_fp = SignedPublicKey::load_self(&bob.ctx) let bob_fp = SignedPublicKey::load_self(&bob.ctx).await?.fingerprint();
.await
.unwrap()
.fingerprint();
assert_eq!( assert_eq!(
*msg.get_header(HeaderDef::SecureJoinFingerprint).unwrap(), *msg.get_header(HeaderDef::SecureJoinFingerprint).unwrap(),
bob_fp.hex() bob_fp.hex()
@@ -1400,21 +1382,18 @@ mod tests {
// Alice should not yet have Bob verified // Alice should not yet have Bob verified
let contact_bob_id = let contact_bob_id =
Contact::lookup_id_by_addr(&alice.ctx, "bob@example.net", Origin::Unknown) Contact::lookup_id_by_addr(&alice.ctx, "bob@example.net", Origin::Unknown)
.await .await?
.expect("Error looking up contact")
.expect("Contact not found"); .expect("Contact not found");
let contact_bob = Contact::load_from_db(&alice.ctx, contact_bob_id) let contact_bob = Contact::load_from_db(&alice.ctx, contact_bob_id).await?;
.await
.unwrap();
assert_eq!( assert_eq!(
contact_bob.is_verified(&alice.ctx).await, contact_bob.is_verified(&alice.ctx).await?,
VerifiedStatus::Unverified VerifiedStatus::Unverified
); );
// Step 5+6: Alice receives vg-request-with-auth, sends vg-member-added // Step 5+6: Alice receives vg-request-with-auth, sends vg-member-added
alice.recv_msg(&sent).await; alice.recv_msg(&sent).await;
assert_eq!( assert_eq!(
contact_bob.is_verified(&alice.ctx).await, contact_bob.is_verified(&alice.ctx).await?,
VerifiedStatus::BidirectVerified VerifiedStatus::BidirectVerified
); );
@@ -1432,18 +1411,16 @@ mod tests {
.await .await
.expect("Error looking up contact") .expect("Error looking up contact")
.expect("Contact not found"); .expect("Contact not found");
let contact_alice = Contact::load_from_db(&bob.ctx, contact_alice_id) let contact_alice = Contact::load_from_db(&bob.ctx, contact_alice_id).await?;
.await
.unwrap();
assert_eq!( assert_eq!(
contact_bob.is_verified(&bob.ctx).await, contact_bob.is_verified(&bob.ctx).await?,
VerifiedStatus::Unverified VerifiedStatus::Unverified
); );
// Step 7: Bob receives vg-member-added, sends vg-member-added-received // Step 7: Bob receives vg-member-added, sends vg-member-added-received
bob.recv_msg(&sent).await; bob.recv_msg(&sent).await;
assert_eq!( assert_eq!(
contact_alice.is_verified(&bob.ctx).await, contact_alice.is_verified(&bob.ctx).await?,
VerifiedStatus::BidirectVerified VerifiedStatus::BidirectVerified
); );
@@ -1456,8 +1433,9 @@ mod tests {
); );
let bob_chatid = joiner.await; let bob_chatid = joiner.await;
let bob_chat = Chat::load_from_db(&bob.ctx, bob_chatid).await.unwrap(); let bob_chat = Chat::load_from_db(&bob.ctx, bob_chatid).await?;
assert!(bob_chat.is_protected()); assert!(bob_chat.is_protected());
assert!(!bob.ctx.has_ongoing().await) assert!(!bob.ctx.has_ongoing().await);
Ok(())
} }
} }