Split off functional contact tools into its own crate (#5444)

I would like to implement
https://github.com/deltachat/deltachat-core-rust/issues/5422 in its own
crate, but it will depend on some functions that are in the `deltachat`
crate.

So, this PR extracts these functions into its own crate so that I can
add https://github.com/deltachat/deltachat-core-rust/issues/5422 into
the new crate.
This commit is contained in:
Hocuri
2024-04-16 19:01:25 +02:00
committed by GitHub
parent 6ca6a439bd
commit 5d34b225b7
28 changed files with 381 additions and 290 deletions

View File

@@ -6,6 +6,7 @@ use std::collections::BTreeSet;
use std::fmt;
use anyhow::Result;
use deltachat_contact_tools::EmailAddress;
use mailparse::MailHeaderMap;
use mailparse::ParsedMail;
use once_cell::sync::Lazy;
@@ -14,7 +15,6 @@ use crate::config::Config;
use crate::context::Context;
use crate::headerdef::HeaderDef;
use crate::tools::time;
use crate::tools::EmailAddress;
/// `authres` is short for the Authentication-Results header, defined in
/// <https://datatracker.ietf.org/doc/html/rfc8601>, which contains info

View File

@@ -8,6 +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_derive::{FromSql, ToSql};
use serde::{Deserialize, Serialize};
use strum_macros::EnumIter;
@@ -22,7 +23,7 @@ use crate::constants::{
self, Blocked, Chattype, DC_CHAT_ID_ALLDONE_HINT, DC_CHAT_ID_ARCHIVED_LINK,
DC_CHAT_ID_LAST_SPECIAL, DC_CHAT_ID_TRASH, DC_RESEND_USER_AVATAR_DAYS,
};
use crate::contact::{self, Contact, ContactAddress, ContactId, Origin};
use crate::contact::{self, Contact, ContactId, Origin};
use crate::context::Context;
use crate::debug_logging::maybe_set_logging_xdc;
use crate::download::DownloadState;
@@ -44,7 +45,7 @@ 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, strip_rtlo_characters, time, IsNoneOrEmpty, SystemTime,
smeared_time, time, IsNoneOrEmpty, SystemTime,
};
use crate::webxdc::WEBXDC_SUFFIX;

View File

@@ -6,6 +6,7 @@ use std::str::FromStr;
use anyhow::{ensure, Context as _, Result};
use base64::Engine as _;
use deltachat_contact_tools::addr_cmp;
use serde::{Deserialize, Serialize};
use strum::{EnumProperty, IntoEnumIterator};
use strum_macros::{AsRefStr, Display, EnumIter, EnumProperty, EnumString};
@@ -13,7 +14,6 @@ use tokio::fs;
use crate::blob::BlobObject;
use crate::constants::{self, DC_VERSION_STR};
use crate::contact::addr_cmp;
use crate::context::Context;
use crate::events::EventType;
use crate::log::LogExt;

View File

