diff --git a/deltachat-contact-tools/src/lib.rs b/deltachat-contact-tools/src/lib.rs index 1c38886aa..8e98f9cec 100644 --- a/deltachat-contact-tools/src/lib.rs +++ b/deltachat-contact-tools/src/lib.rs @@ -22,7 +22,8 @@ clippy::bool_assert_comparison, clippy::manual_split_once, clippy::format_push_string, - clippy::bool_to_int_with_if + clippy::bool_to_int_with_if, + clippy::manual_range_contains )] use std::fmt; @@ -36,7 +37,6 @@ use once_cell::sync::Lazy; use regex::Regex; // TODOs to clean up: -// - Check if sanitizing is done correctly everywhere // - Apply lints everywhere (https://doc.rust-lang.org/cargo/reference/workspaces.html#the-lints-table) #[derive(Debug)] @@ -263,27 +263,27 @@ impl rusqlite::types::ToSql for ContactAddress { } } -/// Make the name and address +/// Takes a name and an address and sanitizes them: +/// - Extracts a name from the addr if the addr is in form "Alice " +/// - Removes special characters from the name, see [`sanitize_name()`] +/// - Removes the name if it is equal to the address by setting it to "" pub fn sanitize_name_and_addr(name: &str, addr: &str) -> (String, String) { static ADDR_WITH_NAME_REGEX: Lazy = Lazy::new(|| Regex::new("(.*)<(.*)>").unwrap()); let (name, addr) = if let Some(captures) = ADDR_WITH_NAME_REGEX.captures(addr.as_ref()) { ( if name.is_empty() { - strip_rtlo_characters(captures.get(1).map_or("", |m| m.as_str())) + captures.get(1).map_or("", |m| m.as_str()) } else { - strip_rtlo_characters(name) + name }, captures .get(2) .map_or("".to_string(), |m| m.as_str().to_string()), ) } else { - ( - strip_rtlo_characters(&normalize_name(name)), - addr.to_string(), - ) + (name, addr.to_string()) }; - let mut name = normalize_name(&name); + let mut name = sanitize_name(name); // If the 'display name' is just the address, remove it: // Otherwise, the contact would sometimes be shown as "alice@example.com (alice@example.com)" (see `get_name_n_addr()`). @@ -295,31 +295,77 @@ pub fn sanitize_name_and_addr(name: &str, addr: &str) -> (String, String) { (name, addr) } -/// Normalize a name. +/// Sanitizes a name. /// -/// - Remove quotes (come from some bad MUA implementations) -/// - Trims the resulting string -/// -/// Typically, this function is not needed as it is called implicitly by `Contact::add_address_book`. -pub fn normalize_name(full_name: &str) -> String { - let full_name = full_name.trim(); - if full_name.is_empty() { - return full_name.into(); - } +/// - Removes newlines and trims the string +/// - Removes quotes (come from some bad MUA implementations) +/// - Removes potentially-malicious bidi characters +pub fn sanitize_name(name: &str) -> String { + let name = sanitize_single_line(name); - match full_name.as_bytes() { - [b'\'', .., b'\''] | [b'\"', .., b'\"'] | [b'<', .., b'>'] => full_name - .get(1..full_name.len() - 1) + match name.as_bytes() { + [b'\'', .., b'\''] | [b'\"', .., b'\"'] | [b'<', .., b'>'] => name + .get(1..name.len() - 1) .map_or("".to_string(), |s| s.trim().to_string()), - _ => full_name.to_string(), + _ => name.to_string(), } } +/// Sanitizes user input +/// +/// - Removes newlines and trims the string +/// - Removes potentially-malicious bidi characters +pub fn sanitize_single_line(input: &str) -> String { + sanitize_bidi_characters(input.replace(['\n', '\r'], " ").trim()) +} + const RTLO_CHARACTERS: [char; 5] = ['\u{202A}', '\u{202B}', '\u{202C}', '\u{202D}', '\u{202E}']; -/// This method strips all occurrences of the RTLO Unicode character. -/// [Why is this needed](https://github.com/deltachat/deltachat-core-rust/issues/3479)? -pub fn strip_rtlo_characters(input_str: &str) -> String { - input_str.replace(|char| RTLO_CHARACTERS.contains(&char), "") +const ISOLATE_CHARACTERS: [char; 3] = ['\u{2066}', '\u{2067}', '\u{2068}']; +const POP_ISOLATE_CHARACTER: char = '\u{2069}'; +/// Some control unicode characters can influence whether adjacent text is shown from +/// left to right or from right to left. +/// +/// Since user input is not supposed to influence how adjacent text looks, +/// this function removes some of these characters. +/// +/// Also see https://github.com/deltachat/deltachat-core-rust/issues/3479. +pub fn sanitize_bidi_characters(input_str: &str) -> String { + // RTLO_CHARACTERS are apparently rarely used in practice. + // They can impact all following text, so, better remove them all: + let input_str = input_str.replace(|char| RTLO_CHARACTERS.contains(&char), ""); + + // If the ISOLATE characters are not ended with a POP DIRECTIONAL ISOLATE character, + // we regard the input as potentially malicious and simply remove all ISOLATE characters. + // See https://en.wikipedia.org/wiki/Bidirectional_text#Unicode_bidi_support + // and https://www.w3.org/International/questions/qa-bidi-unicode-controls.en + // for an explanation about ISOLATE characters. + fn isolate_characters_are_valid(input_str: &str) -> bool { + let mut isolate_character_nesting: i32 = 0; + for char in input_str.chars() { + if ISOLATE_CHARACTERS.contains(&char) { + isolate_character_nesting += 1; + } else if char == POP_ISOLATE_CHARACTER { + isolate_character_nesting -= 1; + } + + // According to Wikipedia, 125 levels are allowed: + // https://en.wikipedia.org/wiki/Unicode_control_characters + // (although, in practice, we could also significantly lower this number) + if isolate_character_nesting < 0 || isolate_character_nesting > 125 { + return false; + } + } + isolate_character_nesting == 0 + } + + if isolate_characters_are_valid(&input_str) { + input_str + } else { + input_str.replace( + |char| ISOLATE_CHARACTERS.contains(&char) || POP_ISOLATE_CHARACTER == char, + "", + ) + } } /// Returns false if addr is an invalid address, otherwise true. @@ -668,4 +714,64 @@ END:VCARD assert_eq!(contacts[0].profile_image.as_deref().unwrap(), "/9j/4AAQSkZJRgABAQAAAQABAAD/4gIoSUNDX1BST0ZJTEUAAQEAAAIYAAAAAAQwAABtbnRyUkdCIFhZWiAAAAAAAAAAAAAAAABhY3NwAAAAAAAAAAAAAAAAL8bRuAJYoZUYrI4ZY3VWwxw4Ay28AAGBISScmf/2Q=="); } } + + #[test] + fn test_sanitize_name() { + assert_eq!(&sanitize_name(" hello world "), "hello world"); + assert_eq!(&sanitize_name("<"), "<"); + assert_eq!(&sanitize_name(">"), ">"); + assert_eq!(&sanitize_name("'"), "'"); + assert_eq!(&sanitize_name("\""), "\""); + } + + #[test] + fn test_sanitize_single_line() { + assert_eq!(sanitize_single_line("Hi\naiae "), "Hi aiae"); + assert_eq!(sanitize_single_line("\r\nahte\n\r"), "ahte"); + } + + #[test] + fn test_sanitize_bidi_characters() { + // Legit inputs: + assert_eq!( + &sanitize_bidi_characters("Tes\u{2067}ting Delta Chat\u{2069}"), + "Tes\u{2067}ting Delta Chat\u{2069}" + ); + + assert_eq!( + &sanitize_bidi_characters("Tes\u{2067}ting \u{2068} Delta Chat\u{2069}\u{2069}"), + "Tes\u{2067}ting \u{2068} Delta Chat\u{2069}\u{2069}" + ); + + assert_eq!( + &sanitize_bidi_characters("Tes\u{2067}ting\u{2069} Delta Chat\u{2067}\u{2069}"), + "Tes\u{2067}ting\u{2069} Delta Chat\u{2067}\u{2069}" + ); + + // Potentially-malicious inputs: + assert_eq!( + &sanitize_bidi_characters("Tes\u{202C}ting Delta Chat"), + "Testing Delta Chat" + ); + + assert_eq!( + &sanitize_bidi_characters("Testing Delta Chat\u{2069}"), + "Testing Delta Chat" + ); + + assert_eq!( + &sanitize_bidi_characters("Tes\u{2067}ting Delta Chat"), + "Testing Delta Chat" + ); + + assert_eq!( + &sanitize_bidi_characters("Tes\u{2069}ting Delta Chat\u{2067}"), + "Testing Delta Chat" + ); + + assert_eq!( + &sanitize_bidi_characters("Tes\u{2068}ting Delta Chat"), + "Testing Delta Chat" + ); + } } diff --git a/src/chat.rs b/src/chat.rs index c659504cd..368de5fa1 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -8,7 +8,7 @@ use std::str::FromStr; use std::time::Duration; use anyhow::{anyhow, bail, ensure, Context as _, Result}; -use deltachat_contact_tools::{strip_rtlo_characters, ContactAddress}; +use deltachat_contact_tools::{sanitize_bidi_characters, sanitize_single_line, ContactAddress}; use deltachat_derive::{FromSql, ToSql}; use serde::{Deserialize, Serialize}; use strum_macros::EnumIter; @@ -46,8 +46,8 @@ use crate::stock_str; use crate::sync::{self, Sync::*, SyncData}; use crate::tools::{ buf_compress, create_id, create_outgoing_rfc724_mid, create_smeared_timestamp, - create_smeared_timestamps, get_abs_path, gm2local_offset, improve_single_line_input, - smeared_time, time, IsNoneOrEmpty, SystemTime, + create_smeared_timestamps, get_abs_path, gm2local_offset, smeared_time, time, IsNoneOrEmpty, + SystemTime, }; /// An chat item, such as a message or a marker. @@ -321,7 +321,7 @@ impl ChatId { param: Option, timestamp: i64, ) -> Result { - let grpname = strip_rtlo_characters(grpname); + let grpname = sanitize_single_line(grpname); let timestamp = cmp::min(timestamp, smeared_time(context)); let row_id = context.sql.insert( @@ -2858,7 +2858,7 @@ pub async fn send_msg_sync(context: &Context, chat_id: ChatId, msg: &mut Message async fn send_msg_inner(context: &Context, chat_id: ChatId, msg: &mut Message) -> Result { // protect all system messages against RTLO attacks if msg.is_system_message() { - msg.text = strip_rtlo_characters(&msg.text); + msg.text = sanitize_bidi_characters(&msg.text); } if !prepare_send_msg(context, chat_id, msg).await?.is_empty() { @@ -3503,7 +3503,7 @@ pub async fn create_group_chat( protect: ProtectionStatus, chat_name: &str, ) -> Result { - let chat_name = improve_single_line_input(chat_name); + let chat_name = sanitize_single_line(chat_name); ensure!(!chat_name.is_empty(), "Invalid chat name"); let grpid = create_id(); @@ -4017,7 +4017,7 @@ async fn rename_ex( chat_id: ChatId, new_name: &str, ) -> Result<()> { - let new_name = improve_single_line_input(new_name); + let new_name = sanitize_single_line(new_name); /* the function only sets the names of group chats; normal chats get their names from the contacts */ let mut success = false; @@ -4048,7 +4048,7 @@ async fn rename_ex( if chat.is_promoted() && !chat.is_mailing_list() && chat.typ != Chattype::Broadcast - && improve_single_line_input(&chat.name) != new_name + && sanitize_single_line(&chat.name) != new_name { msg.viewtype = Viewtype::Text; msg.text = diff --git a/src/config.rs b/src/config.rs index 0bf4bb055..a0728f986 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,7 +6,7 @@ use std::str::FromStr; use anyhow::{ensure, Context as _, Result}; use base64::Engine as _; -use deltachat_contact_tools::addr_cmp; +use deltachat_contact_tools::{addr_cmp, sanitize_single_line}; use serde::{Deserialize, Serialize}; use strum::{EnumProperty, IntoEnumIterator}; use strum_macros::{AsRefStr, Display, EnumIter, EnumString}; @@ -20,7 +20,7 @@ use crate::log::LogExt; use crate::mimefactory::RECOMMENDED_FILE_SIZE; use crate::provider::{get_provider_by_id, Provider}; use crate::sync::{self, Sync::*, SyncData}; -use crate::tools::{get_abs_path, improve_single_line_input}; +use crate::tools::get_abs_path; /// The available configuration keys. #[derive( @@ -647,7 +647,7 @@ impl Context { } Config::Displayname => { if let Some(v) = value { - better_value = improve_single_line_input(v); + better_value = sanitize_single_line(v); value = Some(&better_value); } self.sql.set_raw_config(key.as_ref(), value).await?; diff --git a/src/contact.rs b/src/contact.rs index 7dbe7f445..9700551e8 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -11,7 +11,7 @@ use async_channel::{self as channel, Receiver, Sender}; use base64::Engine as _; pub use deltachat_contact_tools::may_be_valid_addr; use deltachat_contact_tools::{ - self as contact_tools, addr_cmp, addr_normalize, sanitize_name_and_addr, strip_rtlo_characters, + self as contact_tools, addr_cmp, addr_normalize, sanitize_name, sanitize_name_and_addr, ContactAddress, VcardContact, }; use deltachat_derive::{FromSql, ToSql}; @@ -37,9 +37,7 @@ use crate::param::{Param, Params}; use crate::peerstate::Peerstate; use crate::sql::{self, params_iter}; use crate::sync::{self, Sync::*}; -use crate::tools::{ - duration_to_str, get_abs_path, improve_single_line_input, smeared_time, time, SystemTime, -}; +use crate::tools::{duration_to_str, get_abs_path, smeared_time, time, SystemTime}; use crate::{chat, chatlist_events, stock_str}; /// Time during which a contact is considered as seen recently. @@ -626,9 +624,7 @@ impl Contact { name: &str, addr: &str, ) -> Result { - let name = improve_single_line_input(name); - - let (name, addr) = sanitize_name_and_addr(&name, addr); + let (name, addr) = sanitize_name_and_addr(name, addr); let addr = ContactAddress::new(&addr)?; let (contact_id, sth_modified) = @@ -769,7 +765,7 @@ impl Contact { return Ok((ContactId::SELF, sth_modified)); } - let mut name = strip_rtlo_characters(name); + let mut name = sanitize_name(name); #[allow(clippy::collapsible_if)] if origin <= Origin::OutgoingTo { // The user may accidentally have written to a "noreply" address with another MUA: @@ -1924,7 +1920,7 @@ impl RecentlySeenLoop { #[cfg(test)] mod tests { - use deltachat_contact_tools::{may_be_valid_addr, normalize_name}; + use deltachat_contact_tools::may_be_valid_addr; use super::*; use crate::chat::{get_chat_contacts, send_text_msg, Chat}; @@ -1963,15 +1959,6 @@ mod tests { assert_eq!(may_be_valid_addr("user@domain.tld."), false); } - #[test] - fn test_normalize_name() { - assert_eq!(&normalize_name(" hello world "), "hello world"); - assert_eq!(&normalize_name("<"), "<"); - assert_eq!(&normalize_name(">"), ">"); - assert_eq!(&normalize_name("'"), "'"); - assert_eq!(&normalize_name("\""), "\""); - } - #[test] fn test_normalize_addr() { assert_eq!(addr_normalize("mailto:john@doe.com"), "john@doe.com"); diff --git a/src/imap.rs b/src/imap.rs index 95261e879..632cb258a 100644 --- a/src/imap.rs +++ b/src/imap.rs @@ -16,7 +16,7 @@ use std::{ use anyhow::{bail, format_err, Context as _, Result}; use async_channel::Receiver; use async_imap::types::{Fetch, Flag, Name, NameAttribute, UnsolicitedResponse}; -use deltachat_contact_tools::{normalize_name, ContactAddress}; +use deltachat_contact_tools::ContactAddress; use futures::{FutureExt as _, StreamExt, TryStreamExt}; use futures_lite::FutureExt; use num_traits::FromPrimitive; @@ -2424,12 +2424,6 @@ async fn add_all_recipients_as_contacts( let mut any_modified = false; for recipient in recipients { - let display_name_normalized = recipient - .display_name - .as_ref() - .map(|s| normalize_name(s)) - .unwrap_or_default(); - let recipient_addr = match ContactAddress::new(&recipient.addr) { Err(err) => { warn!( @@ -2445,7 +2439,7 @@ async fn add_all_recipients_as_contacts( let (_, modified) = Contact::add_or_lookup( context, - &display_name_normalized, + &recipient.display_name.unwrap_or_default(), &recipient_addr, Origin::OutgoingTo, ) diff --git a/src/mimeparser.rs b/src/mimeparser.rs index d55add4d0..3d796a211 100644 --- a/src/mimeparser.rs +++ b/src/mimeparser.rs @@ -6,7 +6,7 @@ use std::path::Path; use std::str; use anyhow::{bail, Context as _, Result}; -use deltachat_contact_tools::{addr_cmp, addr_normalize, strip_rtlo_characters}; +use deltachat_contact_tools::{addr_cmp, addr_normalize, sanitize_bidi_characters}; use deltachat_derive::{FromSql, ToSql}; use format_flowed::unformat_flowed; use lettre_email::mime::Mime; @@ -2048,7 +2048,7 @@ fn get_attachment_filename( }; } - let desired_filename = desired_filename.map(|filename| strip_rtlo_characters(&filename)); + let desired_filename = desired_filename.map(|filename| sanitize_bidi_characters(&filename)); Ok(desired_filename) } diff --git a/src/receive_imf.rs b/src/receive_imf.rs index 5cf765eb0..6b9749ce4 100644 --- a/src/receive_imf.rs +++ b/src/receive_imf.rs @@ -4,9 +4,7 @@ use std::collections::HashSet; use std::str::FromStr; use anyhow::{Context as _, Result}; -use deltachat_contact_tools::{ - addr_cmp, may_be_valid_addr, normalize_name, strip_rtlo_characters, ContactAddress, -}; +use deltachat_contact_tools::{addr_cmp, may_be_valid_addr, sanitize_single_line, ContactAddress}; use iroh_gossip::proto::TopicId; use mailparse::{parse_mail, SingleInfo}; use num_traits::FromPrimitive; @@ -660,10 +658,10 @@ pub async fn from_field_to_contact_id( } }; - let from_id = add_or_lookup_contact_by_addr( + let (from_id, _) = Contact::add_or_lookup( context, - display_name, - from_addr, + display_name.unwrap_or_default(), + &from_addr, Origin::IncomingUnknownFrom, ) .await?; @@ -2141,6 +2139,8 @@ async fn apply_group_changes( .map(|grpname| grpname.trim()) .filter(|grpname| grpname.len() < 200) { + let grpname = &sanitize_single_line(grpname); + let old_name = &sanitize_single_line(old_name); if chat_id .update_timestamp( context, @@ -2152,10 +2152,7 @@ async fn apply_group_changes( info!(context, "Updating grpname for chat {chat_id}."); context .sql - .execute( - "UPDATE chats SET name=? WHERE id=?;", - (strip_rtlo_characters(grpname), chat_id), - ) + .execute("UPDATE chats SET name=? WHERE id=?;", (grpname, chat_id)) .await?; send_event_chat_modified = true; } @@ -2428,7 +2425,7 @@ fn compute_mailinglist_name( } } - strip_rtlo_characters(&name) + sanitize_single_line(&name) } /// Set ListId param on the contact and ListPost param the chat. @@ -2850,8 +2847,9 @@ async fn add_or_lookup_contacts_by_address_list( } let display_name = info.display_name.as_deref(); if let Ok(addr) = ContactAddress::new(addr) { - let contact_id = - add_or_lookup_contact_by_addr(context, display_name, addr, origin).await?; + let (contact_id, _) = + Contact::add_or_lookup(context, display_name.unwrap_or_default(), &addr, origin) + .await?; contact_ids.insert(contact_id); } else { warn!(context, "Contact with address {:?} cannot exist.", addr); @@ -2861,22 +2859,5 @@ async fn add_or_lookup_contacts_by_address_list( Ok(contact_ids.into_iter().collect::>()) } -/// Add contacts to database on receiving messages. -async fn add_or_lookup_contact_by_addr( - context: &Context, - display_name: Option<&str>, - addr: ContactAddress, - origin: Origin, -) -> Result { - if context.is_self_addr(&addr).await? { - return Ok(ContactId::SELF); - } - let display_name_normalized = display_name.map(normalize_name).unwrap_or_default(); - - let (contact_id, _modified) = - Contact::add_or_lookup(context, &display_name_normalized, &addr, origin).await?; - Ok(contact_id) -} - #[cfg(test)] mod tests; diff --git a/src/receive_imf/tests.rs b/src/receive_imf/tests.rs index 208027c06..d749bf4c9 100644 --- a/src/receive_imf/tests.rs +++ b/src/receive_imf/tests.rs @@ -4886,19 +4886,63 @@ async fn test_make_n_send_vcard() -> Result<()> { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_group_no_recipients() -> Result<()> { let t = &TestContext::new_alice().await; - let raw = b"From: alice@example.org\n\ - Subject: Group\n\ - Chat-Version: 1.0\n\ - Chat-Group-Name: Group\n\ - Chat-Group-ID: GePFDkwEj2K\n\ - Message-ID: \n\ - \n\ - Hello!"; + let raw = "From: alice@example.org +Subject: Group +Chat-Version: 1.0 +Chat-Group-Name: Group + name\u{202B} +Chat-Group-ID: GePFDkwEj2K +Message-ID: + +Hello!" + .as_bytes(); let received = receive_imf(t, raw, false).await?.unwrap(); let msg = Message::load_from_db(t, *received.msg_ids.last().unwrap()).await?; let chat = Chat::load_from_db(t, msg.chat_id).await?; assert_eq!(chat.typ, Chattype::Group); + // Check that the weird group name is sanitzied correctly: + let mail = mailparse::parse_mail(raw).unwrap(); + assert_eq!( + mail.headers + .get_header(HeaderDef::ChatGroupName) + .unwrap() + .get_value_raw(), + "Group\n name\u{202B}".as_bytes() + ); + assert_eq!(chat.name, "Group name"); + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_group_name_with_newline() -> Result<()> { + let t = &TestContext::new_alice().await; + let raw = "From: alice@example.org +Subject: Group +Chat-Version: 1.0 +Chat-Group-Name: =?utf-8?q?Delta=0D=0AChat?= +Chat-Group-ID: GePFDkwEj2K +Message-ID: + +Hello!" + .as_bytes(); + let received = receive_imf(t, raw, false).await?.unwrap(); + let msg = Message::load_from_db(t, *received.msg_ids.last().unwrap()).await?; + let chat = Chat::load_from_db(t, msg.chat_id).await?; + assert_eq!(chat.typ, Chattype::Group); + + // Check that the weird group name is sanitzied correctly: + let mail = mailparse::parse_mail(raw).unwrap(); + assert_eq!( + mail.headers + .get_header(HeaderDef::ChatGroupName) + .unwrap() + .get_value(), + "Delta\r\nChat" + ); + assert_eq!(chat.name, "Delta Chat"); + Ok(()) } diff --git a/src/tools.rs b/src/tools.rs index 47a3cd88a..4a5233a33 100644 --- a/src/tools.rs +++ b/src/tools.rs @@ -22,7 +22,7 @@ pub use std::time::SystemTime; use anyhow::{bail, Context as _, Result}; use base64::Engine as _; use chrono::{Local, NaiveDateTime, NaiveTime, TimeZone}; -use deltachat_contact_tools::{strip_rtlo_characters, EmailAddress}; +use deltachat_contact_tools::EmailAddress; #[cfg(test)] pub use deltachat_time::SystemTimeTools as SystemTime; use futures::{StreamExt, TryStreamExt}; @@ -511,13 +511,6 @@ pub fn parse_mailto(mailto_url: &str) -> Option { } } -/// Sanitizes user input -/// - strip newlines -/// - strip malicious bidi characters -pub(crate) fn improve_single_line_input(input: &str) -> String { - strip_rtlo_characters(input.replace(['\n', '\r'], " ").trim()) -} - pub(crate) trait IsNoneOrEmpty { /// Returns true if an Option does not contain a string /// or contains an empty string. @@ -1025,12 +1018,6 @@ DKIM Results: Passed=true"; assert_eq!(h, 50); } - #[test] - fn test_improve_single_line_input() { - assert_eq!(improve_single_line_input("Hi\naiae "), "Hi aiae"); - assert_eq!(improve_single_line_input("\r\nahte\n\r"), "ahte"); - } - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_maybe_warn_on_bad_time() { let t = TestContext::new().await; diff --git a/src/update_helper.rs b/src/update_helper.rs index 589c383d2..b343a272a 100644 --- a/src/update_helper.rs +++ b/src/update_helper.rs @@ -183,8 +183,8 @@ mod tests { Message-ID: \n\ Chat-Version: 1.0\n\ Chat-Group-ID: abcde123456\n\ - Chat-Group-Name: another name update\n\ - Chat-Group-Name-Changed: a name update\n\ + Chat-Group-Name: =?utf-8?q?another=0Aname update?=\n\ + Chat-Group-Name-Changed: =?utf-8?q?a=0Aname update?=\n\ Date: Sun, 22 Mar 2021 03:00:00 +0000\n\ \n\ third message\n", @@ -198,7 +198,7 @@ mod tests { Message-ID: \n\ Chat-Version: 1.0\n\ Chat-Group-ID: abcde123456\n\ - Chat-Group-Name: a name update\n\ + Chat-Group-Name: =?utf-8?q?a=0Aname update?=\n\ Chat-Group-Name-Changed: initial name\n\ Date: Sun, 22 Mar 2021 02:00:00 +0000\n\ \n\ @@ -210,6 +210,9 @@ mod tests { let chat = Chat::load_from_db(&t, msg.chat_id).await?; assert_eq!(chat.name, "another name update"); + // Assert that the \n was correctly removed from the group name also in the system message + assert_eq!(msg.text.contains('\n'), false); + Ok(()) } } diff --git a/src/webxdc.rs b/src/webxdc.rs index c864dc180..48b645f00 100644 --- a/src/webxdc.rs +++ b/src/webxdc.rs @@ -22,7 +22,7 @@ use std::path::Path; use anyhow::{anyhow, bail, ensure, format_err, Context as _, Result}; -use deltachat_contact_tools::strip_rtlo_characters; +use deltachat_contact_tools::sanitize_bidi_characters; use deltachat_derive::FromSql; use lettre_email::PartBuilder; use rusqlite::OptionalExtension; @@ -349,7 +349,7 @@ impl Context { { instance .param - .set(Param::WebxdcSummary, strip_rtlo_characters(summary)); + .set(Param::WebxdcSummary, sanitize_bidi_characters(summary)); param_changed = true; } }