Merge stable into master

This commit is contained in:
link2xt
2023-09-30 12:16:34 +00:00
14 changed files with 305 additions and 190 deletions

8
Cargo.lock generated
View File

@@ -4882,9 +4882,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio-tungstenite" name = "tokio-tungstenite"
version = "0.20.0" version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b2dbec703c26b00d74844519606ef15d09a7d6857860f84ad223dec002ddea2" checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c"
dependencies = [ dependencies = [
"futures-util", "futures-util",
"log", "log",
@@ -5095,9 +5095,9 @@ checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
[[package]] [[package]]
name = "tungstenite" name = "tungstenite"
version = "0.20.0" version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e862a1c4128df0112ab625f55cd5c934bcb4312ba80b39ae4b4835a3fd58e649" checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9"
dependencies = [ dependencies = [
"byteorder", "byteorder",
"bytes", "bytes",

View File

@@ -5063,6 +5063,7 @@ int dc_contact_is_verified (dc_contact_t* contact);
* A string containing the verifiers address. If it is the same address as the contact itself, * A string containing the verifiers address. If it is the same address as the contact itself,
* we verified the contact ourself. If it is an empty string, we don't have verifier * we verified the contact ourself. If it is an empty string, we don't have verifier
* information or the contact is not verified. * information or the contact is not verified.
* @deprecated 2023-09-28, use dc_contact_get_verifier_id instead
*/ */
char* dc_contact_get_verifier_addr (dc_contact_t* contact); char* dc_contact_get_verifier_addr (dc_contact_t* contact);
@@ -5075,7 +5076,7 @@ char* dc_contact_get_verifier_addr (dc_contact_t* contact);
* @memberof dc_contact_t * @memberof dc_contact_t
* @param contact The contact object. * @param contact The contact object.
* @return * @return
* The `ContactId` of the verifiers address. If it is the same address as the contact itself, * The contact ID of the verifier. If it is DC_CONTACT_ID_SELF,
* we verified the contact ourself. If it is 0, we don't have verifier information or * we verified the contact ourself. If it is 0, we don't have verifier information or
* the contact is not verified. * the contact is not verified.
*/ */

View File

@@ -2533,7 +2533,12 @@ pub unsafe extern "C" fn dc_set_location(
} }
let ctx = &*context; let ctx = &*context;
block_on(location::set(ctx, latitude, longitude, accuracy)) as _ block_on(async move {
location::set(ctx, latitude, longitude, accuracy)
.await
.log_err(ctx)
.unwrap_or_default()
}) as libc::c_int
} }
#[no_mangle] #[no_mangle]
@@ -4507,7 +4512,14 @@ pub unsafe extern "C" fn dc_provider_new_from_email(
let ctx = &*context; let ctx = &*context;
match block_on(provider::get_provider_info(ctx, addr.as_str(), true)) { match block_on(provider::get_provider_info_by_addr(
ctx,
addr.as_str(),
true,
))
.log_err(ctx)
.unwrap_or_default()
{
Some(provider) => provider, Some(provider) => provider,
None => ptr::null_mut(), None => ptr::null_mut(),
} }
@@ -4534,11 +4546,14 @@ pub unsafe extern "C" fn dc_provider_new_from_email_with_dns(
match socks5_enabled { match socks5_enabled {
Ok(socks5_enabled) => { Ok(socks5_enabled) => {
match block_on(provider::get_provider_info( match block_on(provider::get_provider_info_by_addr(
ctx, ctx,
addr.as_str(), addr.as_str(),
socks5_enabled, socks5_enabled,
)) { ))
.log_err(ctx)
.unwrap_or_default()
{
Some(provider) => provider, Some(provider) => provider,
None => ptr::null_mut(), None => ptr::null_mut(),
} }

View File

@@ -903,7 +903,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
let latitude = arg1.parse()?; let latitude = arg1.parse()?;
let longitude = arg2.parse()?; let longitude = arg2.parse()?;
let continue_streaming = location::set(&context, latitude, longitude, 0.).await; let continue_streaming = location::set(&context, latitude, longitude, 0.).await?;
if continue_streaming { if continue_streaming {
println!("Success, streaming should be continued."); println!("Success, streaming should be continued.");
} else { } else {

View File

@@ -3578,6 +3578,8 @@ pub async fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: ChatId)
msg.param.remove(Param::ForcePlaintext); msg.param.remove(Param::ForcePlaintext);
msg.param.remove(Param::Cmd); msg.param.remove(Param::Cmd);
msg.param.remove(Param::OverrideSenderDisplayname); msg.param.remove(Param::OverrideSenderDisplayname);
msg.param.remove(Param::WebxdcDocument);
msg.param.remove(Param::WebxdcDocumentTimestamp);
msg.param.remove(Param::WebxdcSummary); msg.param.remove(Param::WebxdcSummary);
msg.param.remove(Param::WebxdcSummaryTimestamp); msg.param.remove(Param::WebxdcSummaryTimestamp);
msg.in_reply_to = None; msg.in_reply_to = None;

View File

@@ -1236,13 +1236,24 @@ impl Contact {
/// Returns the ContactId that verified the contact. /// Returns the ContactId that verified the contact.
pub async fn get_verifier_id(&self, context: &Context) -> Result<Option<ContactId>> { pub async fn get_verifier_id(&self, context: &Context) -> Result<Option<ContactId>> {
let verifier_addr = self.get_verifier_addr(context).await?; let Some(verifier_addr) = self.get_verifier_addr(context).await? else {
if let Some(addr) = verifier_addr { return Ok(None);
Ok(Contact::lookup_id_by_addr(context, &addr, Origin::AddressBook).await?) };
} else {
if verifier_addr == self.addr {
// Contact is directly verified via QR code.
return Ok(Some(ContactId::SELF));
}
match Contact::lookup_id_by_addr(context, &verifier_addr, Origin::AddressBook).await? {
Some(contact_id) => Ok(Some(contact_id)),
None => {
let addr = &self.addr;
warn!(context, "Could not lookup contact with address {verifier_addr} which introduced {addr}.");
Ok(None) Ok(None)
} }
} }
}
/// Returns the number of real (i.e. non-special) contacts in the database. /// Returns the number of real (i.e. non-special) contacts in the database.
pub async fn get_real_cnt(context: &Context) -> Result<usize> { pub async fn get_real_cnt(context: &Context) -> Result<usize> {

View File

@@ -382,7 +382,7 @@ impl Context {
translated_stockstrings: stockstrings, translated_stockstrings: stockstrings,
events, events,
scheduler: SchedulerState::new(), scheduler: SchedulerState::new(),
ratelimit: RwLock::new(Ratelimit::new(Duration::new(60, 0), 6.0)), // Allow to send 6 messages immediately, no more than once every 10 seconds. ratelimit: RwLock::new(Ratelimit::new(Duration::new(60, 0), 6.0)), // Allow at least 1 message every 10 seconds + a burst of 6.
quota: RwLock::new(None), quota: RwLock::new(None),
quota_update_request: AtomicBool::new(false), quota_update_request: AtomicBool::new(false),
resync_request: AtomicBool::new(false), resync_request: AtomicBool::new(false),
@@ -820,7 +820,22 @@ impl Context {
pub async fn get_next_msgs(&self) -> Result<Vec<MsgId>> { pub async fn get_next_msgs(&self) -> Result<Vec<MsgId>> {
let last_msg_id = match self.get_config(Config::LastMsgId).await? { let last_msg_id = match self.get_config(Config::LastMsgId).await? {
Some(s) => MsgId::new(s.parse()?), Some(s) => MsgId::new(s.parse()?),
None => MsgId::new_unset(), None => {
// If `last_msg_id` is not set yet,
// subtract 1 from the last id,
// so a single message is returned and can
// be marked as seen.
self.sql
.query_row(
"SELECT IFNULL((SELECT MAX(id) - 1 FROM msgs), 0)",
(),
|row| {
let msg_id: MsgId = row.get(0)?;
Ok(msg_id)
},
)
.await?
}
}; };
let list = self let list = self

View File

@@ -328,13 +328,13 @@ pub async fn is_sending_locations_to_chat(
} }
/// Sets current location of the user device. /// Sets current location of the user device.
pub async fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> bool { pub async fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> Result<bool> {
if latitude == 0.0 && longitude == 0.0 { if latitude == 0.0 && longitude == 0.0 {
return true; return Ok(true);
} }
let mut continue_streaming = false; let mut continue_streaming = false;
if let Ok(chats) = context let chats = context
.sql .sql
.query_map( .query_map(
"SELECT id FROM chats WHERE locations_send_until>?;", "SELECT id FROM chats WHERE locations_send_until>?;",
@@ -346,10 +346,10 @@ pub async fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64
.map_err(Into::into) .map_err(Into::into)
}, },
) )
.await .await?;
{
for chat_id in chats { for chat_id in chats {
if let Err(err) = context.sql.execute( context.sql.execute(
"INSERT INTO locations \ "INSERT INTO locations \
(latitude, longitude, accuracy, timestamp, chat_id, from_id) VALUES (?,?,?,?,?,?);", (latitude, longitude, accuracy, timestamp, chat_id, from_id) VALUES (?,?,?,?,?,?);",
( (
@@ -359,20 +359,16 @@ pub async fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64
time(), time(),
chat_id, chat_id,
ContactId::SELF, ContactId::SELF,
) )).await.context("Failed to store location")?;
).await {
warn!(context, "failed to store location {:#}", err); info!(context, "Stored location for chat {chat_id}.");
} else {
info!(context, "stored location for chat {}", chat_id);
continue_streaming = true; continue_streaming = true;
} }
}
if continue_streaming { if continue_streaming {
context.emit_event(EventType::LocationChanged(Some(ContactId::SELF))); context.emit_event(EventType::LocationChanged(Some(ContactId::SELF)));
}; };
}
continue_streaming Ok(continue_streaming)
} }
/// Searches for locations in the given time range, optionally filtering by chat and contact IDs. /// Searches for locations in the given time range, optionally filtering by chat and contact IDs.
@@ -464,7 +460,7 @@ pub async fn delete_all(context: &Context) -> Result<()> {
} }
/// Returns `location.kml` contents. /// Returns `location.kml` contents.
pub async fn get_kml(context: &Context, chat_id: ChatId) -> Result<(String, u32)> { pub async fn get_kml(context: &Context, chat_id: ChatId) -> Result<Option<(String, u32)>> {
let mut last_added_location_id = 0; let mut last_added_location_id = 0;
let self_addr = context.get_primary_self_addr().await?; let self_addr = context.get_primary_self_addr().await?;
@@ -534,9 +530,11 @@ pub async fn get_kml(context: &Context, chat_id: ChatId) -> Result<(String, u32)
ret += "</Document>\n</kml>"; ret += "</Document>\n</kml>";
} }
ensure!(location_count > 0, "No locations processed"); if location_count > 0 {
Ok(Some((ret, last_added_location_id)))
Ok((ret, last_added_location_id)) } else {
Ok(None)
}
} }
fn get_kml_timestamp(utc: i64) -> String { fn get_kml_timestamp(utc: i64) -> String {
@@ -928,4 +926,38 @@ Content-Disposition: attachment; filename="location.kml"
assert_eq!(locations.len(), 1); assert_eq!(locations.len(), 1);
Ok(()) Ok(())
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_send_locations_to_chat() -> Result<()> {
let alice = TestContext::new_alice().await;
let bob = TestContext::new_bob().await;
let alice_chat = alice.create_chat(&bob).await;
send_locations_to_chat(&alice, alice_chat.id, 1000).await?;
let sent = alice.pop_sent_msg().await;
let msg = bob.recv_msg(&sent).await;
assert_eq!(msg.text, "Location streaming enabled by alice@example.org.");
let bob_chat_id = msg.chat_id;
assert_eq!(set(&alice, 10.0, 20.0, 1.0).await?, true);
// Send image without text.
let file_name = "image.png";
let bytes = include_bytes!("../test-data/image/logo.png");
let file = alice.get_blobdir().join(file_name);
tokio::fs::write(&file, bytes).await?;
let mut msg = Message::new(Viewtype::Image);
msg.set_file(file.to_str().unwrap(), None);
let sent = alice.send_msg(alice_chat.id, &mut msg).await;
let msg = bob.recv_msg_opt(&sent).await.unwrap();
assert!(msg.chat_id == bob_chat_id);
assert_eq!(msg.msg_ids.len(), 1);
let bob_msg = Message::load_from_db(&bob, *msg.msg_ids.get(0).unwrap()).await?;
assert_eq!(bob_msg.chat_id, bob_chat_id);
assert_eq!(bob_msg.viewtype, Viewtype::Image);
Ok(())
}
} }

View File

@@ -860,9 +860,13 @@ impl<'a> MimeFactory<'a> {
} }
/// Returns MIME part with a `location.kml` attachment. /// Returns MIME part with a `location.kml` attachment.
async fn get_location_kml_part(&mut self, context: &Context) -> Result<PartBuilder> { async fn get_location_kml_part(&mut self, context: &Context) -> Result<Option<PartBuilder>> {
let (kml_content, last_added_location_id) = let Some((kml_content, last_added_location_id)) =
location::get_kml(context, self.msg.chat_id).await?; location::get_kml(context, self.msg.chat_id).await?
else {
return Ok(None);
};
let part = PartBuilder::new() let part = PartBuilder::new()
.content_type( .content_type(
&"application/vnd.google-earth.kml+xml" &"application/vnd.google-earth.kml+xml"
@@ -878,7 +882,7 @@ impl<'a> MimeFactory<'a> {
// otherwise, the independent location is already filed // otherwise, the independent location is already filed
self.last_added_location_id = Some(last_added_location_id); self.last_added_location_id = Some(last_added_location_id);
} }
Ok(part) Ok(Some(part))
} }
#[allow(clippy::cognitive_complexity)] #[allow(clippy::cognitive_complexity)]
@@ -1177,7 +1181,10 @@ impl<'a> MimeFactory<'a> {
} }
let flowed_text = format_flowed(final_text); let flowed_text = format_flowed(final_text);
let footer = &self.selfstatus; let is_reaction = self.msg.param.get_int(Param::Reaction).unwrap_or_default() != 0;
let footer = if is_reaction { "" } else { &self.selfstatus };
let message_text = format!( let message_text = format!(
"{}{}{}{}{}{}", "{}{}{}{}{}{}",
fwdhint.unwrap_or_default(), fwdhint.unwrap_or_default(),
@@ -1200,7 +1207,7 @@ impl<'a> MimeFactory<'a> {
)) ))
.body(message_text); .body(message_text);
if self.msg.param.get_int(Param::Reaction).unwrap_or_default() != 0 { if is_reaction {
main_part = main_part.header(("Content-Disposition", "reaction")); main_part = main_part.header(("Content-Disposition", "reaction"));
} }
@@ -1239,11 +1246,8 @@ impl<'a> MimeFactory<'a> {
} }
if location::is_sending_locations_to_chat(context, Some(self.msg.chat_id)).await? { if location::is_sending_locations_to_chat(context, Some(self.msg.chat_id)).await? {
match self.get_location_kml_part(context).await { if let Some(part) = self.get_location_kml_part(context).await? {
Ok(part) => parts.push(part), parts.push(part);
Err(err) => {
warn!(context, "mimefactory: could not send location: {}", err);
}
} }
} }
@@ -1372,15 +1376,16 @@ impl<'a> MimeFactory<'a> {
} }
} }
/// Returns base64-encoded buffer `buf` split into 78-bytes long /// Returns base64-encoded buffer `buf` split into 76-bytes long
/// chunks separated by CRLF. /// chunks separated by CRLF.
/// ///
/// This line length limit is an /// [RFC2045 specification of base64 Content-Transfer-Encoding](https://datatracker.ietf.org/doc/html/rfc2045#section-6.8)
/// [RFC5322 requirement](https://tools.ietf.org/html/rfc5322#section-2.1.1). /// says that "The encoded output stream must be represented in lines of no more than 76 characters each."
/// Longer lines trigger `BASE64_LENGTH_78_79` rule of SpamAssassin.
pub(crate) fn wrapped_base64_encode(buf: &[u8]) -> String { pub(crate) fn wrapped_base64_encode(buf: &[u8]) -> String {
let base64 = base64::engine::general_purpose::STANDARD.encode(buf); let base64 = base64::engine::general_purpose::STANDARD.encode(buf);
let mut chars = base64.chars(); let mut chars = base64.chars();
std::iter::repeat_with(|| chars.by_ref().take(78).collect::<String>()) std::iter::repeat_with(|| chars.by_ref().take(76).collect::<String>())
.take_while(|s| !s.is_empty()) .take_while(|s| !s.is_empty())
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join("\r\n") .join("\r\n")
@@ -1620,8 +1625,8 @@ mod tests {
fn test_wrapped_base64_encode() { fn test_wrapped_base64_encode() {
let input = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; let input = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
let output = let output =
"QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQU\r\n\ "QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFB\r\n\
FBQUFBQUFBQQ=="; QUFBQUFBQUFBQQ==";
assert_eq!(wrapped_base64_encode(input), output); assert_eq!(wrapped_base64_encode(input), output);
} }

View File

@@ -1442,7 +1442,18 @@ impl MimeMessage {
let (report_fields, _) = mailparse::parse_headers(&report_body)?; let (report_fields, _) = mailparse::parse_headers(&report_body)?;
// must be present // must be present
if let Some(_disposition) = report_fields.get_header_value(HeaderDef::Disposition) { if report_fields
.get_header_value(HeaderDef::Disposition)
.is_none()
{
warn!(
context,
"Ignoring unknown disposition-notification, Message-Id: {:?}.",
report_fields.get_header_value(HeaderDef::MessageId)
);
return Ok(None);
};
let original_message_id = report_fields let original_message_id = report_fields
.get_header_value(HeaderDef::OriginalMessageId) .get_header_value(HeaderDef::OriginalMessageId)
// MS Exchange doesn't add an Original-Message-Id header. Instead, they put // MS Exchange doesn't add an Original-Message-Id header. Instead, they put
@@ -1457,18 +1468,10 @@ impl MimeMessage {
.collect() .collect()
}); });
return Ok(Some(Report { Ok(Some(Report {
original_message_id, original_message_id,
additional_message_ids, additional_message_ids,
})); }))
}
warn!(
context,
"ignoring unknown disposition-notification, Message-Id: {:?}",
report_fields.get_header_value(HeaderDef::MessageId)
);
Ok(None)
} }
fn process_delivery_status( fn process_delivery_status(

View File

@@ -8,6 +8,7 @@ use trust_dns_resolver::{config, AsyncResolver, TokioAsyncResolver};
use crate::config::Config; use crate::config::Config;
use crate::context::Context; use crate::context::Context;
use crate::provider::data::{PROVIDER_DATA, PROVIDER_IDS}; use crate::provider::data::{PROVIDER_DATA, PROVIDER_IDS};
use crate::tools::EmailAddress;
/// Provider status according to manual testing. /// Provider status according to manual testing.
#[derive(Debug, Display, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)] #[derive(Debug, Display, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)]
@@ -175,21 +176,30 @@ fn get_resolver() -> Result<TokioAsyncResolver> {
Ok(resolver) Ok(resolver)
} }
/// Returns provider for the given an e-mail address.
///
/// Returns an error if provided address is not valid.
pub async fn get_provider_info_by_addr(
context: &Context,
addr: &str,
skip_mx: bool,
) -> Result<Option<&'static Provider>> {
let addr = EmailAddress::new(addr)?;
let provider = get_provider_info(context, &addr.domain, skip_mx).await;
Ok(provider)
}
/// Returns provider for the given domain. /// Returns provider for the given domain.
/// ///
/// This function looks up domain in offline database first. If not /// This function looks up domain in offline database first. If not
/// found, it queries MX record for the domain and looks up offline /// found, it queries MX record for the domain and looks up offline
/// database for MX domains. /// database for MX domains.
///
/// For compatibility, email address can be passed to this function
/// instead of the domain.
pub async fn get_provider_info( pub async fn get_provider_info(
context: &Context, context: &Context,
domain: &str, domain: &str,
skip_mx: bool, skip_mx: bool,
) -> Option<&'static Provider> { ) -> Option<&'static Provider> {
let domain = domain.rsplit('@').next()?;
if let Some(provider) = get_provider_by_domain(domain) { if let Some(provider) = get_provider_by_domain(domain) {
return Some(provider); return Some(provider);
} }
@@ -314,15 +324,25 @@ mod tests {
let t = TestContext::new().await; let t = TestContext::new().await;
assert!(get_provider_info(&t, "", false).await.is_none()); assert!(get_provider_info(&t, "", false).await.is_none());
assert!(get_provider_info(&t, "google.com", false).await.unwrap().id == "gmail"); assert!(get_provider_info(&t, "google.com", false).await.unwrap().id == "gmail");
assert!(get_provider_info(&t, "example@google.com", false)
// get_provider_info() accepts email addresses for backwards compatibility
assert!(
get_provider_info(&t, "example@google.com", false)
.await .await
.is_none());
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_get_provider_info_by_addr() -> Result<()> {
let t = TestContext::new().await;
assert!(get_provider_info_by_addr(&t, "google.com", false)
.await
.is_err());
assert!(
get_provider_info_by_addr(&t, "example@google.com", false)
.await?
.unwrap() .unwrap()
.id .id
== "gmail" == "gmail"
); );
Ok(())
} }
#[test] #[test]

View File

@@ -464,6 +464,16 @@ Content-Disposition: reaction\n\
let alice = TestContext::new_alice().await; let alice = TestContext::new_alice().await;
let bob = TestContext::new_bob().await; let bob = TestContext::new_bob().await;
// Test that the status does not get mixed up into reactions.
alice
.set_config(
Config::Selfstatus,
Some("Buy Delta Chat today and make this banner go away!"),
)
.await?;
bob.set_config(Config::Selfstatus, Some("Sent from my Delta Chat Pro. 👍"))
.await?;
let chat_alice = alice.create_chat(&bob).await; let chat_alice = alice.create_chat(&bob).await;
let alice_msg = alice.send_text(chat_alice.id, "Hi!").await; let alice_msg = alice.send_text(chat_alice.id, "Hi!").await;
let bob_msg = bob.recv_msg(&alice_msg).await; let bob_msg = bob.recv_msg(&alice_msg).await;

View File

@@ -113,8 +113,11 @@ pub(crate) async fn receive_imf_inner(
{ {
Err(err) => { Err(err) => {
warn!(context, "receive_imf: can't parse MIME: {err:#}."); warn!(context, "receive_imf: can't parse MIME: {err:#}.");
let msg_ids; if rfc724_mid.starts_with(GENERATED_PREFIX) {
if !rfc724_mid.starts_with(GENERATED_PREFIX) { // We don't have an rfc724_mid, there's no point in adding a trash entry
return Ok(None);
}
let row_id = context let row_id = context
.sql .sql
.execute( .execute(
@@ -122,11 +125,7 @@ pub(crate) async fn receive_imf_inner(
(rfc724_mid, DC_CHAT_ID_TRASH), (rfc724_mid, DC_CHAT_ID_TRASH),
) )
.await?; .await?;
msg_ids = vec![MsgId::new(u32::try_from(row_id)?)]; let msg_ids = vec![MsgId::new(u32::try_from(row_id)?)];
} else {
return Ok(None);
// We don't have an rfc724_mid, there's no point in adding a trash entry
}
return Ok(Some(ReceivedMsg { return Ok(Some(ReceivedMsg {
chat_id: DC_CHAT_ID_TRASH, chat_id: DC_CHAT_ID_TRASH,
@@ -1156,7 +1155,8 @@ async fn add_parts(
(&part.msg, part.typ) (&part.msg, part.typ)
}; };
let part_is_empty = part.msg.is_empty() && part.param.get(Param::Quote).is_none(); let part_is_empty =
typ == Viewtype::Text && msg.is_empty() && part.param.get(Param::Quote).is_none();
let mime_modified = save_mime_modified && !part_is_empty; let mime_modified = save_mime_modified && !part_is_empty;
if mime_modified { if mime_modified {
// Avoid setting mime_modified for more than one part. // Avoid setting mime_modified for more than one part.
@@ -1181,7 +1181,8 @@ async fn add_parts(
// If you change which information is skipped if the message is trashed, // If you change which information is skipped if the message is trashed,
// also change `MsgId::trash()` and `delete_expired_messages()` // also change `MsgId::trash()` and `delete_expired_messages()`
let trash = chat_id.is_trash() || (is_location_kml && msg.is_empty()); let trash =
chat_id.is_trash() || (is_location_kml && msg.is_empty() && typ == Viewtype::Text);
let row_id = context let row_id = context
.sql .sql
@@ -1453,7 +1454,10 @@ async fn lookup_chat_by_reply(
) -> Result<Option<(ChatId, Blocked)>> { ) -> Result<Option<(ChatId, Blocked)>> {
// Try to assign message to the same chat as the parent message. // Try to assign message to the same chat as the parent message.
if let Some(parent) = parent { let Some(parent) = parent else {
return Ok(None);
};
let parent_chat = Chat::load_from_db(context, parent.chat_id).await?; let parent_chat = Chat::load_from_db(context, parent.chat_id).await?;
if parent.download_state != DownloadState::Done if parent.download_state != DownloadState::Done
@@ -1484,10 +1488,7 @@ async fn lookup_chat_by_reply(
// If the parent chat is a 1:1 chat, and the sender is a classical MUA and added // If the parent chat is a 1:1 chat, and the sender is a classical MUA and added
// a new person to TO/CC, then the message should not go to the 1:1 chat, but to a // a new person to TO/CC, then the message should not go to the 1:1 chat, but to a
// newly created ad-hoc group. // newly created ad-hoc group.
if parent_chat.typ == Chattype::Single if parent_chat.typ == Chattype::Single && !mime_parser.has_chat_version() && to_ids.len() > 1 {
&& !mime_parser.has_chat_version()
&& to_ids.len() > 1
{
let mut chat_contacts = chat::get_chat_contacts(context, parent_chat.id).await?; let mut chat_contacts = chat::get_chat_contacts(context, parent_chat.id).await?;
chat_contacts.push(ContactId::SELF); chat_contacts.push(ContactId::SELF);
if to_ids.iter().any(|id| !chat_contacts.contains(id)) { if to_ids.iter().any(|id| !chat_contacts.contains(id)) {
@@ -1499,10 +1500,7 @@ async fn lookup_chat_by_reply(
context, context,
"Assigning message to {} as it's a reply to {}.", parent_chat.id, parent.rfc724_mid "Assigning message to {} as it's a reply to {}.", parent_chat.id, parent.rfc724_mid
); );
return Ok(Some((parent_chat.id, parent_chat.blocked))); Ok(Some((parent_chat.id, parent_chat.blocked)))
}
Ok(None)
} }
/// If this method returns true, the message shall be assigned to the 1:1 chat with the sender. /// If this method returns true, the message shall be assigned to the 1:1 chat with the sender.
@@ -2058,7 +2056,10 @@ async fn apply_mailinglist_changes(
mime_parser: &MimeMessage, mime_parser: &MimeMessage,
chat_id: ChatId, chat_id: ChatId,
) -> Result<()> { ) -> Result<()> {
if let Some(list_post) = &mime_parser.list_post { let Some(list_post) = &mime_parser.list_post else {
return Ok(());
};
let mut chat = Chat::load_from_db(context, chat_id).await?; let mut chat = Chat::load_from_db(context, chat_id).await?;
if chat.typ != Chattype::Mailinglist { if chat.typ != Chattype::Mailinglist {
return Ok(()); return Ok(());
@@ -2072,8 +2073,7 @@ async fn apply_mailinglist_changes(
return Ok(()); return Ok(());
} }
}; };
let (contact_id, _) = let (contact_id, _) = Contact::add_or_lookup(context, "", list_post, Origin::Hidden).await?;
Contact::add_or_lookup(context, "", list_post, Origin::Hidden).await?;
let mut contact = Contact::get_by_id(context, contact_id).await?; let mut contact = Contact::get_by_id(context, contact_id).await?;
if contact.param.get(Param::ListId) != Some(listid) { if contact.param.get(Param::ListId) != Some(listid) {
contact.param.set(Param::ListId, listid); contact.param.set(Param::ListId, listid);
@@ -2091,7 +2091,6 @@ async fn apply_mailinglist_changes(
chat.param.set(Param::ListPost, list_post); chat.param.set(Param::ListPost, list_post);
chat.update_param(context).await?; chat.update_param(context).await?;
} }
}
Ok(()) Ok(())
} }

View File

@@ -1012,7 +1012,7 @@ mod tests {
let instance = send_webxdc_instance(&t, chat_id).await?; let instance = send_webxdc_instance(&t, chat_id).await?;
t.send_webxdc_status_update( t.send_webxdc_status_update(
instance.id, instance.id,
r#"{"info": "foo", "summary":"bar", "payload": 42}"#, r#"{"info": "foo", "summary":"bar", "document":"doc", "payload": 42}"#,
"descr", "descr",
) )
.await?; .await?;
@@ -1020,7 +1020,7 @@ mod tests {
assert_eq!( assert_eq!(
t.get_webxdc_status_updates(instance.id, StatusUpdateSerial(0)) t.get_webxdc_status_updates(instance.id, StatusUpdateSerial(0))
.await?, .await?,
r#"[{"payload":42,"info":"foo","summary":"bar","serial":1,"max_serial":1}]"# r#"[{"payload":42,"info":"foo","document":"doc","summary":"bar","serial":1,"max_serial":1}]"#
); );
assert_eq!(chat_id.get_msg_cnt(&t).await?, 2); // instance and info assert_eq!(chat_id.get_msg_cnt(&t).await?, 2); // instance and info
let info = Message::load_from_db(&t, instance.id) let info = Message::load_from_db(&t, instance.id)
@@ -1028,6 +1028,7 @@ mod tests {
.get_webxdc_info(&t) .get_webxdc_info(&t)
.await?; .await?;
assert_eq!(info.summary, "bar".to_string()); assert_eq!(info.summary, "bar".to_string());
assert_eq!(info.document, "doc".to_string());
// forwarding an instance creates a fresh instance; updates etc. are not forwarded // forwarding an instance creates a fresh instance; updates etc. are not forwarded
forward_msgs(&t, &[instance.get_id()], chat_id).await?; forward_msgs(&t, &[instance.get_id()], chat_id).await?;
@@ -1044,6 +1045,7 @@ mod tests {
.get_webxdc_info(&t) .get_webxdc_info(&t)
.await?; .await?;
assert_eq!(info.summary, "".to_string()); assert_eq!(info.summary, "".to_string());
assert_eq!(info.document, "".to_string());
Ok(()) Ok(())
} }