From 52f4293bc523366b3cfc67426394f9b0d2019d37 Mon Sep 17 00:00:00 2001 From: link2xt Date: Sun, 15 Mar 2026 20:17:41 +0000 Subject: [PATCH] feat: decode `dcaccount://` URLs and error out on empty URLs early The problem was reported at iOS typically transforms `:` into `://`, we already handle this in `dclogin` URLs, so handle it for `dcaccount` as well. --- src/qr.rs | 12 ++++++++++++ src/qr/qr_tests.rs | 17 +++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/qr.rs b/src/qr.rs index 1cbf734a6..1d48b34dc 100644 --- a/src/qr.rs +++ b/src/qr.rs @@ -682,6 +682,12 @@ fn decode_account(qr: &str) -> Result { let payload = qr .get(DCACCOUNT_SCHEME.len()..) .context("Invalid DCACCOUNT payload")?; + + // Handle `dcaccount://...` URLs. + let payload = payload.strip_prefix("//").unwrap_or(payload); + if payload.is_empty() { + bail!("dcaccount payload is empty"); + } if payload.starts_with("https://") { let url = url::Url::parse(payload).context("Invalid account URL")?; if url.scheme() == "https" { @@ -695,6 +701,12 @@ fn decode_account(qr: &str) -> Result { bail!("Bad scheme for account URL: {:?}.", url.scheme()); } } else { + if payload.starts_with("/") { + // Handle `dcaccount:///` URL reported to have been created + // by Telegram link parser at + // + bail!("Hostname in dcaccount URL cannot start with /"); + } Ok(Qr::Account { domain: payload.to_string(), }) diff --git a/src/qr/qr_tests.rs b/src/qr/qr_tests.rs index 43516c419..cf0550c7f 100644 --- a/src/qr/qr_tests.rs +++ b/src/qr/qr_tests.rs @@ -721,7 +721,9 @@ async fn test_decode_account() -> Result<()> { for text in [ "DCACCOUNT:example.org", + "DCACCOUNT://example.org", "dcaccount:example.org", + "dcaccount://example.org", "DCACCOUNT:https://example.org/new_email?t=1w_7wDjgjelxeX884x96v3", "dcaccount:https://example.org/new_email?t=1w_7wDjgjelxeX884x96v3", ] { @@ -737,6 +739,21 @@ async fn test_decode_account() -> Result<()> { Ok(()) } +/// Tests that decoding empty `dcaccount://` URL results in an error. +/// We should not suggest trying to configure an account in this case. +/// Such links may be created by copy-paste error or because of incorrect parsing. +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_decode_empty_account() -> Result<()> { + let ctx = TestContext::new().await; + + for text in ["DCACCOUNT:", "dcaccount:", "dcaccount://", "dcaccount:///"] { + let qr = check_qr(&ctx.ctx, text).await; + assert!(qr.is_err(), "Invalid {text:?} is parsed as dcaccount URL"); + } + + Ok(()) +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_decode_tg_socks_proxy() -> Result<()> { let t = TestContext::new().await;