build: increase MSRV to 1.88.0

It is required by rPGP 0.18.0.

All the changes in `.rs` files are made automatically with `clippy --fix`.
This commit is contained in:
link2xt
2025-11-16 11:08:32 +00:00
committed by l
parent 22ebd6436f
commit c6ace749e3
28 changed files with 860 additions and 934 deletions

View File

@@ -23,7 +23,7 @@ env:
RUST_VERSION: 1.91.0 RUST_VERSION: 1.91.0
# Minimum Supported Rust Version # Minimum Supported Rust Version
MSRV: 1.85.0 MSRV: 1.88.0
jobs: jobs:
lint_rust: lint_rust:

View File

@@ -3,7 +3,7 @@ name = "deltachat"
version = "2.27.0" version = "2.27.0"
edition = "2024" edition = "2024"
license = "MPL-2.0" license = "MPL-2.0"
rust-version = "1.85" rust-version = "1.88"
repository = "https://github.com/chatmail/core" repository = "https://github.com/chatmail/core"
[profile.dev] [profile.dev]

View File

@@ -678,13 +678,12 @@ impl Config {
// Convert them to relative paths. // Convert them to relative paths.
let mut modified = false; let mut modified = false;
for account in &mut config.inner.accounts { for account in &mut config.inner.accounts {
if account.dir.is_absolute() { if account.dir.is_absolute()
if let Some(old_path_parent) = account.dir.parent() { && let Some(old_path_parent) = account.dir.parent()
if let Ok(new_path) = account.dir.strip_prefix(old_path_parent) { && let Ok(new_path) = account.dir.strip_prefix(old_path_parent)
account.dir = new_path.to_path_buf(); {
modified = true; account.dir = new_path.to_path_buf();
} modified = true;
}
} }
} }
if modified && writable { if modified && writable {

View File

@@ -740,16 +740,15 @@ impl ChatId {
} }
} }
_ => { _ => {
if msg.viewtype == Viewtype::File { if msg.viewtype == Viewtype::File
if let Some((better_type, _)) = message::guess_msgtype_from_suffix(msg) && let Some((better_type, _)) = message::guess_msgtype_from_suffix(msg)
// We do not do an automatic conversion to other viewtypes here so that // We do not do an automatic conversion to other viewtypes here so that
// users can send images as "files" to preserve the original quality // users can send images as "files" to preserve the original quality
// (usually we compress images). The remaining conversions are done by // (usually we compress images). The remaining conversions are done by
// `prepare_msg_blob()` later. // `prepare_msg_blob()` later.
.filter(|&(vt, _)| vt == Viewtype::Webxdc || vt == Viewtype::Vcard) .filter(|&(vt, _)| vt == Viewtype::Webxdc || vt == Viewtype::Vcard)
{ {
msg.viewtype = better_type; msg.viewtype = better_type;
}
} }
if msg.viewtype == Viewtype::Vcard { if msg.viewtype == Viewtype::Vcard {
let blob = msg let blob = msg
@@ -767,13 +766,13 @@ impl ChatId {
msg.chat_id = self; msg.chat_id = self;
// if possible, replace existing draft and keep id // if possible, replace existing draft and keep id
if !msg.id.is_special() { if !msg.id.is_special()
if let Some(old_draft) = self.get_draft(context).await? { && let Some(old_draft) = self.get_draft(context).await?
if old_draft.id == msg.id && old_draft.id == msg.id
&& old_draft.chat_id == self && old_draft.chat_id == self
&& old_draft.state == MessageState::OutDraft && old_draft.state == MessageState::OutDraft
{ {
let affected_rows = context let affected_rows = context
.sql.execute( .sql.execute(
"UPDATE msgs "UPDATE msgs
SET timestamp=?1,type=?2,txt=?3,txt_normalized=?4,param=?5,mime_in_reply_to=?6 SET timestamp=?1,type=?2,txt=?3,txt_normalized=?4,param=?5,mime_in_reply_to=?6
@@ -793,9 +792,7 @@ impl ChatId {
msg.id, msg.id,
), ),
).await?; ).await?;
return Ok(affected_rows > 0); return Ok(affected_rows > 0);
}
}
} }
let row_id = context let row_id = context
@@ -993,11 +990,11 @@ impl ChatId {
let mut res = Vec::new(); let mut res = Vec::new();
let now = time(); let now = time();
for (chat_id, metric) in chats_with_metrics { for (chat_id, metric) in chats_with_metrics {
if let Some(chat_timestamp) = chat_id.get_timestamp(context).await? { if let Some(chat_timestamp) = chat_id.get_timestamp(context).await?
if now > chat_timestamp + 42 * 24 * 3600 { && now > chat_timestamp + 42 * 24 * 3600
// Chat was inactive for 42 days, skip. {
continue; // Chat was inactive for 42 days, skip.
} continue;
} }
if metric < 0.1 { if metric < 0.1 {
@@ -1252,10 +1249,10 @@ impl ChatId {
None None
}; };
if let Some(last_msg_time) = last_msg_time { if let Some(last_msg_time) = last_msg_time
if last_msg_time > sort_timestamp { && last_msg_time > sort_timestamp
sort_timestamp = last_msg_time; {
} sort_timestamp = last_msg_time;
} }
Ok(sort_timestamp) Ok(sort_timestamp)
@@ -1376,10 +1373,10 @@ impl Chat {
let mut chat_name = "Err [Name not found]".to_owned(); let mut chat_name = "Err [Name not found]".to_owned();
match get_chat_contacts(context, chat.id).await { match get_chat_contacts(context, chat.id).await {
Ok(contacts) => { Ok(contacts) => {
if let Some(contact_id) = contacts.first() { if let Some(contact_id) = contacts.first()
if let Ok(contact) = Contact::get_by_id(context, *contact_id).await { && let Ok(contact) = Contact::get_by_id(context, *contact_id).await
contact.get_display_name().clone_into(&mut chat_name); {
} contact.get_display_name().clone_into(&mut chat_name);
} }
} }
Err(err) => { Err(err) => {
@@ -1576,10 +1573,10 @@ impl Chat {
if self.typ == Chattype::Single { if self.typ == Chattype::Single {
let contacts = get_chat_contacts(context, self.id).await?; let contacts = get_chat_contacts(context, self.id).await?;
if let Some(contact_id) = contacts.first() { if let Some(contact_id) = contacts.first()
if let Ok(contact) = Contact::get_by_id(context, *contact_id).await { && let Ok(contact) = Contact::get_by_id(context, *contact_id).await
color = contact.get_color(); {
} color = contact.get_color();
} }
} else if !self.grpid.is_empty() { } else if !self.grpid.is_empty() {
color = str_to_color(&self.grpid); color = str_to_color(&self.grpid);
@@ -1841,8 +1838,8 @@ impl Chat {
} }
// add independent location to database // add independent location to database
if msg.param.exists(Param::SetLatitude) { if msg.param.exists(Param::SetLatitude)
if let Ok(row_id) = context && let Ok(row_id) = context
.sql .sql
.insert( .insert(
"INSERT INTO locations \ "INSERT INTO locations \
@@ -1857,9 +1854,8 @@ impl Chat {
), ),
) )
.await .await
{ {
location_id = row_id; location_id = row_id;
}
} }
let ephemeral_timer = if msg.param.get_cmd() == SystemMessage::EphemeralTimerChanged { let ephemeral_timer = if msg.param.get_cmd() == SystemMessage::EphemeralTimerChanged {
@@ -2497,18 +2493,18 @@ async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<()> {
msg.param.set(Param::File, blob.as_name()); msg.param.set(Param::File, blob.as_name());
} }
if !msg.param.exists(Param::MimeType) { if !msg.param.exists(Param::MimeType)
if let Some((viewtype, mime)) = message::guess_msgtype_from_suffix(msg) { && let Some((viewtype, mime)) = message::guess_msgtype_from_suffix(msg)
// If we unexpectedly didn't recognize the file as image, don't send it as such, {
// either the format is unsupported or the image is corrupted. // If we unexpectedly didn't recognize the file as image, don't send it as such,
let mime = match viewtype != Viewtype::Image // either the format is unsupported or the image is corrupted.
|| matches!(msg.viewtype, Viewtype::Image | Viewtype::Sticker) let mime = match viewtype != Viewtype::Image
{ || matches!(msg.viewtype, Viewtype::Image | Viewtype::Sticker)
true => mime, {
false => "application/octet-stream", true => mime,
}; false => "application/octet-stream",
msg.param.set(Param::MimeType, mime); };
} msg.param.set(Param::MimeType, mime);
} }
msg.try_calc_and_set_dimensions(context).await?; msg.try_calc_and_set_dimensions(context).await?;
@@ -2692,15 +2688,15 @@ async fn prepare_send_msg(
// This is meant as a last line of defence, the UI should check that before as well. // This is meant as a last line of defence, the UI should check that before as well.
// (We allow Chattype::Single in general for "Reply Privately"; // (We allow Chattype::Single in general for "Reply Privately";
// checking for exact contact_id will produce false positives when ppl just left the group) // checking for exact contact_id will produce false positives when ppl just left the group)
if chat.typ != Chattype::Single && !context.get_config_bool(Config::Bot).await? { if chat.typ != Chattype::Single
if let Some(quoted_message) = msg.quoted_message(context).await? { && !context.get_config_bool(Config::Bot).await?
if quoted_message.chat_id != chat_id { && let Some(quoted_message) = msg.quoted_message(context).await?
bail!( && quoted_message.chat_id != chat_id
"Quote of message from {} cannot be sent to {chat_id}", {
quoted_message.chat_id bail!(
); "Quote of message from {} cannot be sent to {chat_id}",
} quoted_message.chat_id
} );
} }
// check current MessageState for drafts (to keep msg_id) ... // check current MessageState for drafts (to keep msg_id) ...
@@ -2830,16 +2826,15 @@ pub(crate) async fn create_send_msg_jobs(context: &Context, msg: &mut Message) -
let now = smeared_time(context); let now = smeared_time(context);
if rendered_msg.last_added_location_id.is_some() { if rendered_msg.last_added_location_id.is_some()
if let Err(err) = location::set_kml_sent_timestamp(context, msg.chat_id, now).await { && let Err(err) = location::set_kml_sent_timestamp(context, msg.chat_id, now).await
error!(context, "Failed to set kml sent_timestamp: {err:#}."); {
} error!(context, "Failed to set kml sent_timestamp: {err:#}.");
} }
if attach_selfavatar { if attach_selfavatar && let Err(err) = msg.chat_id.set_selfavatar_timestamp(context, now).await
if let Err(err) = msg.chat_id.set_selfavatar_timestamp(context, now).await { {
error!(context, "Failed to set selfavatar timestamp: {err:#}."); error!(context, "Failed to set selfavatar timestamp: {err:#}.");
}
} }
if rendered_msg.is_encrypted { if rendered_msg.is_encrypted {
@@ -4483,11 +4478,11 @@ pub async fn add_device_msg_with_importance(
let mut chat_id = ChatId::new(0); let mut chat_id = ChatId::new(0);
let mut msg_id = MsgId::new_unset(); let mut msg_id = MsgId::new_unset();
if let Some(label) = label { if let Some(label) = label
if was_device_msg_ever_added(context, label).await? { && was_device_msg_ever_added(context, label).await?
info!(context, "Device-message {label} already added."); {
return Ok(msg_id); info!(context, "Device-message {label} already added.");
} return Ok(msg_id);
} }
if let Some(msg) = msg { if let Some(msg) = msg {
@@ -4499,10 +4494,10 @@ pub async fn add_device_msg_with_importance(
// makes sure, the added message is the last one, // makes sure, the added message is the last one,
// even if the date is wrong (useful esp. when warning about bad dates) // even if the date is wrong (useful esp. when warning about bad dates)
msg.timestamp_sort = timestamp_sent; msg.timestamp_sort = timestamp_sent;
if let Some(last_msg_time) = chat_id.get_timestamp(context).await? { if let Some(last_msg_time) = chat_id.get_timestamp(context).await?
if msg.timestamp_sort <= last_msg_time { && msg.timestamp_sort <= last_msg_time
msg.timestamp_sort = last_msg_time + 1; {
} msg.timestamp_sort = last_msg_time + 1;
} }
prepare_msg_blob(context, msg).await?; prepare_msg_blob(context, msg).await?;
let state = MessageState::InFresh; let state = MessageState::InFresh;

View File

@@ -258,19 +258,18 @@ async fn on_configure_completed(
} }
} }
if let Some(new_addr) = context.get_config(Config::ConfiguredAddr).await? { if let Some(new_addr) = context.get_config(Config::ConfiguredAddr).await?
if let Some(old_addr) = old_addr { && let Some(old_addr) = old_addr
if !addr_cmp(&new_addr, &old_addr) { && !addr_cmp(&new_addr, &old_addr)
let mut msg = Message::new_text( {
stock_str::aeap_explanation_and_link(context, &old_addr, &new_addr).await, let mut msg = Message::new_text(
); stock_str::aeap_explanation_and_link(context, &old_addr, &new_addr).await,
chat::add_device_msg(context, None, Some(&mut msg)) );
.await chat::add_device_msg(context, None, Some(&mut msg))
.context("Cannot add AEAP explanation") .await
.log_err(context) .context("Cannot add AEAP explanation")
.ok(); .log_err(context)
} .ok();
}
} }
Ok(()) Ok(())

View File

@@ -154,10 +154,10 @@ fn parse_xml_reader<B: BufRead>(
if let Some(incoming_server) = parse_server(reader, event)? { if let Some(incoming_server) = parse_server(reader, event)? {
incoming_servers.push(incoming_server); incoming_servers.push(incoming_server);
} }
} else if tag == "outgoingserver" { } else if tag == "outgoingserver"
if let Some(outgoing_server) = parse_server(reader, event)? { && let Some(outgoing_server) = parse_server(reader, event)?
outgoing_servers.push(outgoing_server); {
} outgoing_servers.push(outgoing_server);
} }
} }
Event::Eof => break, Event::Eof => break,

View File

@@ -138,27 +138,27 @@ impl ContactId {
}) })
.await?; .await?;
if sync.into() { if sync.into()
if let Some((addr, fingerprint)) = row { && let Some((addr, fingerprint)) = row
if fingerprint.is_empty() { {
chat::sync( if fingerprint.is_empty() {
context, chat::sync(
chat::SyncId::ContactAddr(addr), context,
chat::SyncAction::Rename(name.to_string()), chat::SyncId::ContactAddr(addr),
) chat::SyncAction::Rename(name.to_string()),
.await )
.log_err(context) .await
.ok(); .log_err(context)
} else { .ok();
chat::sync( } else {
context, chat::sync(
chat::SyncId::ContactFingerprint(fingerprint), context,
chat::SyncAction::Rename(name.to_string()), chat::SyncId::ContactFingerprint(fingerprint),
) chat::SyncAction::Rename(name.to_string()),
.await )
.log_err(context) .await
.ok(); .log_err(context)
} .ok();
} }
} }
Ok(()) Ok(())
@@ -393,13 +393,13 @@ async fn import_vcard_contact(context: &Context, contact: &VcardContact) -> Resu
); );
} }
} }
if let Some(biography) = &contact.biography { if let Some(biography) = &contact.biography
if let Err(e) = set_status(context, id, biography.to_owned(), false, false).await { && let Err(e) = set_status(context, id, biography.to_owned(), false, false).await
warn!( {
context, warn!(
"import_vcard_contact: Could not set biography for {}: {e:#}.", contact.addr context,
); "import_vcard_contact: Could not set biography for {}: {e:#}.", contact.addr
} );
} }
Ok(id) Ok(id)
} }
@@ -1564,10 +1564,10 @@ impl Contact {
if show_fallback_icon && !self.id.is_special() && !self.is_key_contact() { if show_fallback_icon && !self.id.is_special() && !self.is_key_contact() {
return Ok(Some(chat::get_unencrypted_icon(context).await?)); return Ok(Some(chat::get_unencrypted_icon(context).await?));
} }
if let Some(image_rel) = self.param.get(Param::ProfileImage) { if let Some(image_rel) = self.param.get(Param::ProfileImage)
if !image_rel.is_empty() { && !image_rel.is_empty()
return Ok(Some(get_abs_path(context, Path::new(image_rel)))); {
} return Ok(Some(get_abs_path(context, Path::new(image_rel))));
} }
Ok(None) Ok(None)
} }
@@ -1800,10 +1800,11 @@ WHERE type=? AND id IN (
// also unblock mailinglist // also unblock mailinglist
// if the contact is a mailinglist address explicitly created to allow unblocking // if the contact is a mailinglist address explicitly created to allow unblocking
if !new_blocking && contact.origin == Origin::MailinglistAddress { if !new_blocking
if let Some((chat_id, ..)) = chat::get_chat_id_by_grpid(context, &contact.addr).await? { && contact.origin == Origin::MailinglistAddress
chat_id.unblock_ex(context, Nosync).await?; && let Some((chat_id, ..)) = chat::get_chat_id_by_grpid(context, &contact.addr).await?
} {
chat_id.unblock_ex(context, Nosync).await?;
} }
if sync.into() { if sync.into() {

View File

@@ -608,10 +608,9 @@ impl Context {
if self if self
.quota_needs_update(DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT) .quota_needs_update(DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT)
.await .await
&& let Err(err) = self.update_recent_quota(&mut session).await
{ {
if let Err(err) = self.update_recent_quota(&mut session).await { warn!(self, "Failed to update quota: {err:#}.");
warn!(self, "Failed to update quota: {err:#}.");
}
} }
} }

View File

@@ -115,15 +115,13 @@ pub async fn maybe_set_logging_xdc_inner(
filename: Option<&str>, filename: Option<&str>,
msg_id: MsgId, msg_id: MsgId,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
if viewtype == Viewtype::Webxdc { if viewtype == Viewtype::Webxdc
if let Some(filename) = filename { && let Some(filename) = filename
if filename.starts_with("debug_logging") && filename.starts_with("debug_logging")
&& filename.ends_with(".xdc") && filename.ends_with(".xdc")
&& chat_id.is_self_talk(context).await? && chat_id.is_self_talk(context).await?
{ {
set_debug_logging_xdc(context, Some(msg_id)).await?; set_debug_logging_xdc(context, Some(msg_id)).await?;
}
}
} }
Ok(()) Ok(())
} }

View File

@@ -169,27 +169,28 @@ impl HtmlMsgParser {
MimeMultipartType::Single => { MimeMultipartType::Single => {
let mimetype = mail.ctype.mimetype.parse::<Mime>()?; let mimetype = mail.ctype.mimetype.parse::<Mime>()?;
if mimetype == mime::TEXT_HTML { if mimetype == mime::TEXT_HTML {
if self.html.is_empty() { if self.html.is_empty()
if let Ok(decoded_data) = mail.get_body() { && let Ok(decoded_data) = mail.get_body()
self.html = decoded_data; {
} self.html = decoded_data;
}
} else if mimetype == mime::TEXT_PLAIN && self.plain.is_none() {
if let Ok(decoded_data) = mail.get_body() {
self.plain = Some(PlainText {
text: decoded_data,
flowed: if let Some(format) = mail.ctype.params.get("format") {
format.as_str().eq_ignore_ascii_case("flowed")
} else {
false
},
delsp: if let Some(delsp) = mail.ctype.params.get("delsp") {
delsp.as_str().eq_ignore_ascii_case("yes")
} else {
false
},
});
} }
} else if mimetype == mime::TEXT_PLAIN
&& self.plain.is_none()
&& let Ok(decoded_data) = mail.get_body()
{
self.plain = Some(PlainText {
text: decoded_data,
flowed: if let Some(format) = mail.ctype.params.get("format") {
format.as_str().eq_ignore_ascii_case("flowed")
} else {
false
},
delsp: if let Some(delsp) = mail.ctype.params.get("delsp") {
delsp.as_str().eq_ignore_ascii_case("yes")
} else {
false
},
});
} }
Ok(()) Ok(())
} }
@@ -213,31 +214,29 @@ impl HtmlMsgParser {
MimeMultipartType::Message => Ok(()), MimeMultipartType::Message => Ok(()),
MimeMultipartType::Single => { MimeMultipartType::Single => {
let mimetype = mail.ctype.mimetype.parse::<Mime>()?; let mimetype = mail.ctype.mimetype.parse::<Mime>()?;
if mimetype.type_() == mime::IMAGE { if mimetype.type_() == mime::IMAGE
if let Some(cid) = mail.headers.get_header_value(HeaderDef::ContentId) { && let Some(cid) = mail.headers.get_header_value(HeaderDef::ContentId)
if let Ok(cid) = parse_message_id(&cid) { && let Ok(cid) = parse_message_id(&cid)
if let Ok(replacement) = mimepart_to_data_url(mail) { && let Ok(replacement) = mimepart_to_data_url(mail)
let re_string = format!( {
"(<img[^>]*src[^>]*=[^>]*)(cid:{})([^>]*>)", let re_string = format!(
regex::escape(&cid) "(<img[^>]*src[^>]*=[^>]*)(cid:{})([^>]*>)",
); regex::escape(&cid)
match regex::Regex::new(&re_string) { );
Ok(re) => { match regex::Regex::new(&re_string) {
self.html = re Ok(re) => {
.replace_all( self.html = re
&self.html, .replace_all(
format!("${{1}}{replacement}${{3}}").as_str(), &self.html,
) format!("${{1}}{replacement}${{3}}").as_str(),
.as_ref() )
.to_string() .as_ref()
} .to_string()
Err(e) => warn!(
context,
"Cannot create regex for cid: {} throws {}", re_string, e
),
}
}
} }
Err(e) => warn!(
context,
"Cannot create regex for cid: {} throws {}", re_string, e
),
} }
} }
Ok(()) Ok(())

View File

@@ -1259,15 +1259,14 @@ impl Session {
continue; continue;
}; };
let is_seen = fetch.flags().any(|flag| flag == Flag::Seen); let is_seen = fetch.flags().any(|flag| flag == Flag::Seen);
if is_seen { if is_seen
if let Some(chat_id) = mark_seen_by_uid(context, folder, uid_validity, uid) && let Some(chat_id) = mark_seen_by_uid(context, folder, uid_validity, uid)
.await .await
.with_context(|| { .with_context(|| {
format!("failed to update seen status for msg {folder}/{uid}") format!("failed to update seen status for msg {folder}/{uid}")
})? })?
{ {
updated_chat_ids.insert(chat_id); updated_chat_ids.insert(chat_id);
}
} }
if let Some(modseq) = fetch.modseq { if let Some(modseq) = fetch.modseq {
@@ -1321,10 +1320,10 @@ impl Session {
while let Some(msg) = list.try_next().await? { while let Some(msg) = list.try_next().await? {
match get_fetch_headers(&msg) { match get_fetch_headers(&msg) {
Ok(headers) => { Ok(headers) => {
if let Some(from) = mimeparser::get_from(&headers) { if let Some(from) = mimeparser::get_from(&headers)
if context.is_self_addr(&from.addr).await? { && context.is_self_addr(&from.addr).await?
result.extend(mimeparser::get_recipients(&headers)); {
} result.extend(mimeparser::get_recipients(&headers));
} }
} }
Err(err) => { Err(err) => {
@@ -1557,17 +1556,17 @@ impl Session {
.await?; .await?;
let mut got_turn_server = false; let mut got_turn_server = false;
for m in metadata { for m in metadata {
if m.entry == "/shared/vendor/deltachat/turn" { if m.entry == "/shared/vendor/deltachat/turn"
if let Some(value) = m.value { && let Some(value) = m.value
match create_ice_servers_from_metadata(context, &value).await { {
Ok((parsed_timestamp, parsed_ice_servers)) => { match create_ice_servers_from_metadata(context, &value).await {
old_metadata.ice_servers_expiration_timestamp = parsed_timestamp; Ok((parsed_timestamp, parsed_ice_servers)) => {
old_metadata.ice_servers = parsed_ice_servers; old_metadata.ice_servers_expiration_timestamp = parsed_timestamp;
got_turn_server = false; old_metadata.ice_servers = parsed_ice_servers;
} got_turn_server = false;
Err(err) => { }
warn!(context, "Failed to parse TURN server metadata: {err:#}."); Err(err) => {
} warn!(context, "Failed to parse TURN server metadata: {err:#}.");
} }
} }
} }
@@ -1852,11 +1851,13 @@ impl Imap {
info!(context, "Scanning folder: {:?}", folder); info!(context, "Scanning folder: {:?}", folder);
// Update the delimiter iff there is a different one, but only once. // Update the delimiter iff there is a different one, but only once.
if let Some(d) = folder.delimiter() { if let Some(d) = folder.delimiter()
if delimiter_is_default && !d.is_empty() && delimiter != d { && delimiter_is_default
delimiter = d.to_string(); && !d.is_empty()
delimiter_is_default = false; && delimiter != d
} {
delimiter = d.to_string();
delimiter_is_default = false;
} }
let folder_meaning = get_folder_meaning_by_attrs(folder.attributes()); let folder_meaning = get_folder_meaning_by_attrs(folder.attributes());
@@ -2097,12 +2098,10 @@ async fn needs_move_to_mvbox(
.get_header_value(HeaderDef::AutoSubmitted) .get_header_value(HeaderDef::AutoSubmitted)
.filter(|val| val.eq_ignore_ascii_case("auto-generated")) .filter(|val| val.eq_ignore_ascii_case("auto-generated"))
.is_some() .is_some()
&& let Some(from) = mimeparser::get_from(headers)
&& context.is_self_addr(&from.addr).await?
{ {
if let Some(from) = mimeparser::get_from(headers) { return Ok(true);
if context.is_self_addr(&from.addr).await? {
return Ok(true);
}
}
} }
if !context.get_config_bool(Config::MvboxMove).await? { if !context.get_config_bool(Config::MvboxMove).await? {
return Ok(false); return Ok(false);
@@ -2272,12 +2271,13 @@ pub(crate) async fn prefetch_should_download(
// We do not know the Message-ID or the Message-ID is missing (in this case, we create one in // We do not know the Message-ID or the Message-ID is missing (in this case, we create one in
// the further process). // the further process).
if let Some(chat) = prefetch_get_chat(context, headers).await? { if let Some(chat) = prefetch_get_chat(context, headers).await?
if chat.typ == Chattype::Group && !chat.id.is_special() { && chat.typ == Chattype::Group
// This might be a group command, like removing a group member. && !chat.id.is_special()
// We really need to fetch this to avoid inconsistent group state. {
return Ok(true); // This might be a group command, like removing a group member.
} // We really need to fetch this to avoid inconsistent group state.
return Ok(true);
} }
let maybe_ndn = if let Some(from) = headers.get_header_value(HeaderDef::From_) { let maybe_ndn = if let Some(from) = headers.get_header_value(HeaderDef::From_) {
@@ -2524,11 +2524,11 @@ fn build_sequence_sets(uids: &[u32]) -> Result<Vec<(Vec<u32>, String)>> {
let mut ranges: Vec<UidRange> = vec![]; let mut ranges: Vec<UidRange> = vec![];
for &current in uids { for &current in uids {
if let Some(last) = ranges.last_mut() { if let Some(last) = ranges.last_mut()
if last.end + 1 == current { && last.end + 1 == current
last.end = current; {
continue; last.end = current;
} continue;
} }
ranges.push(UidRange { ranges.push(UidRange {

View File

@@ -34,16 +34,16 @@ impl ImapSession {
/// because no EXPUNGE responses are sent, see /// because no EXPUNGE responses are sent, see
/// <https://tools.ietf.org/html/rfc3501#section-6.4.2> /// <https://tools.ietf.org/html/rfc3501#section-6.4.2>
pub(super) async fn maybe_close_folder(&mut self, context: &Context) -> anyhow::Result<()> { pub(super) async fn maybe_close_folder(&mut self, context: &Context) -> anyhow::Result<()> {
if let Some(folder) = &self.selected_folder { if let Some(folder) = &self.selected_folder
if self.selected_folder_needs_expunge { && self.selected_folder_needs_expunge
info!(context, "Expunge messages in {folder:?}."); {
info!(context, "Expunge messages in {folder:?}.");
self.close().await.context("IMAP close/expunge failed")?; self.close().await.context("IMAP close/expunge failed")?;
info!(context, "Close/expunge succeeded."); info!(context, "Close/expunge succeeded.");
self.selected_folder = None; self.selected_folder = None;
self.selected_folder_needs_expunge = false; self.selected_folder_needs_expunge = false;
self.new_mail = false; self.new_mail = false;
}
} }
Ok(()) Ok(())
} }
@@ -54,10 +54,10 @@ impl ImapSession {
async fn select_folder(&mut self, context: &Context, folder: &str) -> Result<NewlySelected> { async fn select_folder(&mut self, context: &Context, folder: &str) -> Result<NewlySelected> {
// if there is a new folder and the new folder is equal to the selected one, there's nothing to do. // if there is a new folder and the new folder is equal to the selected one, there's nothing to do.
// if there is _no_ new folder, we continue as we might want to expunge below. // if there is _no_ new folder, we continue as we might want to expunge below.
if let Some(selected_folder) = &self.selected_folder { if let Some(selected_folder) = &self.selected_folder
if folder == selected_folder { && folder == selected_folder
return Ok(NewlySelected::No); {
} return Ok(NewlySelected::No);
} }
// deselect existing folder, if needed (it's also done implicitly by SELECT, however, without EXPUNGE then) // deselect existing folder, if needed (it's also done implicitly by SELECT, however, without EXPUNGE then)

View File

@@ -638,33 +638,33 @@ impl Message {
pub(crate) async fn try_calc_and_set_dimensions(&mut self, context: &Context) -> Result<()> { pub(crate) async fn try_calc_and_set_dimensions(&mut self, context: &Context) -> Result<()> {
if self.viewtype.has_file() { if self.viewtype.has_file() {
let file_param = self.param.get_file_path(context)?; let file_param = self.param.get_file_path(context)?;
if let Some(path_and_filename) = file_param { if let Some(path_and_filename) = file_param
if matches!( && matches!(
self.viewtype, self.viewtype,
Viewtype::Image | Viewtype::Gif | Viewtype::Sticker Viewtype::Image | Viewtype::Gif | Viewtype::Sticker
) && !self.param.exists(Param::Width) )
{ && !self.param.exists(Param::Width)
let buf = read_file(context, &path_and_filename).await?; {
let buf = read_file(context, &path_and_filename).await?;
match get_filemeta(&buf) { match get_filemeta(&buf) {
Ok((width, height)) => { Ok((width, height)) => {
self.param.set_int(Param::Width, width as i32); self.param.set_int(Param::Width, width as i32);
self.param.set_int(Param::Height, height as i32); self.param.set_int(Param::Height, height as i32);
}
Err(err) => {
self.param.set_int(Param::Width, 0);
self.param.set_int(Param::Height, 0);
warn!(
context,
"Failed to get width and height for {}: {err:#}.",
path_and_filename.display()
);
}
} }
Err(err) => {
self.param.set_int(Param::Width, 0);
self.param.set_int(Param::Height, 0);
warn!(
context,
"Failed to get width and height for {}: {err:#}.",
path_and_filename.display()
);
}
}
if !self.id.is_unset() { if !self.id.is_unset() {
self.update_param(context).await?; self.update_param(context).await?;
}
} }
} }
} }
@@ -992,14 +992,12 @@ impl Message {
return None; return None;
} }
if let Some(filename) = self.get_file(context) { if let Some(filename) = self.get_file(context)
if let Ok(ref buf) = read_file(context, &filename).await { && let Ok(ref buf) = read_file(context, &filename).await
if let Ok((typ, headers, _)) = split_armored_data(buf) { && let Ok((typ, headers, _)) = split_armored_data(buf)
if typ == pgp::armor::BlockType::Message { && typ == pgp::armor::BlockType::Message
return headers.get(crate::pgp::HEADER_SETUPCODE).cloned(); {
} return headers.get(crate::pgp::HEADER_SETUPCODE).cloned();
}
}
} }
None None
@@ -1224,26 +1222,25 @@ impl Message {
/// ///
/// `References` header is not taken into account. /// `References` header is not taken into account.
pub async fn parent(&self, context: &Context) -> Result<Option<Message>> { pub async fn parent(&self, context: &Context) -> Result<Option<Message>> {
if let Some(in_reply_to) = &self.in_reply_to { if let Some(in_reply_to) = &self.in_reply_to
if let Some(msg_id) = rfc724_mid_exists(context, in_reply_to).await? { && let Some(msg_id) = rfc724_mid_exists(context, in_reply_to).await?
let msg = Message::load_from_db_optional(context, msg_id).await?; {
return Ok(msg); let msg = Message::load_from_db_optional(context, msg_id).await?;
} return Ok(msg);
} }
Ok(None) Ok(None)
} }
/// Returns original message ID for message from "Saved Messages". /// Returns original message ID for message from "Saved Messages".
pub async fn get_original_msg_id(&self, context: &Context) -> Result<Option<MsgId>> { pub async fn get_original_msg_id(&self, context: &Context) -> Result<Option<MsgId>> {
if !self.original_msg_id.is_special() { if !self.original_msg_id.is_special()
if let Some(msg) = Message::load_from_db_optional(context, self.original_msg_id).await? && let Some(msg) = Message::load_from_db_optional(context, self.original_msg_id).await?
{ {
return if msg.chat_id.is_trash() { return if msg.chat_id.is_trash() {
Ok(None) Ok(None)
} else { } else {
Ok(Some(msg.id)) Ok(Some(msg.id))
}; };
}
} }
Ok(None) Ok(None)
} }
@@ -1613,10 +1610,10 @@ pub(crate) async fn delete_msg_locally(context: &Context, msg: &Message) -> Resu
.expect("RwLock is poisoned") .expect("RwLock is poisoned")
.as_ref() .as_ref()
.map(|dl| dl.msg_id); .map(|dl| dl.msg_id);
if let Some(id) = logging_xdc_id { if let Some(id) = logging_xdc_id
if id == msg.id { && id == msg.id
set_debug_logging_xdc(context, None).await?; {
} set_debug_logging_xdc(context, None).await?;
} }
Ok(()) Ok(())

View File

@@ -39,17 +39,16 @@ async fn test_get_width_height() {
let mut has_image = false; let mut has_image = false;
let chatitems = chat::get_chat_msgs(&t, device_chat_id).await.unwrap(); let chatitems = chat::get_chat_msgs(&t, device_chat_id).await.unwrap();
for chatitem in chatitems { for chatitem in chatitems {
if let ChatItem::Message { msg_id } = chatitem { if let ChatItem::Message { msg_id } = chatitem
if let Ok(msg) = Message::load_from_db(&t, msg_id).await { && let Ok(msg) = Message::load_from_db(&t, msg_id).await
if msg.get_viewtype() == Viewtype::Image { && msg.get_viewtype() == Viewtype::Image
has_image = true; {
// just check that width/height are inside some reasonable ranges has_image = true;
assert!(msg.get_width() > 100); // just check that width/height are inside some reasonable ranges
assert!(msg.get_height() > 100); assert!(msg.get_width() > 100);
assert!(msg.get_width() < 4000); assert!(msg.get_height() > 100);
assert!(msg.get_height() < 4000); assert!(msg.get_width() < 4000);
} assert!(msg.get_height() < 4000);
}
} }
} }
assert!(has_image); assert!(has_image);

View File

@@ -292,11 +292,10 @@ impl MimeFactory {
// In a broadcast channel, only send member-added/removed messages // In a broadcast channel, only send member-added/removed messages
// to the affected member: // to the affected member:
if let Some(fp) = must_have_only_one_recipient(&msg, &chat) { if let Some(fp) = must_have_only_one_recipient(&msg, &chat)
if fp? != fingerprint { && fp? != fingerprint {
continue; continue;
} }
}
let public_key_opt = if let Some(public_key_bytes) = &public_key_bytes_opt { let public_key_opt = if let Some(public_key_bytes) = &public_key_bytes_opt {
Some(SignedPublicKey::from_slice(public_key_bytes)?) Some(SignedPublicKey::from_slice(public_key_bytes)?)
@@ -347,8 +346,8 @@ impl MimeFactory {
// Row is a tombstone, // Row is a tombstone,
// member is not actually part of the group. // member is not actually part of the group.
if !recipients_contain_addr(&past_members, &addr) { if !recipients_contain_addr(&past_members, &addr) {
if let Some(email_to_remove) = email_to_remove { if let Some(email_to_remove) = email_to_remove
if email_to_remove == addr { && email_to_remove == addr {
// This is a "member removed" message, // This is a "member removed" message,
// we need to notify removed member // we need to notify removed member
// that it was removed. // that it was removed.
@@ -365,7 +364,6 @@ impl MimeFactory {
} }
} }
} }
}
if !undisclosed_recipients { if !undisclosed_recipients {
past_members.push((name, addr.clone())); past_members.push((name, addr.clone()));
past_member_timestamps.push(remove_timestamp); past_member_timestamps.push(remove_timestamp);
@@ -395,15 +393,14 @@ impl MimeFactory {
"member_fingerprints.len() ({}) < to.len() ({})", "member_fingerprints.len() ({}) < to.len() ({})",
member_fingerprints.len(), to.len()); member_fingerprints.len(), to.len());
if to.len() > 1 { if to.len() > 1
if let Some(position) = to.iter().position(|(_, x)| x == &from_addr) { && let Some(position) = to.iter().position(|(_, x)| x == &from_addr) {
to.remove(position); to.remove(position);
member_timestamps.remove(position); member_timestamps.remove(position);
if is_encrypted { if is_encrypted {
member_fingerprints.remove(position); member_fingerprints.remove(position);
} }
} }
}
member_timestamps.extend(past_member_timestamps); member_timestamps.extend(past_member_timestamps);
if is_encrypted { if is_encrypted {
@@ -733,36 +730,35 @@ impl MimeFactory {
)); ));
} }
if let Loaded::Message { chat, .. } = &self.loaded { if let Loaded::Message { chat, .. } = &self.loaded
if chat.typ == Chattype::Group { && chat.typ == Chattype::Group
if !self.member_timestamps.is_empty() && !chat.member_list_is_stale(context).await? {
{ if !self.member_timestamps.is_empty() && !chat.member_list_is_stale(context).await? {
headers.push(( headers.push((
"Chat-Group-Member-Timestamps", "Chat-Group-Member-Timestamps",
mail_builder::headers::raw::Raw::new( mail_builder::headers::raw::Raw::new(
self.member_timestamps self.member_timestamps
.iter() .iter()
.map(|ts| ts.to_string()) .map(|ts| ts.to_string())
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(" "), .join(" "),
) )
.into(), .into(),
)); ));
} }
if !self.member_fingerprints.is_empty() { if !self.member_fingerprints.is_empty() {
headers.push(( headers.push((
"Chat-Group-Member-Fpr", "Chat-Group-Member-Fpr",
mail_builder::headers::raw::Raw::new( mail_builder::headers::raw::Raw::new(
self.member_fingerprints self.member_fingerprints
.iter() .iter()
.map(|fp| fp.to_string()) .map(|fp| fp.to_string())
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(" "), .join(" "),
) )
.into(), .into(),
)); ));
}
} }
} }
@@ -814,37 +810,34 @@ impl MimeFactory {
"Auto-Submitted", "Auto-Submitted",
mail_builder::headers::raw::Raw::new("auto-generated".to_string()).into(), mail_builder::headers::raw::Raw::new("auto-generated".to_string()).into(),
)); ));
} else if let Loaded::Message { msg, .. } = &self.loaded { } else if let Loaded::Message { msg, .. } = &self.loaded
if msg.param.get_cmd() == SystemMessage::SecurejoinMessage { && msg.param.get_cmd() == SystemMessage::SecurejoinMessage
let step = msg.param.get(Param::Arg).unwrap_or_default(); {
if step != "vg-request" && step != "vc-request" { let step = msg.param.get(Param::Arg).unwrap_or_default();
headers.push(( if step != "vg-request" && step != "vc-request" {
"Auto-Submitted", headers.push((
mail_builder::headers::raw::Raw::new("auto-replied".to_string()).into(), "Auto-Submitted",
)); mail_builder::headers::raw::Raw::new("auto-replied".to_string()).into(),
} ));
} }
} }
if let Loaded::Message { msg, chat } = &self.loaded { if let Loaded::Message { msg, chat } = &self.loaded
if chat.typ == Chattype::OutBroadcast || chat.typ == Chattype::InBroadcast { && (chat.typ == Chattype::OutBroadcast || chat.typ == Chattype::InBroadcast)
headers.push(( {
"Chat-List-ID", headers.push((
mail_builder::headers::text::Text::new(format!( "Chat-List-ID",
"{} <{}>", mail_builder::headers::text::Text::new(format!("{} <{}>", chat.name, chat.grpid))
chat.name, chat.grpid
))
.into(), .into(),
)); ));
if msg.param.get_cmd() == SystemMessage::MemberAddedToGroup { if msg.param.get_cmd() == SystemMessage::MemberAddedToGroup
if let Some(secret) = msg.param.get(PARAM_BROADCAST_SECRET) { && let Some(secret) = msg.param.get(PARAM_BROADCAST_SECRET)
headers.push(( {
"Chat-Broadcast-Secret", headers.push((
mail_builder::headers::text::Text::new(secret.to_string()).into(), "Chat-Broadcast-Secret",
)); mail_builder::headers::text::Text::new(secret.to_string()).into(),
} ));
}
} }
} }
@@ -1204,16 +1197,16 @@ impl MimeFactory {
// Set the appropriate Content-Type for the inner message. // Set the appropriate Content-Type for the inner message.
for (h, v) in &mut message.headers { for (h, v) in &mut message.headers {
if h == "Content-Type" { if h == "Content-Type"
if let mail_builder::headers::HeaderType::ContentType(ct) = v { && let mail_builder::headers::HeaderType::ContentType(ct) = v
let mut ct_new = ct.clone(); {
ct_new = ct_new.attribute("protected-headers", "v1"); let mut ct_new = ct.clone();
if use_std_header_protection { ct_new = ct_new.attribute("protected-headers", "v1");
ct_new = ct_new.attribute("hp", "cipher"); if use_std_header_protection {
} ct_new = ct_new.attribute("hp", "cipher");
*ct = ct_new;
break;
} }
*ct = ct_new;
break;
} }
} }
@@ -1257,10 +1250,10 @@ impl MimeFactory {
// once new core versions are sufficiently deployed. // once new core versions are sufficiently deployed.
let anonymous_recipients = false; let anonymous_recipients = false;
if context.get_config_bool(Config::TestHooks).await? { if context.get_config_bool(Config::TestHooks).await?
if let Some(hook) = &*context.pre_encrypt_mime_hook.lock() { && let Some(hook) = &*context.pre_encrypt_mime_hook.lock()
message = hook(context, message); {
} message = hook(context, message);
} }
let encrypted = if let Some(shared_secret) = shared_secret { let encrypted = if let Some(shared_secret) = shared_secret {
@@ -1348,16 +1341,16 @@ impl MimeFactory {
message message
} else { } else {
for (h, v) in &mut message.headers { for (h, v) in &mut message.headers {
if h == "Content-Type" { if h == "Content-Type"
if let mail_builder::headers::HeaderType::ContentType(ct) = v { && let mail_builder::headers::HeaderType::ContentType(ct) = v
let mut ct_new = ct.clone(); {
ct_new = ct_new.attribute("protected-headers", "v1"); let mut ct_new = ct.clone();
if use_std_header_protection { ct_new = ct_new.attribute("protected-headers", "v1");
ct_new = ct_new.attribute("hp", "clear"); if use_std_header_protection {
} ct_new = ct_new.attribute("hp", "clear");
*ct = ct_new;
break;
} }
*ct = ct_new;
break;
} }
} }
@@ -1882,10 +1875,10 @@ impl MimeFactory {
parts.push(msg_kml_part); parts.push(msg_kml_part);
} }
if location::is_sending_locations_to_chat(context, Some(msg.chat_id)).await? { if location::is_sending_locations_to_chat(context, Some(msg.chat_id)).await?
if let Some(part) = self.get_location_kml_part(context).await? { && let Some(part) = self.get_location_kml_part(context).await?
parts.push(part); {
} parts.push(part);
} }
// we do not piggyback sync-files to other self-sent-messages // we do not piggyback sync-files to other self-sent-messages

View File

@@ -310,13 +310,14 @@ impl MimeMessage {
// Currently we do not sign unencrypted messages by default. // Currently we do not sign unencrypted messages by default.
(&mail, mimetype) (&mail, mimetype)
}; };
if mimetype.type_() == mime::MULTIPART && mimetype.subtype().as_str() == "mixed" { if mimetype.type_() == mime::MULTIPART
if let Some(part) = part.subparts.first() { && mimetype.subtype().as_str() == "mixed"
for field in &part.headers { && let Some(part) = part.subparts.first()
let key = field.get_key().to_lowercase(); {
if !headers.contains_key(&key) && is_hidden(&key) || key == "message-id" { for field in &part.headers {
headers.insert(key.to_string(), field.get_value()); let key = field.get_key().to_lowercase();
} if !headers.contains_key(&key) && is_hidden(&key) || key == "message-id" {
headers.insert(key.to_string(), field.get_value());
} }
} }
} }
@@ -805,22 +806,20 @@ impl MimeMessage {
{ {
part.typ = Viewtype::Voice; part.typ = Viewtype::Voice;
} }
if part.typ == Viewtype::Image || part.typ == Viewtype::Gif { if (part.typ == Viewtype::Image || part.typ == Viewtype::Gif)
if let Some(value) = self.get_header(HeaderDef::ChatContent) { && let Some(value) = self.get_header(HeaderDef::ChatContent)
if value == "sticker" { && value == "sticker"
part.typ = Viewtype::Sticker;
}
}
}
if part.typ == Viewtype::Audio
|| part.typ == Viewtype::Voice
|| part.typ == Viewtype::Video
{ {
if let Some(field_0) = self.get_header(HeaderDef::ChatDuration) { part.typ = Viewtype::Sticker;
let duration_ms = field_0.parse().unwrap_or_default(); }
if duration_ms > 0 && duration_ms < 24 * 60 * 60 * 1000 { if (part.typ == Viewtype::Audio
part.param.set_int(Param::Duration, duration_ms); || part.typ == Viewtype::Voice
} || part.typ == Viewtype::Video)
&& let Some(field_0) = self.get_header(HeaderDef::ChatDuration)
{
let duration_ms = field_0.parse().unwrap_or_default();
if duration_ms > 0 && duration_ms < 24 * 60 * 60 * 1000 {
part.param.set_int(Param::Duration, duration_ms);
} }
} }
@@ -836,38 +835,38 @@ impl MimeMessage {
self.squash_attachment_parts(); self.squash_attachment_parts();
} }
if !context.get_config_bool(Config::Bot).await? { if !context.get_config_bool(Config::Bot).await?
if let Some(ref subject) = self.get_subject() { && let Some(ref subject) = self.get_subject()
let mut prepend_subject = true; {
if !self.decrypting_failed { let mut prepend_subject = true;
let colon = subject.find(':'); if !self.decrypting_failed {
if colon == Some(2) let colon = subject.find(':');
|| colon == Some(3) if colon == Some(2)
|| self.has_chat_version() || colon == Some(3)
|| subject.contains("Chat:") || self.has_chat_version()
{ || subject.contains("Chat:")
prepend_subject = false {
} prepend_subject = false
} }
}
// For mailing lists, always add the subject because sometimes there are different topics // For mailing lists, always add the subject because sometimes there are different topics
// and otherwise it might be hard to keep track: // and otherwise it might be hard to keep track:
if self.is_mailinglist_message() && !self.has_chat_version() { if self.is_mailinglist_message() && !self.has_chat_version() {
prepend_subject = true; prepend_subject = true;
} }
if prepend_subject && !subject.is_empty() { if prepend_subject && !subject.is_empty() {
let part_with_text = self let part_with_text = self
.parts .parts
.iter_mut() .iter_mut()
.find(|part| !part.msg.is_empty() && !part.is_reaction); .find(|part| !part.msg.is_empty() && !part.is_reaction);
if let Some(part) = part_with_text { if let Some(part) = part_with_text {
// Message bubbles are small, so we use en dash to save space. In some // Message bubbles are small, so we use en dash to save space. In some
// languages there may be em dashes in the message text added by the author, // languages there may be em dashes in the message text added by the author,
// they may look stronger than Subject separation, this is a known thing. // they may look stronger than Subject separation, this is a known thing.
// Anyway, classic email support isn't a priority as of 2025. // Anyway, classic email support isn't a priority as of 2025.
part.msg = format!("{} {}", subject, part.msg); part.msg = format!("{} {}", subject, part.msg);
}
} }
} }
} }
@@ -881,21 +880,22 @@ impl MimeMessage {
self.parse_attachments(); self.parse_attachments();
// See if an MDN is requested from the other side // See if an MDN is requested from the other side
if !self.decrypting_failed && !self.parts.is_empty() { if !self.decrypting_failed
if let Some(ref dn_to) = self.chat_disposition_notification_to { && !self.parts.is_empty()
// Check that the message is not outgoing. && let Some(ref dn_to) = self.chat_disposition_notification_to
let from = &self.from.addr; {
if !context.is_self_addr(from).await? { // Check that the message is not outgoing.
if from.to_lowercase() == dn_to.addr.to_lowercase() { let from = &self.from.addr;
if let Some(part) = self.parts.last_mut() { if !context.is_self_addr(from).await? {
part.param.set_int(Param::WantsMdn, 1); if from.to_lowercase() == dn_to.addr.to_lowercase() {
} if let Some(part) = self.parts.last_mut() {
} else { part.param.set_int(Param::WantsMdn, 1);
warn!(
context,
"{} requested a read receipt to {}, ignoring", from, dn_to.addr
);
} }
} else {
warn!(
context,
"{} requested a read receipt to {}, ignoring", from, dn_to.addr
);
} }
} }
} }
@@ -910,10 +910,11 @@ impl MimeMessage {
..Default::default() ..Default::default()
}; };
if let Some(ref subject) = self.get_subject() { if let Some(ref subject) = self.get_subject()
if !self.has_chat_version() && self.webxdc_status_update.is_none() { && !self.has_chat_version()
part.msg = subject.to_string(); && self.webxdc_status_update.is_none()
} {
part.msg = subject.to_string();
} }
self.do_add_single_part(part); self.do_add_single_part(part);
@@ -952,15 +953,15 @@ impl MimeMessage {
let mut i = 0; let mut i = 0;
while let Some(part) = self.parts.get_mut(i) { while let Some(part) = self.parts.get_mut(i) {
if let Some(part_filename) = &part.org_filename { if let Some(part_filename) = &part.org_filename
if part_filename == &header_value { && part_filename == &header_value
if let Some(blob) = part.param.get(Param::File) { {
let res = Some(AvatarAction::Change(blob.to_string())); if let Some(blob) = part.param.get(Param::File) {
self.parts.remove(i); let res = Some(AvatarAction::Change(blob.to_string()));
return Ok(res); self.parts.remove(i);
} return Ok(res);
break;
} }
break;
} }
i += 1; i += 1;
} }
@@ -1604,13 +1605,13 @@ impl MimeMessage {
} else if let Some(sender) = self.get_header(HeaderDef::Sender) { } else if let Some(sender) = self.get_header(HeaderDef::Sender) {
// the `Sender:`-header alone is no indicator for mailing list // the `Sender:`-header alone is no indicator for mailing list
// as also used for bot-impersonation via `set_override_sender_name()` // as also used for bot-impersonation via `set_override_sender_name()`
if let Some(precedence) = self.get_header(HeaderDef::Precedence) { if let Some(precedence) = self.get_header(HeaderDef::Precedence)
if precedence == "list" || precedence == "bulk" { && (precedence == "list" || precedence == "bulk")
// The message belongs to a mailing list, but there is no `ListId:`-header; {
// `Sender:`-header is be used to get a unique id. // The message belongs to a mailing list, but there is no `ListId:`-header;
// This method is used by implementations as Majordomo. // `Sender:`-header is be used to get a unique id.
return Some(sender); // This method is used by implementations as Majordomo.
} return Some(sender);
} }
} }
None None
@@ -1651,10 +1652,10 @@ impl MimeMessage {
remove_header(headers, "autocrypt-gossip", removed); remove_header(headers, "autocrypt-gossip", removed);
// Secure-Join is secured unless it is an initial "vc-request"/"vg-request". // Secure-Join is secured unless it is an initial "vc-request"/"vg-request".
if let Some(secure_join) = remove_header(headers, "secure-join", removed) { if let Some(secure_join) = remove_header(headers, "secure-join", removed)
if secure_join == "vc-request" || secure_join == "vg-request" { && (secure_join == "vc-request" || secure_join == "vg-request")
headers.insert("secure-join".to_string(), secure_join); {
} headers.insert("secure-join".to_string(), secure_join);
} }
} }
@@ -1889,12 +1890,11 @@ impl MimeMessage {
.iter() .iter()
.filter(|p| p.typ == Viewtype::Text) .filter(|p| p.typ == Viewtype::Text)
.count(); .count();
if text_part_cnt == 2 { if text_part_cnt == 2
if let Some(last_part) = self.parts.last() { && let Some(last_part) = self.parts.last()
if last_part.typ == Viewtype::Text { && last_part.typ == Viewtype::Text
self.parts.pop(); {
} self.parts.pop();
}
} }
} }
} }
@@ -1947,15 +1947,15 @@ impl MimeMessage {
} }
} }
if let Some(delivery_report) = &self.delivery_report { if let Some(delivery_report) = &self.delivery_report
if delivery_report.failure { && delivery_report.failure
let error = parts {
.iter() let error = parts
.find(|p| p.typ == Viewtype::Text) .iter()
.map(|p| p.msg.clone()); .find(|p| p.typ == Viewtype::Text)
if let Err(err) = handle_ndn(context, delivery_report, error).await { .map(|p| p.msg.clone());
warn!(context, "Could not handle NDN: {err:#}."); if let Err(err) = handle_ndn(context, delivery_report, error).await {
} warn!(context, "Could not handle NDN: {err:#}.");
} }
} }
} }
@@ -2303,14 +2303,14 @@ fn get_attachment_filename(
// `Content-Disposition: ... filename=...` // `Content-Disposition: ... filename=...`
let mut desired_filename = ct.params.get("filename").map(|s| s.to_string()); let mut desired_filename = ct.params.get("filename").map(|s| s.to_string());
if desired_filename.is_none() { if desired_filename.is_none()
if let Some(name) = ct.params.get("filename*").map(|s| s.to_string()) { && let Some(name) = ct.params.get("filename*").map(|s| s.to_string())
// be graceful and just use the original name. {
// some MUA, including Delta Chat up to core1.50, // be graceful and just use the original name.
// use `filename*` mistakenly for simple encoded-words without following rfc2231 // some MUA, including Delta Chat up to core1.50,
warn!(context, "apostrophed encoding invalid: {}", name); // use `filename*` mistakenly for simple encoded-words without following rfc2231
desired_filename = Some(name); warn!(context, "apostrophed encoding invalid: {}", name);
} desired_filename = Some(name);
} }
// if no filename is set, try `Content-Disposition: ... name=...` // if no filename is set, try `Content-Disposition: ... name=...`
@@ -2383,24 +2383,23 @@ fn get_all_addresses_from_header(headers: &[MailHeader], header: &str) -> Vec<Si
.iter() .iter()
.rev() .rev()
.find(|h| h.get_key().to_lowercase() == header) .find(|h| h.get_key().to_lowercase() == header)
&& let Ok(addrs) = mailparse::addrparse_header(header)
{ {
if let Ok(addrs) = mailparse::addrparse_header(header) { for addr in addrs.iter() {
for addr in addrs.iter() { match addr {
match addr { mailparse::MailAddr::Single(info) => {
mailparse::MailAddr::Single(info) => { result.push(SingleInfo {
addr: addr_normalize(&info.addr).to_lowercase(),
display_name: info.display_name.clone(),
});
}
mailparse::MailAddr::Group(infos) => {
for info in &infos.addrs {
result.push(SingleInfo { result.push(SingleInfo {
addr: addr_normalize(&info.addr).to_lowercase(), addr: addr_normalize(&info.addr).to_lowercase(),
display_name: info.display_name.clone(), display_name: info.display_name.clone(),
}); });
} }
mailparse::MailAddr::Group(infos) => {
for info in &infos.addrs {
result.push(SingleInfo {
addr: addr_normalize(&info.addr).to_lowercase(),
display_name: info.display_name.clone(),
});
}
}
} }
} }
} }

View File

@@ -1774,10 +1774,10 @@ async fn test_hp_legacy_display() -> Result<()> {
alice.set_config_bool(Config::TestHooks, true).await?; alice.set_config_bool(Config::TestHooks, true).await?;
*alice.pre_encrypt_mime_hook.lock() = Some(|_, mut mime| { *alice.pre_encrypt_mime_hook.lock() = Some(|_, mut mime| {
for (h, v) in &mut mime.headers { for (h, v) in &mut mime.headers {
if h == "Content-Type" { if h == "Content-Type"
if let mail_builder::headers::HeaderType::ContentType(ct) = v { && let mail_builder::headers::HeaderType::ContentType(ct) = v
*ct = ct.clone().attribute("hp-legacy-display", "1"); {
} *ct = ct.clone().attribute("hp-legacy-display", "1");
} }
} }
mime mime

View File

@@ -139,10 +139,10 @@ pub(crate) async fn get_oauth2_access_token(
value = &redirect_uri; value = &redirect_uri;
} else if value == "$CODE" { } else if value == "$CODE" {
value = code; value = code;
} else if value == "$REFRESH_TOKEN" { } else if value == "$REFRESH_TOKEN"
if let Some(refresh_token) = refresh_token.as_ref() { && let Some(refresh_token) = refresh_token.as_ref()
value = refresh_token; {
} value = refresh_token;
} }
post_param.insert(key, value); post_param.insert(key, value);
@@ -261,14 +261,12 @@ impl Oauth2 {
if let Some(domain) = addr_normalized if let Some(domain) = addr_normalized
.find('@') .find('@')
.map(|index| addr_normalized.split_at(index + 1).1) .map(|index| addr_normalized.split_at(index + 1).1)
{ && let Some(oauth2_authorizer) = provider::get_provider_info(domain)
if let Some(oauth2_authorizer) = provider::get_provider_info(domain)
.and_then(|provider| provider.oauth2_authorizer.as_ref()) .and_then(|provider| provider.oauth2_authorizer.as_ref())
{ {
return Some(match oauth2_authorizer { return Some(match oauth2_authorizer {
Oauth2Authorizer::Yandex => OAUTH2_YANDEX, Oauth2Authorizer::Yandex => OAUTH2_YANDEX,
}); });
}
} }
None None
} }

View File

@@ -884,53 +884,35 @@ pub(crate) async fn receive_imf_inner(
} }
} }
if let Some(avatar_action) = &mime_parser.user_avatar { if let Some(avatar_action) = &mime_parser.user_avatar
if from_id != ContactId::UNDEFINED && from_id != ContactId::UNDEFINED
&& context && context
.update_contacts_timestamp( .update_contacts_timestamp(from_id, Param::AvatarTimestamp, mime_parser.timestamp_sent)
from_id, .await?
Param::AvatarTimestamp, && let Err(err) =
mime_parser.timestamp_sent, contact::set_profile_image(context, from_id, avatar_action, mime_parser.was_encrypted())
) .await
.await? {
{ warn!(context, "receive_imf cannot update profile image: {err:#}.");
if let Err(err) = contact::set_profile_image( };
context,
from_id,
avatar_action,
mime_parser.was_encrypted(),
)
.await
{
warn!(context, "receive_imf cannot update profile image: {err:#}.");
};
}
}
// Ignore footers from mailinglists as they are often created or modified by the mailinglist software. // Ignore footers from mailinglists as they are often created or modified by the mailinglist software.
if let Some(footer) = &mime_parser.footer { if let Some(footer) = &mime_parser.footer
if !mime_parser.is_mailinglist_message() && !mime_parser.is_mailinglist_message()
&& from_id != ContactId::UNDEFINED && from_id != ContactId::UNDEFINED
&& context && context
.update_contacts_timestamp( .update_contacts_timestamp(from_id, Param::StatusTimestamp, mime_parser.timestamp_sent)
from_id, .await?
Param::StatusTimestamp, && let Err(err) = contact::set_status(
mime_parser.timestamp_sent, context,
) from_id,
.await? footer.to_string(),
{ mime_parser.was_encrypted(),
if let Err(err) = contact::set_status( mime_parser.has_chat_version(),
context, )
from_id, .await
footer.to_string(), {
mime_parser.was_encrypted(), warn!(context, "Cannot update contact status: {err:#}.");
mime_parser.has_chat_version(),
)
.await
{
warn!(context, "Cannot update contact status: {err:#}.");
}
}
} }
// Get user-configured server deletion // Get user-configured server deletion
@@ -1341,8 +1323,8 @@ async fn do_chat_assignment(
if let Some((id, blocked)) = chat::get_chat_id_by_grpid(context, grpid).await? { if let Some((id, blocked)) = chat::get_chat_id_by_grpid(context, grpid).await? {
chat_id = Some(id); chat_id = Some(id);
chat_id_blocked = blocked; chat_id_blocked = blocked;
} else if allow_creation || test_normal_chat.is_some() { } else if (allow_creation || test_normal_chat.is_some())
if let Some((new_chat_id, new_chat_id_blocked)) = create_group( && let Some((new_chat_id, new_chat_id_blocked)) = create_group(
context, context,
mime_parser, mime_parser,
is_partial_download.is_some(), is_partial_download.is_some(),
@@ -1353,16 +1335,15 @@ async fn do_chat_assignment(
grpid, grpid,
) )
.await? .await?
{ {
chat_id = Some(new_chat_id); chat_id = Some(new_chat_id);
chat_id_blocked = new_chat_id_blocked; chat_id_blocked = new_chat_id_blocked;
chat_created = true; chat_created = true;
}
} }
} }
ChatAssignment::MailingListOrBroadcast => { ChatAssignment::MailingListOrBroadcast => {
if let Some(mailinglist_header) = mime_parser.get_mailinglist_header() { if let Some(mailinglist_header) = mime_parser.get_mailinglist_header()
if let Some((new_chat_id, new_chat_id_blocked, new_chat_created)) = && let Some((new_chat_id, new_chat_id_blocked, new_chat_created)) =
create_or_lookup_mailinglist_or_broadcast( create_or_lookup_mailinglist_or_broadcast(
context, context,
allow_creation, allow_creation,
@@ -1372,13 +1353,12 @@ async fn do_chat_assignment(
mime_parser, mime_parser,
) )
.await? .await?
{ {
chat_id = Some(new_chat_id); chat_id = Some(new_chat_id);
chat_id_blocked = new_chat_id_blocked; chat_id_blocked = new_chat_id_blocked;
chat_created = new_chat_created; chat_created = new_chat_created;
apply_mailinglist_changes(context, mime_parser, new_chat_id).await?; apply_mailinglist_changes(context, mime_parser, new_chat_id).await?;
}
} }
} }
ChatAssignment::ExistingChat { ChatAssignment::ExistingChat {
@@ -1413,11 +1393,10 @@ async fn do_chat_assignment(
if chat_id_blocked != Blocked::Not if chat_id_blocked != Blocked::Not
&& create_blocked != Blocked::Yes && create_blocked != Blocked::Yes
&& !matches!(chat_assignment, ChatAssignment::MailingListOrBroadcast) && !matches!(chat_assignment, ChatAssignment::MailingListOrBroadcast)
&& let Some(chat_id) = chat_id
{ {
if let Some(chat_id) = chat_id { chat_id.set_blocked(context, create_blocked).await?;
chat_id.set_blocked(context, create_blocked).await?; chat_id_blocked = create_blocked;
chat_id_blocked = create_blocked;
}
} }
if chat_id.is_none() { if chat_id.is_none() {
@@ -1441,21 +1420,20 @@ async fn do_chat_assignment(
chat_created = true; chat_created = true;
} }
if let Some(chat_id) = chat_id { if let Some(chat_id) = chat_id
if chat_id_blocked != Blocked::Not { && chat_id_blocked != Blocked::Not
if chat_id_blocked != create_blocked { {
chat_id.set_blocked(context, create_blocked).await?; if chat_id_blocked != create_blocked {
} chat_id.set_blocked(context, create_blocked).await?;
if create_blocked == Blocked::Request && parent_message.is_some() { }
// we do not want any chat to be created implicitly. Because of the origin-scale-up, if create_blocked == Blocked::Request && parent_message.is_some() {
// the contact requests will pop up and this should be just fine. // we do not want any chat to be created implicitly. Because of the origin-scale-up,
ContactId::scaleup_origin(context, &[from_id], Origin::IncomingReplyTo) // the contact requests will pop up and this should be just fine.
.await?; ContactId::scaleup_origin(context, &[from_id], Origin::IncomingReplyTo).await?;
info!( info!(
context, context,
"Message is a reply to a known message, mark sender as known.", "Message is a reply to a known message, mark sender as known.",
); );
}
} }
} }
} }
@@ -1476,8 +1454,8 @@ async fn do_chat_assignment(
if let Some((id, blocked)) = chat::get_chat_id_by_grpid(context, grpid).await? { if let Some((id, blocked)) = chat::get_chat_id_by_grpid(context, grpid).await? {
chat_id = Some(id); chat_id = Some(id);
chat_id_blocked = blocked; chat_id_blocked = blocked;
} else if allow_creation { } else if allow_creation
if let Some((new_chat_id, new_chat_id_blocked)) = create_group( && let Some((new_chat_id, new_chat_id_blocked)) = create_group(
context, context,
mime_parser, mime_parser,
is_partial_download.is_some(), is_partial_download.is_some(),
@@ -1488,11 +1466,10 @@ async fn do_chat_assignment(
grpid, grpid,
) )
.await? .await?
{ {
chat_id = Some(new_chat_id); chat_id = Some(new_chat_id);
chat_id_blocked = new_chat_id_blocked; chat_id_blocked = new_chat_id_blocked;
chat_created = true; chat_created = true;
}
} }
} }
ChatAssignment::ExistingChat { ChatAssignment::ExistingChat {
@@ -1574,11 +1551,12 @@ async fn do_chat_assignment(
chat_created = true; chat_created = true;
} }
} }
if chat_id.is_none() && mime_parser.has_chat_version() { if chat_id.is_none()
if let Some(chat) = ChatIdBlocked::lookup_by_contact(context, to_id).await? { && mime_parser.has_chat_version()
chat_id = Some(chat.id); && let Some(chat) = ChatIdBlocked::lookup_by_contact(context, to_id).await?
chat_id_blocked = chat.blocked; {
} chat_id = Some(chat.id);
chat_id_blocked = chat.blocked;
} }
} }
@@ -1598,11 +1576,11 @@ async fn do_chat_assignment(
} }
// automatically unblock chat when the user sends a message // automatically unblock chat when the user sends a message
if chat_id_blocked != Blocked::Not { if chat_id_blocked != Blocked::Not
if let Some(chat_id) = chat_id { && let Some(chat_id) = chat_id
chat_id.unblock_ex(context, Nosync).await?; {
chat_id_blocked = Blocked::Not; chat_id.unblock_ex(context, Nosync).await?;
} chat_id_blocked = Blocked::Not;
} }
} }
let chat_id = chat_id.unwrap_or_else(|| { let chat_id = chat_id.unwrap_or_else(|| {
@@ -1640,11 +1618,9 @@ async fn add_parts(
// if contact renaming is prevented (for mailinglists and bots), // if contact renaming is prevented (for mailinglists and bots),
// we use name from From:-header as override name // we use name from From:-header as override name
if prevent_rename { if prevent_rename && let Some(name) = &mime_parser.from.display_name {
if let Some(name) = &mime_parser.from.display_name { for part in &mut mime_parser.parts {
for part in &mut mime_parser.parts { part.param.set(Param::OverrideSenderDisplayname, name);
part.param.set(Param::OverrideSenderDisplayname, name);
}
} }
} }
@@ -2270,36 +2246,36 @@ async fn handle_edit_delete(
"Edit message: rfc724_mid {rfc724_mid:?} not found." "Edit message: rfc724_mid {rfc724_mid:?} not found."
); );
} }
} else if let Some(rfc724_mid_list) = mime_parser.get_header(HeaderDef::ChatDelete) { } else if let Some(rfc724_mid_list) = mime_parser.get_header(HeaderDef::ChatDelete)
if let Some(part) = mime_parser.parts.first() { && let Some(part) = mime_parser.parts.first()
// See `message::delete_msgs_ex()`, unlike edit requests, DC doesn't send unencrypted {
// deletion requests, so there's no need to support them. // See `message::delete_msgs_ex()`, unlike edit requests, DC doesn't send unencrypted
if part.param.get_bool(Param::GuaranteeE2ee).unwrap_or(false) { // deletion requests, so there's no need to support them.
let mut modified_chat_ids = HashSet::new(); if part.param.get_bool(Param::GuaranteeE2ee).unwrap_or(false) {
let mut msg_ids = Vec::new(); let mut modified_chat_ids = HashSet::new();
let mut msg_ids = Vec::new();
let rfc724_mid_vec: Vec<&str> = rfc724_mid_list.split_whitespace().collect(); let rfc724_mid_vec: Vec<&str> = rfc724_mid_list.split_whitespace().collect();
for rfc724_mid in rfc724_mid_vec { for rfc724_mid in rfc724_mid_vec {
if let Some(msg_id) = message::rfc724_mid_exists(context, rfc724_mid).await? { if let Some(msg_id) = message::rfc724_mid_exists(context, rfc724_mid).await? {
if let Some(msg) = Message::load_from_db_optional(context, msg_id).await? { if let Some(msg) = Message::load_from_db_optional(context, msg_id).await? {
if msg.from_id == from_id { if msg.from_id == from_id {
message::delete_msg_locally(context, &msg).await?; message::delete_msg_locally(context, &msg).await?;
msg_ids.push(msg.id); msg_ids.push(msg.id);
modified_chat_ids.insert(msg.chat_id); modified_chat_ids.insert(msg.chat_id);
} else {
warn!(context, "Delete message: Bad sender.");
}
} else { } else {
warn!(context, "Delete message: Database entry does not exist."); warn!(context, "Delete message: Bad sender.");
} }
} else { } else {
warn!(context, "Delete message: {rfc724_mid:?} not found."); warn!(context, "Delete message: Database entry does not exist.");
} }
} else {
warn!(context, "Delete message: {rfc724_mid:?} not found.");
} }
message::delete_msgs_locally_done(context, &msg_ids, modified_chat_ids).await?;
} else {
warn!(context, "Delete message: Not encrypted.");
} }
message::delete_msgs_locally_done(context, &msg_ids, modified_chat_ids).await?;
} else {
warn!(context, "Delete message: Not encrypted.");
} }
} }
Ok(()) Ok(())
@@ -2356,33 +2332,32 @@ async fn save_locations(
let mut send_event = false; let mut send_event = false;
if let Some(message_kml) = &mime_parser.message_kml { if let Some(message_kml) = &mime_parser.message_kml
if let Some(newest_location_id) = && let Some(newest_location_id) =
location::save(context, chat_id, from_id, &message_kml.locations, true).await? location::save(context, chat_id, from_id, &message_kml.locations, true).await?
{ {
location::set_msg_location_id(context, msg_id, newest_location_id).await?; location::set_msg_location_id(context, msg_id, newest_location_id).await?;
send_event = true; send_event = true;
}
} }
if let Some(location_kml) = &mime_parser.location_kml { if let Some(location_kml) = &mime_parser.location_kml
if let Some(addr) = &location_kml.addr { && let Some(addr) = &location_kml.addr
let contact = Contact::get_by_id(context, from_id).await?; {
if contact.get_addr().to_lowercase() == addr.to_lowercase() { let contact = Contact::get_by_id(context, from_id).await?;
if location::save(context, chat_id, from_id, &location_kml.locations, false) if contact.get_addr().to_lowercase() == addr.to_lowercase() {
.await? if location::save(context, chat_id, from_id, &location_kml.locations, false)
.is_some() .await?
{ .is_some()
send_event = true; {
} send_event = true;
} else {
warn!(
context,
"Address in location.kml {:?} is not the same as the sender address {:?}.",
addr,
contact.get_addr()
);
} }
} else {
warn!(
context,
"Address in location.kml {:?} is not the same as the sender address {:?}.",
addr,
contact.get_addr()
);
} }
} }
if send_event { if send_event {
@@ -2734,13 +2709,13 @@ async fn update_chats_contacts_timestamps(
to_ids.iter(), to_ids.iter(),
chat_group_member_timestamps.iter().take(to_ids.len()), chat_group_member_timestamps.iter().take(to_ids.len()),
) { ) {
if let Some(contact_id) = contact_id { if let Some(contact_id) = contact_id
if Some(*contact_id) != ignored_id { && Some(*contact_id) != ignored_id
// It could be that member was already added, {
// but updated addition timestamp // It could be that member was already added,
// is also a modification worth notifying about. // but updated addition timestamp
modified |= add_statement.execute((chat_id, contact_id, ts))? > 0; // is also a modification worth notifying about.
} modified |= add_statement.execute((chat_id, contact_id, ts))? > 0;
} }
} }
@@ -3003,13 +2978,14 @@ async fn apply_group_changes(
.copied() .copied()
.collect(); .collect();
if let Some(added_id) = added_id { if let Some(added_id) = added_id
if !added_ids.remove(&added_id) && added_id != ContactId::SELF { && !added_ids.remove(&added_id)
// No-op "Member added" message. An exception is self-addition messages because they at && added_id != ContactId::SELF
// least must be shown when a chat is created on our side. {
info!(context, "No-op 'Member added' message (TRASH)"); // No-op "Member added" message. An exception is self-addition messages because they at
better_msg = Some(String::new()); // least must be shown when a chat is created on our side.
} info!(context, "No-op 'Member added' message (TRASH)");
better_msg = Some(String::new());
} }
if let Some(removed_id) = removed_id { if let Some(removed_id) = removed_id {
removed_ids.remove(&removed_id); removed_ids.remove(&removed_id);
@@ -3064,59 +3040,52 @@ async fn apply_chat_name_and_avatar_changes(
Some(_) => Some(chat.name.as_str()), Some(_) => Some(chat.name.as_str()),
None => None, None => None,
}) })
{ && let Some(grpname) = mime_parser
if let Some(grpname) = mime_parser
.get_header(HeaderDef::ChatGroupName) .get_header(HeaderDef::ChatGroupName)
.map(|grpname| grpname.trim()) .map(|grpname| grpname.trim())
.filter(|grpname| grpname.len() < 200) .filter(|grpname| grpname.len() < 200)
{ {
let grpname = &sanitize_single_line(grpname); let grpname = &sanitize_single_line(grpname);
let chat_group_name_timestamp = let chat_group_name_timestamp = chat.param.get_i64(Param::GroupNameTimestamp).unwrap_or(0);
chat.param.get_i64(Param::GroupNameTimestamp).unwrap_or(0); let group_name_timestamp = group_name_timestamp.unwrap_or(mime_parser.timestamp_sent);
let group_name_timestamp = group_name_timestamp.unwrap_or(mime_parser.timestamp_sent); // To provide group name consistency, compare names if timestamps are equal.
// To provide group name consistency, compare names if timestamps are equal. if (chat_group_name_timestamp, grpname) < (group_name_timestamp, &chat.name)
if (chat_group_name_timestamp, grpname) < (group_name_timestamp, &chat.name) && chat
&& chat .id
.id .update_timestamp(context, Param::GroupNameTimestamp, group_name_timestamp)
.update_timestamp(context, Param::GroupNameTimestamp, group_name_timestamp) .await?
.await? && grpname != &chat.name
&& grpname != &chat.name {
{ info!(context, "Updating grpname for chat {}.", chat.id);
info!(context, "Updating grpname for chat {}.", chat.id); context
context .sql
.sql .execute("UPDATE chats SET name=? WHERE id=?;", (grpname, chat.id))
.execute("UPDATE chats SET name=? WHERE id=?;", (grpname, chat.id)) .await?;
.await?; *send_event_chat_modified = true;
*send_event_chat_modified = true; }
} if mime_parser
if mime_parser .get_header(HeaderDef::ChatGroupNameChanged)
.get_header(HeaderDef::ChatGroupNameChanged) .is_some()
.is_some() {
{ let old_name = &sanitize_single_line(old_name);
let old_name = &sanitize_single_line(old_name); better_msg
better_msg.get_or_insert( .get_or_insert(stock_str::msg_grp_name(context, old_name, grpname, from_id).await);
stock_str::msg_grp_name(context, old_name, grpname, from_id).await,
);
}
} }
} }
// ========== Apply chat avatar changes ========== // ========== Apply chat avatar changes ==========
if let (Some(value), None) = (mime_parser.get_header(HeaderDef::ChatContent), &better_msg) { if let (Some(value), None) = (mime_parser.get_header(HeaderDef::ChatContent), &better_msg)
if value == "group-avatar-changed" { && value == "group-avatar-changed"
if let Some(avatar_action) = &mime_parser.group_avatar { && let Some(avatar_action) = &mime_parser.group_avatar
// this is just an explicit message containing the group-avatar, {
// apart from that, the group-avatar is send along with various other messages // this is just an explicit message containing the group-avatar,
better_msg.get_or_insert(match avatar_action { // apart from that, the group-avatar is send along with various other messages
AvatarAction::Delete => stock_str::msg_grp_img_deleted(context, from_id).await, better_msg.get_or_insert(match avatar_action {
AvatarAction::Change(_) => { AvatarAction::Delete => stock_str::msg_grp_img_deleted(context, from_id).await,
stock_str::msg_grp_img_changed(context, from_id).await AvatarAction::Change(_) => stock_str::msg_grp_img_changed(context, from_id).await,
} });
});
}
}
} }
if let Some(avatar_action) = &mime_parser.group_avatar { if let Some(avatar_action) = &mime_parser.group_avatar {
@@ -3297,10 +3266,10 @@ fn compute_mailinglist_name(
// for mailchimp lists, the name in `ListId` is just a long number. // for mailchimp lists, the name in `ListId` is just a long number.
// a usable name for these lists is in the `From` header // a usable name for these lists is in the `From` header
// and we can detect these lists by a unique `ListId`-suffix. // and we can detect these lists by a unique `ListId`-suffix.
if listid.ends_with(".list-id.mcsv.net") { if listid.ends_with(".list-id.mcsv.net")
if let Some(display_name) = &mime_parser.from.display_name { && let Some(display_name) = &mime_parser.from.display_name
name.clone_from(display_name); {
} name.clone_from(display_name);
} }
// additional names in square brackets in the subject are preferred // additional names in square brackets in the subject are preferred
@@ -3325,10 +3294,9 @@ fn compute_mailinglist_name(
|| mime_parser.from.addr.starts_with("notifications@") || mime_parser.from.addr.starts_with("notifications@")
|| mime_parser.from.addr.starts_with("newsletter@") || mime_parser.from.addr.starts_with("newsletter@")
|| listid.ends_with(".xt.local")) || listid.ends_with(".xt.local"))
&& let Some(display_name) = &mime_parser.from.display_name
{ {
if let Some(display_name) = &mime_parser.from.display_name { name.clone_from(display_name);
name.clone_from(display_name);
}
} }
// as a last resort, use the ListId as the name // as a last resort, use the ListId as the name
@@ -3476,15 +3444,15 @@ async fn apply_out_broadcast_changes(
chat::remove_from_chat_contacts_table_without_trace(context, chat.id, from_id).await?; chat::remove_from_chat_contacts_table_without_trace(context, chat.id, from_id).await?;
info!(context, "Broadcast leave message (TRASH)"); info!(context, "Broadcast leave message (TRASH)");
better_msg = Some("".to_string()); better_msg = Some("".to_string());
} else if from_id == ContactId::SELF { } else if from_id == ContactId::SELF
if let Some(removed_id) = removed_id { && let Some(removed_id) = removed_id
chat::remove_from_chat_contacts_table_without_trace(context, chat.id, removed_id) {
.await?; chat::remove_from_chat_contacts_table_without_trace(context, chat.id, removed_id)
.await?;
better_msg.get_or_insert( better_msg.get_or_insert(
stock_str::msg_del_member_local(context, removed_id, ContactId::SELF).await, stock_str::msg_del_member_local(context, removed_id, ContactId::SELF).await,
); );
}
} }
} }
@@ -3508,14 +3476,14 @@ async fn apply_in_broadcast_changes(
) -> Result<GroupChangesInfo> { ) -> Result<GroupChangesInfo> {
ensure!(chat.typ == Chattype::InBroadcast); ensure!(chat.typ == Chattype::InBroadcast);
if let Some(part) = mime_parser.parts.first() { if let Some(part) = mime_parser.parts.first()
if let Some(error) = &part.error { && let Some(error) = &part.error
warn!( {
context, warn!(
"Not applying broadcast changes from message with error: {error}" context,
); "Not applying broadcast changes from message with error: {error}"
return Ok(GroupChangesInfo::default()); );
} return Ok(GroupChangesInfo::default());
} }
let mut send_event_chat_modified = false; let mut send_event_chat_modified = false;
@@ -3531,21 +3499,21 @@ async fn apply_in_broadcast_changes(
) )
.await?; .await?;
if let Some(added_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberAdded) { if let Some(added_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberAdded)
if context.is_self_addr(added_addr).await? { && context.is_self_addr(added_addr).await?
let msg = if chat.is_self_in_chat(context).await? { {
// Self is already in the chat. let msg = if chat.is_self_in_chat(context).await? {
// Probably Alice has two devices and her second device added us again; // Self is already in the chat.
// just hide the message. // Probably Alice has two devices and her second device added us again;
info!(context, "No-op broadcast 'Member added' message (TRASH)"); // just hide the message.
"".to_string() info!(context, "No-op broadcast 'Member added' message (TRASH)");
} else { "".to_string()
stock_str::msg_you_joined_broadcast(context).await } else {
}; stock_str::msg_you_joined_broadcast(context).await
};
better_msg.get_or_insert(msg); better_msg.get_or_insert(msg);
send_event_chat_modified = true; send_event_chat_modified = true;
}
} }
if let Some(removed_fpr) = mime_parser.get_header(HeaderDef::ChatGroupMemberRemovedFpr) { if let Some(removed_fpr) = mime_parser.get_header(HeaderDef::ChatGroupMemberRemovedFpr) {
@@ -3756,12 +3724,11 @@ async fn get_previous_message(
context: &Context, context: &Context,
mime_parser: &MimeMessage, mime_parser: &MimeMessage,
) -> Result<Option<Message>> { ) -> Result<Option<Message>> {
if let Some(field) = mime_parser.get_header(HeaderDef::References) { if let Some(field) = mime_parser.get_header(HeaderDef::References)
if let Some(rfc724mid) = parse_message_ids(field).last() { && let Some(rfc724mid) = parse_message_ids(field).last()
if let Some(msg_id) = rfc724_mid_exists(context, rfc724mid).await? { && let Some(msg_id) = rfc724_mid_exists(context, rfc724mid).await?
return Message::load_from_db_optional(context, msg_id).await; {
} return Message::load_from_db_optional(context, msg_id).await;
}
} }
Ok(None) Ok(None)
} }

View File

@@ -474,17 +474,17 @@ async fn inbox_fetch_idle(ctx: &Context, imap: &mut Imap, mut session: Session)
} }
// Update quota no more than once a minute. // Update quota no more than once a minute.
if ctx.quota_needs_update(60).await { if ctx.quota_needs_update(60).await
if let Err(err) = ctx.update_recent_quota(&mut session).await { && let Err(err) = ctx.update_recent_quota(&mut session).await
warn!(ctx, "Failed to update quota: {:#}.", err); {
} warn!(ctx, "Failed to update quota: {:#}.", err);
} }
if let Ok(()) = imap.resync_request_receiver.try_recv() { if let Ok(()) = imap.resync_request_receiver.try_recv()
if let Err(err) = session.resync_folders(ctx).await { && let Err(err) = session.resync_folders(ctx).await
warn!(ctx, "Failed to resync folders: {:#}.", err); {
imap.resync_request_sender.try_send(()).ok(); warn!(ctx, "Failed to resync folders: {:#}.", err);
} imap.resync_request_sender.try_send(()).ok();
} }
maybe_add_time_based_warnings(ctx).await; maybe_add_time_based_warnings(ctx).await;

View File

@@ -199,19 +199,17 @@ fn remove_bottom_quote<'a>(lines: &'a [&str]) -> (&'a [&'a str], Option<String>)
}) })
.collect::<Vec<&str>>() .collect::<Vec<&str>>()
.join("\n"); .join("\n");
if l_last > 1 { if l_last > 1
if let Some(line) = lines.get(l_last - 1) { && let Some(line) = lines.get(l_last - 1)
if is_empty_line(line) { && is_empty_line(line)
l_last -= 1 {
} l_last -= 1
}
} }
if l_last > 1 { if l_last > 1
if let Some(line) = lines.get(l_last - 1) { && let Some(line) = lines.get(l_last - 1)
if is_quoted_headline(line) { && is_quoted_headline(line)
l_last -= 1 {
} l_last -= 1
}
} }
(lines.get(..l_last).unwrap_or(lines), Some(quoted_text)) (lines.get(..l_last).unwrap_or(lines), Some(quoted_text))
} else { } else {

View File

@@ -307,24 +307,23 @@ pub(crate) async fn smtp_send(
Ok(()) => SendResult::Success, Ok(()) => SendResult::Success,
}; };
if let SendResult::Failure(err) = &status { if let SendResult::Failure(err) = &status
if let Some(msg_id) = msg_id { && let Some(msg_id) = msg_id
// We couldn't send the message, so mark it as failed {
match Message::load_from_db(context, msg_id).await { // We couldn't send the message, so mark it as failed
Ok(mut msg) => { match Message::load_from_db(context, msg_id).await {
if let Err(err) = Ok(mut msg) => {
message::set_msg_failed(context, &mut msg, &err.to_string()).await if let Err(err) = message::set_msg_failed(context, &mut msg, &err.to_string()).await
{ {
error!(context, "Failed to mark {msg_id} as failed: {err:#}."); error!(context, "Failed to mark {msg_id} as failed: {err:#}.");
}
}
Err(err) => {
error!(
context,
"Failed to load {msg_id} to mark it as failed: {err:#}."
);
} }
} }
Err(err) => {
error!(
context,
"Failed to load {msg_id} to mark it as failed: {err:#}."
);
}
} }
} }
status status

View File

@@ -235,26 +235,24 @@ impl Sql {
} }
} }
if recode_avatar { if recode_avatar && let Some(avatar) = context.get_config(Config::Selfavatar).await? {
if let Some(avatar) = context.get_config(Config::Selfavatar).await? { let mut blob = BlobObject::from_path(context, Path::new(&avatar))?;
let mut blob = BlobObject::from_path(context, Path::new(&avatar))?; match blob.recode_to_avatar_size(context).await {
match blob.recode_to_avatar_size(context).await { Ok(()) => {
Ok(()) => { if let Some(path) = blob.to_abs_path().to_str() {
if let Some(path) = blob.to_abs_path().to_str() {
context
.set_config_internal(Config::Selfavatar, Some(path))
.await?;
} else {
warn!(context, "Setting selfavatar failed: non-UTF-8 filename");
}
}
Err(e) => {
warn!(context, "Migrations can't recode avatar, removing. {:#}", e);
context context
.set_config_internal(Config::Selfavatar, None) .set_config_internal(Config::Selfavatar, Some(path))
.await? .await?;
} else {
warn!(context, "Setting selfavatar failed: non-UTF-8 filename");
} }
} }
Err(e) => {
warn!(context, "Migrations can't recode avatar, removing. {:#}", e);
context
.set_config_internal(Config::Selfavatar, None)
.await?
}
} }
} }

View File

@@ -449,12 +449,11 @@ CREATE TABLE imap_sync (folder TEXT PRIMARY KEY, uidvalidity INTEGER DEFAULT 0,
disable_server_delete = true; disable_server_delete = true;
// Don't disable server delete if it was on by default (Nauta): // Don't disable server delete if it was on by default (Nauta):
if let Some(provider) = context.get_configured_provider().await? { if let Some(provider) = context.get_configured_provider().await?
if let Some(defaults) = &provider.config_defaults { && let Some(defaults) = &provider.config_defaults
if defaults.iter().any(|d| d.key == Config::DeleteServerAfter) { && defaults.iter().any(|d| d.key == Config::DeleteServerAfter)
disable_server_delete = false; {
} disable_server_delete = false;
}
} }
} }
sql.set_db_version(73).await?; sql.set_db_version(73).await?;

View File

@@ -151,10 +151,10 @@ pub struct PooledConnection {
impl Drop for PooledConnection { impl Drop for PooledConnection {
fn drop(&mut self) { fn drop(&mut self) {
// Put the connection back unless the pool is already dropped. // Put the connection back unless the pool is already dropped.
if let Some(pool) = self.pool.upgrade() { if let Some(pool) = self.pool.upgrade()
if let Some(conn) = self.conn.take() { && let Some(conn) = self.conn.take()
pool.put(conn); {
} pool.put(conn);
} }
} }
} }

View File

@@ -727,10 +727,10 @@ pub(crate) fn parse_receive_headers(headers: &Headers) -> String {
/// Otherwise, return None. /// Otherwise, return None.
pub(crate) fn single_value<T>(collection: impl IntoIterator<Item = T>) -> Option<T> { pub(crate) fn single_value<T>(collection: impl IntoIterator<Item = T>) -> Option<T> {
let mut iter = collection.into_iter(); let mut iter = collection.into_iter();
if let Some(value) = iter.next() { if let Some(value) = iter.next()
if iter.next().is_none() { && iter.next().is_none()
return Some(value); {
} return Some(value);
} }
None None
} }

View File

@@ -309,13 +309,11 @@ impl Context {
}, },
) )
.await? .await?
&& last_from_id == from_id
&& last_param.get_cmd() == SystemMessage::WebxdcInfoMessage
&& last_in_repl_to == instance.rfc724_mid
{ {
if last_from_id == from_id return Ok(Some(last_msg_id));
&& last_param.get_cmd() == SystemMessage::WebxdcInfoMessage
&& last_in_repl_to == instance.rfc724_mid
{
return Ok(Some(last_msg_id));
}
} }
Ok(None) Ok(None)
} }
@@ -341,63 +339,59 @@ impl Context {
let mut param_changed = false; let mut param_changed = false;
let mut instance = instance.clone(); let mut instance = instance.clone();
if let Some(ref document) = status_update_item.document { if let Some(ref document) = status_update_item.document
if instance && instance
.param .param
.update_timestamp(Param::WebxdcDocumentTimestamp, timestamp)? .update_timestamp(Param::WebxdcDocumentTimestamp, timestamp)?
{ {
instance.param.set(Param::WebxdcDocument, document); instance.param.set(Param::WebxdcDocument, document);
param_changed = true; param_changed = true;
}
} }
if let Some(ref summary) = status_update_item.summary { if let Some(ref summary) = status_update_item.summary
if instance && instance
.param .param
.update_timestamp(Param::WebxdcSummaryTimestamp, timestamp)? .update_timestamp(Param::WebxdcSummaryTimestamp, timestamp)?
{ {
let summary = sanitize_bidi_characters(summary); let summary = sanitize_bidi_characters(summary);
instance.param.set(Param::WebxdcSummary, summary.clone()); instance.param.set(Param::WebxdcSummary, summary.clone());
param_changed = true; param_changed = true;
}
} }
if can_info_msg { if can_info_msg && let Some(ref info) = status_update_item.info {
if let Some(ref info) = status_update_item.info { let info_msg_id = self
let info_msg_id = self .get_overwritable_info_msg_id(&instance, from_id)
.get_overwritable_info_msg_id(&instance, from_id) .await?;
.await?;
if let (Some(info_msg_id), None) = (info_msg_id, &status_update_item.href) { if let (Some(info_msg_id), None) = (info_msg_id, &status_update_item.href) {
chat::update_msg_text_and_timestamp( chat::update_msg_text_and_timestamp(
self, self,
instance.chat_id, instance.chat_id,
info_msg_id, info_msg_id,
info.as_str(), info.as_str(),
timestamp, timestamp,
) )
.await?; .await?;
notify_msg_id = info_msg_id; notify_msg_id = info_msg_id;
} else { } else {
notify_msg_id = chat::add_info_msg_with_cmd( notify_msg_id = chat::add_info_msg_with_cmd(
self, self,
instance.chat_id, instance.chat_id,
info.as_str(), info.as_str(),
SystemMessage::WebxdcInfoMessage, SystemMessage::WebxdcInfoMessage,
timestamp, timestamp,
None, None,
Some(&instance), Some(&instance),
Some(from_id), Some(from_id),
None, None,
) )
.await?; .await?;
} }
if let Some(ref href) = status_update_item.href { if let Some(ref href) = status_update_item.href {
let mut notify_msg = Message::load_from_db(self, notify_msg_id).await?; let mut notify_msg = Message::load_from_db(self, notify_msg_id).await?;
notify_msg.param.set(Param::Arg, href); notify_msg.param.set(Param::Arg, href);
notify_msg.update_param(self).await?; notify_msg.update_param(self).await?;
}
} }
} }
@@ -413,20 +407,19 @@ impl Context {
}); });
} }
if from_id != ContactId::SELF { if from_id != ContactId::SELF
if let Some(notify_list) = status_update_item.notify { && let Some(notify_list) = status_update_item.notify
let self_addr = instance.get_webxdc_self_addr(self).await?; {
if let Some(notify_text) = let self_addr = instance.get_webxdc_self_addr(self).await?;
notify_list.get(&self_addr).or_else(|| notify_list.get("*")) if let Some(notify_text) = notify_list.get(&self_addr).or_else(|| notify_list.get("*"))
{ {
self.emit_event(EventType::IncomingWebxdcNotify { self.emit_event(EventType::IncomingWebxdcNotify {
chat_id: instance.chat_id, chat_id: instance.chat_id,
contact_id: from_id, contact_id: from_id,
msg_id: notify_msg_id, msg_id: notify_msg_id,
text: notify_text.clone(), text: notify_text.clone(),
href: status_update_item.href, href: status_update_item.href,
}); });
}
} }
} }
@@ -882,18 +875,15 @@ impl Message {
let mut archive = self.get_webxdc_archive(context).await?; let mut archive = self.get_webxdc_archive(context).await?;
if name == "index.html" { if name == "index.html"
if let Ok(bytes) = get_blob(&mut archive, "manifest.toml").await { && let Ok(bytes) = get_blob(&mut archive, "manifest.toml").await
if let Ok(manifest) = parse_webxdc_manifest(&bytes) { && let Ok(manifest) = parse_webxdc_manifest(&bytes)
if let Some(min_api) = manifest.min_api { && let Some(min_api) = manifest.min_api
if min_api > WEBXDC_API_VERSION { && min_api > WEBXDC_API_VERSION
return Ok(Vec::from( {
"<!DOCTYPE html>This Webxdc requires a newer Delta Chat version.", return Ok(Vec::from(
)); "<!DOCTYPE html>This Webxdc requires a newer Delta Chat version.",
} ));
}
}
}
} }
get_blob(&mut archive, name).await get_blob(&mut archive, name).await

View File

@@ -122,12 +122,11 @@ pub(crate) async fn intercept_get_updates(
if location.independent != 0 { if location.independent != 0 {
if let Some(marker) = &location.marker { if let Some(marker) = &location.marker {
label = marker.to_string() // marker contains one-char labels only label = marker.to_string() // marker contains one-char labels only
} else if location.msg_id != 0 { } else if location.msg_id != 0
if let Some(msg) = && let Some(msg) =
Message::load_from_db_optional(context, MsgId::new(location.msg_id)).await? Message::load_from_db_optional(context, MsgId::new(location.msg_id)).await?
{ {
label = msg.get_text() label = msg.get_text()
}
} }
} }