From 6b9aac5234c65e27fffbceb6147eecddd7d4fd2e Mon Sep 17 00:00:00 2001 From: adbenitez Date: Sat, 9 Jul 2022 05:42:39 -0400 Subject: [PATCH 01/10] detect draft when scanning QR with mailto link as data --- deltachat-ffi/src/lot.rs | 18 +++++++++++++--- src/qr.rs | 45 ++++++++++++++++++++++++++++++++-------- 2 files changed, 51 insertions(+), 12 deletions(-) diff --git a/deltachat-ffi/src/lot.rs b/deltachat-ffi/src/lot.rs index 9426ef425..8d1281609 100644 --- a/deltachat-ffi/src/lot.rs +++ b/deltachat-ffi/src/lot.rs @@ -51,7 +51,13 @@ impl Lot { Qr::FprWithoutAddr { fingerprint, .. } => Some(fingerprint), Qr::Account { domain } => Some(domain), Qr::WebrtcInstance { domain, .. } => Some(domain), - Qr::Addr { .. } => None, + Qr::Addr { draft, .. } => { + if let Some(draft) = draft { + Some(draft) + } else { + None + } + } Qr::Url { url } => Some(url), Qr::Text { text } => Some(text), Qr::WithdrawVerifyContact { .. } => None, @@ -79,7 +85,13 @@ impl Lot { Some(SummaryPrefix::Username(_username)) => Meaning::Text1Username, Some(SummaryPrefix::Me(_text)) => Meaning::Text1Self, }, - Self::Qr(_qr) => Meaning::None, + Self::Qr(qr) => match qr { + Qr::Addr { draft, .. } => match draft { + None => Meaning::None, + _ => Meaning::Text1Draft, + }, + _ => Meaning::None, + }, Self::Error(_err) => Meaning::None, } } @@ -118,7 +130,7 @@ impl Lot { Qr::FprWithoutAddr { .. } => Default::default(), Qr::Account { .. } => Default::default(), Qr::WebrtcInstance { .. } => Default::default(), - Qr::Addr { contact_id } => contact_id.to_u32(), + Qr::Addr { contact_id, .. } => contact_id.to_u32(), Qr::Url { .. } => Default::default(), Qr::Text { .. } => Default::default(), Qr::WithdrawVerifyContact { contact_id, .. } => contact_id.to_u32(), diff --git a/src/qr.rs b/src/qr.rs index d3693fff8..43ae20ef4 100644 --- a/src/qr.rs +++ b/src/qr.rs @@ -61,6 +61,7 @@ pub enum Qr { }, Addr { contact_id: ContactId, + draft: Option, }, Url { url: String, @@ -451,15 +452,41 @@ pub async fn set_config_from_qr(context: &Context, qr: &str) -> Result<()> { async fn decode_mailto(context: &Context, qr: &str) -> Result { let payload = &qr[MAILTO_SCHEME.len()..]; - let addr = if let Some(query_index) = payload.find('?') { - &payload[..query_index] + let (addr, query) = if let Some(query_index) = payload.find('?') { + (&payload[..query_index], &payload[query_index+1..]) } else { - payload + (payload, "") + }; + + let param: BTreeMap<&str, &str> = query + .split('&') + .filter_map(|s| { + if let [key, value] = s.splitn(2, '=').collect::>()[..] { + Some((key, value)) + } else { + None + } + }) + .collect(); + + let subject = if let Some(subject) = param.get("subject") { + subject.to_string() + } else { + "".to_string() + }; + let draft = if let Some(body) = param.get("body") { + if subject.is_empty() { + body.to_string() + } else { + subject + "\n" + body + } + } else { + subject }; let addr = normalize_address(addr)?; let name = "".to_string(); - Qr::from_address(context, name, addr).await + Qr::from_address(context, name, addr, if draft.is_empty() { None } else { Some(draft) }).await } /// Extract address for the smtp scheme. @@ -477,7 +504,7 @@ async fn decode_smtp(context: &Context, qr: &str) -> Result { let addr = normalize_address(addr)?; let name = "".to_string(); - Qr::from_address(context, name, addr).await + Qr::from_address(context, name, addr, None).await } /// Extract address for the matmsg scheme. @@ -502,7 +529,7 @@ async fn decode_matmsg(context: &Context, qr: &str) -> Result { let addr = normalize_address(addr)?; let name = "".to_string(); - Qr::from_address(context, name, addr).await + Qr::from_address(context, name, addr, None).await } static VCARD_NAME_RE: Lazy = @@ -531,14 +558,14 @@ async fn decode_vcard(context: &Context, qr: &str) -> Result { bail!("Bad e-mail address"); }; - Qr::from_address(context, name, addr).await + Qr::from_address(context, name, addr, None).await } impl Qr { - pub async fn from_address(context: &Context, name: String, addr: String) -> Result { + pub async fn from_address(context: &Context, name: String, addr: String, draft: Option) -> Result { let (contact_id, _) = Contact::add_or_lookup(context, &name, &addr, Origin::UnhandledQrScan).await?; - Ok(Qr::Addr { contact_id }) + Ok(Qr::Addr { contact_id, draft }) } } From 62a0ce29e92693e9655e3eaa574e9038fd555fc1 Mon Sep 17 00:00:00 2001 From: adbenitez Date: Sat, 9 Jul 2022 15:19:39 -0400 Subject: [PATCH 02/10] decode draft --- src/qr.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/qr.rs b/src/qr.rs index 43ae20ef4..78d0479ca 100644 --- a/src/qr.rs +++ b/src/qr.rs @@ -483,6 +483,11 @@ async fn decode_mailto(context: &Context, qr: &str) -> Result { } else { subject }; + let draft = draft.replace('+', "%20"); // sometimes spaces are encoded as `+` + let draft = match percent_decode_str(&draft).decode_utf8() { + Ok(decoded_draft) => decoded_draft.to_string(), + Err(_err) => draft, + }; let addr = normalize_address(addr)?; let name = "".to_string(); From 0b2c3ee1631f816ef53f076662e21469eedd0600 Mon Sep 17 00:00:00 2001 From: link2xt Date: Sat, 9 Jul 2022 19:24:01 +0000 Subject: [PATCH 03/10] Use as_deref() instead of unwrapping Option --- deltachat-ffi/src/lot.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/deltachat-ffi/src/lot.rs b/deltachat-ffi/src/lot.rs index 8d1281609..189ec30a6 100644 --- a/deltachat-ffi/src/lot.rs +++ b/deltachat-ffi/src/lot.rs @@ -51,13 +51,7 @@ impl Lot { Qr::FprWithoutAddr { fingerprint, .. } => Some(fingerprint), Qr::Account { domain } => Some(domain), Qr::WebrtcInstance { domain, .. } => Some(domain), - Qr::Addr { draft, .. } => { - if let Some(draft) = draft { - Some(draft) - } else { - None - } - } + Qr::Addr { draft, .. } => draft.as_deref(), Qr::Url { url } => Some(url), Qr::Text { text } => Some(text), Qr::WithdrawVerifyContact { .. } => None, From 6ae278f73571575663aa5f05c8cdb73aaf1b074f Mon Sep 17 00:00:00 2001 From: link2xt Date: Sat, 9 Jul 2022 19:24:19 +0000 Subject: [PATCH 04/10] cargo fmt --- src/qr.rs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/qr.rs b/src/qr.rs index 78d0479ca..3e8f9e20c 100644 --- a/src/qr.rs +++ b/src/qr.rs @@ -453,7 +453,7 @@ async fn decode_mailto(context: &Context, qr: &str) -> Result { let payload = &qr[MAILTO_SCHEME.len()..]; let (addr, query) = if let Some(query_index) = payload.find('?') { - (&payload[..query_index], &payload[query_index+1..]) + (&payload[..query_index], &payload[query_index + 1..]) } else { (payload, "") }; @@ -478,7 +478,7 @@ async fn decode_mailto(context: &Context, qr: &str) -> Result { if subject.is_empty() { body.to_string() } else { - subject + "\n" + body + subject + "\n" + body } } else { subject @@ -491,7 +491,13 @@ async fn decode_mailto(context: &Context, qr: &str) -> Result { let addr = normalize_address(addr)?; let name = "".to_string(); - Qr::from_address(context, name, addr, if draft.is_empty() { None } else { Some(draft) }).await + Qr::from_address( + context, + name, + addr, + if draft.is_empty() { None } else { Some(draft) }, + ) + .await } /// Extract address for the smtp scheme. @@ -567,7 +573,12 @@ async fn decode_vcard(context: &Context, qr: &str) -> Result { } impl Qr { - pub async fn from_address(context: &Context, name: String, addr: String, draft: Option) -> Result { + pub async fn from_address( + context: &Context, + name: String, + addr: String, + draft: Option, + ) -> Result { let (contact_id, _) = Contact::add_or_lookup(context, &name, &addr, Origin::UnhandledQrScan).await?; Ok(Qr::Addr { contact_id, draft }) From 68a15725d29ce33a8aebc4a186488d58b49a1c5a Mon Sep 17 00:00:00 2001 From: adbenitez Date: Sat, 9 Jul 2022 15:45:06 -0400 Subject: [PATCH 05/10] fix tests --- src/qr.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/qr.rs b/src/qr.rs index 3e8f9e20c..72590e1a6 100644 --- a/src/qr.rs +++ b/src/qr.rs @@ -701,20 +701,22 @@ mod tests { let qr = check_qr( &ctx.ctx, - "mailto:stress@test.local?subject=hello&body=world", + "mailto:stress@test.local?subject=hello&body=beautiful+world", ) .await?; - if let Qr::Addr { contact_id } = qr { + if let Qr::Addr { contact_id, draft } = qr { let contact = Contact::get_by_id(&ctx.ctx, contact_id).await?; assert_eq!(contact.get_addr(), "stress@test.local"); + assert_eq!(draft.unwrap(), "hello\nbeautiful world"); } else { bail!("Wrong QR code type"); } let res = check_qr(&ctx.ctx, "mailto:no-questionmark@example.org").await?; - if let Qr::Addr { contact_id } = res { + if let Qr::Addr { contact_id, draft } = res { let contact = Contact::get_by_id(&ctx.ctx, contact_id).await?; assert_eq!(contact.get_addr(), "no-questionmark@example.org"); + assert!(draft.is_none()); } else { bail!("Wrong QR code type"); } From 50e18665728869ce5f785b7f1ca6755d7279123f Mon Sep 17 00:00:00 2001 From: adbenitez Date: Sat, 9 Jul 2022 15:51:10 -0400 Subject: [PATCH 06/10] update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 601ac1850..f7b398fa8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased ### Changes +- handle drafts from mailto links in scanned QR ### Fixes - replace musl libc name resolution errors with a better message #3485 From 37cb16b95ccfd367ecb55f3256a43895da9bd3d7 Mon Sep 17 00:00:00 2001 From: adbenitez Date: Sat, 9 Jul 2022 16:23:31 -0400 Subject: [PATCH 07/10] update documentation --- deltachat-ffi/deltachat.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 13d2d56ee..9620c560d 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -2292,7 +2292,7 @@ void dc_stop_ongoing_process (dc_context_t* context); * - DC_QR_FPR_MISMATCH with dc_lot_t::id=Contact ID: * scanned fingerprint does not match last seen fingerprint. * - * - DC_QR_FPR_WITHOUT_ADDR with dc_lot_t::test1=Formatted fingerprint + * - DC_QR_FPR_WITHOUT_ADDR with dc_lot_t::text1=Formatted fingerprint * the scanned QR code contains a fingerprint but no e-mail address; * suggest the user to establish an encrypted connection first. * @@ -2305,7 +2305,8 @@ void dc_stop_ongoing_process (dc_context_t* context); * if so, call dc_set_config_from_qr(). * * - DC_QR_ADDR with dc_lot_t::id=Contact ID: - * e-mail address scanned, + * e-mail address scanned, optionally, a draft message could be set in + * dc_lot_t::text1 in which case dc_lot_t::text1_meaning will be DC_TEXT1_DRAFT; * ask the user if they want to start chatting; * if so, call dc_create_chat_by_contact_id(). * From 8c70393c905ff2dccd9eac6fba8006cb8d952ab5 Mon Sep 17 00:00:00 2001 From: adbenitez Date: Sat, 9 Jul 2022 16:35:00 -0400 Subject: [PATCH 08/10] fix other tests --- src/qr.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/qr.rs b/src/qr.rs index 72590e1a6..3f4089f4d 100644 --- a/src/qr.rs +++ b/src/qr.rs @@ -662,12 +662,13 @@ mod tests { "BEGIN:VCARD\nVERSION:3.0\nN:Last;First\nEMAIL;TYPE=INTERNET:stress@test.local\nEND:VCARD" ).await?; - if let Qr::Addr { contact_id } = qr { + if let Qr::Addr { contact_id, draft } = qr { let contact = Contact::get_by_id(&ctx.ctx, contact_id).await?; assert_eq!(contact.get_addr(), "stress@test.local"); assert_eq!(contact.get_name(), "First Last"); assert_eq!(contact.get_authname(), ""); assert_eq!(contact.get_display_name(), "First Last"); + assert!(draft.is_none()); } else { bail!("Wrong QR code type"); } @@ -685,9 +686,10 @@ mod tests { ) .await?; - if let Qr::Addr { contact_id } = qr { + if let Qr::Addr { contact_id, draft } = qr { let contact = Contact::get_by_id(&ctx.ctx, contact_id).await?; assert_eq!(contact.get_addr(), "stress@test.local"); + assert!(draft.is_none()); } else { bail!("Wrong QR code type"); } @@ -731,11 +733,12 @@ mod tests { async fn test_decode_smtp() -> Result<()> { let ctx = TestContext::new().await; - if let Qr::Addr { contact_id } = + if let Qr::Addr { contact_id, draft } = check_qr(&ctx.ctx, "SMTP:stress@test.local:subjecthello:bodyworld").await? { let contact = Contact::get_by_id(&ctx.ctx, contact_id).await?; assert_eq!(contact.get_addr(), "stress@test.local"); + assert!(draft.is_none()); } else { bail!("Wrong QR code type"); } From c9a1ebf2575c5611d754ecd9ffeab298ff09c08a Mon Sep 17 00:00:00 2001 From: link2xt Date: Sat, 9 Jul 2022 22:24:51 +0000 Subject: [PATCH 09/10] Collapse match patterns --- deltachat-ffi/src/lot.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/deltachat-ffi/src/lot.rs b/deltachat-ffi/src/lot.rs index 189ec30a6..cd702932d 100644 --- a/deltachat-ffi/src/lot.rs +++ b/deltachat-ffi/src/lot.rs @@ -80,10 +80,9 @@ impl Lot { Some(SummaryPrefix::Me(_text)) => Meaning::Text1Self, }, Self::Qr(qr) => match qr { - Qr::Addr { draft, .. } => match draft { - None => Meaning::None, - _ => Meaning::Text1Draft, - }, + Qr::Addr { + draft: Some(_draft), .. + } => Meaning::Text1Draft, _ => Meaning::None, }, Self::Error(_err) => Meaning::None, From 39ae44dbf06e78bfd9617575986ea4c3e0f1b02d Mon Sep 17 00:00:00 2001 From: link2xt Date: Sat, 9 Jul 2022 22:27:42 +0000 Subject: [PATCH 10/10] rustfmt --- deltachat-ffi/src/lot.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deltachat-ffi/src/lot.rs b/deltachat-ffi/src/lot.rs index cd702932d..2f1a1ff76 100644 --- a/deltachat-ffi/src/lot.rs +++ b/deltachat-ffi/src/lot.rs @@ -81,7 +81,8 @@ impl Lot { }, Self::Qr(qr) => match qr { Qr::Addr { - draft: Some(_draft), .. + draft: Some(_draft), + .. } => Meaning::Text1Draft, _ => Meaning::None, },