@@ -16,6 +16,7 @@ mod server_params;
use anyhow::{bail, ensure, Context as _, Result};
use auto_mozilla::moz_autoconfigure;
use auto_outlook::outlk_autodiscover;
use deltachat_contact_tools::EmailAddress;
use futures::FutureExt;
use futures_lite::FutureExt as _;
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
@@ -23,7 +24,6 @@ use server_params::{expand_param_vector, ServerParams};
use tokio::task;
use crate::config::{self, Config};
use crate::contact::addr_cmp;
use crate::context::Context;
use crate::imap::{session::Session as ImapSession, Imap};
use crate::log::LogExt;
@@ -35,8 +35,9 @@ use crate::smtp::Smtp;
use crate::socks::Socks5Config;
use crate::stock_str;
use crate::sync::Sync::*;
use crate::tools::{time, EmailAddress};
use crate::tools::time;
use crate::{chat, e2ee, provider};
use deltachat_contact_tools::addr_cmp;
macro_rules! progress {
($context:tt, $progress:expr, $comment:expr) => {

View File

@@ -3,15 +3,17 @@
use std::cmp::Reverse;
use std::collections::BinaryHeap;
use std::fmt;
use std::ops::Deref;
use std::path::{Path, PathBuf};
use std::time::UNIX_EPOCH;
use anyhow::{bail, ensure, Context as _, Result};
use async_channel::{self as channel, Receiver, Sender};
pub use deltachat_contact_tools::may_be_valid_addr;
use deltachat_contact_tools::{
addr_cmp, addr_normalize, normalize_name, sanitize_name_and_addr, strip_rtlo_characters,
ContactAddress,
};
use deltachat_derive::{FromSql, ToSql};
use once_cell::sync::Lazy;
use regex::Regex;
use rusqlite::OptionalExtension;
use serde::{Deserialize, Serialize};
use tokio::task;
@@ -33,60 +35,12 @@ 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, strip_rtlo_characters, time,
EmailAddress, SystemTime,
};
use crate::tools::{duration_to_str, get_abs_path, improve_single_line_input, time, SystemTime};
use crate::{chat, chatlist_events, stock_str};
/// Time during which a contact is considered as seen recently.
const SEEN_RECENTLY_SECONDS: i64 = 600;
/// Valid contact address.
#[derive(Debug, Clone)]
pub(crate) struct ContactAddress(String);
impl Deref for ContactAddress {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl AsRef<str> for ContactAddress {
fn as_ref(&self) -> &str {
&self.0
}
}
impl fmt::Display for ContactAddress {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl ContactAddress {
/// Constructs a new contact address from string,
/// normalizing and validating it.
pub fn new(s: &str) -> Result<Self> {
let addr = addr_normalize(s);
if !may_be_valid_addr(&addr) {
bail!("invalid address {:?}", s);
}
Ok(Self(addr.to_string()))
}
}
/// Allow converting [`ContactAddress`] to an SQLite type.
impl rusqlite::types::ToSql for ContactAddress {
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
let val = rusqlite::types::Value::Text(self.0.to_string());
let out = rusqlite::types::ToSqlOutput::Owned(val);
Ok(out)
}
}
/// Contact ID, including reserved IDs.
///
/// Some contact IDs are reserved to identify special contacts. This
@@ -1415,46 +1369,6 @@ impl Contact {
}
}
/// Returns false if addr is an invalid address, otherwise true.
pub fn may_be_valid_addr(addr: &str) -> bool {
let res = EmailAddress::new(addr);
res.is_ok()
}
/// Returns address lowercased,
/// with whitespace trimmed and `mailto:` prefix removed.
pub fn addr_normalize(addr: &str) -> String {
let norm = addr.trim().to_lowercase();
if norm.starts_with("mailto:") {
norm.get(7..).unwrap_or(&norm).to_string()
} else {
norm
}
}
fn sanitize_name_and_addr(name: &str, addr: &str) -> (String, String) {
static ADDR_WITH_NAME_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new("(.*)<(.*)>").unwrap());
if let Some(captures) = ADDR_WITH_NAME_REGEX.captures(addr.as_ref()) {
(
if name.is_empty() {
strip_rtlo_characters(
&captures
.get(1)
.map_or("".to_string(), |m| normalize_name(m.as_str())),
)
} else {
strip_rtlo_characters(name)
},
captures
.get(2)
.map_or("".to_string(), |m| m.as_str().to_string()),
)
} else {
(strip_rtlo_characters(name), addr.to_string())
}
}
pub(crate) async fn set_blocked(
context: &Context,
sync: sync::Sync,
@@ -1643,26 +1557,6 @@ pub(crate) async fn update_last_seen(
Ok(())
}
/// Normalize 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();
}
match full_name.as_bytes() {
[b'\'', .., b'\''] | [b'\"', .., b'\"'] | [b'<', .., b'>'] => full_name
.get(1..full_name.len() - 1)
.map_or("".to_string(), |s| s.trim().to_string()),
_ => full_name.to_string(),
}
}
fn cat_fingerprint(
ret: &mut String,
addr: &str,
@@ -1686,14 +1580,6 @@ fn cat_fingerprint(
}
}
/// Compares two email addresses, normalizing them beforehand.
pub fn addr_cmp(addr1: &str, addr2: &str) -> bool {
let norm1 = addr_normalize(addr1);
let norm2 = addr_normalize(addr2);
norm1 == norm2
}
fn split_address_book(book: &str) -> Vec<(&str, &str)> {
book.lines()
.collect::<Vec<&str>>()
@@ -1866,6 +1752,8 @@ impl RecentlySeenLoop {
#[cfg(test)]
mod tests {
use deltachat_contact_tools::may_be_valid_addr;
use super::*;
use crate::chat::{get_chat_contacts, send_text_msg, Chat};
use crate::chatlist::Chatlist;
@@ -2005,18 +1893,6 @@ mod tests {
Ok(())
}
#[test]
fn test_contact_address() -> Result<()> {
let alice_addr = "alice@example.org";
let contact_address = ContactAddress::new(alice_addr)?;
assert_eq!(contact_address.as_ref(), alice_addr);
let invalid_addr = "<> foobar";
assert!(ContactAddress::new(invalid_addr).is_err());
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_add_or_lookup() {
// add some contacts, this also tests add_address_book()

View File

@@ -4,12 +4,12 @@ use std::collections::HashSet;
use std::str::FromStr;
use anyhow::Result;
use deltachat_contact_tools::addr_cmp;
use mailparse::ParsedMail;
use crate::aheader::Aheader;
use crate::authres::handle_authres;
use crate::authres::{self, DkimResults};
use crate::contact::addr_cmp;
use crate::context::Context;
use crate::headerdef::{HeaderDef, HeaderDefMap};
use crate::key::{DcKey, Fingerprint, SignedPublicKey, SignedSecretKey};

View File

@@ -15,6 +15,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 futures::{FutureExt as _, StreamExt, TryStreamExt};
use futures_lite::FutureExt;
use num_traits::FromPrimitive;
@@ -25,7 +26,7 @@ use crate::chat::{self, ChatId, ChatIdBlocked};
use crate::chatlist_events;
use crate::config::Config;
use crate::constants::{self, Blocked, Chattype, ShowEmails};
use crate::contact::{normalize_name, Contact, ContactAddress, ContactId, Modifier, Origin};
use crate::contact::{Contact, ContactId, Modifier, Origin};
use crate::context::Context;
use crate::events::EventType;
use crate::headerdef::{HeaderDef, HeaderDefMap};

View File

@@ -6,6 +6,7 @@ use std::path::{Path, PathBuf};
use ::pgp::types::KeyTrait;
use anyhow::{bail, ensure, format_err, Context as _, Result};
use deltachat_contact_tools::EmailAddress;
use futures::StreamExt;
use futures_lite::FutureExt;
use rand::{thread_rng, Rng};
@@ -31,7 +32,6 @@ use crate::sql;
use crate::stock_str;
use crate::tools::{
create_folder, delete_file, get_filesuffix_lc, open_file_std, read_file, time, write_file,
EmailAddress,
};
mod transfer;

View File

@@ -6,6 +6,7 @@ use std::io::Cursor;
use anyhow::{ensure, Context as _, Result};
use base64::Engine as _;
use deltachat_contact_tools::EmailAddress;
use num_traits::FromPrimitive;
use pgp::composed::Deserializable;
pub use pgp::composed::{SignedPublicKey, SignedSecretKey};
@@ -18,7 +19,7 @@ use crate::constants::KeyGenType;
use crate::context::Context;
use crate::log::LogExt;
use crate::pgp::KeyPair;
use crate::tools::{self, time_elapsed, EmailAddress};
use crate::tools::{self, time_elapsed};
/// Convenience trait for working with keys.
///

View File

@@ -1582,6 +1582,7 @@ fn maybe_encode_words(words: &str) -> String {
#[cfg(test)]
mod tests {
use deltachat_contact_tools::ContactAddress;
use mailparse::{addrparse_header, MailHeaderMap};
use std::str;
@@ -1592,7 +1593,7 @@ mod tests {
};
use crate::chatlist::Chatlist;
use crate::constants;
use crate::contact::{ContactAddress, Origin};
use crate::contact::Origin;
use crate::mimeparser::MimeMessage;
use crate::receive_imf::receive_imf;
use crate::test_utils::{get_chat_msg, TestContext, TestContextManager};

View File

@@ -6,6 +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_derive::{FromSql, ToSql};
use format_flowed::unformat_flowed;
use lettre_email::mime::Mime;
@@ -16,7 +17,7 @@ use crate::blob::BlobObject;
use crate::chat::{add_info_msg, ChatId};
use crate::config::Config;
use crate::constants::{self, Chattype, DC_DESIRED_TEXT_LINES, DC_DESIRED_TEXT_LINE_LEN};
use crate::contact::{addr_cmp, addr_normalize, Contact, ContactId, Origin};
use crate::contact::{Contact, ContactId, Origin};
use crate::context::Context;
use crate::decrypt::{
keyring_from_peerstate, prepare_decryption, try_decrypt, validate_detached_signature,
@@ -34,8 +35,7 @@ use crate::peerstate::Peerstate;
use crate::simplify::{simplify, SimplifiedText};
use crate::sync::SyncItems;
use crate::tools::{
create_smeared_timestamp, get_filemeta, parse_receive_headers, smeared_time,
strip_rtlo_characters, truncate_by_lines,
create_smeared_timestamp, get_filemeta, parse_receive_headers, smeared_time, truncate_by_lines,
};
use crate::{chatlist_events, location, stock_str, tools};

View File

@@ -3,6 +3,7 @@
use std::mem;
use anyhow::{Context as _, Error, Result};
use deltachat_contact_tools::{addr_cmp, ContactAddress};
use num_traits::FromPrimitive;
use crate::aheader::{Aheader, EncryptPreference};
@@ -10,7 +11,7 @@ use crate::chat::{self, Chat};
use crate::chatlist::Chatlist;
use crate::config::Config;
use crate::constants::Chattype;
use crate::contact::{addr_cmp, Contact, ContactAddress, Origin};
use crate::contact::{Contact, Origin};
use crate::context::Context;
use crate::events::EventType;
use crate::key::{DcKey, Fingerprint, SignedPublicKey};

View File

@@ -5,6 +5,7 @@ use std::io;
use std::io::Cursor;
use anyhow::{bail, Context as _, Result};
use deltachat_contact_tools::EmailAddress;
use pgp::armor::BlockType;
use pgp::composed::{
Deserializable, KeyType as PgpKeyType, Message, SecretKeyParamsBuilder, SignedPublicKey,
@@ -20,7 +21,6 @@ use tokio::runtime::Handle;
use crate::constants::KeyGenType;
use crate::key::{DcKey, Fingerprint};
use crate::tools::EmailAddress;
#[allow(missing_docs)]
#[cfg(test)]

View File

@@ -3,12 +3,12 @@
mod data;
use anyhow::Result;
use deltachat_contact_tools::EmailAddress;
use hickory_resolver::{config, AsyncResolver, TokioAsyncResolver};
use crate::config::Config;
use crate::context::Context;
use crate::provider::data::{PROVIDER_DATA, PROVIDER_IDS};
use crate::tools::EmailAddress;
/// Provider status according to manual testing.
#[derive(Debug, Display, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)]

View File

@@ -5,6 +5,7 @@ use std::collections::BTreeMap;
use anyhow::{anyhow, bail, ensure, Context as _, Result};
pub use dclogin_scheme::LoginOptions;
use deltachat_contact_tools::{addr_normalize, may_be_valid_addr, ContactAddress};
use once_cell::sync::Lazy;
use percent_encoding::percent_decode_str;
use serde::Deserialize;
@@ -13,9 +14,7 @@ use self::dclogin_scheme::configure_from_login_qr;
use crate::chat::{get_chat_id_by_grpid, ChatIdBlocked};
use crate::config::Config;
use crate::constants::Blocked;
use crate::contact::{
addr_normalize, may_be_valid_addr, Contact, ContactAddress, ContactId, Origin,
};
use crate::contact::{Contact, ContactId, Origin};
use crate::context::Context;
use crate::events::EventType;
use crate::key::Fingerprint;

View File

@@ -1,13 +1,15 @@
use std::collections::HashMap;
use anyhow::{bail, Context as _, Result};
use deltachat_contact_tools::may_be_valid_addr;
use num_traits::cast::ToPrimitive;
use super::{Qr, DCLOGIN_SCHEME};
use crate::config::Config;
use crate::context::Context;
use crate::login_param::CertificateChecks;
use crate::provider::Socket;
use crate::{contact, login_param::CertificateChecks};
/// Options for `dclogin:` scheme.
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -88,7 +90,7 @@ pub(super) fn decode_login(qr: &str) -> Result<Qr> {
.collect();
// check if username is there
if !contact::may_be_valid_addr(addr) {
if !may_be_valid_addr(addr) {
bail!("invalid DCLOGIN payload: invalid username E5");
}

View File

@@ -382,11 +382,13 @@ impl Chat {
#[cfg(test)]
mod tests {
use deltachat_contact_tools::ContactAddress;
use super::*;
use crate::chat::{forward_msgs, get_chat_msgs, send_text_msg};
use crate::chatlist::Chatlist;
use crate::config::Config;
use crate::contact::{Contact, ContactAddress, Origin};
use crate::contact::{Contact, Origin};
use crate::download::DownloadState;
use crate::message::{delete_msgs, MessageState};
use crate::receive_imf::{receive_imf, receive_imf_from_inbox};

View File

@@ -3,6 +3,9 @@
use std::collections::HashSet;
use anyhow::{Context as _, Result};
use deltachat_contact_tools::{
addr_cmp, may_be_valid_addr, normalize_name, strip_rtlo_characters, ContactAddress,
};
use mailparse::{parse_mail, SingleInfo};
use num_traits::FromPrimitive;
use once_cell::sync::Lazy;
@@ -12,9 +15,7 @@ use crate::aheader::EncryptPreference;
use crate::chat::{self, Chat, ChatId, ChatIdBlocked, ProtectionStatus};
use crate::config::Config;
use crate::constants::{self, Blocked, Chattype, ShowEmails, DC_CHAT_ID_TRASH};
use crate::contact::{
addr_cmp, may_be_valid_addr, normalize_name, Contact, ContactAddress, ContactId, Origin,
};
use crate::contact::{Contact, ContactId, Origin};
use crate::context::Context;
use crate::debug_logging::maybe_set_logging_xdc_inner;
use crate::download::DownloadState;
@@ -36,9 +37,7 @@ use crate::simplify;
use crate::sql;
use crate::stock_str;
use crate::sync::Sync::*;
use crate::tools::{
self, buf_compress, extract_grpid_from_rfc724_mid, strip_rtlo_characters, validate_id,
};
use crate::tools::{self, buf_compress, extract_grpid_from_rfc724_mid, validate_id};
use crate::{chatlist_events, location};
use crate::{contact, imap};

View File

@@ -9,8 +9,8 @@ use tokio::sync::Mutex;
use crate::events::EventType;
use crate::imap::{scan_folders::get_watched_folder_configs, FolderMeaning};
use crate::quota::{QUOTA_ERROR_THRESHOLD_PERCENTAGE, QUOTA_WARN_THRESHOLD_PERCENTAGE};
use crate::stock_str;
use crate::{context::Context, log::LogExt};
use crate::{stock_str, tools};
use super::InnerSchedulerState;
@@ -413,7 +413,9 @@ impl Context {
// [======67%===== ]
// =============================================================================================
let domain = &tools::EmailAddress::new(&self.get_primary_self_addr().await?)?.domain;
let domain =
&deltachat_contact_tools::EmailAddress::new(&self.get_primary_self_addr().await?)?
.domain;
let storage_on_domain = stock_str::storage_on_domain(self, domain).await;
ret += &format!("<h3>{storage_on_domain}</h3><ul>");
let quota = self.quota.read().await;

View File

@@ -756,17 +756,18 @@ fn encrypted_and_signed(
#[cfg(test)]
mod tests {
use deltachat_contact_tools::{ContactAddress, EmailAddress};
use super::*;
use crate::chat::remove_contact_from_chat;
use crate::chatlist::Chatlist;
use crate::constants::Chattype;
use crate::contact::ContactAddress;
use crate::imex::{imex, ImexMode};
use crate::receive_imf::receive_imf;
use crate::stock_str::chat_protection_enabled;
use crate::test_utils::get_chat_msg;
use crate::test_utils::{TestContext, TestContextManager};
use crate::tools::{EmailAddress, SystemTime};
use crate::tools::SystemTime;
use std::collections::HashSet;
use std::time::Duration;

View File

@@ -1,6 +1,7 @@
//! Migrations module.
use anyhow::{Context as _, Result};
use deltachat_contact_tools::EmailAddress;
use rusqlite::OptionalExtension;
use crate::config::Config;
@@ -10,7 +11,6 @@ use crate::imap;
use crate::message::MsgId;
use crate::provider::get_provider_by_domain;
use crate::sql::Sql;
use crate::tools::EmailAddress;
const DBVERSION: i32 = 68;
const VERSION_CFG: &str = "dbversion";

View File

@@ -13,6 +13,7 @@ use std::time::{Duration, Instant};
use ansi_term::Color;
use async_channel::{self as channel, Receiver, Sender};
use chat::ChatItem;
use deltachat_contact_tools::{ContactAddress, EmailAddress};
use once_cell::sync::Lazy;
use pretty_assertions::assert_eq;
use rand::Rng;
@@ -27,8 +28,10 @@ use crate::chat::{
};
use crate::chatlist::Chatlist;
use crate::config::Config;
use crate::constants::{Blocked, Chattype, DC_CHAT_ID_TRASH, DC_GCL_NO_SPECIALS};
use crate::contact::{Contact, ContactAddress, ContactId, Modifier, Origin};
use crate::constants::DC_CHAT_ID_TRASH;
use crate::constants::DC_GCL_NO_SPECIALS;
use crate::constants::{Blocked, Chattype};
use crate::contact::{Contact, ContactId, Modifier, Origin};
use crate::context::Context;
use crate::e2ee::EncryptHelper;
use crate::events::{Event, EventType, Events};
@@ -40,7 +43,6 @@ use crate::pgp::KeyPair;
use crate::receive_imf::receive_imf;
use crate::securejoin::{get_securejoin_qr, join_securejoin};
use crate::stock_str::StockStrings;
use crate::tools::EmailAddress;
#[allow(non_upper_case_globals)]
pub const AVATAR_900x900_BYTES: &[u8] = include_bytes!("../test-data/image/avatar900x900.png");

View File

@@ -4,7 +4,6 @@
#![allow(missing_docs)]
use std::borrow::Cow;
use std::fmt;
use std::io::{Cursor, Write};
use std::mem;
use std::path::{Path, PathBuf};
@@ -23,6 +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};
#[cfg(test)]
pub use deltachat_time::SystemTimeTools as SystemTime;
use futures::{StreamExt, TryStreamExt};
@@ -536,80 +536,6 @@ pub fn parse_mailto(mailto_url: &str) -> Option<MailTo> {
}
}
///
/// Represents an email address, right now just the `name@domain` portion.
///
/// # Example
///
/// ```
/// use deltachat::tools::EmailAddress;
/// let email = match EmailAddress::new("someone@example.com") {
/// Ok(addr) => addr,
/// Err(e) => panic!("Error parsing address, error was {}", e),
/// };
/// assert_eq!(&email.local, "someone");
/// assert_eq!(&email.domain, "example.com");
/// assert_eq!(email.to_string(), "someone@example.com");
/// ```
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct EmailAddress {
/// Local part of the email address.
pub local: String,
/// Email address domain.
pub domain: String,
}
impl fmt::Display for EmailAddress {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}@{}", self.local, self.domain)
}
}
impl EmailAddress {
/// Performs a dead-simple parse of an email address.
pub fn new(input: &str) -> Result<EmailAddress> {
if input.is_empty() {
bail!("empty string is not valid");
}
let parts: Vec<&str> = input.rsplitn(2, '@').collect();
if input
.chars()
.any(|c| c.is_whitespace() || c == '<' || c == '>')
{
bail!("Email {:?} must not contain whitespaces, '>' or '<'", input);
}
match &parts[..] {
[domain, local] => {
if local.is_empty() {
bail!("empty string is not valid for local part in {:?}", input);
}
if domain.is_empty() {
bail!("missing domain after '@' in {:?}", input);
}
if domain.ends_with('.') {
bail!("Domain {domain:?} should not contain the dot in the end");
}
Ok(EmailAddress {
local: (*local).to_string(),
domain: (*domain).to_string(),
})
}
_ => bail!("Email {:?} must contain '@' character", input),
}
}
}
impl rusqlite::types::ToSql for EmailAddress {
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
let val = rusqlite::types::Value::Text(self.to_string());
let out = rusqlite::types::ToSqlOutput::Owned(val);
Ok(out)
}
}
/// Sanitizes user input
/// - strip newlines
/// - strip malicious bidi characters
@@ -753,13 +679,6 @@ pub(crate) fn buf_decompress(buf: &[u8]) -> Result<Vec<u8>> {
Ok(mem::take(decompressor.get_mut()))
}
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(crate) fn strip_rtlo_characters(input_str: &str) -> String {
input_str.replace(|char| RTLO_CHARACTERS.contains(&char), "")
}
#[cfg(test)]
mod tests {
#![allow(clippy::indexing_slicing)]
@@ -1042,40 +961,6 @@ DKIM Results: Passed=true, Works=true, Allow_Keychange=true";
assert!(extract_grpid_from_rfc724_mid(mid.as_str()).is_none());
}
#[test]
fn test_emailaddress_parse() {
assert_eq!(EmailAddress::new("").is_ok(), false);
assert_eq!(
EmailAddress::new("user@domain.tld").unwrap(),
EmailAddress {
local: "user".into(),
domain: "domain.tld".into(),
}
);
assert_eq!(
EmailAddress::new("user@localhost").unwrap(),
EmailAddress {
local: "user".into(),
domain: "localhost".into()
}
);
assert_eq!(EmailAddress::new("uuu").is_ok(), false);
assert_eq!(EmailAddress::new("dd.tt").is_ok(), false);
assert!(EmailAddress::new("tt.dd@uu").is_ok());
assert!(EmailAddress::new("u@d").is_ok());
assert!(EmailAddress::new("u@d.").is_err());
assert!(EmailAddress::new("u@d.t").is_ok());
assert_eq!(
EmailAddress::new("u@d.tt").unwrap(),
EmailAddress {
local: "u".into(),
domain: "d.tt".into(),
}
);
assert!(EmailAddress::new("u@tt").is_ok());
assert_eq!(EmailAddress::new("@d.tt").is_ok(), false);
}
use chrono::NaiveDate;
use proptest::prelude::*;

View File

@@ -19,6 +19,7 @@ use std::path::Path;
use anyhow::{anyhow, bail, ensure, format_err, Context as _, Result};
use deltachat_contact_tools::strip_rtlo_characters;
use deltachat_derive::FromSql;
use lettre_email::PartBuilder;
use rusqlite::OptionalExtension;
@@ -37,7 +38,6 @@ use crate::mimeparser::SystemMessage;
use crate::param::Param;
use crate::param::Params;
use crate::tools::create_id;
use crate::tools::strip_rtlo_characters;
use crate::tools::{create_smeared_timestamp, get_abs_path};
/// The current API version.