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;
block_on(async move { chat::is_contact_in_chat(ctx, ChatId::new(chat_id), contact_id).await })
.into()
block_on(chat::is_contact_in_chat(
ctx,
ChatId::new(chat_id),
contact_id,
))
.log_err(ctx, "is_contact_in_chat failed")
.unwrap_or_default() as libc::c_int
}
#[no_mangle]
@@ -1335,9 +1340,13 @@ pub unsafe extern "C" fn dc_add_contact_to_chat(
}
let ctx = &*context;
block_on(async move {
chat::add_contact_to_chat(ctx, ChatId::new(chat_id), contact_id).await as libc::c_int
})
block_on(chat::add_contact_to_chat(
ctx,
ChatId::new(chat_id),
contact_id,
))
.log_err(ctx, "Failed to add contact")
.is_ok() as libc::c_int
}
#[no_mangle]
@@ -1352,12 +1361,13 @@ pub unsafe extern "C" fn dc_remove_contact_from_chat(
}
let ctx = &*context;
block_on(async move {
chat::remove_contact_from_chat(ctx, ChatId::new(chat_id), contact_id)
.await
.map(|_| 1)
.unwrap_or_log_default(ctx, "Failed to remove contact")
})
block_on(chat::remove_contact_from_chat(
ctx,
ChatId::new(chat_id),
contact_id,
))
.log_err(ctx, "Failed to remove contact")
.is_ok() as libc::c_int
}
#[no_mangle]
@@ -2019,12 +2029,9 @@ pub unsafe extern "C" fn dc_get_securejoin_qr(
Some(ChatId::new(chat_id))
};
block_on(async move {
securejoin::dc_get_securejoin_qr(ctx, chat_id)
.await
.unwrap_or_else(|| "".to_string())
.strdup()
})
block_on(securejoin::dc_get_securejoin_qr(ctx, chat_id))
.unwrap_or_else(|_| "".to_string())
.strdup()
}
#[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;
}
let ffi_chat = &*chat;
let cxt = &*ffi_chat.context;
block_on(ffi_chat.chat.can_send(cxt)) as libc::c_int
let ctx = &*ffi_chat.context;
block_on(ffi_chat.chat.can_send(ctx))
.log_err(ctx, "can_send failed")
.unwrap_or_default() as libc::c_int
}
#[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 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

View File

