mirror of
https://github.com/chatmail/core.git
synced 2026-04-17 21:46:35 +03:00
Resultification
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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" => {
|
||||
|
||||
@@ -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" => {
|
||||
|
||||
@@ -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]
|
||||
|
||||
239
src/chat.rs
239
src/chat.rs
@@ -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(())
|
||||
|
||||
@@ -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?;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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\
|
||||
|
||||
@@ -85,7 +85,7 @@ impl MsgId {
|
||||
context,
|
||||
Job::new(Action::DownloadMsg, self.to_u32(), Params::new(), 0),
|
||||
)
|
||||
.await;
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
20
src/e2ee.rs
20
src/e2ee.rs
@@ -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
|
||||
|
||||
@@ -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?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
19
src/job.rs
19
src/job.rs
@@ -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>> {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -104,7 +104,7 @@ impl Context {
|
||||
self,
|
||||
job::Job::new(Action::UpdateRecentQuota, 0, Params::new(), 0),
|
||||
)
|
||||
.await;
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user