Compare commits

..

5 Commits

Author SHA1 Message Date
Septias
c68e25a970 fix tests 2024-11-22 10:26:27 +01:00
Septias
e04c4446d6 fix: Disable setting draft if not in chat 2024-11-20 12:52:33 +01:00
Septias
1b9f3368fa fix: only show draft in message info if self is in chat
This PR adds a check to `get_info` so that `draft` is only added if self
is part of the group determined by `can_send`.

close #6229
2024-11-20 12:28:03 +01:00
gerryfrancis
3b9e6d6ffa Fix type in context.rs 2024-11-19 18:35:42 +01:00
Sebastian Klähn
8f3be764d2 change: Use i.delta.chat in qr codes (#6223)
As discussed in #5467 we want to use `i.delta.chat` in QR codes in favor
of `OPENPGP4FPR:` scheme. This PR does the replacement in
`get_securejoin_qr` which is used in `get_securejoin_qr_svg`.

close #5467
2024-11-19 17:32:42 +01:00
9 changed files with 120 additions and 312 deletions

View File

@@ -1154,13 +1154,7 @@ uint32_t dc_send_videochat_invitation (dc_context_t* context, uint32_t chat_id);
* @memberof dc_context_t
* @param context The context object.
* @param msg_id The ID of the message with the webxdc instance.
* @param json program-readable data, this is create in JS land as:
* - `payload`: any JS object or primitive.
* - `info`: optional informational message. will be shown in chat an may be added as system notification.
* - `notify`: optional array of user `addr` that should be notified eg. by a sound.
* note that still all users get the update payload and the `info` message shown in a chat.
* - `document`: optional document name. shown eg. in title bar.
* - `summary`: optional summary. shown beside app icon.
* @param json program-readable data, the actual payload
* @param descr The user-visible description of JSON data,
* in case of a chess game, e.g. the move.
* @return 1=success, 0=error
@@ -4517,26 +4511,6 @@ int dc_msg_get_info_type (const dc_msg_t* msg);
#define DC_INFO_INVALID_UNENCRYPTED_MAIL 13
#define DC_INFO_WEBXDC_INFO_MESSAGE 32
/**
* Get deeplink attached to an info message.
* The info message need to be of type DC_INFO_WEBXDC_INFO_MESSAGE.
*
* Typically, this is used to start the corresponding webxdc directly or indirectly
* and passing the deeplink to `window.webxdc.deeplink` in JS land.
*
* @memberof dc_context_t
* @param context The context object.
* @param info_msg_id The info message object.
* Not: the webxdc instance.
* @return The deeplink that can be passed to `window.webxdc.deeplink` in JS land.
* The content of the deeplink is a full status update and its semantic is defined by the app.
* Returns NULL if there is no deeplink attached to the info message and on errors.
*
*/
char* dc_msg_get_webxdc_deeplink (const dc_msg_t* msg);
/**
* Check if a message is still in creation. A message is in creation between
* the calls to dc_prepare_msg() and dc_send_msg().
@@ -6111,10 +6085,6 @@ void dc_event_unref(dc_event_t* event);
*
* There is no extra #DC_EVENT_MSGS_CHANGED event send together with this event.
*
* If the message is an webxdc info message,
* dc_msg_get_parent() returns the webxdc instance the notification belongs to.
* Use dc_msg_get_webxdc_deeplink() to get the deeplink to pass to `window.webxdc.deeplink` in JS land.
*
* @param data1 (int) chat_id
* @param data2 (int) msg_id
*/

View File

@@ -3681,31 +3681,6 @@ pub unsafe extern "C" fn dc_msg_get_info_type(msg: *mut dc_msg_t) -> libc::c_int
ffi_msg.message.get_info_type() as libc::c_int
}
#[no_mangle]
pub unsafe extern "C" fn dc_msg_get_webxdc_deeplink(msg: *mut dc_msg_t) -> *mut libc::c_char {
if msg.is_null() {
eprintln!("ignoring careless call to dc_msg_get_webxdc_deeplink()");
return "".strdup();
}
let ffi_msg = &*msg;
let ctx = &*ffi_msg.context;
let res = block_on(async move {
ffi_msg
.message
.get_webxdc_deeplink(ctx)
.await
.context("failed to get deeplink")
.log_err(ctx)
.unwrap_or(None)
});
match res {
Some(str) => str.strdup(),
None => ptr::null_mut(),
}
}
#[no_mangle]
pub unsafe extern "C" fn dc_msg_is_increation(msg: *mut dc_msg_t) -> libc::c_int {
if msg.is_null() {

View File

@@ -705,7 +705,7 @@ class TestOfflineChat:
ac1 = acfactory.get_pseudo_configured_account()
ac2 = acfactory.get_pseudo_configured_account()
qr = ac1.get_setup_contact_qr()
assert qr.startswith("OPENPGP4FPR:")
assert qr.startswith("https://i.delta.chat")
res = ac2.check_qr(qr)
assert res.is_ask_verifycontact()
assert not res.is_ask_verifygroup()

View File

@@ -1,11 +1,11 @@
//! # Chat module.
use std::cmp;
use std::collections::{HashMap, HashSet};
use std::fmt;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::time::Duration;
use std::{cmp, convert};
use anyhow::{anyhow, bail, ensure, Context as _, Result};
use deltachat_contact_tools::{sanitize_bidi_characters, sanitize_single_line, ContactAddress};
@@ -809,6 +809,14 @@ impl ChatId {
///
/// Passing `None` as message just deletes the draft
pub async fn set_draft(self, context: &Context, mut msg: Option<&mut Message>) -> Result<()> {
let self_in_chat = self.is_self_in_chat(context).await?;
if !self_in_chat {
warn!(
context,
"Not setting draft because self is not member of the chat"
);
return Ok(());
}
if self.is_special() {
return Ok(());
}
@@ -875,6 +883,22 @@ impl ChatId {
> 0)
}
async fn get_chat_type(self, context: &Context) -> Result<Chattype> {
context
.sql
.query_get_value("SELECT type FROM chats WHERE id=?;", (self,))
.await
.map(|res| res.ok_or(anyhow!("Can't get type for char")))
.and_then(convert::identity)
}
async fn is_self_in_chat(self, context: &Context) -> Result<bool> {
match self.get_chat_type(context).await? {
Chattype::Single | Chattype::Broadcast | Chattype::Mailinglist => Ok(true),
Chattype::Group => is_contact_in_chat(context, self, ContactId::SELF).await,
}
}
/// Set provided message as draft message for specified chat.
/// Returns true if the draft was added or updated in place.
async fn do_set_draft(self, context: &Context, msg: &mut Message) -> Result<bool> {
@@ -1838,8 +1862,9 @@ impl Chat {
/// This is somewhat experimental, even more so than the rest of
/// deltachat, and the data returned is still subject to change.
pub async fn get_info(&self, context: &Context) -> Result<ChatInfo> {
let self_in_chat = self.is_self_in_chat(context).await?;
let draft = match self.id.get_draft(context).await? {
Some(message) => message.text,
Some(message) if self_in_chat => message.text,
_ => String::new(),
};
Ok(ChatInfo {
@@ -4508,34 +4533,9 @@ pub(crate) async fn delete_and_reset_all_device_msgs(context: &Context) -> Resul
/// Adds an informational message to chat.
///
/// For example, it can be a message showing that a member was added to a group.
/// Doesn't fail if the chat doesn't exist.
#[allow(clippy::too_many_arguments)]
pub(crate) async fn add_info_msg_with_cmd(
context: &Context,
chat_id: ChatId,
text: &str,
cmd: SystemMessage,
timestamp_sort: i64,
timestamp_sent_rcvd: Option<i64>,
parent: Option<&Message>,
from_id: Option<ContactId>,
) -> Result<MsgId> {
add_info_msg_with_importance(
context,
chat_id,
text,
cmd,
timestamp_sort,
timestamp_sent_rcvd,
parent,
from_id,
false,
)
.await
}
/// Adds an informational message to chat, optionally showing a notification for important messages.
#[allow(clippy::too_many_arguments)]
pub(crate) async fn add_info_msg_with_importance(
context: &Context,
chat_id: ChatId,
text: &str,
@@ -4545,7 +4545,6 @@ pub(crate) async fn add_info_msg_with_importance(
timestamp_sent_rcvd: Option<i64>,
parent: Option<&Message>,
from_id: Option<ContactId>,
important: bool,
) -> Result<MsgId> {
let rfc724_mid = create_outgoing_rfc724_mid();
let ephemeral_timer = chat_id.get_ephemeral_timer(context).await?;
@@ -4579,7 +4578,7 @@ pub(crate) async fn add_info_msg_with_importance(
context.new_msgs_notify.notify_one();
let msg_id = MsgId::new(row_id.try_into()?);
chat_id.emit_msg_event(context, msg_id, important);
context.emit_msgs_changed(chat_id, msg_id);
Ok(msg_id)
}
@@ -4920,6 +4919,70 @@ mod tests {
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_draft_only_in_accesible_chat_summary() -> Result<()> {
let mut t = TestContextManager::new();
let alice = t.alice().await;
let bob = t.bob().await;
let chat_id = create_group_chat(&alice, ProtectionStatus::Unprotected, "chat").await?;
let bob_contact = Contact::create(&alice, "Bob", "bob@example.net").await?;
add_contact_to_chat(&alice, chat_id, bob_contact).await?;
let bob_chat_id = bob
.recv_msg(&alice.send_text(chat_id, "hello bob").await)
.await
.chat_id;
bob_chat_id.accept(&bob).await?;
// Set draft and assure it is in chat info.
let draft = String::from("I'm gonna send this!!");
bob_chat_id
.set_draft(&bob, Some(&mut Message::new_text(draft.clone())))
.await?;
let chat = Chat::load_from_db(&bob, bob_chat_id).await?;
let info = chat.get_info(&bob).await?;
assert_eq!(info.draft, draft);
// Alice removes bob, so draft is not shown in chat info.
remove_contact_from_chat(&alice, chat_id, bob_contact).await?;
bob.recv_msg(&alice.pop_sent_msg().await).await;
let chat = Chat::load_from_db(&bob, bob_chat_id).await?;
assert!(!chat.can_send(&bob).await?);
let info = chat.get_info(&bob).await?;
assert_eq!(info.draft, String::from(""));
// Alice re-adds bob, so draft is shown again.
add_contact_to_chat(&alice, chat_id, bob_contact).await?;
let bob_chat_id = bob
.recv_msg(&alice.send_text(chat_id, "hello again, bob").await)
.await
.chat_id;
let chat = Chat::load_from_db(&bob, bob_chat_id).await?;
assert!(chat.can_send(&bob).await?);
let info = chat.get_info(&bob).await?;
assert_eq!(info.draft, draft);
// Bob leaves group so draft is not shown.
remove_contact_from_chat(&bob, bob_chat_id, ContactId::SELF).await?;
let chat = Chat::load_from_db(&bob, bob_chat_id).await?;
assert!(!chat.can_send(&bob).await?);
let info = chat.get_info(&bob).await?;
assert_eq!(info.draft, String::from(""));
// Bob can not set a draft if not in chat.
bob_chat_id
.set_draft(
&bob,
Some(&mut Message::new_text(String::from(
"I'm gonna send this fr fr!!",
))),
)
.await?;
assert_eq!(bob_chat_id.get_draft(&bob).await?.unwrap().text, draft);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_change_quotes_on_reused_message_object() -> Result<()> {
let t = TestContext::new_alice().await;

View File

@@ -2082,7 +2082,7 @@ mod tests {
);
// Change the config circumventing the cache
// This simulates what the notfication plugin on iOS might do
// This simulates what the notification plugin on iOS might do
// because it runs in a different process
alice
.sql

View File

@@ -63,7 +63,6 @@ pub async fn debug_logging_loop(context: &Context, events: Receiver<DebugEventLo
summary: None,
document: None,
uid: None,
notify: None,
},
time,
)

View File

@@ -104,7 +104,7 @@ pub async fn get_securejoin_qr(context: &Context, group: Option<ChatId>) -> Resu
context.scheduler.interrupt_inbox().await;
}
format!(
"OPENPGP4FPR:{}#a={}&g={}&x={}&i={}&s={}",
"https://i.delta.chat/#{}&a={}&g={}&x={}&i={}&s={}",
fingerprint.hex(),
self_addr_urlencoded,
&group_name_urlencoded,
@@ -119,7 +119,7 @@ pub async fn get_securejoin_qr(context: &Context, group: Option<ChatId>) -> Resu
context.scheduler.interrupt_inbox().await;
}
format!(
"OPENPGP4FPR:{}#a={}&n={}&i={}&s={}",
"https://i.delta.chat/#{}&a={}&n={}&i={}&s={}",
fingerprint.hex(),
self_addr_urlencoded,
self_name_urlencoded,

View File

@@ -177,10 +177,6 @@ pub struct StatusUpdateItem {
/// If there is no ID, message is always considered to be unique.
#[serde(skip_serializing_if = "Option::is_none")]
pub uid: Option<String>,
/// Array of Addr that should be notified about this update.
#[serde(skip_serializing_if = "Option::is_none")]
pub notify: Option<Vec<String>>,
}
/// Update items as passed to the UIs.
@@ -315,53 +311,29 @@ impl Context {
if can_info_msg {
if let Some(ref info) = status_update_item.info {
let mut info_msg_id = self.get_overwritable_info_msg_id(instance, from_id).await?;
let notify_list = status_update_item.notify;
if notify_list.is_none() && info_msg_id.is_some() {
if let Some(info_msg_id) = info_msg_id {
chat::update_msg_text_and_timestamp(
self,
instance.chat_id,
info_msg_id,
info.as_str(),
timestamp,
)
.await?;
}
if let Some(info_msg_id) =
self.get_overwritable_info_msg_id(instance, from_id).await?
{
chat::update_msg_text_and_timestamp(
self,
instance.chat_id,
info_msg_id,
info.as_str(),
timestamp,
)
.await?;
} else {
let notify = if let Some(notify_list) = notify_list {
if let Ok(self_addr) = self.get_primary_self_addr().await {
notify_list.contains(&self_addr)
} else {
false
}
} else {
false
};
info_msg_id = Some(
chat::add_info_msg_with_importance(
self,
instance.chat_id,
info.as_str(),
SystemMessage::WebxdcInfoMessage,
timestamp,
None,
Some(instance),
Some(from_id),
notify,
)
.await?,
);
}
if let Some(info_msg_id) = info_msg_id {
let mut info_msg = Message::load_from_db(self, info_msg_id).await?;
info_msg
.param
.set_int(Param::Arg, status_update_serial.to_u32() as i32);
info_msg.update_param(self).await?;
chat::add_info_msg_with_cmd(
self,
instance.chat_id,
info.as_str(),
SystemMessage::WebxdcInfoMessage,
timestamp,
None,
Some(instance),
Some(from_id),
)
.await?;
}
}
}
@@ -934,37 +906,6 @@ impl Message {
internet_access,
})
}
/// Get deeplink attached to an info message.
///
/// The info message need to be of type SystemMessage::WebxdcInfoMessage.
/// Typically, this is used to start the corresponding webxdc directly or indirectly
// and passing the deeplink to `window.webxdc.deeplink` in JS land.
pub async fn get_webxdc_deeplink(&self, context: &Context) -> Result<Option<String>> {
let Some(serial) = self.param.get_int(Param::Arg) else {
return Ok(None);
};
let serial = StatusUpdateSerial::new(serial as u32);
let Some(instance) = self.parent(&context).await? else {
return Ok(None);
};
let update_item_str: String = context
.sql
.query_get_value(
"SELECT update_item FROM msgs_status_updates WHERE msg_id=? AND id=?",
(instance.id, serial),
)
.await?
.context("cannot read update item")?;
let update_item = StatusUpdateItem {
uid: None, // Erase UIDs, apps, bots and tests don't need to know them.
..serde_json::from_str(&update_item_str)?
};
let json = serde_json::to_string(&update_item)?;
Ok(Some(json))
}
}
#[cfg(test)]
@@ -1481,7 +1422,6 @@ mod tests {
document: None,
summary: None,
uid: Some("iecie2Ze".to_string()),
notify: None,
},
1640178619,
true,
@@ -1506,7 +1446,6 @@ mod tests {
document: None,
summary: None,
uid: Some("iecie2Ze".to_string()),
notify: None,
},
1640178619,
true,
@@ -1540,7 +1479,6 @@ mod tests {
document: None,
summary: None,
uid: None,
notify: None,
},
1640178619,
true,
@@ -1560,7 +1498,6 @@ mod tests {
document: None,
summary: None,
uid: None,
notify: None,
},
1640178619,
true,
@@ -2934,139 +2871,4 @@ sth_for_the = "future""#
Ok(())
}
async fn has_incoming_msg_event(t: &TestContext, msg: Message) -> bool {
t.evtracker
.get_matching_opt(t, |evt| {
if let EventType::IncomingMsg { msg_id, .. } = evt {
*msg_id == msg.id
} else {
false
}
})
.await
.is_some()
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_webxdc_notify_one() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = tcm.alice().await;
let bob = tcm.bob().await;
let fiona = tcm.fiona().await;
let grp_id = alice
.create_group_with_members(ProtectionStatus::Unprotected, "grp", &[&bob, &fiona])
.await;
let instance = send_webxdc_instance(&alice, grp_id).await?;
let sent1 = alice.pop_sent_msg().await;
alice
.send_webxdc_status_update(
instance.id,
r#"{"payload":7,"info": "your move!","notify":["bob@example.net"]}"#,
"d",
)
.await?;
alice.flush_status_updates().await?;
let sent2 = alice.pop_sent_msg().await;
let info_msg = alice.get_last_msg().await;
assert!(info_msg.is_info());
assert!(!has_incoming_msg_event(&alice, info_msg).await);
bob.recv_msg(&sent1).await;
bob.recv_msg_trash(&sent2).await;
let info_msg = bob.get_last_msg().await;
assert!(info_msg.is_info());
assert!(has_incoming_msg_event(&bob, info_msg).await);
fiona.recv_msg(&sent1).await;
fiona.recv_msg_trash(&sent2).await;
let info_msg = fiona.get_last_msg().await;
assert!(info_msg.is_info());
assert!(!has_incoming_msg_event(&fiona, info_msg).await);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_webxdc_notify_multiple() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = tcm.alice().await;
let bob = tcm.bob().await;
let fiona = tcm.fiona().await;
let grp_id = alice
.create_group_with_members(ProtectionStatus::Unprotected, "grp", &[&bob, &fiona])
.await;
let instance = send_webxdc_instance(&alice, grp_id).await?;
let sent1 = alice.pop_sent_msg().await;
alice
.send_webxdc_status_update(
instance.id,
r#"{"payload":7,"info": "my move!","notify":["bob@example.net","fiona@example.net"]}"#,
"d",
)
.await?;
alice.flush_status_updates().await?;
let sent2 = alice.pop_sent_msg().await;
let info_msg = alice.get_last_msg().await;
assert!(info_msg.is_info());
assert!(!has_incoming_msg_event(&alice, info_msg).await);
bob.recv_msg(&sent1).await;
bob.recv_msg_trash(&sent2).await;
let info_msg = bob.get_last_msg().await;
assert!(info_msg.is_info());
assert!(has_incoming_msg_event(&bob, info_msg).await);
fiona.recv_msg(&sent1).await;
fiona.recv_msg_trash(&sent2).await;
let info_msg = fiona.get_last_msg().await;
assert!(info_msg.is_info());
assert!(has_incoming_msg_event(&fiona, info_msg).await);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_webxdc_deeplink() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = tcm.alice().await;
let bob = tcm.bob().await;
let grp_id = alice
.create_group_with_members(ProtectionStatus::Unprotected, "grp", &[&bob])
.await;
let instance = send_webxdc_instance(&alice, grp_id).await?;
let sent1 = alice.pop_sent_msg().await;
alice
.send_webxdc_status_update(
instance.id,
r#"{"payload": "my deeplink data", "info": "my move!"}"#,
"d",
)
.await?;
alice.flush_status_updates().await?;
let sent2 = alice.pop_sent_msg().await;
let info_msg = alice.get_last_msg().await;
assert!(info_msg.is_info());
assert_eq!(
info_msg.get_webxdc_deeplink(&alice).await?.unwrap(),
r#"{"payload":"my deeplink data","info":"my move!"}"#
);
bob.recv_msg(&sent1).await;
bob.recv_msg_trash(&sent2).await;
let info_msg = bob.get_last_msg().await;
assert!(info_msg.is_info());
assert_eq!(
info_msg.get_webxdc_deeplink(&bob).await?.unwrap(),
r#"{"payload":"my deeplink data","info":"my move!"}"#
);
Ok(())
}
}

View File

@@ -149,7 +149,6 @@ pub(crate) async fn intercept_get_updates(
document: None,
summary: None,
uid: None,
notify: None,
},
serial: StatusUpdateSerial(location.location_id),
max_serial: StatusUpdateSerial(location.location_id),