diff --git a/deltachat-jsonrpc/src/api/types/login_param.rs b/deltachat-jsonrpc/src/api/types/login_param.rs index 6036709cd..2bbc16f43 100644 --- a/deltachat-jsonrpc/src/api/types/login_param.rs +++ b/deltachat-jsonrpc/src/api/types/login_param.rs @@ -54,6 +54,9 @@ pub struct EnteredLoginParam { /// If true, login via OAUTH2 (not recommended anymore). /// Default: false pub oauth2: Option, + + /// IP addresses for prefilling DNS + pub dns_prefill: Vec, } impl From for EnteredLoginParam { @@ -75,6 +78,7 @@ impl From for EnteredLoginParam { smtp_password: param.smtp.password.into_option(), certificate_checks: certificate_checks.into_option(), oauth2: param.oauth2.into_option(), + dns_prefill: param.dns_prefill, } } } @@ -101,6 +105,7 @@ impl TryFrom for dc::EnteredLoginParam { }, certificate_checks: param.certificate_checks.unwrap_or_default().into(), oauth2: param.oauth2.unwrap_or_default(), + dns_prefill: param.dns_prefill, }) } } diff --git a/src/login_param.rs b/src/login_param.rs index 5c6864f11..1a5e74dc6 100644 --- a/src/login_param.rs +++ b/src/login_param.rs @@ -97,6 +97,9 @@ pub struct EnteredLoginParam { /// If true, login via OAUTH2 (not recommended anymore) pub oauth2: bool, + + /// IP addresses for prefilling DNS + pub dns_prefill: Vec, } impl EnteredLoginParam { @@ -191,6 +194,7 @@ impl EnteredLoginParam { }, certificate_checks, oauth2, + dns_prefill: Default::default(), }) } @@ -360,6 +364,7 @@ mod tests { }, certificate_checks: Default::default(), oauth2: false, + dns_prefill: Default::default(), }; param.save(&t).await?; assert_eq!( diff --git a/src/qr.rs b/src/qr.rs index a289d9aaf..0af1cdc29 100644 --- a/src/qr.rs +++ b/src/qr.rs @@ -12,6 +12,7 @@ use percent_encoding::{NON_ALPHANUMERIC, percent_decode_str, percent_encode}; use rand::TryRngCore as _; use rand::distr::{Alphanumeric, SampleString}; use serde::Deserialize; +use url::Url; use crate::config::Config; use crate::contact::{Contact, ContactId, Origin}; @@ -656,6 +657,7 @@ async fn decode_ideltachat(context: &Context, prefix: &str, qr: &str) -> Result< /// scheme: `DCACCOUNT:example.org` /// or `DCACCOUNT:https://example.org/new` /// or `DCACCOUNT:https://example.org/new_email?t=1w_7wDjgjelxeX884x96v3` +/// or `dcaccount:example.org?a=127.0.0.1,[::1]` fn decode_account(qr: &str) -> Result { let payload = qr .get(DCACCOUNT_SCHEME.len()..) @@ -784,9 +786,33 @@ pub(crate) async fn login_param_from_account_qr( if !payload.starts_with(HTTPS_SCHEME) { let rng = &mut rand::rngs::OsRng.unwrap_err(); let username = Alphanumeric.sample_string(rng, 9); - let addr = username + "@" + payload; + let host = if let Some(start_of_query) = payload.find("?") { + payload + .get(..start_of_query) + .context("failed to ignore query part")? + } else { + payload + }; + let addr = username + "@" + host; let password = Alphanumeric.sample_string(rng, 50); + let dns_prefill: Vec = match Url::parse(qr) { + Ok(url) => { + let options = url.query_pairs(); + let parameter_map: BTreeMap = options + .map(|(key, value)| (key.into_owned(), value.into_owned())) + .collect(); + parameter_map + .get("a") + .map(|ips| ips.split(",").map(|s| s.to_owned()).collect()) + .unwrap_or_default() + } + Err(err) => { + error!(context, "error parsing parameter of account url: {err}"); + Default::default() + } + }; + let param = EnteredLoginParam { addr, imap: EnteredServerLoginParam { @@ -796,6 +822,7 @@ pub(crate) async fn login_param_from_account_qr( smtp: Default::default(), certificate_checks: EnteredCertificateChecks::Strict, oauth2: false, + dns_prefill, }; return Ok(param); } @@ -816,6 +843,7 @@ pub(crate) async fn login_param_from_account_qr( smtp: Default::default(), certificate_checks: EnteredCertificateChecks::Strict, oauth2: false, + dns_prefill: Default::default(), }; Ok(param) diff --git a/src/qr/dclogin_scheme.rs b/src/qr/dclogin_scheme.rs index cd4352498..5262f5d61 100644 --- a/src/qr/dclogin_scheme.rs +++ b/src/qr/dclogin_scheme.rs @@ -191,6 +191,7 @@ pub(crate) fn login_param_from_login_qr( }, certificate_checks: certificate_checks.unwrap_or_default(), oauth2: false, + dns_prefill: Default::default(), }; Ok(param) } diff --git a/src/qr/qr_tests.rs b/src/qr/qr_tests.rs index d6dc88f72..d5e00357e 100644 --- a/src/qr/qr_tests.rs +++ b/src/qr/qr_tests.rs @@ -711,6 +711,31 @@ async fn test_decode_account() -> Result<()> { Ok(()) } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_decode_account_with_dns_prefill() -> Result<()> { + let ctx = &TestContext::new().await; + + for (qr, prefill_ips) in [ + ( + "dcaccount:example.org?a=127.0.0.1,[::1]", + vec!["127.0.0.1", "[::1]"], + ), + ( + "DCACCOUNT:example.org?a=127.0.0.1,[::1]", + vec!["127.0.0.1", "[::1]"], + ), + ("dcaccount:example.org?a=[::1]", vec!["[::1]"]), + ("DCACCOUNT:example.org?a=127.0.0.1", vec!["127.0.0.1"]), + ] { + let param = login_param_from_account_qr(ctx, qr).await?; + println!("addr {}", param.addr); + assert!(param.addr.ends_with("example.org")); + assert_eq!(param.dns_prefill, prefill_ips); + } + + Ok(()) +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_decode_tg_socks_proxy() -> Result<()> { let t = TestContext::new().await;