@@ -2,7 +2,7 @@ extern crate dirs;
use std::str::FromStr;
use anyhow::{bail, ensure, Error};
use anyhow::{bail, ensure, Result};
use async_std::path::Path;
use deltachat::chat::{
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?;
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;
for &msg_id in msglist {
if msg_id == MsgId::new(DC_MSG_ID_DAYMARKER) {
@@ -267,59 +267,59 @@ async fn log_msglist(context: &Context, msglist: &[MsgId]) -> Result<(), Error>
Ok(())
}
async fn log_contactlist(context: &Context, contacts: &[u32]) {
async fn log_contactlist(context: &Context, contacts: &[u32]) -> Result<()> {
for contact_id in contacts {
let line;
let mut line2 = "".to_string();
if let Ok(contact) = Contact::get_by_id(context, *contact_id).await {
let name = contact.get_display_name();
let addr = contact.get_addr();
let verified_state = contact.is_verified(context).await;
let verified_str = if VerifiedStatus::Unverified != verified_state {
if verified_state == VerifiedStatus::BidirectVerified {
" √√"
} else {
""
}
let contact = Contact::get_by_id(context, *contact_id).await?;
let name = contact.get_display_name();
let addr = contact.get_addr();
let verified_state = contact.is_verified(context).await?;
let verified_str = if VerifiedStatus::Unverified != verified_state {
if verified_state == VerifiedStatus::BidirectVerified {
" √√"
} 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
);
""
}
println!("Contact#{}: {}{}", *contact_id, line, line2);
} 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
);
}
println!("Contact#{}: {}{}", *contact_id, line, line2);
}
Ok(())
}
fn chat_prefix(chat: &Chat) -> &'static str {
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() {
Chat::load_from_db(&context, *chat_id).await.ok()
Some(Chat::load_from_db(&context, *chat_id).await?)
} else {
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.");
let contact_id_0: u32 = arg1.parse()?;
if chat::add_contact_to_chat(
&context,
sel_chat.as_ref().unwrap().get_id(),
contact_id_0,
)
.await
{
println!("Contact added to chat.");
} else {
bail!("Cannot add contact to chat.");
}
chat::add_contact_to_chat(&context, sel_chat.as_ref().unwrap().get_id(), contact_id_0)
.await?;
println!("Contact added to chat.");
}
"removemember" => {
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?;
println!("Memberlist:");
log_contactlist(&context, &contacts).await;
log_contactlist(&context, &contacts).await?;
println!(
"{} contacts\nLocation streaming: {}",
contacts.len(),
@@ -1090,7 +1082,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
Some(arg1),
)
.await?;
log_contactlist(&context, &contacts).await;
log_contactlist(&context, &contacts).await?;
println!("{} contacts.", contacts.len());
}
"addcontact" => {
@@ -1155,7 +1147,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
}
"listblocked" => {
let contacts = Contact::get_all_blocked(&context).await?;
log_contactlist(&context, &contacts).await;
log_contactlist(&context, &contacts).await?;
println!("{} blocked contacts.", contacts.len());
}
"checkqr" => {

View File

@@ -409,20 +409,19 @@ async fn handle_cmd(
}
"getqr" | "getbadqr" => {
ctx.start_io().await;
let group = arg1.parse::<u32>().ok().map(ChatId::new);
if let Some(mut qr) = dc_get_securejoin_qr(&ctx, group).await {
if !qr.is_empty() {
if arg0 == "getbadqr" && qr.len() > 40 {
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();
let group = arg1.parse::<u32>().ok().map(|id| ChatId::new(id));
let mut qr = dc_get_securejoin_qr(&ctx, group).await?;
if !qr.is_empty() {
if arg0 == "getbadqr" && qr.len() > 40 {
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();
}
}
"joinqr" => {

View File

@@ -2,12 +2,12 @@
//!
//! 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::str::FromStr;
use std::{fmt, str};
use crate::contact::addr_cmp;
use crate::context::Context;
use crate::headerdef::{HeaderDef, HeaderDefMap};
use crate::key::{DcKey, SignedPublicKey};
@@ -37,13 +37,13 @@ impl fmt::Display 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 {
"mutual" => Ok(EncryptPreference::Mutual),
"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(
context: &Context,
wanted_from: &str,
headers: &[mailparse::MailHeader<'_>],
) -> Option<Self> {
) -> Result<Option<Self>> {
if let Some(value) = headers.get_header_value(HeaderDef::Autocrypt) {
match Self::from_str(&value) {
Ok(header) => {
if addr_cmp(&header.addr, wanted_from) {
return Some(header);
}
}
Err(err) => {
warn!(
context,
"found invalid autocrypt header {}: {:?}", value, err
);
}
let header = Self::from_str(&value)?;
if !addr_cmp(&header.addr, wanted_from) {
bail!(
"Autocrypt header address {:?} is not {:?}",
header.addr,
wanted_from
);
}
Ok(Some(header))
} else {
Ok(None)
}
None
}
}
@@ -120,9 +119,9 @@ impl fmt::Display 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
.split(';')
.filter_map(|a| {
@@ -136,15 +135,20 @@ impl str::FromStr for Aheader {
let addr = match attributes.remove("addr") {
Some(addr) => addr,
None => {
return Err(());
}
None => bail!("Autocrypt header has no addr"),
};
let public_key: SignedPublicKey = attributes
.remove("keydata")
.ok_or(())
.and_then(|raw| SignedPublicKey::from_base64(&raw).or(Err(())))
.and_then(|key| key.verify().and(Ok(key)).or(Err(())))?;
.ok_or_else(|| format_err!("keydata attribute is not found"))
.and_then(|raw| {
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
.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 attribute, treat the header as invalid
if attributes.keys().any(|k| !k.starts_with('_')) {
return Err(());
bail!("Unknown Autocrypt attribute found");
}
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=";
#[test]
fn test_from_str() {
fn test_from_str() -> Result<()> {
let h: Aheader = format!(
"addr=me@mail.com; prefer-encrypt=mutual; keydata={}",
RAWKEY
)
.parse()
.expect("failed to parse");
.parse()?;
assert_eq!(h.addr, "me@mail.com");
assert_eq!(h.prefer_encrypt, EncryptPreference::Mutual);
Ok(())
}
// EncryptPreference::Reset is an internal value, parser should never return it
#[test]
fn test_from_str_reset() {
fn test_from_str_reset() -> Result<()> {
let raw = format!(
"addr=reset@example.com; prefer-encrypt=reset; keydata={}",
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.prefer_encrypt, EncryptPreference::NoPreference);
Ok(())
}
#[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 h: Aheader = raw.parse().expect("failed to parse");
let h: Aheader = raw.parse()?;
assert_eq!(h.addr, "me@mail.com");
assert_eq!(h.prefer_encrypt, EncryptPreference::NoPreference);
Ok(())
}
#[test]
@@ -216,7 +222,7 @@ mod tests {
}
#[test]
fn test_good_headers() {
fn test_good_headers() -> Result<()> {
let fixed_header = concat!(
"addr=a@b.example.org; prefer-encrypt=mutual; ",
"keydata=xsBNBFzG3j0BCAC6iNhT8zydvCXi8LI/gFnkadMbfmSE/rTJskRRra/utGbLyDta/yTrJg",
@@ -242,7 +248,7 @@ mod tests {
" 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.prefer_encrypt, EncryptPreference::Mutual);
assert_eq!(format!("{}", ah), fixed_header);
@@ -250,18 +256,17 @@ mod tests {
let rendered = ah.to_string();
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.prefer_encrypt, EncryptPreference::Mutual);
Aheader::from_str(&format!(
"addr=a@b.example.org; prefer-encrypt=ignoreUnknownValues; keydata={}",
RAWKEY
))
.expect("failed to parse");
))?;
Aheader::from_str(&format!("addr=a@b.example.org; keydata={}", RAWKEY))
.expect("failed to parse");
Aheader::from_str(&format!("addr=a@b.example.org; keydata={}", RAWKEY))?;
Ok(())
}
#[test]

View File

@@ -179,7 +179,7 @@ impl ChatId {
chat.id
}
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
{
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?;
for contact_id in contact_ids.into_iter() {
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());
}
}
@@ -499,7 +499,7 @@ impl ChatId {
job::kill_action(context, Action::Housekeeping).await?;
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() {
let mut msg = Message::new(Viewtype::Text);
@@ -605,7 +605,7 @@ impl ChatId {
}
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");
}
@@ -940,13 +940,13 @@ impl Chat {
}
/// Returns true if user can send messages to this chat.
pub async fn can_send(&self, context: &Context) -> bool {
!self.id.is_special()
pub async fn can_send(&self, context: &Context) -> Result<bool> {
Ok(!self.id.is_special()
&& !self.is_device_talk()
&& !self.is_mailing_list()
&& !self.is_contact_request()
&& (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<()> {
@@ -1097,7 +1097,7 @@ impl Chat {
}
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(
"Cannot send message; self not in group.".into(),
@@ -1667,7 +1667,7 @@ async fn prepare_msg_common(
chat_id.unarchive(context).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
// 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.
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
// for group chats.
// 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)
context
let exists = context
.sql
.exists(
"SELECT COUNT(*) FROM chats_contacts WHERE chat_id=? AND contact_id=?;",
paramsv![chat_id, contact_id as i32],
)
.await
.unwrap_or_default()
.await?;
Ok(exists)
}
/// 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> {
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 {
chat_id: msg.chat_id,
@@ -2161,7 +2165,8 @@ pub async fn create_group_chat(
.await?;
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);
draft_msg.set_text(Some(draft_txt));
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,
chat_id: ChatId,
contact_id: u32,
) -> bool {
match context
) -> Result<()> {
context
.sql
.execute(
"INSERT INTO chats_contacts (chat_id, contact_id) VALUES(?, ?)",
paramsv![chat_id, contact_id as i32],
)
.await
{
Ok(_) => true,
Err(err) => {
error!(
context,
"could not add {} to chat {} table: {}", contact_id, chat_id, err
);
false
}
}
.await?;
Ok(())
}
/// remove a contact from the chats_contact table
@@ -2212,36 +2207,25 @@ pub(crate) async fn remove_from_chat_contacts_table(
context: &Context,
chat_id: ChatId,
contact_id: u32,
) -> bool {
match context
) -> Result<()> {
context
.sql
.execute(
"DELETE FROM chats_contacts WHERE chat_id=? AND contact_id=?",
paramsv![chat_id, contact_id as i32],
)
.await
{
Ok(_) => true,
Err(_) => {
warn!(
context,
"could not remove contact {:?} from chat {:?}", contact_id, chat_id
);
false
}
}
.await?;
Ok(())
}
/// Adds a contact to the chat.
pub async fn add_contact_to_chat(context: &Context, chat_id: ChatId, contact_id: u32) -> bool {
match add_contact_to_chat_ex(context, chat_id, contact_id, false).await {
Ok(res) => res,
Err(err) => {
error!(context, "failed to add contact: {}", err);
false
}
}
pub async fn add_contact_to_chat(
context: &Context,
chat_id: ChatId,
contact_id: u32,
) -> Result<()> {
add_contact_to_chat_ex(context, chat_id, contact_id, false).await?;
Ok(())
}
pub(crate) async fn add_contact_to_chat_ex(
@@ -2264,13 +2248,13 @@ pub(crate) async fn add_contact_to_chat_ex(
chat_id
);
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",
contact_id
);
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! */
context.emit_event(EventType::ErrorSelfNotInGroup(
"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);
}
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 {
return Ok(true);
}
} else {
// else continue and send status mail
if chat.is_protected()
&& contact.is_verified(context).await != VerifiedStatus::BidirectVerified
&& contact.is_verified(context).await? != VerifiedStatus::BidirectVerified
{
error!(
context,
@@ -2310,9 +2294,10 @@ pub(crate) async fn add_contact_to_chat_ex(
);
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);
}
add_to_chat_contacts_table(context, chat_id, contact_id).await?;
}
if chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 0 {
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. */
if let Ok(chat) = Chat::load_from_db(context, chat_id).await {
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(
"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
// removed it first, it would complicate the
// 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));
}
}
@@ -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.name == new_name {
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(
"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"
);
/* 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(
"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?;
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;
let ids = context
.sql
@@ -2755,7 +2742,7 @@ pub async fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: ChatId)
curr_timestamp += 1;
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? {
job::add(context, send_job).await;
job::add(context, send_job).await?;
}
}
created_chats.push(chat_id);
@@ -3014,12 +3001,8 @@ pub(crate) async fn add_info_msg(
chat_id: ChatId,
text: impl AsRef<str>,
timestamp: i64,
) {
if let Err(e) =
add_info_msg_with_cmd(context, chat_id, text, SystemMessage::Unknown, timestamp).await
{
warn!(context, "Could not add info msg: {}", e);
}
) -> Result<MsgId> {
add_info_msg_with_cmd(context, chat_id, text, SystemMessage::Unknown, timestamp).await
}
#[cfg(test)]
@@ -3141,7 +3124,7 @@ mod tests {
}
#[async_std::test]
async fn test_self_talk() {
async fn test_self_talk() -> Result<()> {
let t = TestContext::new().await;
let chat = &t.get_self_chat().await;
assert_eq!(DC_CONTACT_ID_SELF, 1);
@@ -3149,9 +3132,10 @@ mod tests {
assert!(chat.is_self_talk());
assert!(chat.visibility == ChatVisibility::Normal);
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!(chat.get_profile_image(&t).await.unwrap().is_some());
assert!(chat.get_profile_image(&t).await?.is_some());
Ok(())
}
#[async_std::test]
@@ -3190,7 +3174,7 @@ mod tests {
}
#[async_std::test]
async fn test_add_device_msg_labelled() {
async fn test_add_device_msg_labelled() -> Result<()> {
let t = TestContext::new().await;
// 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());
// check added message
let msg1 = message::Message::load_from_db(&t, *msg1_id.as_ref().unwrap()).await;
assert!(msg1.is_ok());
let msg1 = msg1.unwrap();
let msg1 = message::Message::load_from_db(&t, *msg1_id.as_ref().unwrap()).await?;
assert_eq!(msg1_id.as_ref().unwrap(), &msg1.id);
assert_eq!(msg1.text.as_ref().unwrap(), "first message");
assert_eq!(msg1.from_id, DC_CONTACT_ID_DEVICE);
@@ -3220,28 +3202,25 @@ mod tests {
// check device chat
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());
let chat = Chat::load_from_db(&t, chat_id).await;
assert!(chat.is_ok());
let chat = chat.unwrap();
let chat = Chat::load_from_db(&t, chat_id).await?;
assert_eq!(chat.get_type(), Chattype::Single);
assert!(chat.is_device_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!(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
message::delete_msgs(&t, &[*msg1_id.as_ref().unwrap()])
.await
.unwrap();
message::delete_msgs(&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());
let msg3_id = add_device_msg(&t, Some("any-label"), Some(&mut msg2)).await;
assert!(msg3_id.is_ok());
assert!(msg2_id.as_ref().unwrap().is_unset());
Ok(())
}
#[async_std::test]
@@ -3566,24 +3545,21 @@ mod tests {
}
#[async_std::test]
async fn test_shall_attach_selfavatar() {
async fn test_shall_attach_selfavatar() -> Result<()> {
let t = TestContext::new().await;
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo")
.await
.unwrap();
assert!(!shall_attach_selfavatar(&t, chat_id).await.unwrap());
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
assert!(!shall_attach_selfavatar(&t, chat_id).await?);
let (contact_id, _) =
Contact::add_or_lookup(&t, "", "foo@bar.org", Origin::IncomingUnknownTo)
.await
.unwrap();
add_contact_to_chat(&t, chat_id, contact_id).await;
assert!(!shall_attach_selfavatar(&t, chat_id).await.unwrap());
t.set_config(Config::Selfavatar, None).await.unwrap(); // setting to None also forces re-sending
assert!(shall_attach_selfavatar(&t, chat_id).await.unwrap());
Contact::add_or_lookup(&t, "", "foo@bar.org", Origin::IncomingUnknownTo).await?;
add_contact_to_chat(&t, chat_id, contact_id).await?;
assert!(!shall_attach_selfavatar(&t, chat_id).await?);
t.set_config(Config::Selfavatar, None).await?; // setting to None also forces re-sending
assert!(shall_attach_selfavatar(&t, chat_id).await?);
assert!(chat_id.set_selfavatar_timestamp(&t, time()).await.is_ok());
assert!(!shall_attach_selfavatar(&t, chat_id).await.unwrap());
chat_id.set_selfavatar_timestamp(&t, time()).await?;
assert!(!shall_attach_selfavatar(&t, chat_id).await?);
Ok(())
}
#[async_std::test]
@@ -3638,12 +3614,10 @@ mod tests {
}
#[async_std::test]
async fn test_add_info_msg() {
async fn test_add_info_msg() -> Result<()> {
let t = TestContext::new().await;
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo")
.await
.unwrap();
add_info_msg(&t, chat_id, "foo info", 200000).await;
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
add_info_msg(&t, chat_id, "foo info", 200000).await?;
let msg = t.get_last_msg_in(chat_id).await;
assert_eq!(msg.get_chat_id(), chat_id);
@@ -3651,6 +3625,7 @@ mod tests {
assert_eq!(msg.get_text().unwrap(), "foo info");
assert!(msg.is_info());
assert_eq!(msg.get_info_type(), SystemMessage::Unknown);
Ok(())
}
#[async_std::test]
@@ -3807,42 +3782,24 @@ mod tests {
}
#[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
let alice = TestContext::new_alice().await;
let bob = TestContext::new_bob().await;
alice
.set_config(Config::ShowEmails, Some("2"))
.await
.unwrap();
bob.set_config(Config::ShowEmails, Some("2")).await.unwrap();
alice.set_config(Config::ShowEmails, Some("2")).await?;
bob.set_config(Config::ShowEmails, Some("2")).await?;
let (contact_id, _) =
Contact::add_or_lookup(&alice, "", "bob@example.net", Origin::ManuallyCreated)
.await
.unwrap();
let alice_chat_id = create_group_chat(&alice, ProtectionStatus::Unprotected, "grp")
.await
.unwrap();
let alice_chat = Chat::load_from_db(&alice, alice_chat_id).await.unwrap();
Contact::add_or_lookup(&alice, "", "bob@example.net", Origin::ManuallyCreated).await?;
let alice_chat_id = create_group_chat(&alice, ProtectionStatus::Unprotected, "grp").await?;
let alice_chat = Chat::load_from_db(&alice, alice_chat_id).await?;
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!(
get_chat_contacts(&alice, alice_chat_id)
.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(),
get_chat_msgs(&alice, alice_chat_id, 0, None).await?.len(),
1
);
@@ -3858,17 +3815,15 @@ mod tests {
.unwrap();
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);
// 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;
// moreover, Bob's SMTP-server also replaces the `Message-ID:`-header
send_text_msg(&bob, bob_chat.id, "ho!".to_string())
.await
.unwrap();
send_text_msg(&bob, bob_chat.id, "ho!".to_string()).await?;
let msg = bob.pop_sent_msg().await.payload();
let msg = msg.replace("Message-ID: <Gr.", "Message-ID: <XXX");
let msg = msg.replace("Chat-", "XXXX-");
@@ -3882,12 +3837,10 @@ mod tests {
assert_eq!(msg.chat_id, alice_chat_id);
assert_eq!(msg.text, Some("ho!".to_string()));
assert_eq!(
get_chat_msgs(&alice, alice_chat_id, 0, None)
.await
.unwrap()
.len(),
get_chat_msgs(&alice, alice_chat_id, 0, None).await?.len(),
2
);
Ok(())
}
#[async_std::test]
@@ -4202,13 +4155,13 @@ mod tests {
let bob = Contact::create(&alice, "", "bob@f.br").await?;
let chat_id = ChatId::create_for_contact(&alice, bob).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?;
assert_eq!(
Chat::load_from_db(&alice, chat_id)
.await?
.can_send(&alice)
.await,
.await?,
true
);
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)
.await?
.can_send(&alice)
.await,
.await?,
false
);
Ok(())

View File

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

View File

@@ -2,7 +2,7 @@
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 deltachat_derive::{FromSql, ToSql};
use itertools::Itertools;
@@ -174,6 +174,12 @@ pub enum VerifiedStatus {
BidirectVerified = 2,
}
impl Default for VerifiedStatus {
fn default() -> Self {
Self::Unverified
}
}
impl Contact {
pub async fn load_from_db(context: &Context, contact_id: u32) -> Result<Self> {
let mut contact = context
@@ -229,11 +235,9 @@ impl Contact {
}
/// Check if a contact is blocked.
pub async fn is_blocked_load(context: &Context, id: u32) -> bool {
Self::load_from_db(context, id)
.await
.map(|contact| contact.blocked)
.unwrap_or_default()
pub async fn is_blocked_load(context: &Context, id: u32) -> Result<bool> {
let blocked = Self::load_from_db(context, id).await?.blocked;
Ok(blocked)
}
/// Block the given contact.
@@ -263,7 +267,7 @@ impl Contact {
let (contact_id, sth_modified) =
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 {
Modifier::None => {}
Modifier::Modified | Modifier::Created => {
@@ -754,12 +758,9 @@ impl Contact {
/// Get blocked contacts.
pub async fn get_all_blocked(context: &Context) -> Result<Vec<u32>> {
if let Err(e) = Contact::update_blocked_mailinglist_contacts(context).await {
warn!(
context,
"Cannot update blocked mailinglist contacts: {:?}", e
);
}
Contact::update_blocked_mailinglist_contacts(context)
.await
.context("cannot update blocked mailinglist contacts")?;
let list = context
.sql
@@ -1018,7 +1019,7 @@ impl Contact {
///
/// 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
}
@@ -1029,54 +1030,46 @@ impl Contact {
&self,
context: &Context,
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
// on this device
if self.id == DC_CONTACT_ID_SELF {
return VerifiedStatus::BidirectVerified;
return Ok(VerifiedStatus::BidirectVerified);
}
if let Some(peerstate) = peerstate {
if peerstate.verified_key.is_some() {
return VerifiedStatus::BidirectVerified;
return Ok(VerifiedStatus::BidirectVerified);
}
}
let peerstate = match Peerstate::from_addr(context, &self.addr).await {
Ok(peerstate) => peerstate,
Err(err) => {
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;
if let Some(peerstate) = Peerstate::from_addr(context, &self.addr).await? {
if peerstate.verified_key.is_some() {
return Ok(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() {
return false;
return Ok(false);
}
if let Ok(contact) = Contact::load_from_db(context, contact_id).await {
if !contact.addr.is_empty() {
let normalized_addr = addr_normalize(addr);
if contact.addr == normalized_addr {
return true;
}
let contact = Contact::load_from_db(context, contact_id).await?;
if !contact.addr.is_empty() {
let normalized_addr = addr_normalize(addr);
if contact.addr == normalized_addr {
return Ok(true);
}
}
false
Ok(false)
}
pub async fn get_real_cnt(context: &Context) -> Result<usize> {
@@ -1094,19 +1087,19 @@ impl Contact {
Ok(count)
}
pub async fn real_exists_by_id(context: &Context, contact_id: u32) -> bool {
if !context.sql.is_open().await || contact_id <= DC_CONTACT_ID_LAST_SPECIAL {
return false;
pub async fn real_exists_by_id(context: &Context, contact_id: u32) -> Result<bool> {
if contact_id <= DC_CONTACT_ID_LAST_SPECIAL {
return Ok(false);
}
context
let exists = context
.sql
.exists(
"SELECT COUNT(*) FROM contacts WHERE id=?;",
paramsv![contact_id as i32],
)
.await
.unwrap_or_default()
.await?;
Ok(exists)
}
pub async fn scaleup_origin_by_id(

View File

@@ -298,7 +298,7 @@ pub(crate) async fn dc_receive_imf_inner(
0,
),
)
.await;
.await?;
}
} else if insert_msg_id
.needs_move(context, server_folder.as_ref())
@@ -311,7 +311,7 @@ pub(crate) async fn dc_receive_imf_inner(
context,
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() {
// This is a Delta Chat MDN. Mark as read.
job::add(
@@ -323,7 +323,7 @@ pub(crate) async fn dc_receive_imf_inner(
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
// but the From-address is not a member of this chat.
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?;
if chat.is_protected() {
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 {
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
} else {
Blocked::Request
@@ -915,7 +915,7 @@ async fn add_parts(
stock_ephemeral_timer_changed(context, ephemeral_timer, from_id).await,
sort_timestamp,
)
.await;
.await?;
}
}
} else {
@@ -974,7 +974,7 @@ async fn add_parts(
format!("Cannot set protection: {}", e),
sort_timestamp,
)
.await;
.await?;
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)
.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
// absent. We can't use existing member list and need to
// start from scratch.
@@ -1693,20 +1693,20 @@ async fn create_or_lookup_group(
.await
.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
&& !Contact::addr_equals_contact(context, &self_addr, from_id).await
&& !chat::is_contact_in_chat(context, chat_id, 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::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() {
info!(context, "adding to={:?} to chat id={}", to_id, chat_id);
if !Contact::addr_equals_contact(context, &self_addr, to_id).await
&& !chat::is_contact_in_chat(context, chat_id, 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::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;
@@ -1716,7 +1716,7 @@ async fn create_or_lookup_group(
.update_timestamp(context, Param::MemberListTimestamp, sent_timestamp)
.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;
}
}
@@ -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)))
} else {
info!(context, "creating list forbidden by caller");
@@ -1926,7 +1926,7 @@ async fn create_adhoc_group(
)
.await?;
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));
@@ -2047,7 +2047,7 @@ async fn check_verified_properties(
let peerstate = Peerstate::from_addr(context, contact.get_addr()).await?;
if peerstate.is_none()
|| contact.is_verified_ex(context, peerstate.as_ref()).await
|| contact.is_verified_ex(context, peerstate.as_ref()).await?
!= VerifiedStatus::BidirectVerified
{
bail!(
@@ -2511,24 +2511,22 @@ mod tests {
}
#[async_std::test]
async fn test_read_receipt_and_unarchive() {
async fn test_read_receipt_and_unarchive() -> Result<()> {
// create alice's account
let t = TestContext::new_alice().await;
let bob_id = Contact::create(&t, "bob", "bob@example.com").await.unwrap();
let one2one_id = ChatId::create_for_contact(&t, bob_id).await.unwrap();
let bob_id = Contact::create(&t, "bob", "bob@example.com").await?;
let one2one_id = ChatId::create_for_contact(&t, bob_id).await?;
one2one_id
.set_visibility(&t, ChatVisibility::Archived)
.await
.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);
// create a group with bob, archive group
let group_id = chat::create_group_chat(&t, ProtectionStatus::Unprotected, "foo")
.await
.unwrap();
chat::add_contact_to_chat(&t, group_id, bob_id).await;
let group_id = chat::create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
chat::add_contact_to_chat(&t, group_id, bob_id).await?;
assert_eq!(
chat::get_chat_msgs(&t, group_id, 0, None)
.await
@@ -2538,16 +2536,14 @@ mod tests {
);
group_id
.set_visibility(&t, ChatVisibility::Archived)
.await
.unwrap();
let group = Chat::load_from_db(&t, group_id).await.unwrap();
.await?;
let group = Chat::load_from_db(&t, group_id).await?;
assert!(group.get_visibility() == ChatVisibility::Archived);
// everything archived, chatlist should be empty
assert_eq!(
Chatlist::try_load(&t, DC_GCL_NO_SPECIALS, None, None)
.await
.unwrap()
.await?
.len(),
0
);
@@ -2575,13 +2571,12 @@ mod tests {
1,
false,
)
.await
.unwrap();
.await?;
let msg = get_chat_msg(&t, group_id, 0, 1).await;
assert_eq!(msg.is_dc_message, MessengerMessage::Yes);
assert_eq!(msg.text.unwrap(), "hello");
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);
// bob sends a read receipt to the group
@@ -2622,27 +2617,21 @@ mod tests {
1,
false,
)
.await.unwrap();
assert_eq!(
chat::get_chat_msgs(&t, group_id, 0, None)
.await
.unwrap()
.len(),
1
);
let msg = message::Message::load_from_db(&t, msg.id).await.unwrap();
.await?;
assert_eq!(chat::get_chat_msgs(&t, group_id, 0, None).await?.len(), 1);
let msg = message::Message::load_from_db(&t, msg.id).await?;
assert_eq!(msg.state, MessageState::OutMdnRcvd);
// check, the read-receipt has not unarchived the one2one
assert_eq!(
Chatlist::try_load(&t, DC_GCL_NO_SPECIALS, None, None)
.await
.unwrap()
.await?
.len(),
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);
Ok(())
}
#[async_std::test]
@@ -2937,7 +2926,7 @@ mod tests {
}
#[async_std::test]
async fn test_parse_ndn_group_msg() {
async fn test_parse_ndn_group_msg() -> Result<()> {
let t = TestContext::new().await;
t.configure_addr("alice@gmail.com").await;
@@ -2959,32 +2948,32 @@ mod tests {
1,
false,
)
.await
.unwrap();
.await?;
let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
let msg_id = chats.get_msg_id(0).unwrap().unwrap();
let chats = Chatlist::try_load(&t, 0, None, None).await?;
let msg_id = chats.get_msg_id(0)?.unwrap();
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);
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() {
msg_id
} else {
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!(
last_msg.text,
Some(stock_str::failed_sending_to(&t, "assidhfaaspocwaeofi@gmail.com").await,)
);
assert_eq!(last_msg.from_id, DC_CONTACT_ID_INFO);
Ok(())
}
async fn load_imf_email(context: &Context, imf_raw: &[u8]) -> Message {
@@ -3032,57 +3021,43 @@ mod tests {
hello back\n";
#[async_std::test]
async fn test_github_mailing_list() {
async fn test_github_mailing_list() -> Result<()> {
let t = TestContext::new_alice().await;
t.ctx
.set_config(Config::ShowEmails, Some("2"))
.await
.unwrap();
t.ctx.set_config(Config::ShowEmails, Some("2")).await?;
dc_receive_imf(&t.ctx, GH_MAILINGLIST, "INBOX", 1, false)
.await
.unwrap();
dc_receive_imf(&t.ctx, GH_MAILINGLIST, "INBOX", 1, false).await?;
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);
let chat_id = chats.get_chat_id(0);
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_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::get_chat_contacts(&t.ctx, chat_id)
.await
.unwrap()
.len(),
1
);
assert_eq!(chat::get_chat_contacts(&t.ctx, chat_id).await?.len(), 1);
dc_receive_imf(&t.ctx, GH_MAILINGLIST2, "INBOX", 1, false)
.await
.unwrap();
dc_receive_imf(&t.ctx, GH_MAILINGLIST2, "INBOX", 1, false).await?;
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);
let contacts = Contact::get_all(&t.ctx, 0, None as Option<String>)
.await
.unwrap();
let contacts = Contact::get_all(&t.ctx, 0, None as Option<String>).await?;
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 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_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 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!(msg1.get_override_sender_name().unwrap(), "Max Mustermann");
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\

View File

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

View File

@@ -161,15 +161,19 @@ pub async fn try_decrypt(
let mut peerstate = Peerstate::from_addr(context, &from).await?;
// Apply Autocrypt header
if let Some(ref header) = Aheader::from_headers(context, &from, &mail.headers) {
if let Some(ref mut peerstate) = peerstate {
peerstate.apply_header(header, message_time);
peerstate.save_to_db(&context.sql, false).await?;
} else {
let p = Peerstate::from_header(header, message_time);
p.save_to_db(&context.sql, true).await?;
peerstate = Some(p);
match Aheader::from_headers(&from, &mail.headers) {
Ok(Some(ref header)) => {
if let Some(ref mut peerstate) = peerstate {
peerstate.apply_header(header, message_time);
peerstate.save_to_db(&context.sql, false).await?;
} else {
let p = Peerstate::from_header(header, message_time);
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

View File

@@ -1614,13 +1614,13 @@ async fn precheck_imf(
context,
job::Job::new(Action::MoveMsg, msg_id.to_u32(), Params::new(), 0),
)
.await;
.await?;
} else {
job::add(
context,
job::Job::new(Action::MarkseenMsgOnImap, msg_id.to_u32(), Params::new(), 0),
)
.await;
.await?;
}
}
} else if old_server_folder != server_folder {
@@ -1663,7 +1663,7 @@ async fn precheck_imf(
context,
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(())
}
pub async fn action_exists(context: &Context, action: Action) -> bool {
context
pub async fn action_exists(context: &Context, action: Action) -> Result<bool> {
let exists = context
.sql
.exists(
"SELECT COUNT(*) FROM jobs WHERE action=?;",
paramsv![action],
)
.await
.unwrap_or_default()
.await?;
Ok(exists)
}
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();
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(())
}
@@ -1196,7 +1196,7 @@ pub(crate) async fn schedule_resync(context: &Context) -> Result<()> {
context,
Job::new(Action::ResyncFolders, 0, Params::new(), 0),
)
.await;
.await?;
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.
pub async fn add(context: &Context, job: Job) {
pub async fn add(context: &Context, job: Job) -> Result<()> {
let action = job.action;
let delay_seconds = job.delay_seconds();
job.save(context).await.unwrap_or_else(|err| {
error!(context, "failed to save job: {}", err);
});
job.save(context).await.context("failed to save job")?;
if delay_seconds == 0 {
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>> {

View File

@@ -223,11 +223,11 @@ pub async fn send_locations_to_chat(
.unwrap_or_default();
} else if 0 == seconds && is_sending_locations_before {
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));
if 0 != seconds {
schedule_maybe_send_locations(context, false).await;
schedule_maybe_send_locations(context, false).await?;
job::add(
context,
job::Job::new(
@@ -237,19 +237,20 @@ pub async fn send_locations_to_chat(
seconds + 1,
),
)
.await;
.await?;
}
Ok(())
}
async fn schedule_maybe_send_locations(context: &Context, force_schedule: bool) {
if force_schedule || !job::action_exists(context, job::Action::MaybeSendLocations).await {
async fn schedule_maybe_send_locations(context: &Context, force_schedule: bool) -> Result<()> {
if force_schedule || !job::action_exists(context, job::Action::MaybeSendLocations).await? {
job::add(
context,
job::Job::new(job::Action::MaybeSendLocations, 0, Params::new(), 60),
)
.await;
.await?;
};
Ok(())
}
/// 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 {
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
@@ -697,7 +698,7 @@ pub(crate) async fn job_maybe_send_locations(context: &Context, _job: &Job) -> j
}
if continue_streaming {
schedule_maybe_send_locations(context, true).await;
job_try!(schedule_maybe_send_locations(context, true).await);
}
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;
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));
}
}

View File

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

View File

@@ -1605,27 +1605,30 @@ mod tests {
}
#[async_std::test]
async fn test_subject_in_group() {
async fn test_subject_in_group() -> Result<()> {
async fn send_msg_get_subject(
t: &TestContext,
group_id: ChatId,
quote: Option<&Message>,
) -> String {
) -> Result<String> {
let mut new_msg = Message::new(Viewtype::Text);
new_msg.set_text(Some("Hi".to_string()));
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;
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 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);
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
@@ -1634,13 +1637,13 @@ mod tests {
chat::create_group_chat(&t, chat::ProtectionStatus::Unprotected, "groupname") // TODO encodings, ä
.await
.unwrap();
let bob = Contact::create(&t, "", "bob@example.org").await.unwrap();
chat::add_contact_to_chat(&t, group_id, bob).await;
let bob = Contact::create(&t, "", "bob@example.org").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");
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");
dc_receive_imf(
@@ -1662,28 +1665,26 @@ mod tests {
5,
false,
)
.await
.unwrap();
.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");
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;
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");
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");
chat::forward_msgs(&t, &[message_from_bob.id], group_id)
.await
.unwrap();
let subject = get_subject(&t, t.pop_sent_msg().await).await;
chat::forward_msgs(&t, &[message_from_bob.id], group_id).await?;
let subject = get_subject(&t, t.pop_sent_msg().await).await?;
assert_eq!(subject, "Fwd: Different subject");
Ok(())
}
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;
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));
} else {
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),
time(),
)
.await;
.await
.ok();
}
} else if let Some(addr) = addr {
lot.state = LotState::QrFprMismatch;

View File

@@ -104,7 +104,7 @@ impl Context {
self,
job::Job::new(Action::UpdateRecentQuota, 0, Params::new(), 0),
)
.await;
.await?;
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
/// [`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 ====
==== 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 {
Ok(Some(addr)) => addr,
Ok(None) => {
error!(context, "Not configured, cannot generate QR code.");
return None;
bail!("Not configured, cannot generate QR code.");
}
Err(err) => {
error!(
context,
"Unable to retrieve configuration, cannot generate QR code: {:?}", err
bail!(
"Unable to retrieve configuration, cannot generate QR code: {:?}",
err
);
return None;
}
};
let self_name = context
.get_config(Config::Displayname)
.await
.ok()?
.await?
.unwrap_or_default();
let fingerprint: Fingerprint = match get_self_fingerprint(context).await {
Some(fp) => fp,
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 {
// parameters used: a=g=x=i=s=
if let Ok(chat) = Chat::load_from_db(context, group).await {
let group_name = chat.get_name();
let group_name_urlencoded =
utf8_percent_encode(group_name, NON_ALPHANUMERIC).to_string();
let chat = Chat::load_from_db(context, group).await?;
let group_name = chat.get_name();
let group_name_urlencoded = utf8_percent_encode(group_name, NON_ALPHANUMERIC).to_string();
Some(format!(
"OPENPGP4FPR:{}#a={}&g={}&x={}&i={}&s={}",
fingerprint.hex(),
self_addr_urlencoded,
&group_name_urlencoded,
&chat.grpid,
&invitenumber,
&auth,
))
} else {
error!(context, "Cannot get QR-code for chat-id {}", group,);
return None;
}
format!(
"OPENPGP4FPR:{}#a={}&g={}&x={}&i={}&s={}",
fingerprint.hex(),
self_addr_urlencoded,
&group_name_urlencoded,
&chat.grpid,
&invitenumber,
&auth,
)
} else {
// parameters used: a=n=i=s=
Some(format!(
format!(
"OPENPGP4FPR:{}#a={}&n={}&i={}&s={}",
fingerprint.hex(),
self_addr_urlencoded,
self_name_urlencoded,
&invitenumber,
&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> {
@@ -720,7 +712,7 @@ pub(crate) async fn handle_securejoin_handshake(
==========================================================*/
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);
return Ok(HandshakeMessage::Ignore);
}
@@ -860,7 +852,7 @@ async fn secure_connection_established(
"?"
};
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));
info!(context, "StockMessage::ContactVerified posted to 1:1 chat");
@@ -884,7 +876,7 @@ async fn could_not_establish_secure_connection(
)
.await;
chat::add_info_msg(context, contact_chat_id, &msg, time()).await;
chat::add_info_msg(context, contact_chat_id, &msg, time()).await?;
error!(
context,
"StockMessage::ContactNotVerified posted to 1:1 chat ({})", details
@@ -958,7 +950,7 @@ mod tests {
use crate::test_utils::TestContext;
#[async_std::test]
async fn test_setup_contact() {
async fn test_setup_contact() -> Result<()> {
let alice = TestContext::new_alice().await;
let bob = TestContext::new_bob().await;
@@ -975,10 +967,10 @@ mod tests {
.await;
// 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
dc_join_securejoin(&bob.ctx, &qr).await.unwrap();
dc_join_securejoin(&bob.ctx, &qr).await?;
let sent = bob.pop_sent_msg().await;
assert!(!bob.ctx.has_ongoing().await);
@@ -1055,14 +1047,14 @@ mod tests {
.await
.unwrap();
assert_eq!(
contact_bob.is_verified(&alice.ctx).await,
contact_bob.is_verified(&alice.ctx).await?,
VerifiedStatus::Unverified
);
// Step 5+6: Alice receives vc-request-with-auth, sends vc-contact-confirm
alice.recv_msg(&sent).await;
assert_eq!(
contact_bob.is_verified(&alice.ctx).await,
contact_bob.is_verified(&alice.ctx).await?,
VerifiedStatus::BidirectVerified
);
@@ -1104,14 +1096,14 @@ mod tests {
.await
.unwrap();
assert_eq!(
contact_bob.is_verified(&bob.ctx).await,
contact_bob.is_verified(&bob.ctx).await?,
VerifiedStatus::Unverified
);
// Step 7: Bob receives vc-contact-confirm, sends vc-contact-confirm-received
bob.recv_msg(&sent).await;
assert_eq!(
contact_alice.is_verified(&bob.ctx).await,
contact_alice.is_verified(&bob.ctx).await?,
VerifiedStatus::BidirectVerified
);
@@ -1142,6 +1134,7 @@ mod tests {
msg.get_header(HeaderDef::SecureJoin).unwrap(),
"vc-contact-confirm-received"
);
Ok(())
}
#[async_std::test]
@@ -1152,7 +1145,7 @@ mod tests {
}
#[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 bob = TestContext::new_bob().await;
@@ -1169,7 +1162,7 @@ mod tests {
.await;
// 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 {
addr: "alice@example.com".into(),
last_seen: 10,
@@ -1185,10 +1178,10 @@ mod tests {
to_save: Some(ToSave::All),
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
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
dc_join_securejoin(&bob.ctx, &qr).await.unwrap();
@@ -1228,10 +1221,7 @@ mod tests {
"vc-request-with-auth"
);
assert!(msg.get_header(HeaderDef::SecureJoinAuth).is_some());
let bob_fp = SignedPublicKey::load_self(&bob.ctx)
.await
.unwrap()
.fingerprint();
let bob_fp = SignedPublicKey::load_self(&bob.ctx).await?.fingerprint();
assert_eq!(
*msg.get_header(HeaderDef::SecureJoinFingerprint).unwrap(),
bob_fp.hex()
@@ -1244,20 +1234,17 @@ mod tests {
"bob@example.net",
Origin::ManuallyCreated,
)
.await
.unwrap();
let contact_bob = Contact::load_from_db(&alice.ctx, contact_bob_id)
.await
.unwrap();
.await?;
let contact_bob = Contact::load_from_db(&alice.ctx, contact_bob_id).await?;
assert_eq!(
contact_bob.is_verified(&alice.ctx).await,
contact_bob.is_verified(&alice.ctx).await?,
VerifiedStatus::Unverified
);
// Step 5+6: Alice receives vc-request-with-auth, sends vc-contact-confirm
alice.recv_msg(&sent).await;
assert_eq!(
contact_bob.is_verified(&alice.ctx).await,
contact_bob.is_verified(&alice.ctx).await?,
VerifiedStatus::BidirectVerified
);
@@ -1275,18 +1262,16 @@ mod tests {
.await
.expect("Error looking up contact")
.expect("Contact not found");
let contact_alice = Contact::load_from_db(&bob.ctx, contact_alice_id)
.await
.unwrap();
let contact_alice = Contact::load_from_db(&bob.ctx, contact_alice_id).await?;
assert_eq!(
contact_bob.is_verified(&bob.ctx).await,
contact_bob.is_verified(&bob.ctx).await?,
VerifiedStatus::Unverified
);
// Step 7: Bob receives vc-contact-confirm, sends vc-contact-confirm-received
bob.recv_msg(&sent).await;
assert_eq!(
contact_alice.is_verified(&bob.ctx).await,
contact_alice.is_verified(&bob.ctx).await?,
VerifiedStatus::BidirectVerified
);
@@ -1297,10 +1282,11 @@ mod tests {
msg.get_header(HeaderDef::SecureJoin).unwrap(),
"vc-contact-confirm-received"
);
Ok(())
}
#[async_std::test]
async fn test_secure_join() {
async fn test_secure_join() -> Result<()> {
let alice = TestContext::new_alice().await;
let bob = TestContext::new_bob().await;
@@ -1316,9 +1302,8 @@ mod tests {
})
.await;
let chatid = chat::create_group_chat(&alice.ctx, ProtectionStatus::Protected, "the chat")
.await
.unwrap();
let chatid =
chat::create_group_chat(&alice.ctx, ProtectionStatus::Protected, "the chat").await?;
// Step 1: Generate QR-code, secure-join implied by chatid
let qr = dc_get_securejoin_qr(&alice.ctx, Some(chatid))
@@ -1388,10 +1373,7 @@ mod tests {
"vg-request-with-auth"
);
assert!(msg.get_header(HeaderDef::SecureJoinAuth).is_some());
let bob_fp = SignedPublicKey::load_self(&bob.ctx)
.await
.unwrap()
.fingerprint();
let bob_fp = SignedPublicKey::load_self(&bob.ctx).await?.fingerprint();
assert_eq!(
*msg.get_header(HeaderDef::SecureJoinFingerprint).unwrap(),
bob_fp.hex()
@@ -1400,21 +1382,18 @@ mod tests {
// Alice should not yet have Bob verified
let contact_bob_id =
Contact::lookup_id_by_addr(&alice.ctx, "bob@example.net", Origin::Unknown)
.await
.expect("Error looking up contact")
.await?
.expect("Contact not found");
let contact_bob = Contact::load_from_db(&alice.ctx, contact_bob_id)
.await
.unwrap();
let contact_bob = Contact::load_from_db(&alice.ctx, contact_bob_id).await?;
assert_eq!(
contact_bob.is_verified(&alice.ctx).await,
contact_bob.is_verified(&alice.ctx).await?,
VerifiedStatus::Unverified
);
// Step 5+6: Alice receives vg-request-with-auth, sends vg-member-added
alice.recv_msg(&sent).await;
assert_eq!(
contact_bob.is_verified(&alice.ctx).await,
contact_bob.is_verified(&alice.ctx).await?,
VerifiedStatus::BidirectVerified
);
@@ -1432,18 +1411,16 @@ mod tests {
.await
.expect("Error looking up contact")
.expect("Contact not found");
let contact_alice = Contact::load_from_db(&bob.ctx, contact_alice_id)
.await
.unwrap();
let contact_alice = Contact::load_from_db(&bob.ctx, contact_alice_id).await?;
assert_eq!(
contact_bob.is_verified(&bob.ctx).await,
contact_bob.is_verified(&bob.ctx).await?,
VerifiedStatus::Unverified
);
// Step 7: Bob receives vg-member-added, sends vg-member-added-received
bob.recv_msg(&sent).await;
assert_eq!(
contact_alice.is_verified(&bob.ctx).await,
contact_alice.is_verified(&bob.ctx).await?,
VerifiedStatus::BidirectVerified
);
@@ -1456,8 +1433,9 @@ mod tests {
);
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.ctx.has_ongoing().await)
assert!(!bob.ctx.has_ongoing().await);
Ok(())
}
}