diff --git a/.github/workflows/node-package.yml b/.github/workflows/node-package.yml index 41c4ff25d..5e5b79108 100644 --- a/.github/workflows/node-package.yml +++ b/.github/workflows/node-package.yml @@ -67,7 +67,9 @@ jobs: # Build Linux prebuilds inside a container with old glibc for backwards compatibility. # Debian 10 contained glibc 2.28 at the time of the writing (2023-06-04): https://packages.debian.org/buster/libc6 - container: debian:10 + # Ubuntu 18.04 is at the End of Standard Support since June 2023, but it contains glibc 2.27, + # so we are using it to support Ubuntu 18.04 setups that are still not upgraded. + container: ubuntu:18.04 steps: # Working directory is owned by 1001:1001 by default. # Change it to our user. diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e54a6826..d4c1e029d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,27 @@ # Changelog +## [1.121.0] - 2023-09-06 + +### API-Changes + +- Add `dc_context_change_passphrase()`. +- Add `Message.set_file_from_bytes()` API. +- Add experimental API to get similar chats. + +### Build system + +- Build node packages on Ubuntu 18.04 instead of Debian 10. + This reduces the requirement for glibc version from 2.28 to 2.27. + +### Fixes + +- Allow membership changes by a MUA if we're not in the group ([#4624](https://github.com/deltachat/deltachat-core-rust/pull/4624)). +- Save mime headers for messages not signed with a known key ([#4557](https://github.com/deltachat/deltachat-core-rust/pull/4557)). +- Return from `dc_get_chatlist(DC_GCL_FOR_FORWARDING)` only chats where we can send ([#4616](https://github.com/deltachat/deltachat-core-rust/pull/4616)). +- Do not allow dots at the end of email addresses. +- deltachat-rpc-client: Remove `aiodns` optional dependency from required dependencies. + `aiodns` depends on `pycares` which [fails to install in Termux](https://github.com/saghul/aiodns/issues/98). + ## [1.120.0] - 2023-08-28 ### API-Changes @@ -2764,3 +2786,4 @@ https://github.com/deltachat/deltachat-core-rust/pulls?q=is%3Apr+is%3Aclosed [1.119.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.118.0...v1.119.0 [1.119.1]: https://github.com/deltachat/deltachat-core-rust/compare/v1.119.0...v1.119.1 [1.120.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.119.1...v1.120.0 +[1.121.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.120.0...v1.121.0 diff --git a/Cargo.lock b/Cargo.lock index 391ffd08f..0b73af324 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1085,7 +1085,7 @@ dependencies = [ [[package]] name = "deltachat" -version = "1.120.0" +version = "1.121.0" dependencies = [ "ansi_term", "anyhow", @@ -1162,7 +1162,7 @@ dependencies = [ [[package]] name = "deltachat-jsonrpc" -version = "1.120.0" +version = "1.121.0" dependencies = [ "anyhow", "async-channel", @@ -1186,7 +1186,7 @@ dependencies = [ [[package]] name = "deltachat-repl" -version = "1.120.0" +version = "1.121.0" dependencies = [ "ansi_term", "anyhow", @@ -1201,7 +1201,7 @@ dependencies = [ [[package]] name = "deltachat-rpc-server" -version = "1.120.0" +version = "1.121.0" dependencies = [ "anyhow", "deltachat", @@ -1226,7 +1226,7 @@ dependencies = [ [[package]] name = "deltachat_ffi" -version = "1.120.0" +version = "1.121.0" dependencies = [ "anyhow", "deltachat", diff --git a/Cargo.toml b/Cargo.toml index 09094a650..4a9fea5f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "deltachat" -version = "1.120.0" +version = "1.121.0" edition = "2021" license = "MPL-2.0" rust-version = "1.67" diff --git a/deltachat-ffi/Cargo.toml b/deltachat-ffi/Cargo.toml index 546fec81d..5bafbc735 100644 --- a/deltachat-ffi/Cargo.toml +++ b/deltachat-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "deltachat_ffi" -version = "1.120.0" +version = "1.121.0" description = "Deltachat FFI" edition = "2018" readme = "README.md" diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 38b4b97c4..78798e1e3 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -301,6 +301,19 @@ dc_context_t* dc_context_new_closed (const char* dbfile); int dc_context_open (dc_context_t *context, const char* passphrase); +/** + * Changes the passphrase on the open database. + * Existing database must already be encrypted and the passphrase cannot be NULL or empty. + * It is impossible to encrypt unencrypted database with this method and vice versa. + * + * @memberof dc_context_t + * @param context The context object. + * @param passphrase The new passphrase. + * @return 1 on success, 0 on error. + */ +int dc_context_change_passphrase (dc_context_t* context, const char* passphrase); + + /** * Returns 1 if database is open. * @@ -1328,6 +1341,20 @@ int dc_get_msg_cnt (dc_context_t* context, uint32_t ch int dc_get_fresh_msg_cnt (dc_context_t* context, uint32_t chat_id); +/** + * Returns a list of similar chats. + * + * @warning This is an experimental API which may change or be removed in the future. + * + * @memberof dc_context_t + * @param context The context object as returned from dc_context_new(). + * @param chat_id The ID of the chat for which to find similar chats. + * @return The list of similar chats. + * On errors, NULL is returned. + * Must be freed using dc_chatlist_unref() when no longer used. + */ +dc_chatlist_t* dc_get_similar_chatlist (dc_context_t* context, uint32_t chat_id); + /** * Estimate the number of messages that will be deleted diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 58d238fd8..4144f13b3 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -167,6 +167,24 @@ pub unsafe extern "C" fn dc_context_open( .unwrap_or(0) } +#[no_mangle] +pub unsafe extern "C" fn dc_context_change_passphrase( + context: *mut dc_context_t, + passphrase: *const libc::c_char, +) -> libc::c_int { + if context.is_null() { + eprintln!("ignoring careless call to dc_context_change_passphrase()"); + return 0; + } + + let ctx = &*context; + let passphrase = to_string_lossy(passphrase); + block_on(ctx.change_passphrase(passphrase)) + .context("dc_context_change_passphrase() failed") + .log_err(ctx) + .is_ok() as libc::c_int +} + #[no_mangle] pub unsafe extern "C" fn dc_context_is_open(context: *mut dc_context_t) -> libc::c_int { if context.is_null() { @@ -1234,6 +1252,30 @@ pub unsafe extern "C" fn dc_get_fresh_msg_cnt( }) } +#[no_mangle] +pub unsafe extern "C" fn dc_get_similar_chatlist( + context: *mut dc_context_t, + chat_id: u32, +) -> *mut dc_chatlist_t { + if context.is_null() { + eprintln!("ignoring careless call to dc_get_similar_chatlist()"); + return ptr::null_mut(); + } + let ctx = &*context; + + let chat_id = ChatId::new(chat_id); + match block_on(chat_id.get_similar_chatlist(ctx)) + .context("failed to get similar chatlist") + .log_err(ctx) + { + Ok(list) => { + let ffi_list = ChatlistWrapper { context, list }; + Box::into_raw(Box::new(ffi_list)) + } + Err(_) => ptr::null_mut(), + } +} + #[no_mangle] pub unsafe extern "C" fn dc_estimate_deletion_cnt( context: *mut dc_context_t, diff --git a/deltachat-jsonrpc/Cargo.toml b/deltachat-jsonrpc/Cargo.toml index cb8298ad0..f1b4d15a7 100644 --- a/deltachat-jsonrpc/Cargo.toml +++ b/deltachat-jsonrpc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "deltachat-jsonrpc" -version = "1.120.0" +version = "1.121.0" description = "DeltaChat JSON-RPC API" edition = "2021" default-run = "deltachat-jsonrpc-server" diff --git a/deltachat-jsonrpc/src/api/mod.rs b/deltachat-jsonrpc/src/api/mod.rs index 02027af0b..c2c57ea2b 100644 --- a/deltachat-jsonrpc/src/api/mod.rs +++ b/deltachat-jsonrpc/src/api/mod.rs @@ -39,6 +39,7 @@ pub mod types; use num_traits::FromPrimitive; use types::account::Account; use types::chat::FullChat; +use types::chat_list::ChatListEntry; use types::contact::ContactObject; use types::events::Event; use types::http::HttpResponse; @@ -569,6 +570,25 @@ impl CommandApi { Ok(l) } + /// Returns chats similar to the given one. + async fn get_similar_chatlist_entries( + &self, + account_id: u32, + chat_id: u32, + ) -> Result> { + let ctx = self.get_context(account_id).await?; + let chat_id = ChatId::new(chat_id); + let list = chat_id.get_similar_chatlist(&ctx).await?; + let mut l: Vec = Vec::with_capacity(list.len()); + for i in 0..list.len() { + l.push(ChatListEntry( + list.get_chat_id(i)?.to_u32(), + list.get_msg_id(i)?.unwrap_or_default().to_u32(), + )); + } + Ok(l) + } + async fn get_chatlist_items_by_entries( &self, account_id: u32, diff --git a/deltachat-jsonrpc/typescript/package.json b/deltachat-jsonrpc/typescript/package.json index 0c0e4b6bd..3f3fd3d16 100644 --- a/deltachat-jsonrpc/typescript/package.json +++ b/deltachat-jsonrpc/typescript/package.json @@ -55,5 +55,5 @@ }, "type": "module", "types": "dist/deltachat.d.ts", - "version": "1.120.0" + "version": "1.121.0" } diff --git a/deltachat-repl/Cargo.toml b/deltachat-repl/Cargo.toml index 1278938f5..3d52b6b13 100644 --- a/deltachat-repl/Cargo.toml +++ b/deltachat-repl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "deltachat-repl" -version = "1.120.0" +version = "1.121.0" license = "MPL-2.0" edition = "2021" diff --git a/deltachat-repl/src/cmdline.rs b/deltachat-repl/src/cmdline.rs index 7bb4c40bc..080d8d07b 100644 --- a/deltachat-repl/src/cmdline.rs +++ b/deltachat-repl/src/cmdline.rs @@ -814,15 +814,30 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu } "chatinfo" => { ensure!(sel_chat.is_some(), "No chat selected."); + let sel_chat_id = sel_chat.as_ref().unwrap().get_id(); - let contacts = - chat::get_chat_contacts(&context, sel_chat.as_ref().unwrap().get_id()).await?; + let contacts = chat::get_chat_contacts(&context, sel_chat_id).await?; println!("Memberlist:"); log_contactlist(&context, &contacts).await?; + println!("{} contacts", contacts.len()); + + let similar_chats = sel_chat_id.get_similar_chat_ids(&context).await?; + if !similar_chats.is_empty() { + println!("Similar chats: "); + for (similar_chat_id, metric) in similar_chats { + let similar_chat = Chat::load_from_db(&context, similar_chat_id).await?; + println!( + "{} (#{}) {:.1}", + similar_chat.name, + similar_chat_id, + 100.0 * metric + ); + } + } + println!( - "{} contacts\nLocation streaming: {}", - contacts.len(), + "Location streaming: {}", location::is_sending_locations_to_chat( &context, Some(sel_chat.as_ref().unwrap().get_id()) diff --git a/deltachat-rpc-server/Cargo.toml b/deltachat-rpc-server/Cargo.toml index ec28043d5..796e63b17 100644 --- a/deltachat-rpc-server/Cargo.toml +++ b/deltachat-rpc-server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "deltachat-rpc-server" -version = "1.120.0" +version = "1.121.0" description = "DeltaChat JSON-RPC server" edition = "2021" readme = "README.md" diff --git a/package.json b/package.json index 46f77993d..779fe9c0e 100644 --- a/package.json +++ b/package.json @@ -60,5 +60,5 @@ "test:mocha": "mocha -r esm node/test/test.js --growl --reporter=spec --bail --exit" }, "types": "node/dist/index.d.ts", - "version": "1.120.0" + "version": "1.121.0" } diff --git a/release-date.in b/release-date.in index 13b929383..1cef16d38 100644 --- a/release-date.in +++ b/release-date.in @@ -1 +1 @@ -2023-08-28 \ No newline at end of file +2023-09-06 \ No newline at end of file diff --git a/scripts/create-provider-data-rs.py b/scripts/create-provider-data-rs.py index 100a34fe6..589cd0b90 100755 --- a/scripts/create-provider-data-rs.py +++ b/scripts/create-provider-data-rs.py @@ -44,7 +44,7 @@ def file2url(f): def process_opt(data): if not "opt" in data: - return "Default::default()" + return "ProviderOptions::new()" opt = "ProviderOptions {\n" opt_data = data.get("opt", "") for key in opt_data: @@ -54,7 +54,7 @@ def process_opt(data): if value in {"True", "False"}: value = value.lower() opt += " " + key + ": " + value + ",\n" - opt += " ..Default::default()\n" + opt += " ..ProviderOptions::new()\n" opt += " }" return opt @@ -96,11 +96,11 @@ def process_data(data, file): raise TypeError("domain used twice: " + domain) domains_set.add(domain) - domains += ' ("' + domain + '", &*' + file2varname(file) + "),\n" + domains += ' ("' + domain + '", &' + file2varname(file) + "),\n" comment += domain + ", " ids = "" - ids += ' ("' + file2id(file) + '", &*' + file2varname(file) + "),\n" + ids += ' ("' + file2id(file) + '", &' + file2varname(file) + "),\n" server = "" has_imap = False @@ -155,7 +155,7 @@ def process_data(data, file): provider += ( "static " + file2varname(file) - + ": Lazy = Lazy::new(|| Provider {\n" + + ": Provider = Provider {\n" ) provider += ' id: "' + file2id(file) + '",\n' provider += " status: Status::" + status.capitalize() + ",\n" @@ -166,7 +166,7 @@ def process_data(data, file): provider += " opt: " + opt + ",\n" provider += " config_defaults: " + config_defaults + ",\n" provider += " oauth2_authorizer: " + oauth2 + ",\n" - provider += "});\n\n" + provider += "};\n\n" else: raise TypeError("SMTP and IMAP must be specified together or left out both") diff --git a/src/chat.rs b/src/chat.rs index 1369d840c..7a9ab77c9 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -13,6 +13,7 @@ use serde::{Deserialize, Serialize}; use crate::aheader::EncryptPreference; use crate::blob::BlobObject; +use crate::chatlist::Chatlist; use crate::color::str_to_color; use crate::config::Config; use crate::constants::{ @@ -885,6 +886,133 @@ impl ChatId { Ok(count) } + /// Returns timestamp of the latest message in the chat. + pub(crate) async fn get_timestamp(self, context: &Context) -> Result> { + let timestamp = context + .sql + .query_get_value("SELECT MAX(timestamp) FROM msgs WHERE chat_id=?", (self,)) + .await?; + Ok(timestamp) + } + + /// Returns a list of active similar chat IDs sorted by similarity metric. + /// + /// Jaccard similarity coefficient is used to estimate similarity of chat member sets. + /// + /// Chat is considered active if something was posted there within the last 42 days. + pub async fn get_similar_chat_ids(self, context: &Context) -> Result> { + // Count number of common members in this and other chats. + let intersection: Vec<(ChatId, f64)> = context + .sql + .query_map( + "SELECT y.chat_id, SUM(x.contact_id = y.contact_id) + FROM chats_contacts as x + JOIN chats_contacts as y + WHERE x.contact_id > 9 + AND y.contact_id > 9 + AND x.chat_id=? + AND y.chat_id<>x.chat_id + GROUP BY y.chat_id", + (self,), + |row| { + let chat_id: ChatId = row.get(0)?; + let intersection: f64 = row.get(1)?; + Ok((chat_id, intersection)) + }, + |rows| { + rows.collect::, _>>() + .map_err(Into::into) + }, + ) + .await + .context("failed to calculate member set intersections")?; + + let chat_size: HashMap = context + .sql + .query_map( + "SELECT chat_id, count(*) AS n + FROM chats_contacts where contact_id > 9 + GROUP BY chat_id", + (), + |row| { + let chat_id: ChatId = row.get(0)?; + let size: f64 = row.get(1)?; + Ok((chat_id, size)) + }, + |rows| { + rows.collect::, _>>() + .map_err(Into::into) + }, + ) + .await + .context("failed to count chat member sizes")?; + + let our_chat_size = chat_size.get(&self).copied().unwrap_or_default(); + let mut chats_with_metrics = Vec::new(); + for (chat_id, intersection_size) in intersection { + if intersection_size > 0.0 { + let other_chat_size = chat_size.get(&chat_id).copied().unwrap_or_default(); + let union_size = our_chat_size + other_chat_size - intersection_size; + let metric = intersection_size / union_size; + chats_with_metrics.push((chat_id, metric)) + } + } + chats_with_metrics.sort_unstable_by(|(chat_id1, metric1), (chat_id2, metric2)| { + metric2 + .partial_cmp(metric1) + .unwrap_or(chat_id2.cmp(chat_id1)) + }); + + // Select up to five similar active chats. + let mut res = Vec::new(); + let now = time(); + for (chat_id, metric) in chats_with_metrics { + if let Some(chat_timestamp) = chat_id.get_timestamp(context).await? { + if now > chat_timestamp + 42 * 24 * 3600 { + // Chat was inactive for 42 days, skip. + continue; + } + } + + if metric < 0.1 { + // Chat is unrelated. + break; + } + + let chat = Chat::load_from_db(context, chat_id).await?; + if chat.typ != Chattype::Group { + continue; + } + + match chat.visibility { + ChatVisibility::Normal | ChatVisibility::Pinned => {} + ChatVisibility::Archived => continue, + } + + res.push((chat_id, metric)); + if res.len() >= 5 { + break; + } + } + + Ok(res) + } + + /// Returns similar chats as a [`Chatlist`]. + /// + /// [`Chatlist`]: crate::chatlist::Chatlist + pub async fn get_similar_chatlist(self, context: &Context) -> Result { + let chat_ids: Vec = self + .get_similar_chat_ids(context) + .await + .context("failed to get similar chat IDs")? + .into_iter() + .map(|(chat_id, _metric)| chat_id) + .collect(); + let chatlist = Chatlist::from_chat_ids(context, &chat_ids).await?; + Ok(chatlist) + } + pub(crate) async fn get_param(self, context: &Context) -> Result { let res: Option = context .sql @@ -1612,6 +1740,11 @@ impl Chat { None }; + msg.chat_id = self.id; + msg.from_id = ContactId::SELF; + msg.rfc724_mid = new_rfc724_mid; + msg.timestamp_sort = timestamp; + // add message to the database if let Some(update_msg_id) = update_msg_id { context @@ -1625,11 +1758,11 @@ impl Chat { ephemeral_timestamp=? WHERE id=?;", params_slice![ - new_rfc724_mid, - self.id, - ContactId::SELF, + msg.rfc724_mid, + msg.chat_id, + msg.from_id, to_id, - timestamp, + msg.timestamp_sort, msg.viewtype, msg.state, msg.text, @@ -1674,11 +1807,11 @@ impl Chat { ephemeral_timestamp) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,1,?,?,?);", params_slice![ - new_rfc724_mid, - self.id, - ContactId::SELF, + msg.rfc724_mid, + msg.chat_id, + msg.from_id, to_id, - timestamp, + msg.timestamp_sort, msg.viewtype, msg.state, msg.text, @@ -2135,6 +2268,8 @@ async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<()> { } } + msg.try_calc_and_set_dimensions(context).await?; + info!( context, "Attaching \"{}\" for message type #{}.", @@ -2308,7 +2443,7 @@ async fn prepare_send_msg( ); message::update_msg_state(context, msg.id, MessageState::OutPending).await?; } - let row_id = create_send_msg_job(context, msg.id).await?; + let row_id = create_send_msg_job(context, msg).await?; Ok(row_id) } @@ -2318,13 +2453,10 @@ async fn prepare_send_msg( /// group with only self and no BCC-to-self configured. /// /// The caller has to interrupt SMTP loop or otherwise process a new row. -async fn create_send_msg_job(context: &Context, msg_id: MsgId) -> Result> { - let mut msg = Message::load_from_db(context, msg_id).await?; - msg.try_calc_and_set_dimensions(context) - .await - .context("failed to calculate media dimensions")?; - - /* create message */ +pub(crate) async fn create_send_msg_job( + context: &Context, + msg: &mut Message, +) -> Result> { let needs_encryption = msg.param.get_bool(Param::GuaranteeE2ee).unwrap_or_default(); let attach_selfavatar = match shall_attach_selfavatar(context, msg.chat_id).await { @@ -2335,7 +2467,7 @@ async fn create_send_msg_job(context: &Context, msg_id: MsgId) -> Result Result Ok(res), Err(err) => { - message::set_msg_failed(context, msg_id, &err.to_string()).await; + message::set_msg_failed(context, msg, &err.to_string()).await?; Err(err) } }?; @@ -2375,13 +2508,13 @@ async fn create_send_msg_job(context: &Context, msg_id: MsgId) -> Result Result Result<()> { - if let Some(chat_timestamp) = context - .sql - .query_get_value( - "SELECT MAX(timestamp) FROM msgs WHERE chat_id=?", - (chat_id,), - ) - .await? - { + if let Some(chat_timestamp) = chat_id.get_timestamp(context).await? { if timestamp > chat_timestamp { marknoticed_chat(context, chat_id).await?; } @@ -3422,89 +3548,86 @@ pub async fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: ChatId) chat_id .unarchive_if_not_muted(context, MessageState::Undefined) .await?; - if let Ok(mut chat) = Chat::load_from_db(context, chat_id).await { - if let Some(reason) = chat.why_cant_send(context).await? { - bail!("cannot send to {}: {}", chat_id, reason); + let mut chat = Chat::load_from_db(context, chat_id).await?; + if let Some(reason) = chat.why_cant_send(context).await? { + bail!("cannot send to {}: {}", chat_id, reason); + } + curr_timestamp = create_smeared_timestamps(context, msg_ids.len()); + let ids = context + .sql + .query_map( + &format!( + "SELECT id FROM msgs WHERE id IN({}) ORDER BY timestamp,id", + sql::repeat_vars(msg_ids.len()) + ), + rusqlite::params_from_iter(msg_ids), + |row| row.get::<_, MsgId>(0), + |ids| ids.collect::, _>>().map_err(Into::into), + ) + .await?; + + for id in ids { + let src_msg_id: MsgId = id; + let mut msg = Message::load_from_db(context, src_msg_id).await?; + if msg.state == MessageState::OutDraft { + bail!("cannot forward drafts."); } - curr_timestamp = create_smeared_timestamps(context, msg_ids.len()); - let ids = context - .sql - .query_map( - &format!( - "SELECT id FROM msgs WHERE id IN({}) ORDER BY timestamp,id", - sql::repeat_vars(msg_ids.len()) - ), - rusqlite::params_from_iter(msg_ids), - |row| row.get::<_, MsgId>(0), - |ids| ids.collect::, _>>().map_err(Into::into), - ) - .await?; - for id in ids { - let src_msg_id: MsgId = id; - let mut msg = Message::load_from_db(context, src_msg_id).await?; - if msg.state == MessageState::OutDraft { - bail!("cannot forward drafts."); - } + let original_param = msg.param.clone(); - let original_param = msg.param.clone(); + // we tested a sort of broadcast + // by not marking own forwarded messages as such, + // however, this turned out to be to confusing and unclear. - // we tested a sort of broadcast - // by not marking own forwarded messages as such, - // however, this turned out to be to confusing and unclear. + if msg.get_viewtype() != Viewtype::Sticker { + msg.param + .set_int(Param::Forwarded, src_msg_id.to_u32() as i32); + } - if msg.get_viewtype() != Viewtype::Sticker { - msg.param - .set_int(Param::Forwarded, src_msg_id.to_u32() as i32); - } + msg.param.remove(Param::GuaranteeE2ee); + msg.param.remove(Param::ForcePlaintext); + msg.param.remove(Param::Cmd); + msg.param.remove(Param::OverrideSenderDisplayname); + msg.param.remove(Param::WebxdcSummary); + msg.param.remove(Param::WebxdcSummaryTimestamp); + msg.in_reply_to = None; - msg.param.remove(Param::GuaranteeE2ee); - msg.param.remove(Param::ForcePlaintext); - msg.param.remove(Param::Cmd); - msg.param.remove(Param::OverrideSenderDisplayname); - msg.param.remove(Param::WebxdcSummary); - msg.param.remove(Param::WebxdcSummaryTimestamp); - msg.in_reply_to = None; + // do not leak data as group names; a default subject is generated by mimefactory + msg.subject = "".to_string(); - // do not leak data as group names; a default subject is generated by mimefactory - msg.subject = "".to_string(); + let new_msg_id: MsgId; + if msg.state == MessageState::OutPreparing { + new_msg_id = chat + .prepare_msg_raw(context, &mut msg, None, curr_timestamp) + .await?; + curr_timestamp += 1; + msg.param = original_param; + msg.id = src_msg_id; - let new_msg_id: MsgId; - if msg.state == MessageState::OutPreparing { - new_msg_id = chat - .prepare_msg_raw(context, &mut msg, None, curr_timestamp) - .await?; - curr_timestamp += 1; - let save_param = msg.param.clone(); - msg.param = original_param; - msg.id = src_msg_id; - - if let Some(old_fwd) = msg.param.get(Param::PrepForwards) { - let new_fwd = format!("{} {}", old_fwd, new_msg_id.to_u32()); - msg.param.set(Param::PrepForwards, new_fwd); - } else { - msg.param - .set(Param::PrepForwards, new_msg_id.to_u32().to_string()); - } - - msg.update_param(context).await?; - msg.param = save_param; + if let Some(old_fwd) = msg.param.get(Param::PrepForwards) { + let new_fwd = format!("{} {}", old_fwd, new_msg_id.to_u32()); + msg.param.set(Param::PrepForwards, new_fwd); } else { - msg.state = MessageState::OutPending; - new_msg_id = chat - .prepare_msg_raw(context, &mut msg, None, curr_timestamp) - .await?; - curr_timestamp += 1; - if create_send_msg_job(context, new_msg_id).await?.is_some() { - context - .scheduler - .interrupt_smtp(InterruptInfo::new(false)) - .await; - } + msg.param + .set(Param::PrepForwards, new_msg_id.to_u32().to_string()); + } + + msg.update_param(context).await?; + } else { + msg.state = MessageState::OutPending; + new_msg_id = chat + .prepare_msg_raw(context, &mut msg, None, curr_timestamp) + .await?; + curr_timestamp += 1; + if create_send_msg_job(context, &mut msg).await?.is_some() { + context + .scheduler + .interrupt_smtp(InterruptInfo::new(false)) + .await; } - created_chats.push(chat_id); - created_msgs.push(new_msg_id); } + created_chats.push(chat_id); + created_msgs.push(new_msg_id); } for (chat_id, msg_id) in created_chats.iter().zip(created_msgs.iter()) { context.emit_msgs_changed(*chat_id, *msg_id); @@ -3536,29 +3659,31 @@ pub async fn resend_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> { msgs.push(msg) } - if let Some(chat_id) = chat_id { - let chat = Chat::load_from_db(context, chat_id).await?; - for mut msg in msgs { - if msg.get_showpadlock() && !chat.is_protected() { - msg.param.remove(Param::GuaranteeE2ee); - msg.update_param(context).await?; - } - match msg.get_state() { - MessageState::OutFailed | MessageState::OutDelivered | MessageState::OutMdnRcvd => { - message::update_msg_state(context, msg.id, MessageState::OutPending).await? - } - _ => bail!("unexpected message state"), - } - context.emit_event(EventType::MsgsChanged { - chat_id: msg.chat_id, - msg_id: msg.id, - }); - if create_send_msg_job(context, msg.id).await?.is_some() { - context - .scheduler - .interrupt_smtp(InterruptInfo::new(false)) - .await; + let Some(chat_id) = chat_id else { + return Ok(()); + }; + + let chat = Chat::load_from_db(context, chat_id).await?; + for mut msg in msgs { + if msg.get_showpadlock() && !chat.is_protected() { + msg.param.remove(Param::GuaranteeE2ee); + msg.update_param(context).await?; + } + match msg.get_state() { + MessageState::OutFailed | MessageState::OutDelivered | MessageState::OutMdnRcvd => { + message::update_msg_state(context, msg.id, MessageState::OutPending).await? } + _ => bail!("unexpected message state"), + } + context.emit_event(EventType::MsgsChanged { + chat_id: msg.chat_id, + msg_id: msg.id, + }); + if create_send_msg_job(context, &mut msg).await?.is_some() { + context + .scheduler + .interrupt_smtp(InterruptInfo::new(false)) + .await; } } Ok(()) @@ -3628,7 +3753,6 @@ pub async fn add_device_msg_with_importance( chat_id = ChatId::get_for_contact(context, ContactId::DEVICE).await?; let rfc724_mid = create_outgoing_rfc724_mid(None, "@device"); - msg.try_calc_and_set_dimensions(context).await.ok(); prepare_msg_blob(context, msg).await?; let timestamp_sent = create_smeared_timestamp(context); diff --git a/src/chatlist.rs b/src/chatlist.rs index beafa1206..d809b42d6 100644 --- a/src/chatlist.rs +++ b/src/chatlist.rs @@ -296,6 +296,27 @@ impl Chatlist { Ok(Chatlist { ids }) } + /// Converts list of chat IDs to a chatlist. + pub(crate) async fn from_chat_ids(context: &Context, chat_ids: &[ChatId]) -> Result { + let mut ids = Vec::new(); + for &chat_id in chat_ids { + let msg_id: Option = context + .sql + .query_get_value( + "SELECT id + FROM msgs + WHERE chat_id=?1 + AND (hidden=0 OR state=?2) + ORDER BY timestamp DESC, id DESC LIMIT 1", + (chat_id, MessageState::OutDraft), + ) + .await + .with_context(|| format!("failed to get msg ID for chat {}", chat_id))?; + ids.push((chat_id, msg_id)); + } + Ok(Chatlist { ids }) + } + /// Find out the number of chats. pub fn len(&self) -> usize { self.ids.len() diff --git a/src/contact.rs b/src/contact.rs index a5f172a9b..e771e15de 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -1716,7 +1716,7 @@ mod tests { assert_eq!(may_be_valid_addr("dd.tt"), false); assert_eq!(may_be_valid_addr("tt.dd@uu"), true); assert_eq!(may_be_valid_addr("u@d"), true); - assert_eq!(may_be_valid_addr("u@d."), true); + assert_eq!(may_be_valid_addr("u@d."), false); assert_eq!(may_be_valid_addr("u@d.t"), true); assert_eq!(may_be_valid_addr("u@d.tt"), true); assert_eq!(may_be_valid_addr("u@.tt"), true); @@ -1725,6 +1725,7 @@ mod tests { assert_eq!(may_be_valid_addr("sk <@d.tt>"), false); assert_eq!(may_be_valid_addr("as@sd.de>"), false); assert_eq!(may_be_valid_addr("ask dkl@dd.tt"), false); + assert_eq!(may_be_valid_addr("user@domain.tld."), false); } #[test] diff --git a/src/context.rs b/src/context.rs index a6d649c76..fe29cb8e8 100644 --- a/src/context.rs +++ b/src/context.rs @@ -332,6 +332,12 @@ impl Context { } } + /// Changes encrypted database passphrase. + pub async fn change_passphrase(&self, passphrase: String) -> Result<()> { + self.sql.change_passphrase(passphrase).await?; + Ok(()) + } + /// Returns true if database is open. pub async fn is_open(&self) -> bool { self.sql.is_open().await diff --git a/src/message.rs b/src/message.rs index 4ebc46790..7e86f8c99 100644 --- a/src/message.rs +++ b/src/message.rs @@ -7,6 +7,7 @@ use anyhow::{ensure, format_err, Context as _, Result}; use deltachat_derive::{FromSql, ToSql}; use serde::{Deserialize, Serialize}; +use crate::blob::BlobObject; use crate::chat::{Chat, ChatId}; use crate::config::Config; use crate::constants::{ @@ -574,14 +575,22 @@ impl Message { if (self.viewtype == Viewtype::Image || self.viewtype == Viewtype::Gif) && !self.param.exists(Param::Width) { - self.param.set_int(Param::Width, 0); - self.param.set_int(Param::Height, 0); + let buf = read_file(context, &path_and_filename).await?; - if let Ok(buf) = read_file(context, path_and_filename).await { - if let Ok((width, height)) = get_filemeta(&buf) { + match get_filemeta(&buf) { + Ok((width, height)) => { self.param.set_int(Param::Width, width 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() + ); + } } if !self.id.is_unset() { @@ -972,19 +981,28 @@ impl Message { } } self.param.set(Param::File, file); - if let Some(filemime) = filemime { - self.param.set(Param::MimeType, filemime); - } + self.param.set_optional(Param::MimeType, filemime); + } + + /// Creates a new blob and sets it as a file associated with a message. + pub async fn set_file_from_bytes( + &mut self, + context: &Context, + suggested_name: &str, + data: &[u8], + filemime: Option<&str>, + ) -> Result<()> { + let blob = BlobObject::create(context, suggested_name, data).await?; + self.param.set(Param::File, blob.as_name()); + self.param.set_optional(Param::MimeType, filemime); + Ok(()) } /// Set different sender name for a message. /// This overrides the name set by the `set_config()`-option `displayname`. pub fn set_override_sender_name(&mut self, name: Option) { - if let Some(name) = name { - self.param.set(Param::OverrideSenderDisplayname, name); - } else { - self.param.remove(Param::OverrideSenderDisplayname); - } + self.param + .set_optional(Param::OverrideSenderDisplayname, name); } /// Sets the dimensions of associated image or video file. @@ -1641,35 +1659,36 @@ pub(crate) async fn update_msg_state( // Context functions to work with messages -pub(crate) async fn set_msg_failed(context: &Context, msg_id: MsgId, error: &str) { - if let Ok(mut msg) = Message::load_from_db(context, msg_id).await { - if msg.state.can_fail() { - msg.state = MessageState::OutFailed; - warn!(context, "{} failed: {}", msg_id, error); - } else { - warn!( - context, - "{} seems to have failed ({}), but state is {}", msg_id, error, msg.state - ) - } - - match context - .sql - .execute( - "UPDATE msgs SET state=?, error=? WHERE id=?;", - (msg.state, error, msg_id), - ) - .await - { - Ok(_) => context.emit_event(EventType::MsgFailed { - chat_id: msg.chat_id, - msg_id, - }), - Err(e) => { - warn!(context, "{:?}", e); - } - } +pub(crate) async fn set_msg_failed( + context: &Context, + msg: &mut Message, + error: &str, +) -> Result<()> { + if msg.state.can_fail() { + msg.state = MessageState::OutFailed; + warn!(context, "{} failed: {}", msg.id, error); + } else { + warn!( + context, + "{} seems to have failed ({}), but state is {}", msg.id, error, msg.state + ) } + msg.error = Some(error.to_string()); + + context + .sql + .execute( + "UPDATE msgs SET state=?, error=? WHERE id=?;", + (msg.state, error, msg.id), + ) + .await?; + + context.emit_event(EventType::MsgFailed { + chat_id: msg.chat_id, + msg_id: msg.id, + }); + + Ok(()) } /// The number of messages assigned to unblocked chats @@ -2276,7 +2295,7 @@ mod tests { update_msg_state(&alice, alice_msg.id, MessageState::OutMdnRcvd).await?; assert_state(&alice, alice_msg.id, MessageState::OutMdnRcvd).await; - set_msg_failed(&alice, alice_msg.id, "badly failed").await; + set_msg_failed(&alice, &mut alice_msg, "badly failed").await?; assert_state(&alice, alice_msg.id, MessageState::OutFailed).await; // check incoming message states on receiver side diff --git a/src/mimeparser.rs b/src/mimeparser.rs index 47ac368df..b358a5a88 100644 --- a/src/mimeparser.rs +++ b/src/mimeparser.rs @@ -28,7 +28,9 @@ use crate::dehtml::dehtml; use crate::events::EventType; use crate::headerdef::{HeaderDef, HeaderDefMap}; use crate::key::{load_self_secret_key, DcKey, Fingerprint, SignedPublicKey}; -use crate::message::{self, set_msg_failed, update_msg_state, MessageState, MsgId, Viewtype}; +use crate::message::{ + self, set_msg_failed, update_msg_state, Message, MessageState, MsgId, Viewtype, +}; use crate::param::{Param, Params}; use crate::peerstate::Peerstate; use crate::simplify::{simplify, SimplifiedText}; @@ -2156,7 +2158,8 @@ async fn handle_ndn( let mut first = true; for msg in msgs { let (msg_id, chat_id, chat_type) = msg?; - set_msg_failed(context, msg_id, &error).await; + let mut message = Message::load_from_db(context, msg_id).await?; + set_msg_failed(context, &mut message, &error).await?; if first { // Add only one info msg for all failed messages ndn_maybe_add_info_msg(context, failed, chat_id, chat_type).await?; diff --git a/src/param.rs b/src/param.rs index 8768f996d..d0f3bb1aa 100644 --- a/src/param.rs +++ b/src/param.rs @@ -281,6 +281,16 @@ impl Params { self } + /// Sets the given key from an optional value. + /// Removes the key if the value is `None`. + pub fn set_optional(&mut self, key: Param, value: Option) -> &mut Self { + if let Some(value) = value { + self.set(key, value) + } else { + self.remove(key) + } + } + /// Check if there are any values in this. pub fn is_empty(&self) -> bool { self.inner.is_empty() diff --git a/src/provider.rs b/src/provider.rs index e27f9d631..c36f8a77e 100644 --- a/src/provider.rs +++ b/src/provider.rs @@ -149,8 +149,8 @@ pub struct ProviderOptions { pub delete_to_trash: bool, } -impl Default for ProviderOptions { - fn default() -> Self { +impl ProviderOptions { + const fn new() -> Self { Self { strict_tls: true, max_smtp_rcpt_to: None, diff --git a/src/provider/data.rs b/src/provider/data.rs index fedd5630f..4fa7f2e55 100644 --- a/src/provider/data.rs +++ b/src/provider/data.rs @@ -11,7 +11,7 @@ use std::collections::HashMap; use once_cell::sync::Lazy; // 163.md: 163.com -static P_163: Lazy = Lazy::new(|| Provider { +static P_163: Provider = Provider { id: "163", status: Status::Ok, before_login_hint: "", @@ -33,13 +33,13 @@ static P_163: Lazy = Lazy::new(|| Provider { username_pattern: Email, }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // aktivix.org.md: aktivix.org -static P_AKTIVIX_ORG: Lazy = Lazy::new(|| Provider { +static P_AKTIVIX_ORG: Provider = Provider { id: "aktivix.org", status: Status::Ok, before_login_hint: "", @@ -61,14 +61,13 @@ static P_AKTIVIX_ORG: Lazy = Lazy::new(|| Provider { username_pattern: Email, }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // aol.md: aol.com -static P_AOL: Lazy = Lazy::new(|| { - Provider { +static P_AOL: Provider = Provider { id: "aol", status: Status::Preparation, before_login_hint: "To log in to AOL with Delta Chat, you need to set up an app password in the AOL web interface.", @@ -78,14 +77,13 @@ static P_AOL: Lazy = Lazy::new(|| { Server { protocol: Imap, socket: Ssl, hostname: "imap.aol.com", port: 993, username_pattern: Email }, Server { protocol: Smtp, socket: Ssl, hostname: "smtp.aol.com", port: 465, username_pattern: Email }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -} -}); +}; // arcor.de.md: arcor.de -static P_ARCOR_DE: Lazy = Lazy::new(|| Provider { +static P_ARCOR_DE: Provider = Provider { id: "arcor.de", status: Status::Ok, before_login_hint: "", @@ -107,13 +105,13 @@ static P_ARCOR_DE: Lazy = Lazy::new(|| Provider { username_pattern: Email, }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // autistici.org.md: autistici.org -static P_AUTISTICI_ORG: Lazy = Lazy::new(|| Provider { +static P_AUTISTICI_ORG: Provider = Provider { id: "autistici.org", status: Status::Ok, before_login_hint: "", @@ -135,13 +133,13 @@ static P_AUTISTICI_ORG: Lazy = Lazy::new(|| Provider { username_pattern: Email, }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // blindzeln.org.md: delta.blinzeln.de, delta.blindzeln.org -static P_BLINDZELN_ORG: Lazy = Lazy::new(|| Provider { +static P_BLINDZELN_ORG: Provider = Provider { id: "blindzeln.org", status: Status::Ok, before_login_hint: "", @@ -163,13 +161,13 @@ static P_BLINDZELN_ORG: Lazy = Lazy::new(|| Provider { username_pattern: Email, }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // bluewin.ch.md: bluewin.ch -static P_BLUEWIN_CH: Lazy = Lazy::new(|| Provider { +static P_BLUEWIN_CH: Provider = Provider { id: "bluewin.ch", status: Status::Ok, before_login_hint: "", @@ -191,13 +189,13 @@ static P_BLUEWIN_CH: Lazy = Lazy::new(|| Provider { username_pattern: Email, }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // buzon.uy.md: buzon.uy -static P_BUZON_UY: Lazy = Lazy::new(|| Provider { +static P_BUZON_UY: Provider = Provider { id: "buzon.uy", status: Status::Ok, before_login_hint: "", @@ -219,13 +217,13 @@ static P_BUZON_UY: Lazy = Lazy::new(|| Provider { username_pattern: Email, }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // chello.at.md: chello.at -static P_CHELLO_AT: Lazy = Lazy::new(|| Provider { +static P_CHELLO_AT: Provider = Provider { id: "chello.at", status: Status::Ok, before_login_hint: "", @@ -247,39 +245,39 @@ static P_CHELLO_AT: Lazy = Lazy::new(|| Provider { username_pattern: Email, }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // comcast.md: xfinity.com, comcast.net -static P_COMCAST: Lazy = Lazy::new(|| Provider { +static P_COMCAST: Provider = Provider { id: "comcast", status: Status::Ok, before_login_hint: "", after_login_hint: "", overview_page: "https://providers.delta.chat/comcast", server: &[], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // dismail.de.md: dismail.de -static P_DISMAIL_DE: Lazy = Lazy::new(|| Provider { +static P_DISMAIL_DE: Provider = Provider { id: "dismail.de", status: Status::Ok, before_login_hint: "", after_login_hint: "", overview_page: "https://providers.delta.chat/dismail-de", server: &[], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // disroot.md: disroot.org -static P_DISROOT: Lazy = Lazy::new(|| Provider { +static P_DISROOT: Provider = Provider { id: "disroot", status: Status::Ok, before_login_hint: "", @@ -301,13 +299,13 @@ static P_DISROOT: Lazy = Lazy::new(|| Provider { username_pattern: Email, }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // e.email.md: e.email -static P_E_EMAIL: Lazy = Lazy::new(|| Provider { +static P_E_EMAIL: Provider = Provider { id: "e.email", status: Status::Ok, before_login_hint: "", @@ -329,27 +327,26 @@ static P_E_EMAIL: Lazy = Lazy::new(|| Provider { username_pattern: Email, }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // espiv.net.md: espiv.net -static P_ESPIV_NET: Lazy = Lazy::new(|| Provider { +static P_ESPIV_NET: Provider = Provider { id: "espiv.net", status: Status::Ok, before_login_hint: "", after_login_hint: "", overview_page: "https://providers.delta.chat/espiv-net", server: &[], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // example.com.md: example.com, example.org, example.net -static P_EXAMPLE_COM: Lazy = Lazy::new(|| { - Provider { +static P_EXAMPLE_COM: Provider = Provider { id: "example.com", status: Status::Broken, before_login_hint: "Hush this provider doesn't exist!", @@ -359,14 +356,13 @@ static P_EXAMPLE_COM: Lazy = Lazy::new(|| { Server { protocol: Imap, socket: Ssl, hostname: "imap.example.com", port: 1337, username_pattern: Email }, Server { protocol: Smtp, socket: Starttls, hostname: "smtp.example.com", port: 1337, username_pattern: Email }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -} -}); +}; // fastmail.md: 123mail.org, 150mail.com, 150ml.com, 16mail.com, 2-mail.com, 4email.net, 50mail.com, airpost.net, allmail.net, bestmail.us, cluemail.com, elitemail.org, emailcorner.net, emailengine.net, emailengine.org, emailgroups.net, emailplus.org, emailuser.net, eml.cc, f-m.fm, fast-email.com, fast-mail.org, fastem.com, fastemail.us, fastemailer.com, fastest.cc, fastimap.com, fastmail.cn, fastmail.co.uk, fastmail.com, fastmail.com.au, fastmail.de, fastmail.es, fastmail.fm, fastmail.fr, fastmail.im, fastmail.in, fastmail.jp, fastmail.mx, fastmail.net, fastmail.nl, fastmail.org, fastmail.se, fastmail.to, fastmail.tw, fastmail.uk, fastmail.us, fastmailbox.net, fastmessaging.com, fea.st, fmail.co.uk, fmailbox.com, fmgirl.com, fmguy.com, ftml.net, h-mail.us, hailmail.net, imap-mail.com, imap.cc, imapmail.org, inoutbox.com, internet-e-mail.com, internet-mail.org, internetemails.net, internetmailing.net, jetemail.net, justemail.net, letterboxes.org, mail-central.com, mail-page.com, mailandftp.com, mailas.com, mailbolt.com, mailc.net, mailcan.com, mailforce.net, mailftp.com, mailhaven.com, mailingaddress.org, mailite.com, mailmight.com, mailnew.com, mailsent.net, mailservice.ms, mailup.net, mailworks.org, ml1.net, mm.st, myfastmail.com, mymacmail.com, nospammail.net, ownmail.net, petml.com, postinbox.com, postpro.net, proinbox.com, promessage.com, realemail.net, reallyfast.biz, reallyfast.info, rushpost.com, sent.as, sent.at, sent.com, speedpost.net, speedymail.org, ssl-mail.com, swift-mail.com, the-fastest.net, the-quickest.com, theinternetemail.com, veryfast.biz, veryspeedy.net, warpmail.net, xsmail.com, yepmail.net, your-mail.com -static P_FASTMAIL: Lazy = Lazy::new(|| Provider { +static P_FASTMAIL: Provider = Provider { id: "fastmail", status: Status::Preparation, before_login_hint: @@ -389,14 +385,13 @@ static P_FASTMAIL: Lazy = Lazy::new(|| Provider { username_pattern: Email, }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // firemail.de.md: firemail.at, firemail.de -static P_FIREMAIL_DE: Lazy = Lazy::new(|| { - Provider { +static P_FIREMAIL_DE: Provider = Provider { id: "firemail.de", status: Status::Preparation, before_login_hint: "Firemail erlaubt nur bei bezahlten Accounts den vollen Zugriff auf das E-Mail-Protokoll. Wenn Sie nicht für Firemail bezahlen, verwenden Sie bitte einen anderen E-Mail-Anbieter.", @@ -404,21 +399,20 @@ static P_FIREMAIL_DE: Lazy = Lazy::new(|| { overview_page: "https://providers.delta.chat/firemail-de", server: &[ ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -} -}); +}; // five.chat.md: five.chat -static P_FIVE_CHAT: Lazy = Lazy::new(|| Provider { +static P_FIVE_CHAT: Provider = Provider { id: "five.chat", status: Status::Ok, before_login_hint: "", after_login_hint: "", overview_page: "https://providers.delta.chat/five-chat", server: &[], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: Some(&[ ConfigDefault { key: Config::BccSelf, @@ -434,11 +428,10 @@ static P_FIVE_CHAT: Lazy = Lazy::new(|| Provider { }, ]), oauth2_authorizer: None, -}); +}; // freenet.de.md: freenet.de -static P_FREENET_DE: Lazy = Lazy::new(|| { - Provider { +static P_FREENET_DE: Provider = Provider { id: "freenet.de", status: Status::Preparation, before_login_hint: "Um deine freenet.de E-Mail-Adresse mit Delta Chat zu benutzen, musst du erst auf der freenet.de-Webseite \"POP3/IMAP/SMTP\" aktivieren.", @@ -448,15 +441,13 @@ static P_FREENET_DE: Lazy = Lazy::new(|| { Server { protocol: Imap, socket: Ssl, hostname: "mx.freenet.de", port: 993, username_pattern: Email }, Server { protocol: Smtp, socket: Starttls, hostname: "mx.freenet.de", port: 587, username_pattern: Email }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -} -}); +}; // gmail.md: gmail.com, googlemail.com, google.com -static P_GMAIL: Lazy = Lazy::new(|| { - Provider { +static P_GMAIL: Provider = Provider { id: "gmail", status: Status::Preparation, before_login_hint: "For Gmail accounts, you need to create an app-password if you have \"2-Step Verification\" enabled. If this setting is not available, you need to enable \"less secure apps\".", @@ -468,15 +459,14 @@ static P_GMAIL: Lazy = Lazy::new(|| { ], opt: ProviderOptions { delete_to_trash: true, - ..Default::default() + ..ProviderOptions::new() }, config_defaults: None, oauth2_authorizer: Some(Oauth2Authorizer::Gmail), -} -}); +}; // gmx.net.md: gmx.net, gmx.de, gmx.at, gmx.ch, gmx.org, gmx.eu, gmx.info, gmx.biz, gmx.com -static P_GMX_NET: Lazy = Lazy::new(|| Provider { +static P_GMX_NET: Provider = Provider { id: "gmx.net", status: Status::Preparation, before_login_hint: "You must allow IMAP access to your account before you can login.", @@ -505,13 +495,13 @@ static P_GMX_NET: Lazy = Lazy::new(|| Provider { username_pattern: Email, }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // hermes.radio.md: ac.hermes.radio, ac1.hermes.radio, ac2.hermes.radio, ac3.hermes.radio, ac4.hermes.radio, ac5.hermes.radio, ac6.hermes.radio, ac7.hermes.radio, ac8.hermes.radio, ac9.hermes.radio, ac10.hermes.radio, ac11.hermes.radio, ac12.hermes.radio, ac13.hermes.radio, ac14.hermes.radio, ac15.hermes.radio, ka.hermes.radio, ka1.hermes.radio, ka2.hermes.radio, ka3.hermes.radio, ka4.hermes.radio, ka5.hermes.radio, ka6.hermes.radio, ka7.hermes.radio, ka8.hermes.radio, ka9.hermes.radio, ka10.hermes.radio, ka11.hermes.radio, ka12.hermes.radio, ka13.hermes.radio, ka14.hermes.radio, ka15.hermes.radio, ec.hermes.radio, ec1.hermes.radio, ec2.hermes.radio, ec3.hermes.radio, ec4.hermes.radio, ec5.hermes.radio, ec6.hermes.radio, ec7.hermes.radio, ec8.hermes.radio, ec9.hermes.radio, ec10.hermes.radio, ec11.hermes.radio, ec12.hermes.radio, ec13.hermes.radio, ec14.hermes.radio, ec15.hermes.radio, hermes.radio -static P_HERMES_RADIO: Lazy = Lazy::new(|| Provider { +static P_HERMES_RADIO: Provider = Provider { id: "hermes.radio", status: Status::Ok, before_login_hint: "", @@ -520,7 +510,7 @@ static P_HERMES_RADIO: Lazy = Lazy::new(|| Provider { server: &[], opt: ProviderOptions { strict_tls: false, - ..Default::default() + ..ProviderOptions::new() }, config_defaults: Some(&[ ConfigDefault { @@ -537,11 +527,10 @@ static P_HERMES_RADIO: Lazy = Lazy::new(|| Provider { }, ]), oauth2_authorizer: None, -}); +}; // hey.com.md: hey.com -static P_HEY_COM: Lazy = Lazy::new(|| { - Provider { +static P_HEY_COM: Provider = Provider { id: "hey.com", status: Status::Broken, before_login_hint: "hey.com does not offer the standard IMAP e-mail protocol, so you cannot log in with Delta Chat to hey.com.", @@ -549,40 +538,39 @@ static P_HEY_COM: Lazy = Lazy::new(|| { overview_page: "https://providers.delta.chat/hey-com", server: &[ ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -} -}); +}; // i.ua.md: i.ua -static P_I_UA: Lazy = Lazy::new(|| Provider { +static P_I_UA: Provider = Provider { id: "i.ua", status: Status::Broken, before_login_hint: "Протокол IMAP не предоставляется и не планируется.", after_login_hint: "", overview_page: "https://providers.delta.chat/i-ua", server: &[], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // i3.net.md: i3.net -static P_I3_NET: Lazy = Lazy::new(|| Provider { +static P_I3_NET: Provider = Provider { id: "i3.net", status: Status::Ok, before_login_hint: "", after_login_hint: "", overview_page: "https://providers.delta.chat/i3-net", server: &[], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // icloud.md: icloud.com, me.com, mac.com -static P_ICLOUD: Lazy = Lazy::new(|| Provider { +static P_ICLOUD: Provider = Provider { id: "icloud", status: Status::Preparation, before_login_hint: "You must create an app-specific password for Delta Chat before login.", @@ -604,13 +592,13 @@ static P_ICLOUD: Lazy = Lazy::new(|| Provider { username_pattern: Email, }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // infomaniak.com.md: ik.me -static P_INFOMANIAK_COM: Lazy = Lazy::new(|| Provider { +static P_INFOMANIAK_COM: Provider = Provider { id: "infomaniak.com", status: Status::Ok, before_login_hint: "", @@ -634,40 +622,40 @@ static P_INFOMANIAK_COM: Lazy = Lazy::new(|| Provider { ], opt: ProviderOptions { max_smtp_rcpt_to: Some(10), - ..Default::default() + ..ProviderOptions::new() }, config_defaults: None, oauth2_authorizer: None, -}); +}; // kolst.com.md: kolst.com -static P_KOLST_COM: Lazy = Lazy::new(|| Provider { +static P_KOLST_COM: Provider = Provider { id: "kolst.com", status: Status::Ok, before_login_hint: "", after_login_hint: "", overview_page: "https://providers.delta.chat/kolst-com", server: &[], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // kontent.com.md: kontent.com -static P_KONTENT_COM: Lazy = Lazy::new(|| Provider { +static P_KONTENT_COM: Provider = Provider { id: "kontent.com", status: Status::Ok, before_login_hint: "", after_login_hint: "", overview_page: "https://providers.delta.chat/kontent-com", server: &[], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // mail.de.md: mail.de -static P_MAIL_DE: Lazy = Lazy::new(|| Provider { +static P_MAIL_DE: Provider = Provider { id: "mail.de", status: Status::Ok, before_login_hint: "", @@ -689,14 +677,13 @@ static P_MAIL_DE: Lazy = Lazy::new(|| Provider { username_pattern: Email, }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // mail.ru.md: mail.ru, inbox.ru, internet.ru, bk.ru, list.ru -static P_MAIL_RU: Lazy = Lazy::new(|| { - Provider { +static P_MAIL_RU: Provider = Provider { id: "mail.ru", status: Status::Preparation, before_login_hint: "Вам необходимо сгенерировать \"пароль для внешнего приложения\" в веб-интерфейсе mail.ru, чтобы mail.ru работал с Delta Chat.", @@ -706,14 +693,13 @@ static P_MAIL_RU: Lazy = Lazy::new(|| { Server { protocol: Imap, socket: Ssl, hostname: "imap.mail.ru", port: 993, username_pattern: Email }, Server { protocol: Smtp, socket: Ssl, hostname: "smtp.mail.ru", port: 465, username_pattern: Email }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -} -}); +}; // mail2tor.md: mail2tor.com -static P_MAIL2TOR: Lazy = Lazy::new(|| Provider { +static P_MAIL2TOR: Provider = Provider { id: "mail2tor", status: Status::Preparation, before_login_hint: "Tor is needed to connect to the email servers.", @@ -735,13 +721,13 @@ static P_MAIL2TOR: Lazy = Lazy::new(|| Provider { username_pattern: Email, }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // mailbox.org.md: mailbox.org, secure.mailbox.org -static P_MAILBOX_ORG: Lazy = Lazy::new(|| Provider { +static P_MAILBOX_ORG: Provider = Provider { id: "mailbox.org", status: Status::Ok, before_login_hint: "", @@ -763,13 +749,13 @@ static P_MAILBOX_ORG: Lazy = Lazy::new(|| Provider { username_pattern: Email, }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // mailo.com.md: mailo.com -static P_MAILO_COM: Lazy = Lazy::new(|| Provider { +static P_MAILO_COM: Provider = Provider { id: "mailo.com", status: Status::Ok, before_login_hint: "", @@ -791,13 +777,13 @@ static P_MAILO_COM: Lazy = Lazy::new(|| Provider { username_pattern: Email, }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // nauta.cu.md: nauta.cu -static P_NAUTA_CU: Lazy = Lazy::new(|| Provider { +static P_NAUTA_CU: Provider = Provider { id: "nauta.cu", status: Status::Ok, before_login_hint: "", @@ -822,7 +808,7 @@ static P_NAUTA_CU: Lazy = Lazy::new(|| Provider { opt: ProviderOptions { max_smtp_rcpt_to: Some(20), strict_tls: false, - ..Default::default() + ..ProviderOptions::new() }, config_defaults: Some(&[ ConfigDefault { @@ -851,10 +837,10 @@ static P_NAUTA_CU: Lazy = Lazy::new(|| Provider { }, ]), oauth2_authorizer: None, -}); +}; // naver.md: naver.com -static P_NAVER: Lazy = Lazy::new(|| Provider { +static P_NAVER: Provider = Provider { id: "naver", status: Status::Preparation, before_login_hint: "Manually enabling IMAP/SMTP is required.", @@ -876,13 +862,13 @@ static P_NAVER: Lazy = Lazy::new(|| Provider { username_pattern: Email, }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // nubo.coop.md: nubo.coop -static P_NUBO_COOP: Lazy = Lazy::new(|| Provider { +static P_NUBO_COOP: Provider = Provider { id: "nubo.coop", status: Status::Ok, before_login_hint: "", @@ -904,13 +890,13 @@ static P_NUBO_COOP: Lazy = Lazy::new(|| Provider { username_pattern: Email, }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // outlook.com.md: hotmail.com, outlook.com, office365.com, outlook.com.tr, live.com, outlook.de -static P_OUTLOOK_COM: Lazy = Lazy::new(|| Provider { +static P_OUTLOOK_COM: Provider = Provider { id: "outlook.com", status: Status::Ok, before_login_hint: "", @@ -932,13 +918,13 @@ static P_OUTLOOK_COM: Lazy = Lazy::new(|| Provider { username_pattern: Email, }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // ouvaton.coop.md: ouvaton.org -static P_OUVATON_COOP: Lazy = Lazy::new(|| Provider { +static P_OUVATON_COOP: Provider = Provider { id: "ouvaton.coop", status: Status::Ok, before_login_hint: "", @@ -960,13 +946,13 @@ static P_OUVATON_COOP: Lazy = Lazy::new(|| Provider { username_pattern: Email, }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // posteo.md: posteo.de, posteo.af, posteo.at, posteo.be, posteo.ca, posteo.ch, posteo.cl, posteo.co, posteo.co.uk, posteo.com.br, posteo.cr, posteo.cz, posteo.dk, posteo.ee, posteo.es, posteo.eu, posteo.fi, posteo.gl, posteo.gr, posteo.hn, posteo.hr, posteo.hu, posteo.ie, posteo.in, posteo.is, posteo.it, posteo.jp, posteo.la, posteo.li, posteo.lt, posteo.lu, posteo.me, posteo.mx, posteo.my, posteo.net, posteo.nl, posteo.no, posteo.nz, posteo.org, posteo.pe, posteo.pl, posteo.pm, posteo.pt, posteo.ro, posteo.ru, posteo.se, posteo.sg, posteo.si, posteo.tn, posteo.uk, posteo.us -static P_POSTEO: Lazy = Lazy::new(|| Provider { +static P_POSTEO: Provider = Provider { id: "posteo", status: Status::Ok, before_login_hint: "", @@ -1002,14 +988,13 @@ static P_POSTEO: Lazy = Lazy::new(|| Provider { username_pattern: Email, }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // protonmail.md: protonmail.com, protonmail.ch, pm.me -static P_PROTONMAIL: Lazy = Lazy::new(|| { - Provider { +static P_PROTONMAIL: Provider = Provider { id: "protonmail", status: Status::Broken, before_login_hint: "Protonmail does not offer the standard IMAP e-mail protocol, so you cannot log in with Delta Chat to Protonmail.", @@ -1017,15 +1002,13 @@ static P_PROTONMAIL: Lazy = Lazy::new(|| { overview_page: "https://providers.delta.chat/protonmail", server: &[ ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -} -}); +}; // qq.md: qq.com, foxmail.com -static P_QQ: Lazy = Lazy::new(|| { - Provider { +static P_QQ: Provider = Provider { id: "qq", status: Status::Preparation, before_login_hint: "Manually enabling IMAP/SMTP and creating an app-specific password for Delta Chat are required.", @@ -1035,14 +1018,13 @@ static P_QQ: Lazy = Lazy::new(|| { Server { protocol: Imap, socket: Ssl, hostname: "imap.qq.com", port: 993, username_pattern: Emaillocalpart }, Server { protocol: Smtp, socket: Ssl, hostname: "smtp.qq.com", port: 465, username_pattern: Email }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -} -}); +}; // riseup.net.md: riseup.net -static P_RISEUP_NET: Lazy = Lazy::new(|| Provider { +static P_RISEUP_NET: Provider = Provider { id: "riseup.net", status: Status::Ok, before_login_hint: "", @@ -1064,39 +1046,39 @@ static P_RISEUP_NET: Lazy = Lazy::new(|| Provider { username_pattern: Email, }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // rogers.com.md: rogers.com -static P_ROGERS_COM: Lazy = Lazy::new(|| Provider { +static P_ROGERS_COM: Provider = Provider { id: "rogers.com", status: Status::Ok, before_login_hint: "", after_login_hint: "", overview_page: "https://providers.delta.chat/rogers-com", server: &[], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // sonic.md: sonic.net -static P_SONIC: Lazy = Lazy::new(|| Provider { +static P_SONIC: Provider = Provider { id: "sonic", status: Status::Ok, before_login_hint: "", after_login_hint: "", overview_page: "https://providers.delta.chat/sonic", server: &[], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // systemausfall.org.md: systemausfall.org, solidaris.me -static P_SYSTEMAUSFALL_ORG: Lazy = Lazy::new(|| Provider { +static P_SYSTEMAUSFALL_ORG: Provider = Provider { id: "systemausfall.org", status: Status::Ok, before_login_hint: "", @@ -1118,13 +1100,13 @@ static P_SYSTEMAUSFALL_ORG: Lazy = Lazy::new(|| Provider { username_pattern: Email, }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // systemli.org.md: systemli.org -static P_SYSTEMLI_ORG: Lazy = Lazy::new(|| Provider { +static P_SYSTEMLI_ORG: Provider = Provider { id: "systemli.org", status: Status::Ok, before_login_hint: "", @@ -1146,14 +1128,13 @@ static P_SYSTEMLI_ORG: Lazy = Lazy::new(|| Provider { username_pattern: Email, }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // t-online.md: t-online.de, magenta.de -static P_T_ONLINE: Lazy = Lazy::new(|| { - Provider { +static P_T_ONLINE: Provider = Provider { id: "t-online", status: Status::Preparation, before_login_hint: "To use Delta Chat with a T-Online email address, you need to create an app password in the web interface.", @@ -1163,14 +1144,13 @@ static P_T_ONLINE: Lazy = Lazy::new(|| { Server { protocol: Imap, socket: Ssl, hostname: "secureimap.t-online.de", port: 993, username_pattern: Email }, Server { protocol: Smtp, socket: Ssl, hostname: "securesmtp.t-online.de", port: 465, username_pattern: Email }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -} -}); +}; // testrun.md: testrun.org -static P_TESTRUN: Lazy = Lazy::new(|| Provider { +static P_TESTRUN: Provider = Provider { id: "testrun", status: Status::Ok, before_login_hint: "", @@ -1199,7 +1179,7 @@ static P_TESTRUN: Lazy = Lazy::new(|| Provider { username_pattern: Email, }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: Some(&[ ConfigDefault { key: Config::BccSelf, @@ -1215,10 +1195,10 @@ static P_TESTRUN: Lazy = Lazy::new(|| Provider { }, ]), oauth2_authorizer: None, -}); +}; // tiscali.it.md: tiscali.it -static P_TISCALI_IT: Lazy = Lazy::new(|| Provider { +static P_TISCALI_IT: Provider = Provider { id: "tiscali.it", status: Status::Ok, before_login_hint: "", @@ -1240,14 +1220,13 @@ static P_TISCALI_IT: Lazy = Lazy::new(|| Provider { username_pattern: Email, }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // tutanota.md: tutanota.com, tutanota.de, tutamail.com, tuta.io, keemail.me -static P_TUTANOTA: Lazy = Lazy::new(|| { - Provider { +static P_TUTANOTA: Provider = Provider { id: "tutanota", status: Status::Broken, before_login_hint: "Tutanota does not offer the standard IMAP e-mail protocol, so you cannot log in with Delta Chat to Tutanota.", @@ -1255,27 +1234,26 @@ static P_TUTANOTA: Lazy = Lazy::new(|| { overview_page: "https://providers.delta.chat/tutanota", server: &[ ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -} -}); +}; // ukr.net.md: ukr.net -static P_UKR_NET: Lazy = Lazy::new(|| Provider { +static P_UKR_NET: Provider = Provider { id: "ukr.net", status: Status::Ok, before_login_hint: "", after_login_hint: "", overview_page: "https://providers.delta.chat/ukr-net", server: &[], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // undernet.uy.md: undernet.uy -static P_UNDERNET_UY: Lazy = Lazy::new(|| Provider { +static P_UNDERNET_UY: Provider = Provider { id: "undernet.uy", status: Status::Ok, before_login_hint: "", @@ -1297,26 +1275,26 @@ static P_UNDERNET_UY: Lazy = Lazy::new(|| Provider { username_pattern: Email, }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // vfemail.md: vfemail.net -static P_VFEMAIL: Lazy = Lazy::new(|| Provider { +static P_VFEMAIL: Provider = Provider { id: "vfemail", status: Status::Ok, before_login_hint: "", after_login_hint: "", overview_page: "https://providers.delta.chat/vfemail", server: &[], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // vivaldi.md: vivaldi.net -static P_VIVALDI: Lazy = Lazy::new(|| Provider { +static P_VIVALDI: Provider = Provider { id: "vivaldi", status: Status::Ok, before_login_hint: "", @@ -1338,13 +1316,13 @@ static P_VIVALDI: Lazy = Lazy::new(|| Provider { username_pattern: Email, }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // vodafone.de.md: vodafone.de, vodafonemail.de -static P_VODAFONE_DE: Lazy = Lazy::new(|| Provider { +static P_VODAFONE_DE: Provider = Provider { id: "vodafone.de", status: Status::Ok, before_login_hint: "", @@ -1366,14 +1344,13 @@ static P_VODAFONE_DE: Lazy = Lazy::new(|| Provider { username_pattern: Email, }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // web.de.md: web.de, email.de, flirt.ms, hallo.ms, kuss.ms, love.ms, magic.ms, singles.ms, cool.ms, kanzler.ms, okay.ms, party.ms, pop.ms, stars.ms, techno.ms, clever.ms, deutschland.ms, genial.ms, ich.ms, online.ms, smart.ms, wichtig.ms, action.ms, fussball.ms, joker.ms, planet.ms, power.ms -static P_WEB_DE: Lazy = Lazy::new(|| { - Provider { +static P_WEB_DE: Provider = Provider { id: "web.de", status: Status::Preparation, before_login_hint: "You must allow IMAP access to your account before you can login.", @@ -1384,15 +1361,13 @@ static P_WEB_DE: Lazy = Lazy::new(|| { Server { protocol: Imap, socket: Starttls, hostname: "imap.web.de", port: 143, username_pattern: Emaillocalpart }, Server { protocol: Smtp, socket: Starttls, hostname: "smtp.web.de", port: 587, username_pattern: Emaillocalpart }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -} -}); +}; // yahoo.md: yahoo.com, yahoo.de, yahoo.it, yahoo.fr, yahoo.es, yahoo.se, yahoo.co.uk, yahoo.co.nz, yahoo.com.au, yahoo.com.ar, yahoo.com.br, yahoo.com.mx, ymail.com, rocketmail.com, yahoodns.net -static P_YAHOO: Lazy = Lazy::new(|| { - Provider { +static P_YAHOO: Provider = Provider { id: "yahoo", status: Status::Preparation, before_login_hint: "To use Delta Chat with your Yahoo email address you have to create an \"App-Password\" in the account security screen.", @@ -1402,14 +1377,13 @@ static P_YAHOO: Lazy = Lazy::new(|| { Server { protocol: Imap, socket: Ssl, hostname: "imap.mail.yahoo.com", port: 993, username_pattern: Email }, Server { protocol: Smtp, socket: Ssl, hostname: "smtp.mail.yahoo.com", port: 465, username_pattern: Email }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -} -}); +}; // yandex.ru.md: yandex.com, yandex.by, yandex.kz, yandex.ru, yandex.ua, ya.ru, narod.ru -static P_YANDEX_RU: Lazy = Lazy::new(|| Provider { +static P_YANDEX_RU: Provider = Provider { id: "yandex.ru", status: Status::Preparation, before_login_hint: "For Yandex accounts, you have to set IMAP protocol option turned on.", @@ -1431,14 +1405,13 @@ static P_YANDEX_RU: Lazy = Lazy::new(|| Provider { username_pattern: Email, }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: Some(Oauth2Authorizer::Yandex), -}); +}; // yggmail.md: yggmail -static P_YGGMAIL: Lazy = Lazy::new(|| { - Provider { +static P_YGGMAIL: Provider = Provider { id: "yggmail", status: Status::Preparation, before_login_hint: "An Yggmail companion app needs to be installed on your device to access the Yggmail network.", @@ -1448,16 +1421,15 @@ static P_YGGMAIL: Lazy = Lazy::new(|| { Server { protocol: Imap, socket: Plain, hostname: "localhost", port: 1143, username_pattern: Email }, Server { protocol: Smtp, socket: Plain, hostname: "localhost", port: 1025, username_pattern: Email }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: Some(&[ ConfigDefault { key: Config::MvboxMove, value: "0" }, ]), oauth2_authorizer: None, -} -}); +}; // ziggo.nl.md: ziggo.nl -static P_ZIGGO_NL: Lazy = Lazy::new(|| Provider { +static P_ZIGGO_NL: Provider = Provider { id: "ziggo.nl", status: Status::Ok, before_login_hint: "", @@ -1479,13 +1451,13 @@ static P_ZIGGO_NL: Lazy = Lazy::new(|| Provider { username_pattern: Email, }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; // zoho.md: zohomail.eu, zohomail.com, zoho.com -static P_ZOHO: Lazy = Lazy::new(|| Provider { +static P_ZOHO: Provider = Provider { id: "zoho", status: Status::Preparation, before_login_hint: "To use Zoho Mail, you have to turn on IMAP in the Zoho Mail backend.", @@ -1507,441 +1479,441 @@ static P_ZOHO: Lazy = Lazy::new(|| Provider { username_pattern: Email, }, ], - opt: Default::default(), + opt: ProviderOptions::new(), config_defaults: None, oauth2_authorizer: None, -}); +}; pub(crate) static PROVIDER_DATA: Lazy> = Lazy::new(|| { HashMap::from([ - ("163.com", &*P_163), - ("aktivix.org", &*P_AKTIVIX_ORG), - ("aol.com", &*P_AOL), - ("arcor.de", &*P_ARCOR_DE), - ("autistici.org", &*P_AUTISTICI_ORG), - ("delta.blinzeln.de", &*P_BLINDZELN_ORG), - ("delta.blindzeln.org", &*P_BLINDZELN_ORG), - ("bluewin.ch", &*P_BLUEWIN_CH), - ("buzon.uy", &*P_BUZON_UY), - ("chello.at", &*P_CHELLO_AT), - ("xfinity.com", &*P_COMCAST), - ("comcast.net", &*P_COMCAST), - ("dismail.de", &*P_DISMAIL_DE), - ("disroot.org", &*P_DISROOT), - ("e.email", &*P_E_EMAIL), - ("espiv.net", &*P_ESPIV_NET), - ("example.com", &*P_EXAMPLE_COM), - ("example.org", &*P_EXAMPLE_COM), - ("example.net", &*P_EXAMPLE_COM), - ("123mail.org", &*P_FASTMAIL), - ("150mail.com", &*P_FASTMAIL), - ("150ml.com", &*P_FASTMAIL), - ("16mail.com", &*P_FASTMAIL), - ("2-mail.com", &*P_FASTMAIL), - ("4email.net", &*P_FASTMAIL), - ("50mail.com", &*P_FASTMAIL), - ("airpost.net", &*P_FASTMAIL), - ("allmail.net", &*P_FASTMAIL), - ("bestmail.us", &*P_FASTMAIL), - ("cluemail.com", &*P_FASTMAIL), - ("elitemail.org", &*P_FASTMAIL), - ("emailcorner.net", &*P_FASTMAIL), - ("emailengine.net", &*P_FASTMAIL), - ("emailengine.org", &*P_FASTMAIL), - ("emailgroups.net", &*P_FASTMAIL), - ("emailplus.org", &*P_FASTMAIL), - ("emailuser.net", &*P_FASTMAIL), - ("eml.cc", &*P_FASTMAIL), - ("f-m.fm", &*P_FASTMAIL), - ("fast-email.com", &*P_FASTMAIL), - ("fast-mail.org", &*P_FASTMAIL), - ("fastem.com", &*P_FASTMAIL), - ("fastemail.us", &*P_FASTMAIL), - ("fastemailer.com", &*P_FASTMAIL), - ("fastest.cc", &*P_FASTMAIL), - ("fastimap.com", &*P_FASTMAIL), - ("fastmail.cn", &*P_FASTMAIL), - ("fastmail.co.uk", &*P_FASTMAIL), - ("fastmail.com", &*P_FASTMAIL), - ("fastmail.com.au", &*P_FASTMAIL), - ("fastmail.de", &*P_FASTMAIL), - ("fastmail.es", &*P_FASTMAIL), - ("fastmail.fm", &*P_FASTMAIL), - ("fastmail.fr", &*P_FASTMAIL), - ("fastmail.im", &*P_FASTMAIL), - ("fastmail.in", &*P_FASTMAIL), - ("fastmail.jp", &*P_FASTMAIL), - ("fastmail.mx", &*P_FASTMAIL), - ("fastmail.net", &*P_FASTMAIL), - ("fastmail.nl", &*P_FASTMAIL), - ("fastmail.org", &*P_FASTMAIL), - ("fastmail.se", &*P_FASTMAIL), - ("fastmail.to", &*P_FASTMAIL), - ("fastmail.tw", &*P_FASTMAIL), - ("fastmail.uk", &*P_FASTMAIL), - ("fastmail.us", &*P_FASTMAIL), - ("fastmailbox.net", &*P_FASTMAIL), - ("fastmessaging.com", &*P_FASTMAIL), - ("fea.st", &*P_FASTMAIL), - ("fmail.co.uk", &*P_FASTMAIL), - ("fmailbox.com", &*P_FASTMAIL), - ("fmgirl.com", &*P_FASTMAIL), - ("fmguy.com", &*P_FASTMAIL), - ("ftml.net", &*P_FASTMAIL), - ("h-mail.us", &*P_FASTMAIL), - ("hailmail.net", &*P_FASTMAIL), - ("imap-mail.com", &*P_FASTMAIL), - ("imap.cc", &*P_FASTMAIL), - ("imapmail.org", &*P_FASTMAIL), - ("inoutbox.com", &*P_FASTMAIL), - ("internet-e-mail.com", &*P_FASTMAIL), - ("internet-mail.org", &*P_FASTMAIL), - ("internetemails.net", &*P_FASTMAIL), - ("internetmailing.net", &*P_FASTMAIL), - ("jetemail.net", &*P_FASTMAIL), - ("justemail.net", &*P_FASTMAIL), - ("letterboxes.org", &*P_FASTMAIL), - ("mail-central.com", &*P_FASTMAIL), - ("mail-page.com", &*P_FASTMAIL), - ("mailandftp.com", &*P_FASTMAIL), - ("mailas.com", &*P_FASTMAIL), - ("mailbolt.com", &*P_FASTMAIL), - ("mailc.net", &*P_FASTMAIL), - ("mailcan.com", &*P_FASTMAIL), - ("mailforce.net", &*P_FASTMAIL), - ("mailftp.com", &*P_FASTMAIL), - ("mailhaven.com", &*P_FASTMAIL), - ("mailingaddress.org", &*P_FASTMAIL), - ("mailite.com", &*P_FASTMAIL), - ("mailmight.com", &*P_FASTMAIL), - ("mailnew.com", &*P_FASTMAIL), - ("mailsent.net", &*P_FASTMAIL), - ("mailservice.ms", &*P_FASTMAIL), - ("mailup.net", &*P_FASTMAIL), - ("mailworks.org", &*P_FASTMAIL), - ("ml1.net", &*P_FASTMAIL), - ("mm.st", &*P_FASTMAIL), - ("myfastmail.com", &*P_FASTMAIL), - ("mymacmail.com", &*P_FASTMAIL), - ("nospammail.net", &*P_FASTMAIL), - ("ownmail.net", &*P_FASTMAIL), - ("petml.com", &*P_FASTMAIL), - ("postinbox.com", &*P_FASTMAIL), - ("postpro.net", &*P_FASTMAIL), - ("proinbox.com", &*P_FASTMAIL), - ("promessage.com", &*P_FASTMAIL), - ("realemail.net", &*P_FASTMAIL), - ("reallyfast.biz", &*P_FASTMAIL), - ("reallyfast.info", &*P_FASTMAIL), - ("rushpost.com", &*P_FASTMAIL), - ("sent.as", &*P_FASTMAIL), - ("sent.at", &*P_FASTMAIL), - ("sent.com", &*P_FASTMAIL), - ("speedpost.net", &*P_FASTMAIL), - ("speedymail.org", &*P_FASTMAIL), - ("ssl-mail.com", &*P_FASTMAIL), - ("swift-mail.com", &*P_FASTMAIL), - ("the-fastest.net", &*P_FASTMAIL), - ("the-quickest.com", &*P_FASTMAIL), - ("theinternetemail.com", &*P_FASTMAIL), - ("veryfast.biz", &*P_FASTMAIL), - ("veryspeedy.net", &*P_FASTMAIL), - ("warpmail.net", &*P_FASTMAIL), - ("xsmail.com", &*P_FASTMAIL), - ("yepmail.net", &*P_FASTMAIL), - ("your-mail.com", &*P_FASTMAIL), - ("firemail.at", &*P_FIREMAIL_DE), - ("firemail.de", &*P_FIREMAIL_DE), - ("five.chat", &*P_FIVE_CHAT), - ("freenet.de", &*P_FREENET_DE), - ("gmail.com", &*P_GMAIL), - ("googlemail.com", &*P_GMAIL), - ("google.com", &*P_GMAIL), - ("gmx.net", &*P_GMX_NET), - ("gmx.de", &*P_GMX_NET), - ("gmx.at", &*P_GMX_NET), - ("gmx.ch", &*P_GMX_NET), - ("gmx.org", &*P_GMX_NET), - ("gmx.eu", &*P_GMX_NET), - ("gmx.info", &*P_GMX_NET), - ("gmx.biz", &*P_GMX_NET), - ("gmx.com", &*P_GMX_NET), - ("ac.hermes.radio", &*P_HERMES_RADIO), - ("ac1.hermes.radio", &*P_HERMES_RADIO), - ("ac2.hermes.radio", &*P_HERMES_RADIO), - ("ac3.hermes.radio", &*P_HERMES_RADIO), - ("ac4.hermes.radio", &*P_HERMES_RADIO), - ("ac5.hermes.radio", &*P_HERMES_RADIO), - ("ac6.hermes.radio", &*P_HERMES_RADIO), - ("ac7.hermes.radio", &*P_HERMES_RADIO), - ("ac8.hermes.radio", &*P_HERMES_RADIO), - ("ac9.hermes.radio", &*P_HERMES_RADIO), - ("ac10.hermes.radio", &*P_HERMES_RADIO), - ("ac11.hermes.radio", &*P_HERMES_RADIO), - ("ac12.hermes.radio", &*P_HERMES_RADIO), - ("ac13.hermes.radio", &*P_HERMES_RADIO), - ("ac14.hermes.radio", &*P_HERMES_RADIO), - ("ac15.hermes.radio", &*P_HERMES_RADIO), - ("ka.hermes.radio", &*P_HERMES_RADIO), - ("ka1.hermes.radio", &*P_HERMES_RADIO), - ("ka2.hermes.radio", &*P_HERMES_RADIO), - ("ka3.hermes.radio", &*P_HERMES_RADIO), - ("ka4.hermes.radio", &*P_HERMES_RADIO), - ("ka5.hermes.radio", &*P_HERMES_RADIO), - ("ka6.hermes.radio", &*P_HERMES_RADIO), - ("ka7.hermes.radio", &*P_HERMES_RADIO), - ("ka8.hermes.radio", &*P_HERMES_RADIO), - ("ka9.hermes.radio", &*P_HERMES_RADIO), - ("ka10.hermes.radio", &*P_HERMES_RADIO), - ("ka11.hermes.radio", &*P_HERMES_RADIO), - ("ka12.hermes.radio", &*P_HERMES_RADIO), - ("ka13.hermes.radio", &*P_HERMES_RADIO), - ("ka14.hermes.radio", &*P_HERMES_RADIO), - ("ka15.hermes.radio", &*P_HERMES_RADIO), - ("ec.hermes.radio", &*P_HERMES_RADIO), - ("ec1.hermes.radio", &*P_HERMES_RADIO), - ("ec2.hermes.radio", &*P_HERMES_RADIO), - ("ec3.hermes.radio", &*P_HERMES_RADIO), - ("ec4.hermes.radio", &*P_HERMES_RADIO), - ("ec5.hermes.radio", &*P_HERMES_RADIO), - ("ec6.hermes.radio", &*P_HERMES_RADIO), - ("ec7.hermes.radio", &*P_HERMES_RADIO), - ("ec8.hermes.radio", &*P_HERMES_RADIO), - ("ec9.hermes.radio", &*P_HERMES_RADIO), - ("ec10.hermes.radio", &*P_HERMES_RADIO), - ("ec11.hermes.radio", &*P_HERMES_RADIO), - ("ec12.hermes.radio", &*P_HERMES_RADIO), - ("ec13.hermes.radio", &*P_HERMES_RADIO), - ("ec14.hermes.radio", &*P_HERMES_RADIO), - ("ec15.hermes.radio", &*P_HERMES_RADIO), - ("hermes.radio", &*P_HERMES_RADIO), - ("hey.com", &*P_HEY_COM), - ("i.ua", &*P_I_UA), - ("i3.net", &*P_I3_NET), - ("icloud.com", &*P_ICLOUD), - ("me.com", &*P_ICLOUD), - ("mac.com", &*P_ICLOUD), - ("ik.me", &*P_INFOMANIAK_COM), - ("kolst.com", &*P_KOLST_COM), - ("kontent.com", &*P_KONTENT_COM), - ("mail.de", &*P_MAIL_DE), - ("mail.ru", &*P_MAIL_RU), - ("inbox.ru", &*P_MAIL_RU), - ("internet.ru", &*P_MAIL_RU), - ("bk.ru", &*P_MAIL_RU), - ("list.ru", &*P_MAIL_RU), - ("mail2tor.com", &*P_MAIL2TOR), - ("mailbox.org", &*P_MAILBOX_ORG), - ("secure.mailbox.org", &*P_MAILBOX_ORG), - ("mailo.com", &*P_MAILO_COM), - ("nauta.cu", &*P_NAUTA_CU), - ("naver.com", &*P_NAVER), - ("nubo.coop", &*P_NUBO_COOP), - ("hotmail.com", &*P_OUTLOOK_COM), - ("outlook.com", &*P_OUTLOOK_COM), - ("office365.com", &*P_OUTLOOK_COM), - ("outlook.com.tr", &*P_OUTLOOK_COM), - ("live.com", &*P_OUTLOOK_COM), - ("outlook.de", &*P_OUTLOOK_COM), - ("ouvaton.org", &*P_OUVATON_COOP), - ("posteo.de", &*P_POSTEO), - ("posteo.af", &*P_POSTEO), - ("posteo.at", &*P_POSTEO), - ("posteo.be", &*P_POSTEO), - ("posteo.ca", &*P_POSTEO), - ("posteo.ch", &*P_POSTEO), - ("posteo.cl", &*P_POSTEO), - ("posteo.co", &*P_POSTEO), - ("posteo.co.uk", &*P_POSTEO), - ("posteo.com.br", &*P_POSTEO), - ("posteo.cr", &*P_POSTEO), - ("posteo.cz", &*P_POSTEO), - ("posteo.dk", &*P_POSTEO), - ("posteo.ee", &*P_POSTEO), - ("posteo.es", &*P_POSTEO), - ("posteo.eu", &*P_POSTEO), - ("posteo.fi", &*P_POSTEO), - ("posteo.gl", &*P_POSTEO), - ("posteo.gr", &*P_POSTEO), - ("posteo.hn", &*P_POSTEO), - ("posteo.hr", &*P_POSTEO), - ("posteo.hu", &*P_POSTEO), - ("posteo.ie", &*P_POSTEO), - ("posteo.in", &*P_POSTEO), - ("posteo.is", &*P_POSTEO), - ("posteo.it", &*P_POSTEO), - ("posteo.jp", &*P_POSTEO), - ("posteo.la", &*P_POSTEO), - ("posteo.li", &*P_POSTEO), - ("posteo.lt", &*P_POSTEO), - ("posteo.lu", &*P_POSTEO), - ("posteo.me", &*P_POSTEO), - ("posteo.mx", &*P_POSTEO), - ("posteo.my", &*P_POSTEO), - ("posteo.net", &*P_POSTEO), - ("posteo.nl", &*P_POSTEO), - ("posteo.no", &*P_POSTEO), - ("posteo.nz", &*P_POSTEO), - ("posteo.org", &*P_POSTEO), - ("posteo.pe", &*P_POSTEO), - ("posteo.pl", &*P_POSTEO), - ("posteo.pm", &*P_POSTEO), - ("posteo.pt", &*P_POSTEO), - ("posteo.ro", &*P_POSTEO), - ("posteo.ru", &*P_POSTEO), - ("posteo.se", &*P_POSTEO), - ("posteo.sg", &*P_POSTEO), - ("posteo.si", &*P_POSTEO), - ("posteo.tn", &*P_POSTEO), - ("posteo.uk", &*P_POSTEO), - ("posteo.us", &*P_POSTEO), - ("protonmail.com", &*P_PROTONMAIL), - ("protonmail.ch", &*P_PROTONMAIL), - ("pm.me", &*P_PROTONMAIL), - ("qq.com", &*P_QQ), - ("foxmail.com", &*P_QQ), - ("riseup.net", &*P_RISEUP_NET), - ("rogers.com", &*P_ROGERS_COM), - ("sonic.net", &*P_SONIC), - ("systemausfall.org", &*P_SYSTEMAUSFALL_ORG), - ("solidaris.me", &*P_SYSTEMAUSFALL_ORG), - ("systemli.org", &*P_SYSTEMLI_ORG), - ("t-online.de", &*P_T_ONLINE), - ("magenta.de", &*P_T_ONLINE), - ("testrun.org", &*P_TESTRUN), - ("tiscali.it", &*P_TISCALI_IT), - ("tutanota.com", &*P_TUTANOTA), - ("tutanota.de", &*P_TUTANOTA), - ("tutamail.com", &*P_TUTANOTA), - ("tuta.io", &*P_TUTANOTA), - ("keemail.me", &*P_TUTANOTA), - ("ukr.net", &*P_UKR_NET), - ("undernet.uy", &*P_UNDERNET_UY), - ("vfemail.net", &*P_VFEMAIL), - ("vivaldi.net", &*P_VIVALDI), - ("vodafone.de", &*P_VODAFONE_DE), - ("vodafonemail.de", &*P_VODAFONE_DE), - ("web.de", &*P_WEB_DE), - ("email.de", &*P_WEB_DE), - ("flirt.ms", &*P_WEB_DE), - ("hallo.ms", &*P_WEB_DE), - ("kuss.ms", &*P_WEB_DE), - ("love.ms", &*P_WEB_DE), - ("magic.ms", &*P_WEB_DE), - ("singles.ms", &*P_WEB_DE), - ("cool.ms", &*P_WEB_DE), - ("kanzler.ms", &*P_WEB_DE), - ("okay.ms", &*P_WEB_DE), - ("party.ms", &*P_WEB_DE), - ("pop.ms", &*P_WEB_DE), - ("stars.ms", &*P_WEB_DE), - ("techno.ms", &*P_WEB_DE), - ("clever.ms", &*P_WEB_DE), - ("deutschland.ms", &*P_WEB_DE), - ("genial.ms", &*P_WEB_DE), - ("ich.ms", &*P_WEB_DE), - ("online.ms", &*P_WEB_DE), - ("smart.ms", &*P_WEB_DE), - ("wichtig.ms", &*P_WEB_DE), - ("action.ms", &*P_WEB_DE), - ("fussball.ms", &*P_WEB_DE), - ("joker.ms", &*P_WEB_DE), - ("planet.ms", &*P_WEB_DE), - ("power.ms", &*P_WEB_DE), - ("yahoo.com", &*P_YAHOO), - ("yahoo.de", &*P_YAHOO), - ("yahoo.it", &*P_YAHOO), - ("yahoo.fr", &*P_YAHOO), - ("yahoo.es", &*P_YAHOO), - ("yahoo.se", &*P_YAHOO), - ("yahoo.co.uk", &*P_YAHOO), - ("yahoo.co.nz", &*P_YAHOO), - ("yahoo.com.au", &*P_YAHOO), - ("yahoo.com.ar", &*P_YAHOO), - ("yahoo.com.br", &*P_YAHOO), - ("yahoo.com.mx", &*P_YAHOO), - ("ymail.com", &*P_YAHOO), - ("rocketmail.com", &*P_YAHOO), - ("yahoodns.net", &*P_YAHOO), - ("yandex.com", &*P_YANDEX_RU), - ("yandex.by", &*P_YANDEX_RU), - ("yandex.kz", &*P_YANDEX_RU), - ("yandex.ru", &*P_YANDEX_RU), - ("yandex.ua", &*P_YANDEX_RU), - ("ya.ru", &*P_YANDEX_RU), - ("narod.ru", &*P_YANDEX_RU), - ("yggmail", &*P_YGGMAIL), - ("ziggo.nl", &*P_ZIGGO_NL), - ("zohomail.eu", &*P_ZOHO), - ("zohomail.com", &*P_ZOHO), - ("zoho.com", &*P_ZOHO), + ("163.com", &P_163), + ("aktivix.org", &P_AKTIVIX_ORG), + ("aol.com", &P_AOL), + ("arcor.de", &P_ARCOR_DE), + ("autistici.org", &P_AUTISTICI_ORG), + ("delta.blinzeln.de", &P_BLINDZELN_ORG), + ("delta.blindzeln.org", &P_BLINDZELN_ORG), + ("bluewin.ch", &P_BLUEWIN_CH), + ("buzon.uy", &P_BUZON_UY), + ("chello.at", &P_CHELLO_AT), + ("xfinity.com", &P_COMCAST), + ("comcast.net", &P_COMCAST), + ("dismail.de", &P_DISMAIL_DE), + ("disroot.org", &P_DISROOT), + ("e.email", &P_E_EMAIL), + ("espiv.net", &P_ESPIV_NET), + ("example.com", &P_EXAMPLE_COM), + ("example.org", &P_EXAMPLE_COM), + ("example.net", &P_EXAMPLE_COM), + ("123mail.org", &P_FASTMAIL), + ("150mail.com", &P_FASTMAIL), + ("150ml.com", &P_FASTMAIL), + ("16mail.com", &P_FASTMAIL), + ("2-mail.com", &P_FASTMAIL), + ("4email.net", &P_FASTMAIL), + ("50mail.com", &P_FASTMAIL), + ("airpost.net", &P_FASTMAIL), + ("allmail.net", &P_FASTMAIL), + ("bestmail.us", &P_FASTMAIL), + ("cluemail.com", &P_FASTMAIL), + ("elitemail.org", &P_FASTMAIL), + ("emailcorner.net", &P_FASTMAIL), + ("emailengine.net", &P_FASTMAIL), + ("emailengine.org", &P_FASTMAIL), + ("emailgroups.net", &P_FASTMAIL), + ("emailplus.org", &P_FASTMAIL), + ("emailuser.net", &P_FASTMAIL), + ("eml.cc", &P_FASTMAIL), + ("f-m.fm", &P_FASTMAIL), + ("fast-email.com", &P_FASTMAIL), + ("fast-mail.org", &P_FASTMAIL), + ("fastem.com", &P_FASTMAIL), + ("fastemail.us", &P_FASTMAIL), + ("fastemailer.com", &P_FASTMAIL), + ("fastest.cc", &P_FASTMAIL), + ("fastimap.com", &P_FASTMAIL), + ("fastmail.cn", &P_FASTMAIL), + ("fastmail.co.uk", &P_FASTMAIL), + ("fastmail.com", &P_FASTMAIL), + ("fastmail.com.au", &P_FASTMAIL), + ("fastmail.de", &P_FASTMAIL), + ("fastmail.es", &P_FASTMAIL), + ("fastmail.fm", &P_FASTMAIL), + ("fastmail.fr", &P_FASTMAIL), + ("fastmail.im", &P_FASTMAIL), + ("fastmail.in", &P_FASTMAIL), + ("fastmail.jp", &P_FASTMAIL), + ("fastmail.mx", &P_FASTMAIL), + ("fastmail.net", &P_FASTMAIL), + ("fastmail.nl", &P_FASTMAIL), + ("fastmail.org", &P_FASTMAIL), + ("fastmail.se", &P_FASTMAIL), + ("fastmail.to", &P_FASTMAIL), + ("fastmail.tw", &P_FASTMAIL), + ("fastmail.uk", &P_FASTMAIL), + ("fastmail.us", &P_FASTMAIL), + ("fastmailbox.net", &P_FASTMAIL), + ("fastmessaging.com", &P_FASTMAIL), + ("fea.st", &P_FASTMAIL), + ("fmail.co.uk", &P_FASTMAIL), + ("fmailbox.com", &P_FASTMAIL), + ("fmgirl.com", &P_FASTMAIL), + ("fmguy.com", &P_FASTMAIL), + ("ftml.net", &P_FASTMAIL), + ("h-mail.us", &P_FASTMAIL), + ("hailmail.net", &P_FASTMAIL), + ("imap-mail.com", &P_FASTMAIL), + ("imap.cc", &P_FASTMAIL), + ("imapmail.org", &P_FASTMAIL), + ("inoutbox.com", &P_FASTMAIL), + ("internet-e-mail.com", &P_FASTMAIL), + ("internet-mail.org", &P_FASTMAIL), + ("internetemails.net", &P_FASTMAIL), + ("internetmailing.net", &P_FASTMAIL), + ("jetemail.net", &P_FASTMAIL), + ("justemail.net", &P_FASTMAIL), + ("letterboxes.org", &P_FASTMAIL), + ("mail-central.com", &P_FASTMAIL), + ("mail-page.com", &P_FASTMAIL), + ("mailandftp.com", &P_FASTMAIL), + ("mailas.com", &P_FASTMAIL), + ("mailbolt.com", &P_FASTMAIL), + ("mailc.net", &P_FASTMAIL), + ("mailcan.com", &P_FASTMAIL), + ("mailforce.net", &P_FASTMAIL), + ("mailftp.com", &P_FASTMAIL), + ("mailhaven.com", &P_FASTMAIL), + ("mailingaddress.org", &P_FASTMAIL), + ("mailite.com", &P_FASTMAIL), + ("mailmight.com", &P_FASTMAIL), + ("mailnew.com", &P_FASTMAIL), + ("mailsent.net", &P_FASTMAIL), + ("mailservice.ms", &P_FASTMAIL), + ("mailup.net", &P_FASTMAIL), + ("mailworks.org", &P_FASTMAIL), + ("ml1.net", &P_FASTMAIL), + ("mm.st", &P_FASTMAIL), + ("myfastmail.com", &P_FASTMAIL), + ("mymacmail.com", &P_FASTMAIL), + ("nospammail.net", &P_FASTMAIL), + ("ownmail.net", &P_FASTMAIL), + ("petml.com", &P_FASTMAIL), + ("postinbox.com", &P_FASTMAIL), + ("postpro.net", &P_FASTMAIL), + ("proinbox.com", &P_FASTMAIL), + ("promessage.com", &P_FASTMAIL), + ("realemail.net", &P_FASTMAIL), + ("reallyfast.biz", &P_FASTMAIL), + ("reallyfast.info", &P_FASTMAIL), + ("rushpost.com", &P_FASTMAIL), + ("sent.as", &P_FASTMAIL), + ("sent.at", &P_FASTMAIL), + ("sent.com", &P_FASTMAIL), + ("speedpost.net", &P_FASTMAIL), + ("speedymail.org", &P_FASTMAIL), + ("ssl-mail.com", &P_FASTMAIL), + ("swift-mail.com", &P_FASTMAIL), + ("the-fastest.net", &P_FASTMAIL), + ("the-quickest.com", &P_FASTMAIL), + ("theinternetemail.com", &P_FASTMAIL), + ("veryfast.biz", &P_FASTMAIL), + ("veryspeedy.net", &P_FASTMAIL), + ("warpmail.net", &P_FASTMAIL), + ("xsmail.com", &P_FASTMAIL), + ("yepmail.net", &P_FASTMAIL), + ("your-mail.com", &P_FASTMAIL), + ("firemail.at", &P_FIREMAIL_DE), + ("firemail.de", &P_FIREMAIL_DE), + ("five.chat", &P_FIVE_CHAT), + ("freenet.de", &P_FREENET_DE), + ("gmail.com", &P_GMAIL), + ("googlemail.com", &P_GMAIL), + ("google.com", &P_GMAIL), + ("gmx.net", &P_GMX_NET), + ("gmx.de", &P_GMX_NET), + ("gmx.at", &P_GMX_NET), + ("gmx.ch", &P_GMX_NET), + ("gmx.org", &P_GMX_NET), + ("gmx.eu", &P_GMX_NET), + ("gmx.info", &P_GMX_NET), + ("gmx.biz", &P_GMX_NET), + ("gmx.com", &P_GMX_NET), + ("ac.hermes.radio", &P_HERMES_RADIO), + ("ac1.hermes.radio", &P_HERMES_RADIO), + ("ac2.hermes.radio", &P_HERMES_RADIO), + ("ac3.hermes.radio", &P_HERMES_RADIO), + ("ac4.hermes.radio", &P_HERMES_RADIO), + ("ac5.hermes.radio", &P_HERMES_RADIO), + ("ac6.hermes.radio", &P_HERMES_RADIO), + ("ac7.hermes.radio", &P_HERMES_RADIO), + ("ac8.hermes.radio", &P_HERMES_RADIO), + ("ac9.hermes.radio", &P_HERMES_RADIO), + ("ac10.hermes.radio", &P_HERMES_RADIO), + ("ac11.hermes.radio", &P_HERMES_RADIO), + ("ac12.hermes.radio", &P_HERMES_RADIO), + ("ac13.hermes.radio", &P_HERMES_RADIO), + ("ac14.hermes.radio", &P_HERMES_RADIO), + ("ac15.hermes.radio", &P_HERMES_RADIO), + ("ka.hermes.radio", &P_HERMES_RADIO), + ("ka1.hermes.radio", &P_HERMES_RADIO), + ("ka2.hermes.radio", &P_HERMES_RADIO), + ("ka3.hermes.radio", &P_HERMES_RADIO), + ("ka4.hermes.radio", &P_HERMES_RADIO), + ("ka5.hermes.radio", &P_HERMES_RADIO), + ("ka6.hermes.radio", &P_HERMES_RADIO), + ("ka7.hermes.radio", &P_HERMES_RADIO), + ("ka8.hermes.radio", &P_HERMES_RADIO), + ("ka9.hermes.radio", &P_HERMES_RADIO), + ("ka10.hermes.radio", &P_HERMES_RADIO), + ("ka11.hermes.radio", &P_HERMES_RADIO), + ("ka12.hermes.radio", &P_HERMES_RADIO), + ("ka13.hermes.radio", &P_HERMES_RADIO), + ("ka14.hermes.radio", &P_HERMES_RADIO), + ("ka15.hermes.radio", &P_HERMES_RADIO), + ("ec.hermes.radio", &P_HERMES_RADIO), + ("ec1.hermes.radio", &P_HERMES_RADIO), + ("ec2.hermes.radio", &P_HERMES_RADIO), + ("ec3.hermes.radio", &P_HERMES_RADIO), + ("ec4.hermes.radio", &P_HERMES_RADIO), + ("ec5.hermes.radio", &P_HERMES_RADIO), + ("ec6.hermes.radio", &P_HERMES_RADIO), + ("ec7.hermes.radio", &P_HERMES_RADIO), + ("ec8.hermes.radio", &P_HERMES_RADIO), + ("ec9.hermes.radio", &P_HERMES_RADIO), + ("ec10.hermes.radio", &P_HERMES_RADIO), + ("ec11.hermes.radio", &P_HERMES_RADIO), + ("ec12.hermes.radio", &P_HERMES_RADIO), + ("ec13.hermes.radio", &P_HERMES_RADIO), + ("ec14.hermes.radio", &P_HERMES_RADIO), + ("ec15.hermes.radio", &P_HERMES_RADIO), + ("hermes.radio", &P_HERMES_RADIO), + ("hey.com", &P_HEY_COM), + ("i.ua", &P_I_UA), + ("i3.net", &P_I3_NET), + ("icloud.com", &P_ICLOUD), + ("me.com", &P_ICLOUD), + ("mac.com", &P_ICLOUD), + ("ik.me", &P_INFOMANIAK_COM), + ("kolst.com", &P_KOLST_COM), + ("kontent.com", &P_KONTENT_COM), + ("mail.de", &P_MAIL_DE), + ("mail.ru", &P_MAIL_RU), + ("inbox.ru", &P_MAIL_RU), + ("internet.ru", &P_MAIL_RU), + ("bk.ru", &P_MAIL_RU), + ("list.ru", &P_MAIL_RU), + ("mail2tor.com", &P_MAIL2TOR), + ("mailbox.org", &P_MAILBOX_ORG), + ("secure.mailbox.org", &P_MAILBOX_ORG), + ("mailo.com", &P_MAILO_COM), + ("nauta.cu", &P_NAUTA_CU), + ("naver.com", &P_NAVER), + ("nubo.coop", &P_NUBO_COOP), + ("hotmail.com", &P_OUTLOOK_COM), + ("outlook.com", &P_OUTLOOK_COM), + ("office365.com", &P_OUTLOOK_COM), + ("outlook.com.tr", &P_OUTLOOK_COM), + ("live.com", &P_OUTLOOK_COM), + ("outlook.de", &P_OUTLOOK_COM), + ("ouvaton.org", &P_OUVATON_COOP), + ("posteo.de", &P_POSTEO), + ("posteo.af", &P_POSTEO), + ("posteo.at", &P_POSTEO), + ("posteo.be", &P_POSTEO), + ("posteo.ca", &P_POSTEO), + ("posteo.ch", &P_POSTEO), + ("posteo.cl", &P_POSTEO), + ("posteo.co", &P_POSTEO), + ("posteo.co.uk", &P_POSTEO), + ("posteo.com.br", &P_POSTEO), + ("posteo.cr", &P_POSTEO), + ("posteo.cz", &P_POSTEO), + ("posteo.dk", &P_POSTEO), + ("posteo.ee", &P_POSTEO), + ("posteo.es", &P_POSTEO), + ("posteo.eu", &P_POSTEO), + ("posteo.fi", &P_POSTEO), + ("posteo.gl", &P_POSTEO), + ("posteo.gr", &P_POSTEO), + ("posteo.hn", &P_POSTEO), + ("posteo.hr", &P_POSTEO), + ("posteo.hu", &P_POSTEO), + ("posteo.ie", &P_POSTEO), + ("posteo.in", &P_POSTEO), + ("posteo.is", &P_POSTEO), + ("posteo.it", &P_POSTEO), + ("posteo.jp", &P_POSTEO), + ("posteo.la", &P_POSTEO), + ("posteo.li", &P_POSTEO), + ("posteo.lt", &P_POSTEO), + ("posteo.lu", &P_POSTEO), + ("posteo.me", &P_POSTEO), + ("posteo.mx", &P_POSTEO), + ("posteo.my", &P_POSTEO), + ("posteo.net", &P_POSTEO), + ("posteo.nl", &P_POSTEO), + ("posteo.no", &P_POSTEO), + ("posteo.nz", &P_POSTEO), + ("posteo.org", &P_POSTEO), + ("posteo.pe", &P_POSTEO), + ("posteo.pl", &P_POSTEO), + ("posteo.pm", &P_POSTEO), + ("posteo.pt", &P_POSTEO), + ("posteo.ro", &P_POSTEO), + ("posteo.ru", &P_POSTEO), + ("posteo.se", &P_POSTEO), + ("posteo.sg", &P_POSTEO), + ("posteo.si", &P_POSTEO), + ("posteo.tn", &P_POSTEO), + ("posteo.uk", &P_POSTEO), + ("posteo.us", &P_POSTEO), + ("protonmail.com", &P_PROTONMAIL), + ("protonmail.ch", &P_PROTONMAIL), + ("pm.me", &P_PROTONMAIL), + ("qq.com", &P_QQ), + ("foxmail.com", &P_QQ), + ("riseup.net", &P_RISEUP_NET), + ("rogers.com", &P_ROGERS_COM), + ("sonic.net", &P_SONIC), + ("systemausfall.org", &P_SYSTEMAUSFALL_ORG), + ("solidaris.me", &P_SYSTEMAUSFALL_ORG), + ("systemli.org", &P_SYSTEMLI_ORG), + ("t-online.de", &P_T_ONLINE), + ("magenta.de", &P_T_ONLINE), + ("testrun.org", &P_TESTRUN), + ("tiscali.it", &P_TISCALI_IT), + ("tutanota.com", &P_TUTANOTA), + ("tutanota.de", &P_TUTANOTA), + ("tutamail.com", &P_TUTANOTA), + ("tuta.io", &P_TUTANOTA), + ("keemail.me", &P_TUTANOTA), + ("ukr.net", &P_UKR_NET), + ("undernet.uy", &P_UNDERNET_UY), + ("vfemail.net", &P_VFEMAIL), + ("vivaldi.net", &P_VIVALDI), + ("vodafone.de", &P_VODAFONE_DE), + ("vodafonemail.de", &P_VODAFONE_DE), + ("web.de", &P_WEB_DE), + ("email.de", &P_WEB_DE), + ("flirt.ms", &P_WEB_DE), + ("hallo.ms", &P_WEB_DE), + ("kuss.ms", &P_WEB_DE), + ("love.ms", &P_WEB_DE), + ("magic.ms", &P_WEB_DE), + ("singles.ms", &P_WEB_DE), + ("cool.ms", &P_WEB_DE), + ("kanzler.ms", &P_WEB_DE), + ("okay.ms", &P_WEB_DE), + ("party.ms", &P_WEB_DE), + ("pop.ms", &P_WEB_DE), + ("stars.ms", &P_WEB_DE), + ("techno.ms", &P_WEB_DE), + ("clever.ms", &P_WEB_DE), + ("deutschland.ms", &P_WEB_DE), + ("genial.ms", &P_WEB_DE), + ("ich.ms", &P_WEB_DE), + ("online.ms", &P_WEB_DE), + ("smart.ms", &P_WEB_DE), + ("wichtig.ms", &P_WEB_DE), + ("action.ms", &P_WEB_DE), + ("fussball.ms", &P_WEB_DE), + ("joker.ms", &P_WEB_DE), + ("planet.ms", &P_WEB_DE), + ("power.ms", &P_WEB_DE), + ("yahoo.com", &P_YAHOO), + ("yahoo.de", &P_YAHOO), + ("yahoo.it", &P_YAHOO), + ("yahoo.fr", &P_YAHOO), + ("yahoo.es", &P_YAHOO), + ("yahoo.se", &P_YAHOO), + ("yahoo.co.uk", &P_YAHOO), + ("yahoo.co.nz", &P_YAHOO), + ("yahoo.com.au", &P_YAHOO), + ("yahoo.com.ar", &P_YAHOO), + ("yahoo.com.br", &P_YAHOO), + ("yahoo.com.mx", &P_YAHOO), + ("ymail.com", &P_YAHOO), + ("rocketmail.com", &P_YAHOO), + ("yahoodns.net", &P_YAHOO), + ("yandex.com", &P_YANDEX_RU), + ("yandex.by", &P_YANDEX_RU), + ("yandex.kz", &P_YANDEX_RU), + ("yandex.ru", &P_YANDEX_RU), + ("yandex.ua", &P_YANDEX_RU), + ("ya.ru", &P_YANDEX_RU), + ("narod.ru", &P_YANDEX_RU), + ("yggmail", &P_YGGMAIL), + ("ziggo.nl", &P_ZIGGO_NL), + ("zohomail.eu", &P_ZOHO), + ("zohomail.com", &P_ZOHO), + ("zoho.com", &P_ZOHO), ]) }); pub(crate) static PROVIDER_IDS: Lazy> = Lazy::new(|| { HashMap::from([ - ("163", &*P_163), - ("aktivix.org", &*P_AKTIVIX_ORG), - ("aol", &*P_AOL), - ("arcor.de", &*P_ARCOR_DE), - ("autistici.org", &*P_AUTISTICI_ORG), - ("blindzeln.org", &*P_BLINDZELN_ORG), - ("bluewin.ch", &*P_BLUEWIN_CH), - ("buzon.uy", &*P_BUZON_UY), - ("chello.at", &*P_CHELLO_AT), - ("comcast", &*P_COMCAST), - ("dismail.de", &*P_DISMAIL_DE), - ("disroot", &*P_DISROOT), - ("e.email", &*P_E_EMAIL), - ("espiv.net", &*P_ESPIV_NET), - ("example.com", &*P_EXAMPLE_COM), - ("fastmail", &*P_FASTMAIL), - ("firemail.de", &*P_FIREMAIL_DE), - ("five.chat", &*P_FIVE_CHAT), - ("freenet.de", &*P_FREENET_DE), - ("gmail", &*P_GMAIL), - ("gmx.net", &*P_GMX_NET), - ("hermes.radio", &*P_HERMES_RADIO), - ("hey.com", &*P_HEY_COM), - ("i.ua", &*P_I_UA), - ("i3.net", &*P_I3_NET), - ("icloud", &*P_ICLOUD), - ("infomaniak.com", &*P_INFOMANIAK_COM), - ("kolst.com", &*P_KOLST_COM), - ("kontent.com", &*P_KONTENT_COM), - ("mail.de", &*P_MAIL_DE), - ("mail.ru", &*P_MAIL_RU), - ("mail2tor", &*P_MAIL2TOR), - ("mailbox.org", &*P_MAILBOX_ORG), - ("mailo.com", &*P_MAILO_COM), - ("nauta.cu", &*P_NAUTA_CU), - ("naver", &*P_NAVER), - ("nubo.coop", &*P_NUBO_COOP), - ("outlook.com", &*P_OUTLOOK_COM), - ("ouvaton.coop", &*P_OUVATON_COOP), - ("posteo", &*P_POSTEO), - ("protonmail", &*P_PROTONMAIL), - ("qq", &*P_QQ), - ("riseup.net", &*P_RISEUP_NET), - ("rogers.com", &*P_ROGERS_COM), - ("sonic", &*P_SONIC), - ("systemausfall.org", &*P_SYSTEMAUSFALL_ORG), - ("systemli.org", &*P_SYSTEMLI_ORG), - ("t-online", &*P_T_ONLINE), - ("testrun", &*P_TESTRUN), - ("tiscali.it", &*P_TISCALI_IT), - ("tutanota", &*P_TUTANOTA), - ("ukr.net", &*P_UKR_NET), - ("undernet.uy", &*P_UNDERNET_UY), - ("vfemail", &*P_VFEMAIL), - ("vivaldi", &*P_VIVALDI), - ("vodafone.de", &*P_VODAFONE_DE), - ("web.de", &*P_WEB_DE), - ("yahoo", &*P_YAHOO), - ("yandex.ru", &*P_YANDEX_RU), - ("yggmail", &*P_YGGMAIL), - ("ziggo.nl", &*P_ZIGGO_NL), - ("zoho", &*P_ZOHO), + ("163", &P_163), + ("aktivix.org", &P_AKTIVIX_ORG), + ("aol", &P_AOL), + ("arcor.de", &P_ARCOR_DE), + ("autistici.org", &P_AUTISTICI_ORG), + ("blindzeln.org", &P_BLINDZELN_ORG), + ("bluewin.ch", &P_BLUEWIN_CH), + ("buzon.uy", &P_BUZON_UY), + ("chello.at", &P_CHELLO_AT), + ("comcast", &P_COMCAST), + ("dismail.de", &P_DISMAIL_DE), + ("disroot", &P_DISROOT), + ("e.email", &P_E_EMAIL), + ("espiv.net", &P_ESPIV_NET), + ("example.com", &P_EXAMPLE_COM), + ("fastmail", &P_FASTMAIL), + ("firemail.de", &P_FIREMAIL_DE), + ("five.chat", &P_FIVE_CHAT), + ("freenet.de", &P_FREENET_DE), + ("gmail", &P_GMAIL), + ("gmx.net", &P_GMX_NET), + ("hermes.radio", &P_HERMES_RADIO), + ("hey.com", &P_HEY_COM), + ("i.ua", &P_I_UA), + ("i3.net", &P_I3_NET), + ("icloud", &P_ICLOUD), + ("infomaniak.com", &P_INFOMANIAK_COM), + ("kolst.com", &P_KOLST_COM), + ("kontent.com", &P_KONTENT_COM), + ("mail.de", &P_MAIL_DE), + ("mail.ru", &P_MAIL_RU), + ("mail2tor", &P_MAIL2TOR), + ("mailbox.org", &P_MAILBOX_ORG), + ("mailo.com", &P_MAILO_COM), + ("nauta.cu", &P_NAUTA_CU), + ("naver", &P_NAVER), + ("nubo.coop", &P_NUBO_COOP), + ("outlook.com", &P_OUTLOOK_COM), + ("ouvaton.coop", &P_OUVATON_COOP), + ("posteo", &P_POSTEO), + ("protonmail", &P_PROTONMAIL), + ("qq", &P_QQ), + ("riseup.net", &P_RISEUP_NET), + ("rogers.com", &P_ROGERS_COM), + ("sonic", &P_SONIC), + ("systemausfall.org", &P_SYSTEMAUSFALL_ORG), + ("systemli.org", &P_SYSTEMLI_ORG), + ("t-online", &P_T_ONLINE), + ("testrun", &P_TESTRUN), + ("tiscali.it", &P_TISCALI_IT), + ("tutanota", &P_TUTANOTA), + ("ukr.net", &P_UKR_NET), + ("undernet.uy", &P_UNDERNET_UY), + ("vfemail", &P_VFEMAIL), + ("vivaldi", &P_VIVALDI), + ("vodafone.de", &P_VODAFONE_DE), + ("web.de", &P_WEB_DE), + ("yahoo", &P_YAHOO), + ("yandex.ru", &P_YANDEX_RU), + ("yggmail", &P_YGGMAIL), + ("ziggo.nl", &P_ZIGGO_NL), + ("zoho", &P_ZOHO), ]) }); diff --git a/src/smtp.rs b/src/smtp.rs index 5195826a0..457023623 100644 --- a/src/smtp.rs +++ b/src/smtp.rs @@ -493,7 +493,20 @@ pub(crate) async fn smtp_send( if let SendResult::Failure(err) = &status { // We couldn't send the message, so mark it as failed - message::set_msg_failed(context, msg_id, &err.to_string()).await; + match Message::load_from_db(context, msg_id).await { + Ok(mut msg) => { + 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:#}."); + } + } + Err(err) => { + error!( + context, + "Failed to load {msg_id} to mark it as failed: {err:#}." + ); + } + } } status } @@ -540,7 +553,8 @@ pub(crate) async fn send_msg_to_smtp( ) .await?; if retries > 6 { - message::set_msg_failed(context, msg_id, "Number of retries exceeded the limit.").await; + let mut msg = Message::load_from_db(context, msg_id).await?; + message::set_msg_failed(context, &mut msg, "Number of retries exceeded the limit.").await?; context .sql .execute("DELETE FROM smtp WHERE id=?", (rowid,)) diff --git a/src/sql.rs b/src/sql.rs index 90549dd4b..ab780a452 100644 --- a/src/sql.rs +++ b/src/sql.rs @@ -304,6 +304,20 @@ impl Sql { } } + /// Changes the passphrase of encrypted database. + /// + /// The database must already be encrypted and the passphrase cannot be empty. + /// It is impossible to turn encrypted database into unencrypted + /// and vice versa this way, use import/export for this. + pub async fn change_passphrase(&self, passphrase: String) -> Result<()> { + self.call_write(move |conn| { + conn.pragma_update(None, "rekey", passphrase) + .context("failed to set PRAGMA rekey")?; + Ok(()) + }) + .await + } + /// Locks the write transactions mutex in order to make sure that there never are /// multiple write transactions at once. /// @@ -1246,6 +1260,49 @@ mod tests { sql.open(&t, "foo".to_string()) .await .context("failed to open the database second time")?; + + Ok(()) + } + + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn test_change_passphrase() -> Result<()> { + use tempfile::tempdir; + + // The context is used only for logging. + let t = TestContext::new().await; + + // Create a separate empty database for testing. + let dir = tempdir()?; + let dbfile = dir.path().join("testdb.sqlite"); + let sql = Sql::new(dbfile.clone()); + + sql.open(&t, "foo".to_string()) + .await + .context("failed to open the database first time")?; + sql.close().await; + + // Change the passphrase from "foo" to "bar". + let sql = Sql::new(dbfile.clone()); + sql.open(&t, "foo".to_string()) + .await + .context("failed to open the database second time")?; + sql.change_passphrase("bar".to_string()) + .await + .context("failed to change passphrase")?; + sql.close().await; + + let sql = Sql::new(dbfile); + + // Test that old passphrase is not working. + assert!(sql.open(&t, "foo".to_string()).await.is_err()); + + // Open the database with the new passphrase. + sql.check_passphrase("bar".to_string()).await?; + sql.open(&t, "bar".to_string()) + .await + .context("failed to open the database third time")?; + sql.close().await; + Ok(()) } } diff --git a/src/sql/migrations.rs b/src/sql/migrations.rs index 7a6e1f2a3..b358adb84 100644 --- a/src/sql/migrations.rs +++ b/src/sql/migrations.rs @@ -516,7 +516,8 @@ DO UPDATE SET rfc724_mid=excluded.rfc724_mid, id INTEGER PRIMARY KEY AUTOINCREMENT, msg_id INTEGER, update_item TEXT DEFAULT '', -update_item_read INTEGER DEFAULT 0); +update_item_read INTEGER DEFAULT 0 -- XXX unused +); CREATE INDEX msgs_status_updates_index1 ON msgs_status_updates (msg_id);"#, 84, ) diff --git a/src/tools.rs b/src/tools.rs index 18754a835..43dd51f01 100644 --- a/src/tools.rs +++ b/src/tools.rs @@ -535,6 +535,9 @@ impl EmailAddress { 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(), @@ -996,7 +999,7 @@ DKIM Results: Passed=true, Works=true, Allow_Keychange=true"; 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_ok()); + assert!(EmailAddress::new("u@d.").is_err()); assert!(EmailAddress::new("u@d.t").is_ok()); assert_eq!( EmailAddress::new("u@d.tt").unwrap(), diff --git a/src/webxdc.rs b/src/webxdc.rs index a47f3d8e0..3b00028fd 100644 --- a/src/webxdc.rs +++ b/src/webxdc.rs @@ -1,4 +1,18 @@ //! # Handle webxdc messages. +//! +//! Internally status updates are stored in the `msgs_status_updates` SQL table. +//! `msgs_status_updates` contains the following columns: +//! - `id` - status update serial number +//! - `msg_id` - ID of the message in the `msgs` table +//! - `update_item` - JSON representation of the status update +//! +//! Status updates are scheduled for sending by adding a record +//! to `smtp_status_updates_table` SQL table. +//! `smtp_status_updates` contains the following columns: +//! - `msg_id` - ID of the message in the `msgs` table +//! - `first_serial` - serial number of the first status update to send +//! - `last_serial` - serial number of the last status update to send +//! - `descr` - text to send along with the updates use std::convert::TryFrom; use std::path::Path; @@ -665,6 +679,10 @@ impl Context { /// /// Example: `{"updates": [{"payload":"any update data"}, /// {"payload":"another update data"}]}` + /// + /// `range` is an optional range of status update serials to send. + /// If it is `None`, all updates are sent. + /// This is used when a message is resent using [`crate::chat::resend_msgs`]. pub(crate) async fn render_webxdc_status_update_object( &self, instance_msg_id: MsgId, @@ -895,10 +913,8 @@ mod tests { } async fn create_webxdc_instance(t: &TestContext, name: &str, bytes: &[u8]) -> Result { - let file = t.get_blobdir().join(name); - tokio::fs::write(&file, bytes).await?; let mut instance = Message::new(Viewtype::File); - instance.set_file(file.to_str().unwrap(), None); + instance.set_file_from_bytes(t, name, bytes, None).await?; Ok(instance) } @@ -926,10 +942,10 @@ mod tests { assert_eq!(instance.chat_id, chat_id); // sending using bad extension is not working, even when setting Viewtype to webxdc - let file = t.get_blobdir().join("index.html"); - tokio::fs::write(&file, b"ola!").await?; let mut instance = Message::new(Viewtype::Webxdc); - instance.set_file(file.to_str().unwrap(), None); + instance + .set_file_from_bytes(&t, "index.html", b"ola!", None) + .await?; assert!(send_msg(&t, chat_id, &mut instance).await.is_err()); Ok(()) @@ -953,14 +969,15 @@ mod tests { assert_eq!(test.viewtype, Viewtype::File); // sending invalid .xdc as Viewtype::Webxdc should fail already on sending - let file = t.get_blobdir().join("invalid2.xdc"); - tokio::fs::write( - &file, - include_bytes!("../test-data/webxdc/invalid-no-zip-but-7z.xdc"), - ) - .await?; let mut instance = Message::new(Viewtype::Webxdc); - instance.set_file(file.to_str().unwrap(), None); + instance + .set_file_from_bytes( + &t, + "invalid2.xdc", + include_bytes!("../test-data/webxdc/invalid-no-zip-but-7z.xdc"), + None, + ) + .await?; assert!(send_msg(&t, chat_id, &mut instance).await.is_err()); Ok(())