diff --git a/Cargo.lock b/Cargo.lock index be04272f1..fc37eb2ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -685,17 +685,6 @@ dependencies = [ "thread-local-object 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "deltachat-provider-database" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "deltachat_derive" version = "2.0.0" @@ -709,7 +698,6 @@ name = "deltachat_ffi" version = "1.0.0-beta.24" dependencies = [ "deltachat 1.0.0-beta.24", - "deltachat-provider-database 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "human-panic 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1167,11 +1155,6 @@ name = "glob" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "glob" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "h2" version = "0.2.1" @@ -3258,14 +3241,6 @@ dependencies = [ "zeroize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "yaml-rust" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "zeroize" version = "1.1.0" @@ -3359,7 +3334,6 @@ dependencies = [ "checksum darling_macro 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" "checksum debug_stub_derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "496b7f8a2f853313c3ca370641d7ff3e42c32974fdccda8f0684599ed0a3ff6b" "checksum deflate 0.7.20 (registry+https://github.com/rust-lang/crates.io-index)" = "707b6a7b384888a70c8d2e8650b3e60170dfc6a67bb4aa67b6dfca57af4bedb4" -"checksum deltachat-provider-database 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "814dba060d9fdc7a989fccdc4810ada9d1c7a1f09131c78e42412bc6c634b93b" "checksum derive_builder 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a2658621297f2cf68762a6f7dc0bb7e1ff2cfd6583daef8ee0fed6f7ec468ec0" "checksum derive_builder_core 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2791ea3e372c8495c0bc2033991d76b512cd799d07491fbd6890124db9458bef" "checksum des 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "74ba5f1b5aee9772379c2670ba81306e65a93c0ee3caade7a1d22b188d88a3af" @@ -3415,7 +3389,6 @@ dependencies = [ "checksum getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "e7db7ca94ed4cd01190ceee0d8a8052f08a247aa1b469a7f68c6a3b71afcf407" "checksum gif 0.10.3 (registry+https://github.com/rust-lang/crates.io-index)" = "471d90201b3b223f3451cd4ad53e34295f16a1df17b1edf3736d47761c3981af" "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" -"checksum glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" "checksum h2 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b9433d71e471c1736fd5a61b671fc0b148d7a2992f666c958d03cd8feb3b88d1" "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" "checksum hermit-abi 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f629dc602392d3ec14bfc8a09b5e644d7ffd725102b48b81e59f90f2633621d7" @@ -3648,6 +3621,5 @@ dependencies = [ "checksum winutil 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7daf138b6b14196e3830a588acf1e86966c694d3e8fb026fb105b8b5dca07e6e" "checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" "checksum x25519-dalek 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "637ff90c9540fa3073bb577e65033069e4bae7c79d49d74aa3ffdf5342a53217" -"checksum yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "65923dd1784f44da1d2c3dbbc5e822045628c590ba72123e1c73d3c230c4434d" "checksum zeroize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3cbac2ed2ba24cc90f5e06485ac8c7c1e5449fe8911aef4d8877218af021a5b8" "checksum zeroize_derive 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de251eec69fc7c1bc3923403d18ececb929380e016afe103da75f396704f8ca2" diff --git a/deltachat-ffi/Cargo.toml b/deltachat-ffi/Cargo.toml index e82037ee8..92c994811 100644 --- a/deltachat-ffi/Cargo.toml +++ b/deltachat-ffi/Cargo.toml @@ -16,7 +16,6 @@ crate-type = ["cdylib", "staticlib"] [dependencies] deltachat = { path = "../", default-features = false } -deltachat-provider-database = "0.2.1" libc = "0.2" human-panic = "1.0.1" num-traits = "0.2.6" diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index c5c300a87..2e50f4cce 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -3686,30 +3686,19 @@ int dc_contact_is_verified (dc_contact_t* contact); */ -/** - * Create a provider struct for the given domain. - * - * @memberof dc_provider_t - * @param domain The domain to get provider info for. - * @return a dc_provider_t struct which can be used with the dc_provider_get_* - * accessor functions. If no provider info is found, NULL will be - * returned. - */ -dc_provider_t* dc_provider_new_from_domain (const char* domain); - - /** * Create a provider struct for the given email address. * * The provider is extracted from the email address and it's information is returned. * * @memberof dc_provider_t + * @param context The context object as created by dc_context_new(). * @param email The user's email address to extract the provider info form. * @return a dc_provider_t struct which can be used with the dc_provider_get_* * accessor functions. If no provider info is found, NULL will be * returned. */ -dc_provider_t* dc_provider_new_from_email (const char* email); +dc_provider_t* dc_provider_new_from_email (const dc_context_t*, const char* email); /** @@ -3719,53 +3708,35 @@ dc_provider_t* dc_provider_new_from_email (const char* email); * * @memberof dc_provider_t * @param provider The dc_provider_t struct. - * @return A string which must be released using dc_str_unref(). + * @return String with a fully-qualified URL, + * if there is no such URL, an empty string is returned, NULL is never returned. + * The returned value must be released using dc_str_unref(). */ char* dc_provider_get_overview_page (const dc_provider_t* provider); /** - * The provider's name. + * Get hints to be shown to the user on the login screen. + * Depending on the @ref DC_PROVIDER_STATUS returned by dc_provider_get_status(), + * the ui may want to highlight the hint. * - * The name of the provider, e.g. "POSTEO". + * Moreover, the ui should display a "More information" link + * that forwards to the url returned by dc_provider_get_overview_page(). * * @memberof dc_provider_t * @param provider The dc_provider_t struct. - * @return A string which must be released using dc_str_unref(). + * @return A string with the hint to show to the user, may contain multiple lines, + * if there is no such hint, an empty string is returned, NULL is never returned. + * The returned value must be released using dc_str_unref(). */ -char* dc_provider_get_name (const dc_provider_t* provider); - - -/** - * The markdown content of the providers page. - * - * This contains the preparation steps or additional information if the status - * is @ref DC_PROVIDER_STATUS_BROKEN. - * - * @memberof dc_provider_t - * @param provider The dc_provider_t struct. - * @return A string which must be released using dc_str_unref(). - */ -char* dc_provider_get_markdown (const dc_provider_t* provider); - - -/** - * Date of when the state was last checked/updated. - * - * This is returned as a string. - * - * @memberof dc_provider_t - * @param provider The dc_provider_t struct. - * @return A string which must be released using dc_str_unref(). - */ -char* dc_provider_get_status_date (const dc_provider_t* provider); +char* dc_provider_get_before_login_hint (const dc_provider_t* provider); /** * Whether DC works with this provider. * - * Can be one of @ref DC_PROVIDER_STATUS_OK, @ref - * DC_PROVIDER_STATUS_PREPARATION and @ref DC_PROVIDER_STATUS_BROKEN. + * Can be one of #DC_PROVIDER_STATUS_OK, + * #DC_PROVIDER_STATUS_PREPARATION or #DC_PROVIDER_STATUS_BROKEN. * * @memberof dc_provider_t * @param provider The dc_provider_t struct. @@ -3780,7 +3751,7 @@ int dc_provider_get_status (const dc_provider_t* prov * @memberof dc_provider_t * @param provider The dc_provider_t struct. */ -void dc_provider_unref (const dc_provider_t* provider); +void dc_provider_unref (dc_provider_t* provider); /** @@ -4506,23 +4477,43 @@ void dc_array_add_id (dc_array_t*, uint32_t); // depreca */ /** - * Provider status returned by dc_provider_get_status(). + * Prover works out-of-the-box. + * This provider status is returned for provider where the login + * works by just entering the name or the email-address. * - * Works right out of the box without any preperation steps needed + * - There is no need for the user to do any special things + * (enable IMAP or so) in the provider's webinterface or at other places. + * - There is no need for the user to enter advanced settings; + * server, port etc. are known by the core. + * + * The status is returned by dc_provider_get_status(). */ #define DC_PROVIDER_STATUS_OK 1 /** - * Provider status returned by dc_provider_get_status(). + * Provider works, but there are preparations needed. * - * Works, but preparation steps are needed + * - The user has to do some special things as "Enable IMAP in the Webinterface", + * what exactly, is described in the string returnd by dc_provider_get_before_login_hints() + * and, typically more detailed, in the page linked by dc_provider_get_overview_page(). + * - There is no need for the user to enter advanced settings; + * server, port etc. should be known by the core. + * + * The status is returned by dc_provider_get_status(). */ #define DC_PROVIDER_STATUS_PREPARATION 2 /** - * Provider status returned by dc_provider_get_status(). + * Provider is not working. + * This provider status is returned for providers + * that are known to not work with Delta Chat. + * The ui should block logging in with this provider. * - * Doesn't work (too unstable to use falls also in this category) + * More information about that is typically provided + * in the string returned by dc_provider_get_before_login_hints() + * and in the page linked by dc_provider_get_overview_page(). + * + * The status is returned by dc_provider_get_status(). */ #define DC_PROVIDER_STATUS_BROKEN 3 diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 1eb0bf9cc..98ff0a577 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -3156,8 +3156,6 @@ pub unsafe extern "C" fn dc_str_unref(s: *mut libc::c_char) { libc::free(s as *mut _) } -pub mod providers; - pub trait ResultExt { fn unwrap_or_log_default(self, context: &context::Context, message: &str) -> T; fn log_err(self, context: &context::Context, message: &str) -> Result; @@ -3212,3 +3210,68 @@ fn convert_and_prune_message_ids(msg_ids: *const u32, msg_cnt: libc::c_int) -> V msg_ids } + +// dc_provider_t + +#[no_mangle] +pub type dc_provider_t = provider::Provider; + +#[no_mangle] +pub unsafe extern "C" fn dc_provider_new_from_email( + context: *const dc_context_t, + addr: *const libc::c_char, +) -> *const dc_provider_t { + if context.is_null() || addr.is_null() { + eprintln!("ignoring careless call to dc_provider_new_from_email()"); + return ptr::null(); + } + let addr = to_string_lossy(addr); + match provider::get_provider_info(addr.as_str()) { + Some(provider) => provider, + None => ptr::null_mut(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn dc_provider_get_overview_page( + provider: *const dc_provider_t, +) -> *mut libc::c_char { + if provider.is_null() { + eprintln!("ignoring careless call to dc_provider_get_overview_page()"); + return "".strdup(); + } + let provider = &*provider; + provider.overview_page.strdup() +} + +#[no_mangle] +pub unsafe extern "C" fn dc_provider_get_before_login_hint( + provider: *const dc_provider_t, +) -> *mut libc::c_char { + if provider.is_null() { + eprintln!("ignoring careless call to dc_provider_get_before_login_hint()"); + return "".strdup(); + } + let provider = &*provider; + provider.before_login_hint.strdup() +} + +#[no_mangle] +pub unsafe extern "C" fn dc_provider_get_status(provider: *const dc_provider_t) -> libc::c_int { + if provider.is_null() { + eprintln!("ignoring careless call to dc_provider_get_status()"); + return 0; + } + let provider = &*provider; + provider.status as libc::c_int +} + +#[no_mangle] +pub unsafe extern "C" fn dc_provider_unref(provider: *mut dc_provider_t) { + if provider.is_null() { + eprintln!("ignoring careless call to dc_provider_unref()"); + return; + } + // currently, there is nothing to free, the provider info is a static object. + // this may change once we start localizing string. +} diff --git a/deltachat-ffi/src/providers.rs b/deltachat-ffi/src/providers.rs deleted file mode 100644 index b3b58a30b..000000000 --- a/deltachat-ffi/src/providers.rs +++ /dev/null @@ -1,91 +0,0 @@ -extern crate deltachat_provider_database; - -use std::ptr; - -use crate::string::{to_string_lossy, StrExt}; -use deltachat_provider_database::StatusState; - -#[no_mangle] -pub type dc_provider_t = deltachat_provider_database::Provider; - -#[no_mangle] -pub unsafe extern "C" fn dc_provider_new_from_domain( - domain: *const libc::c_char, -) -> *const dc_provider_t { - match deltachat_provider_database::get_provider_info(&to_string_lossy(domain)) { - Some(provider) => provider, - None => ptr::null(), - } -} - -#[no_mangle] -pub unsafe extern "C" fn dc_provider_new_from_email( - email: *const libc::c_char, -) -> *const dc_provider_t { - let email = to_string_lossy(email); - let domain = deltachat_provider_database::get_domain_from_email(&email); - match deltachat_provider_database::get_provider_info(domain) { - Some(provider) => provider, - None => ptr::null(), - } -} - -macro_rules! null_guard { - ($context:tt) => { - if $context.is_null() { - return ptr::null_mut() as *mut libc::c_char; - } - }; -} - -#[no_mangle] -pub unsafe extern "C" fn dc_provider_get_overview_page( - provider: *const dc_provider_t, -) -> *mut libc::c_char { - null_guard!(provider); - format!( - "{}/{}", - deltachat_provider_database::PROVIDER_OVERVIEW_URL, - (*provider).overview_page - ) - .strdup() -} - -#[no_mangle] -pub unsafe extern "C" fn dc_provider_get_name(provider: *const dc_provider_t) -> *mut libc::c_char { - null_guard!(provider); - (*provider).name.strdup() -} - -#[no_mangle] -pub unsafe extern "C" fn dc_provider_get_markdown( - provider: *const dc_provider_t, -) -> *mut libc::c_char { - null_guard!(provider); - (*provider).markdown.strdup() -} - -#[no_mangle] -pub unsafe extern "C" fn dc_provider_get_status_date( - provider: *const dc_provider_t, -) -> *mut libc::c_char { - null_guard!(provider); - (*provider).status.date.strdup() -} - -#[no_mangle] -pub unsafe extern "C" fn dc_provider_get_status(provider: *const dc_provider_t) -> u32 { - if provider.is_null() { - return 0; - } - match (*provider).status.state { - StatusState::OK => 1, - StatusState::PREPARATION => 2, - StatusState::BROKEN => 3, - } -} - -#[no_mangle] -pub unsafe extern "C" fn dc_provider_unref(_provider: *const dc_provider_t) {} - -// TODO expose general provider overview url? diff --git a/examples/repl/cmdline.rs b/examples/repl/cmdline.rs index adefb5926..b944a9577 100644 --- a/examples/repl/cmdline.rs +++ b/examples/repl/cmdline.rs @@ -3,7 +3,6 @@ use std::str::FromStr; use deltachat::chat::{self, Chat, ChatId}; use deltachat::chatlist::*; -use deltachat::config; use deltachat::constants::*; use deltachat::contact::*; use deltachat::context::*; @@ -19,6 +18,7 @@ use deltachat::peerstate::*; use deltachat::qr::*; use deltachat::sql; use deltachat::Event; +use deltachat::{config, provider}; /// Reset database tables. /// Argument is a bitmask, executing single or multiple actions in one call. @@ -392,6 +392,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { getqr []\n\ getbadqr\n\ checkqr \n\ + providerinfo \n\ event \n\ fileinfo \n\ emptyserver (1=MVBOX 2=INBOX)\n\ @@ -966,6 +967,24 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { res.get_text2() ); } + "providerinfo" => { + ensure!(!arg1.is_empty(), "Argument missing."); + match provider::get_provider_info(arg1) { + Some(info) => { + println!("Information for provider belonging to {}:", arg1); + println!("status: {}", info.status as u32); + println!("before_login_hint: {}", info.before_login_hint); + println!("after_login_hint: {}", info.after_login_hint); + println!("overview_page: {}", info.overview_page); + for server in info.server.iter() { + println!("server: {}:{}", server.hostname, server.port,); + } + } + None => { + println!("No information for provider belonging to {} found.", arg1); + } + } + } // TODO: implement this again, unclear how to match this through though, without writing a parser. // "event" => { // ensure!(!arg1.is_empty(), "Argument missing."); diff --git a/python/src/deltachat/provider.py b/python/src/deltachat/provider.py index ad98daf96..487a84192 100644 --- a/python/src/deltachat/provider.py +++ b/python/src/deltachat/provider.py @@ -11,27 +11,18 @@ class ProviderNotFoundError(Exception): class Provider(object): """Provider information. - :param domain: The domain to get the provider info for, this is - normally the part following the `@` of the domain. + :param domain: The email to get the provider info for. """ - def __init__(self, domain): + def __init__(self, account, addr): provider = ffi.gc( - lib.dc_provider_new_from_domain(as_dc_charpointer(domain)), + lib.dc_provider_new_from_email(account._dc_context, as_dc_charpointer(addr)), lib.dc_provider_unref, ) if provider == ffi.NULL: raise ProviderNotFoundError("Provider not found") self._provider = provider - @classmethod - def from_email(cls, email): - """Create provider info from an email address. - - :param email: Email address to get provider info for. - """ - return cls(email.split('@')[-1]) - @property def overview_page(self): """URL to the overview page of the provider on providers.delta.chat.""" @@ -39,21 +30,10 @@ class Provider(object): lib.dc_provider_get_overview_page(self._provider)) @property - def name(self): - """The name of the provider.""" - return from_dc_charpointer(lib.dc_provider_get_name(self._provider)) - - @property - def markdown(self): - """Content of the information page, formatted as markdown.""" + def get_before_login_hints(self): + """Should be shown to the user on login.""" return from_dc_charpointer( - lib.dc_provider_get_markdown(self._provider)) - - @property - def status_date(self): - """The date the provider info was last updated, as a string.""" - return from_dc_charpointer( - lib.dc_provider_get_status_date(self._provider)) + lib.dc_provider_get_before_login_hints(self._provider)) @property def status(self): diff --git a/python/tests/test_lowlevel.py b/python/tests/test_lowlevel.py index 915631895..273414e99 100644 --- a/python/tests/test_lowlevel.py +++ b/python/tests/test_lowlevel.py @@ -102,19 +102,12 @@ def test_get_special_message_id_returns_empty_message(acfactory): assert msg.id == 0 -def test_provider_info(): - provider = lib.dc_provider_new_from_email(cutil.as_dc_charpointer("ex@example.com")) - assert cutil.from_dc_charpointer( - lib.dc_provider_get_overview_page(provider) - ) == "https://providers.delta.chat/example.com" - assert cutil.from_dc_charpointer(lib.dc_provider_get_name(provider)) == "Example" - assert cutil.from_dc_charpointer(lib.dc_provider_get_markdown(provider)) == "\n..." - assert cutil.from_dc_charpointer(lib.dc_provider_get_status_date(provider)) == "2018-09" - assert lib.dc_provider_get_status(provider) == const.DC_PROVIDER_STATUS_PREPARATION - - def test_provider_info_none(): - assert lib.dc_provider_new_from_email(cutil.as_dc_charpointer("email@unexistent.no")) == ffi.NULL + ctx = ffi.gc( + lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL), + lib.dc_context_unref, + ) + assert lib.dc_provider_new_from_email(ctx, cutil.as_dc_charpointer("email@unexistent.no")) == ffi.NULL def test_get_info_closed(): diff --git a/python/tests/test_provider_info.py b/python/tests/test_provider_info.py deleted file mode 100644 index 10c9fb27d..000000000 --- a/python/tests/test_provider_info.py +++ /dev/null @@ -1,27 +0,0 @@ -import pytest - -from deltachat import const -from deltachat import provider - - -def test_provider_info_from_email(): - example = provider.Provider.from_email("email@example.com") - assert example.overview_page == "https://providers.delta.chat/example.com" - assert example.name == "Example" - assert example.markdown == "\n..." - assert example.status_date == "2018-09" - assert example.status == const.DC_PROVIDER_STATUS_PREPARATION - - -def test_provider_info_from_domain(): - example = provider.Provider("example.com") - assert example.overview_page == "https://providers.delta.chat/example.com" - assert example.name == "Example" - assert example.markdown == "\n..." - assert example.status_date == "2018-09" - assert example.status == const.DC_PROVIDER_STATUS_PREPARATION - - -def test_provider_info_none(): - with pytest.raises(provider.ProviderNotFoundError): - provider.Provider.from_email("email@unexistent.no") diff --git a/src/configure/mod.rs b/src/configure/mod.rs index f1ed112c5..68755ba8b 100644 --- a/src/configure/mod.rs +++ b/src/configure/mod.rs @@ -12,12 +12,13 @@ use crate::config::Config; use crate::constants::*; use crate::context::Context; use crate::dc_tools::*; -use crate::e2ee; use crate::job::{self, job_add, job_kill_action}; use crate::login_param::{CertificateChecks, LoginParam}; use crate::oauth2::*; use crate::param::Params; +use crate::{chat, e2ee, provider}; +use crate::message::Message; use auto_mozilla::moz_autoconfigure; use auto_outlook::outlk_autodiscover; @@ -436,45 +437,77 @@ pub fn JobConfigureImap(context: &Context) -> job::Status { LoginParam::from_database(context, "configured_raw_").save_to_database(context, ""); } + if let Some(provider) = provider::get_provider_info(¶m.addr) { + if !provider.after_login_hint.is_empty() { + let mut msg = Message::new(Viewtype::Text); + msg.text = Some(provider.after_login_hint.to_string()); + if chat::add_device_msg(context, Some("core-provider-info"), Some(&mut msg)).is_err() { + warn!(context, "cannot add after_login_hint as core-provider-info"); + } + } + } + context.free_ongoing(); progress!(context, if success { 1000 } else { 0 }); job::Status::Finished(Ok(())) } +#[allow(clippy::unnecessary_unwrap)] fn get_offline_autoconfig(context: &Context, param: &LoginParam) -> Option { - // XXX we don't have https://github.com/deltachat/provider-db APIs - // integrated yet but we'll already add nauta as a first use case, also - // showing what we need from provider-db in the future. info!( context, "checking internal provider-info for offline autoconfig" ); - if param.addr.ends_with("@nauta.cu") { - let mut p = LoginParam::new(); + if let Some(provider) = provider::get_provider_info(¶m.addr) { + match provider.status { + provider::Status::OK | provider::Status::PREPARATION => { + let imap = provider.get_imap_server(); + let smtp = provider.get_smtp_server(); + // clippy complains about these is_some()/unwrap() settings, + // however, rewriting the code to "if let" would make things less obvious, + // esp. if we allow more combinations of servers (pop, jmap). + // therefore, #[allow(clippy::unnecessary_unwrap)] is added above. + if imap.is_some() && smtp.is_some() { + let imap = imap.unwrap(); + let smtp = smtp.unwrap(); - p.addr = param.addr.clone(); - p.mail_server = "imap.nauta.cu".to_string(); - p.mail_user = param.addr.clone(); - p.mail_pw = param.mail_pw.clone(); - p.mail_port = 143; - p.imap_certificate_checks = CertificateChecks::AcceptInvalidCertificates; + let mut p = LoginParam::new(); + p.addr = param.addr.clone(); - p.send_server = "smtp.nauta.cu".to_string(); - p.send_user = param.addr.clone(); - p.send_pw = param.mail_pw.clone(); - p.send_port = 25; - p.smtp_certificate_checks = CertificateChecks::AcceptInvalidCertificates; - p.server_flags = DC_LP_AUTH_NORMAL as i32 - | DC_LP_IMAP_SOCKET_STARTTLS as i32 - | DC_LP_SMTP_SOCKET_STARTTLS as i32; + p.mail_server = imap.hostname.to_string(); + p.mail_user = imap.apply_username_pattern(param.addr.clone()); + p.mail_port = imap.port as i32; + p.imap_certificate_checks = CertificateChecks::AcceptInvalidCertificates; + p.server_flags |= match imap.socket { + provider::Socket::STARTTLS => DC_LP_IMAP_SOCKET_STARTTLS, + provider::Socket::SSL => DC_LP_IMAP_SOCKET_SSL, + }; - info!(context, "found offline autoconfig: {}", p); - Some(p) - } else { - info!(context, "no offline autoconfig found"); - None + p.send_server = smtp.hostname.to_string(); + p.send_user = smtp.apply_username_pattern(param.addr.clone()); + p.send_port = smtp.port as i32; + p.smtp_certificate_checks = CertificateChecks::AcceptInvalidCertificates; + p.server_flags |= match smtp.socket { + provider::Socket::STARTTLS => DC_LP_SMTP_SOCKET_STARTTLS as i32, + provider::Socket::SSL => DC_LP_SMTP_SOCKET_SSL as i32, + }; + + info!(context, "offline autoconfig found: {}", p); + return Some(p); + } else { + info!(context, "offline autoconfig found, but no servers defined"); + return None; + } + } + provider::Status::BROKEN => { + info!(context, "offline autoconfig found, provider is broken"); + return None; + } + } } + info!(context, "no offline autoconfig found"); + None } fn try_imap_connections( diff --git a/src/lib.rs b/src/lib.rs index 06f676298..1f6edf406 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -54,6 +54,7 @@ pub mod oauth2; mod param; pub mod peerstate; pub mod pgp; +pub mod provider; pub mod qr; pub mod securejoin; mod simplify; diff --git a/src/provider/data.rs b/src/provider/data.rs new file mode 100644 index 000000000..b6cf2f25b --- /dev/null +++ b/src/provider/data.rs @@ -0,0 +1,308 @@ +// file generated by src/provider/update.py + +use crate::provider::Protocol::*; +use crate::provider::Socket::*; +use crate::provider::UsernamePattern::*; +use crate::provider::*; +use std::collections::HashMap; + +lazy_static::lazy_static! { + + // aktivix.org.md: aktivix.org + static ref P_AKTIVIX_ORG: Provider = Provider { + status: Status::OK, + before_login_hint: "", + after_login_hint: "", + overview_page: "https://providers.delta.chat/aktivix-org", + server: vec![ + Server { protocol: IMAP, socket: STARTTLS, hostname: "newyear.aktivix.org", port: 143, username_pattern: EMAIL }, + Server { protocol: SMTP, socket: STARTTLS, hostname: "newyear.aktivix.org", port: 25, username_pattern: EMAIL }, + ], + }; + + // autistici.org.md: autistici.org + static ref P_AUTISTICI_ORG: Provider = Provider { + status: Status::OK, + before_login_hint: "", + after_login_hint: "", + overview_page: "https://providers.delta.chat/autistici-org", + server: vec![ + Server { protocol: IMAP, socket: SSL, hostname: "mail.autistici.org", port: 993, username_pattern: EMAIL }, + Server { protocol: SMTP, socket: SSL, hostname: "smtp.autistici.org", port: 465, username_pattern: EMAIL }, + ], + }; + + // bluewin.ch.md: bluewin.ch + static ref P_BLUEWIN_CH: Provider = Provider { + status: Status::OK, + before_login_hint: "", + after_login_hint: "", + overview_page: "https://providers.delta.chat/bluewin-ch", + server: vec![ + Server { protocol: IMAP, socket: SSL, hostname: "imaps.bluewin.ch", port: 993, username_pattern: EMAIL }, + Server { protocol: SMTP, socket: SSL, hostname: "smtpauths.bluewin.ch", port: 465, username_pattern: EMAIL }, + ], + }; + + // comcast.md: xfinity.com, comcast.net + // - skipping provider with status OK and no special things to do + + // dismail.de.md: dismail.de + // - skipping provider with status OK and no special things to do + + // disroot.md: disroot.org + // - skipping provider with status OK and no special things to do + + // example.com.md: example.com, example.org + static ref P_EXAMPLE_COM: Provider = Provider { + status: Status::BROKEN, + before_login_hint: "Hush this provider doesn't exist!", + after_login_hint: "This provider doesn't really exist, so you can't use it :/ If you need an email provider for Delta Chat, take a look at providers.delta.chat!", + overview_page: "https://providers.delta.chat/example-com", + server: vec![ + 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 }, + ], + }; + + // freenet.de.md: freenet.de + static ref P_FREENET_DE: Provider = Provider { + status: Status::OK, + before_login_hint: "", + after_login_hint: "", + overview_page: "https://providers.delta.chat/freenet-de", + server: vec![ + 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 }, + ], + }; + + // gmail.md: gmail.com, googlemail.com + static ref P_GMAIL: Provider = Provider { + 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\".", + after_login_hint: "", + overview_page: "https://providers.delta.chat/gmail", + server: vec![ + Server { protocol: IMAP, socket: SSL, hostname: "imap.gmail.com", port: 993, username_pattern: EMAIL }, + Server { protocol: SMTP, socket: SSL, hostname: "smtp.gmail.com", port: 465, username_pattern: EMAIL }, + ], + }; + + // gmx.net.md: gmx.net, gmx.de, gmx.at, gmx.ch, gmx.org, gmx.eu, gmx.info, gmx.biz, gmx.com + static ref P_GMX_NET: Provider = Provider { + status: Status::PREPARATION, + before_login_hint: "You must allow IMAP access to your account before you can login.", + after_login_hint: "", + overview_page: "https://providers.delta.chat/gmx-net", + server: vec![ + Server { protocol: IMAP, socket: SSL, hostname: "imap.gmx.net", port: 993, username_pattern: EMAIL }, + Server { protocol: SMTP, socket: SSL, hostname: "mail.gmx.net", port: 465, username_pattern: EMAIL }, + Server { protocol: SMTP, socket: STARTTLS, hostname: "mail.gmx.net", port: 587, username_pattern: EMAIL }, + ], + }; + + // i.ua.md: i.ua + // - skipping provider with status OK and no special things to do + + // icloud.md: icloud.com, me.com, mac.com + static ref P_ICLOUD: Provider = Provider { + status: Status::PREPARATION, + before_login_hint: "You must create an app-specific password for Delta Chat before you can login.", + after_login_hint: "", + overview_page: "https://providers.delta.chat/icloud", + server: vec![ + Server { protocol: IMAP, socket: SSL, hostname: "imap.mail.me.com", port: 993, username_pattern: EMAILLOCALPART }, + Server { protocol: SMTP, socket: STARTTLS, hostname: "smtp.mail.me.com", port: 587, username_pattern: EMAIL }, + ], + }; + + // kolst.com.md: kolst.com + // - skipping provider with status OK and no special things to do + + // kontent.com.md: kontent.com + // - skipping provider with status OK and no special things to do + + // mail.ru.md: mail.ru, inbox.ru, bk.ru, list.ru + // - skipping provider with status OK and no special things to do + + // mailbox.org.md: mailbox.org, secure.mailbox.org + // - skipping provider with status OK and no special things to do + + // nauta.cu.md: nauta.cu + static ref P_NAUTA_CU: Provider = Provider { + status: Status::OK, + before_login_hint: "", + after_login_hint: "Atención - con nauta.cu, puede enviar mensajes sólo a un máximo de 20 personas a la vez. En grupos más grandes, no puede enviar mensajes o abandonar el grupo.", + overview_page: "https://providers.delta.chat/nauta-cu", + server: vec![ + Server { protocol: IMAP, socket: STARTTLS, hostname: "imap.nauta.cu", port: 143, username_pattern: EMAIL }, + Server { protocol: SMTP, socket: STARTTLS, hostname: "smtp.nauta.cu", port: 25, username_pattern: EMAIL }, + ], + }; + + // outlook.com.md: hotmail.com, outlook.com, office365.com, outlook.com.tr, live.com + static ref P_OUTLOOK_COM: Provider = Provider { + status: Status::BROKEN, + before_login_hint: "Outlook.com email addresses will not work as expected as these servers remove some important transport information. Hopefully sooner or later there will be a fix, for now we suggest to use another email address.", + after_login_hint: "Outlook.com email addresses will not work as expected as these servers remove some important transport information. Unencrypted 1-on-1 chats kind of work, but groups and encryption don't. Hopefully sooner or later there will be a fix, for now we suggest to use another email address.", + overview_page: "https://providers.delta.chat/outlook-com", + server: vec![ + Server { protocol: IMAP, socket: SSL, hostname: "imap-mail.outlook.com", port: 993, username_pattern: EMAIL }, + Server { protocol: SMTP, socket: STARTTLS, hostname: "smtp-mail.outlook.com", port: 587, username_pattern: EMAIL }, + ], + }; + + // posteo.md: posteo.de + static ref P_POSTEO: Provider = Provider { + status: Status::OK, + before_login_hint: "", + after_login_hint: "", + overview_page: "https://providers.delta.chat/posteo", + server: vec![ + Server { protocol: IMAP, socket: STARTTLS, hostname: "posteo.de", port: 143, username_pattern: EMAIL }, + Server { protocol: SMTP, socket: STARTTLS, hostname: "posteo.de", port: 587, username_pattern: EMAIL }, + ], + }; + + // riseup.net.md: riseup.net + // - skipping provider with status OK and no special things to do + + // rogers.com.md: rogers.com + // - skipping provider with status OK and no special things to do + + // tiscali.it.md: tiscali.it + static ref P_TISCALI_IT: Provider = Provider { + status: Status::OK, + before_login_hint: "", + after_login_hint: "", + overview_page: "https://providers.delta.chat/tiscali-it", + server: vec![ + Server { protocol: IMAP, socket: SSL, hostname: "imap.tiscali.it", port: 993, username_pattern: EMAIL }, + Server { protocol: SMTP, socket: SSL, hostname: "smtp.tiscali.it", port: 465, username_pattern: EMAIL }, + ], + }; + + // ukr.net.md: ukr.net + // - skipping provider with status OK and no special things to do + + // vfemail.md: vfemail.net + // - skipping provider with status OK and no special things to do + + // 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 ref P_WEB_DE: Provider = Provider { + status: Status::PREPARATION, + before_login_hint: "You must allow IMAP access to your account before you can login.", + after_login_hint: "Note: if you have your web.de spam settings too strict, you won't receive contact requests from new people. If you want to receive contact requests, you should disable the \"3-Wege-Spamschutz\" in the web.de settings. Read how: https://hilfe.web.de/email/spam-und-viren/spamschutz-einstellungen.html", + overview_page: "https://providers.delta.chat/web-de", + server: vec![ + Server { protocol: IMAP, socket: SSL, hostname: "imap.web.de", port: 993, username_pattern: EMAILLOCALPART }, + 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 }, + ], + }; + + // 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 ref P_YAHOO: Provider = Provider { + status: Status::PREPARATION, + before_login_hint: "To use Delta Chat with your Yahoo email address you have to allow \"less secure apps\" in the Yahoo webinterface.", + after_login_hint: "", + overview_page: "https://providers.delta.chat/yahoo", + server: vec![ + 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 }, + ], + }; + + // yandex.ru.md: yandex.ru, yandex.com + // - skipping provider with status OK and no special things to do + + // ziggo.nl.md: ziggo.nl + static ref P_ZIGGO_NL: Provider = Provider { + status: Status::OK, + before_login_hint: "", + after_login_hint: "", + overview_page: "https://providers.delta.chat/ziggo-nl", + server: vec![ + Server { protocol: IMAP, socket: SSL, hostname: "imap.ziggo.nl", port: 993, username_pattern: EMAIL }, + Server { protocol: SMTP, socket: STARTTLS, hostname: "smtp.ziggo.nl", port: 587, username_pattern: EMAIL }, + ], + }; + + // zoho.com.md: zoho.com + // - skipping provider with status OK and no special things to do + + pub static ref PROVIDER_DATA: HashMap<&'static str, &'static Provider> = [ + ("aktivix.org", &*P_AKTIVIX_ORG), + ("autistici.org", &*P_AUTISTICI_ORG), + ("bluewin.ch", &*P_BLUEWIN_CH), + ("example.com", &*P_EXAMPLE_COM), + ("example.org", &*P_EXAMPLE_COM), + ("freenet.de", &*P_FREENET_DE), + ("gmail.com", &*P_GMAIL), + ("googlemail.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), + ("icloud.com", &*P_ICLOUD), + ("me.com", &*P_ICLOUD), + ("mac.com", &*P_ICLOUD), + ("nauta.cu", &*P_NAUTA_CU), + ("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), + ("posteo.de", &*P_POSTEO), + ("tiscali.it", &*P_TISCALI_IT), + ("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), + ("ziggo.nl", &*P_ZIGGO_NL), + ].iter().copied().collect(); +} diff --git a/src/provider/mod.rs b/src/provider/mod.rs new file mode 100644 index 000000000..a52a41905 --- /dev/null +++ b/src/provider/mod.rs @@ -0,0 +1,144 @@ +mod data; + +use crate::dc_tools::EmailAddress; +use crate::provider::data::PROVIDER_DATA; + +#[derive(Debug, Copy, Clone, PartialEq, ToPrimitive)] +#[repr(u8)] +pub enum Status { + OK = 1, + PREPARATION = 2, + BROKEN = 3, +} + +#[derive(Debug, PartialEq)] +#[repr(u8)] +pub enum Protocol { + SMTP = 1, + IMAP = 2, +} + +#[derive(Debug, PartialEq)] +#[repr(u8)] +pub enum Socket { + STARTTLS = 1, + SSL = 2, +} + +#[derive(Debug, PartialEq)] +#[repr(u8)] +pub enum UsernamePattern { + EMAIL = 1, + EMAILLOCALPART = 2, +} + +#[derive(Debug)] +pub struct Server { + pub protocol: Protocol, + pub socket: Socket, + pub hostname: &'static str, + pub port: u16, + pub username_pattern: UsernamePattern, +} + +impl Server { + pub fn apply_username_pattern(&self, addr: String) -> String { + match self.username_pattern { + UsernamePattern::EMAIL => addr, + UsernamePattern::EMAILLOCALPART => { + if let Some(at) = addr.find('@') { + return addr.split_at(at).0.to_string(); + } + addr + } + } + } +} + +#[derive(Debug)] +pub struct Provider { + pub status: Status, + pub before_login_hint: &'static str, + pub after_login_hint: &'static str, + pub overview_page: &'static str, + pub server: Vec, +} + +impl Provider { + pub fn get_server(&self, protocol: Protocol) -> Option<&Server> { + for record in self.server.iter() { + if record.protocol == protocol { + return Some(record); + } + } + None + } + + pub fn get_imap_server(&self) -> Option<&Server> { + self.get_server(Protocol::IMAP) + } + + pub fn get_smtp_server(&self) -> Option<&Server> { + self.get_server(Protocol::SMTP) + } +} + +pub fn get_provider_info(addr: &str) -> Option<&Provider> { + let domain = match addr.parse::() { + Ok(addr) => addr.domain, + Err(_err) => return None, + } + .to_lowercase(); + + if let Some(provider) = PROVIDER_DATA.get(domain.as_str()) { + return Some(*provider); + } + + None +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_get_provider_info_unexistant() { + let provider = get_provider_info("user@unexistant.org"); + assert!(provider.is_none()); + } + + #[test] + fn test_get_provider_info_mixed_case() { + let provider = get_provider_info("uSer@nAUta.Cu").unwrap(); + assert!(provider.status == Status::OK); + } + + #[test] + fn test_get_provider_info() { + let provider = get_provider_info("nauta.cu"); // this is no email address + assert!(provider.is_none()); + + let provider = get_provider_info("user@nauta.cu").unwrap(); + assert!(provider.status == Status::OK); + let server = provider.get_imap_server().unwrap(); + assert_eq!(server.protocol, Protocol::IMAP); + assert_eq!(server.socket, Socket::STARTTLS); + assert_eq!(server.hostname, "imap.nauta.cu"); + assert_eq!(server.port, 143); + assert_eq!(server.username_pattern, UsernamePattern::EMAIL); + let server = provider.get_smtp_server().unwrap(); + assert_eq!(server.protocol, Protocol::SMTP); + assert_eq!(server.socket, Socket::STARTTLS); + assert_eq!(server.hostname, "smtp.nauta.cu"); + assert_eq!(server.port, 25); + assert_eq!(server.username_pattern, UsernamePattern::EMAIL); + + let provider = get_provider_info("user@gmail.com").unwrap(); + assert!(provider.status == Status::PREPARATION); + assert!(!provider.before_login_hint.is_empty()); + assert!(!provider.overview_page.is_empty()); + + let provider = get_provider_info("user@googlemail.com").unwrap(); + assert!(provider.status == Status::PREPARATION); + } +} diff --git a/src/provider/update.py b/src/provider/update.py new file mode 100755 index 000000000..f0a19db76 --- /dev/null +++ b/src/provider/update.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python3 +# if the yaml import fails, run "pip install pyyaml" + +import sys +import os +import yaml + +out_all = "" +out_domains = "" +domains_dict = {} + + +def cleanstr(s): + s = s.strip() + s = s.replace("\n", " ") + s = s.replace("\\", "\\\\") + s = s.replace("\"", "\\\"") + return s + + +def file2varname(f): + f = f[f.rindex("/")+1:].replace(".md", "") + f = f.replace(".", "_") + f = f.replace("-", "_") + return "P_" + f.upper() + + +def file2url(f): + f = f[f.rindex("/")+1:].replace(".md", "") + f = f.replace(".", "-") + return "https://providers.delta.chat/" + f + + +def process_data(data, file): + status = data.get("status", "") + if status != "OK" and status != "PREPARATION" and status != "BROKEN": + raise TypeError("bad status") + + comment = "" + domains = "" + if not "domains" in data: + raise TypeError("no domains found") + for domain in data["domains"]: + domain = cleanstr(domain) + if domain == "" or domain.count(".") < 1 or domain.lower() != domain: + raise TypeError("bad domain: " + domain) + + global domains_dict + if domains_dict.get(domain, False): + raise TypeError("domain used twice: " + domain) + domains_dict[domain] = True + + domains += " (\"" + domain + "\", &*" + file2varname(file) + "),\n" + comment += domain + ", " + + + server = "" + has_imap = False + has_smtp = False + if "server" in data: + for s in data["server"]: + hostname = cleanstr(s.get("hostname", "")) + port = int(s.get("port", "")) + if hostname == "" or hostname.count(".") < 1 or port <= 0: + raise TypeError("bad hostname or port") + + protocol = s.get("type", "").upper() + if protocol == "IMAP": + has_imap = True + elif protocol == "SMTP": + has_smtp = True + else: + raise TypeError("bad protocol") + + socket = s.get("socket", "").upper() + if socket != "STARTTLS" and socket != "SSL": + raise TypeError("bad socket") + + username_pattern = s.get("username_pattern", "EMAIL").upper() + if username_pattern != "EMAIL" and username_pattern != "EMAILLOCALPART": + raise TypeError("bad username pattern") + + server += (" Server { protocol: " + protocol + ", socket: " + socket + ", hostname: \"" + + hostname + "\", port: " + str(port) + ", username_pattern: " + username_pattern + " },\n") + + provider = "" + before_login_hint = cleanstr(data.get("before_login_hint", "")) + after_login_hint = cleanstr(data.get("after_login_hint", "")) + if (not has_imap and not has_smtp) or (has_imap and has_smtp): + provider += " static ref " + file2varname(file) + ": Provider = Provider {\n" + provider += " status: Status::" + status + ",\n" + provider += " before_login_hint: \"" + before_login_hint + "\",\n" + provider += " after_login_hint: \"" + after_login_hint + "\",\n" + provider += " overview_page: \"" + file2url(file) + "\",\n" + provider += " server: vec![\n" + server + " ],\n" + provider += " };\n\n" + else: + raise TypeError("SMTP and IMAP must be specified together or left out both") + + if status != "OK" and before_login_hint == "": + raise TypeError("status PREPARATION or BROKEN requires before_login_hint: " + file) + + # finally, add the provider + global out_all, out_domains + out_all += " // " + file[file.rindex("/")+1:] + ": " + comment.strip(", ") + "\n" + if status == "OK" and before_login_hint == "" and after_login_hint == "" and server == "": + out_all += " // - skipping provider with status OK and no special things to do\n\n" + else: + out_all += provider + out_domains += domains + + +def process_file(file): + print("processing file: " + file, file=sys.stderr) + with open(file) as f: + # load_all() loads "---"-separated yamls - + # by coincidence, this is also the frontmatter separator :) + data = next(yaml.load_all(f, Loader=yaml.SafeLoader)) + process_data(data, file) + + +def process_dir(dir): + print("processing directory: " + dir, file=sys.stderr) + files = [f for f in os.listdir(dir) if f.endswith(".md")] + files.sort() + for f in files: + process_file(os.path.join(dir, f)) + + +if __name__ == "__main__": + if len(sys.argv) < 2: + raise SystemExit("usage: update.py DIR_WITH_MD_FILES > data.rs") + + out_all = ("// file generated by src/provider/update.py\n\n" + "use crate::provider::Protocol::*;\n" + "use crate::provider::Socket::*;\n" + "use crate::provider::UsernamePattern::*;\n" + "use crate::provider::*;\n" + "use std::collections::HashMap;\n\n" + "lazy_static::lazy_static! {\n\n") + + process_dir(sys.argv[1]) + + out_all += " pub static ref PROVIDER_DATA: HashMap<&'static str, &'static Provider> = [\n" + out_all += out_domains; + out_all += " ].iter().copied().collect();\n}" + + print(out_all)