From 1019a9399162e385e8f84f270405b8e51b26d82d Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Wed, 29 Jul 2020 19:56:13 +0200 Subject: [PATCH 1/2] add qr-code-type for a webrtc-instance-pattern introduce the qr-code-type `DCWEBRTC:[webrtc_instance]`. dc_check_qr() returns this as the type DC_QR_WEBRTC and these qr-codes can be passed to dc_set_config_from_qr() then; this finally sets `webrtc_instance` using dc_set_config(). --- deltachat-ffi/deltachat.h | 13 ++++-- src/lot.rs | 3 ++ src/qr.rs | 94 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 105 insertions(+), 5 deletions(-) diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index ff38042ba..74682e92d 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -375,12 +375,13 @@ int dc_set_stock_translation(dc_context_t* context, uint32_t stock_i /** - * Set configuration values from a QR code containing an account. + * Set configuration values from a QR code. * Before this function is called, dc_check_qr() should confirm the type of the - * QR code is DC_QR_ACCOUNT. + * QR code is DC_QR_ACCOUNT or DC_QR_WEBRTC_INSTANCE. * - * Internally, the function will call dc_set_config() - * at least with the keys `addr` and `mail_pw`. + * Internally, the function will call dc_set_config() with the appropriate keys, + * eg. `addr` and `mail_pw` for DC_QR_ACCOUNT + * or `webrtc_instance` for DC_QR_WEBRTC_INSTANCE. * * @memberof dc_context_t * @param context The context object @@ -1927,6 +1928,7 @@ void dc_stop_ongoing_process (dc_context_t* context); #define DC_QR_FPR_MISMATCH 220 // id=contact #define DC_QR_FPR_WITHOUT_ADDR 230 // test1=formatted fingerprint #define DC_QR_ACCOUNT 250 // text1=domain +#define DC_QR_WEBRTC_INSTANCE 260 // text1=domain #define DC_QR_ADDR 320 // id=contact #define DC_QR_TEXT 330 // text1=text #define DC_QR_URL 332 // text1=URL @@ -1945,6 +1947,9 @@ void dc_stop_ongoing_process (dc_context_t* context); * - DC_QR_FPR_MISMATCH with dc_lot_t::id=Contact ID * - DC_QR_FPR_WITHOUT_ADDR with dc_lot_t::test1=Formatted fingerprint * - DC_QR_ACCOUNT allows creation of an account, dc_lot_t::text1=domain + * - DC_QR_WEBRTC_INSTANCE - a shared webrtc-instance + * that will be set if dc_set_config_from_qr() is called with the qr-code, + * dc_lot_t::text1=domain could be used to ask the user * - DC_QR_ADDR with dc_lot_t::id=Contact ID * - DC_QR_TEXT with dc_lot_t::text1=Text * - DC_QR_URL with dc_lot_t::text1=URL diff --git a/src/lot.rs b/src/lot.rs index 1fa426bac..3a069e473 100644 --- a/src/lot.rs +++ b/src/lot.rs @@ -91,6 +91,9 @@ pub enum LotState { /// text1=domain QrAccount = 250, + /// text1=domain, text2=instance pattern + QrWebrtcInstance = 260, + /// id=contact QrAddr = 320, diff --git a/src/qr.rs b/src/qr.rs index ee5a70562..37004690e 100644 --- a/src/qr.rs +++ b/src/qr.rs @@ -12,11 +12,13 @@ use crate::context::Context; use crate::error::{bail, ensure, format_err, Error}; use crate::key::Fingerprint; use crate::lot::{Lot, LotState}; +use crate::message::Message; use crate::param::*; use crate::peerstate::*; const OPENPGP4FPR_SCHEME: &str = "OPENPGP4FPR:"; // yes: uppercase const DCACCOUNT_SCHEME: &str = "DCACCOUNT:"; +const DCWEBRTC_SCHEME: &str = "DCWEBRTC:"; const MAILTO_SCHEME: &str = "mailto:"; const MATMSG_SCHEME: &str = "MATMSG:"; const VCARD_SCHEME: &str = "BEGIN:VCARD"; @@ -51,6 +53,8 @@ pub async fn check_qr(context: &Context, qr: impl AsRef) -> Lot { decode_openpgp(context, qr).await } else if starts_with_ignore_case(qr, DCACCOUNT_SCHEME) { decode_account(context, qr) + } else if starts_with_ignore_case(qr, DCWEBRTC_SCHEME) { + decode_webrtc_instance(context, qr) } else if qr.starts_with(MAILTO_SCHEME) { decode_mailto(context, qr).await } else if qr.starts_with(SMTP_SCHEME) { @@ -210,6 +214,31 @@ fn decode_account(_context: &Context, qr: &str) -> Lot { lot } +/// scheme: `DCWEBRTC:https://meet.jit.si/$ROOM` +#[allow(clippy::indexing_slicing)] +fn decode_webrtc_instance(_context: &Context, qr: &str) -> Lot { + let payload = &qr[DCWEBRTC_SCHEME.len()..]; + + let mut lot = Lot::new(); + + let (_type, url) = Message::parse_webrtc_instance(payload); + if let Ok(url) = url::Url::parse(&url) { + if url.scheme() == "http" || url.scheme() == "https" { + lot.state = LotState::QrWebrtcInstance; + lot.text1 = url.host_str().map(|x| x.to_string()); + lot.text2 = Some(payload.to_string()) + } else { + lot.state = LotState::QrError; + lot.text1 = Some(format!("Bad scheme for webrtc instance: {}", payload)); + } + } else { + lot.state = LotState::QrError; + lot.text1 = Some(format!("Invalid webrtc instance: {}", payload)); + } + + lot +} + #[derive(Debug, Deserialize)] struct CreateAccountResponse { email: String, @@ -220,7 +249,7 @@ struct CreateAccountResponse { /// download additional information from the contained url and set the parameters. /// on success, a configure::configure() should be able to log in to the account #[allow(clippy::indexing_slicing)] -pub async fn set_config_from_qr(context: &Context, qr: &str) -> Result<(), Error> { +async fn set_account_from_qr(context: &Context, qr: &str) -> Result<(), Error> { let url_str = &qr[DCACCOUNT_SCHEME.len()..]; let response: Result = @@ -240,6 +269,20 @@ pub async fn set_config_from_qr(context: &Context, qr: &str) -> Result<(), Error Ok(()) } +pub async fn set_config_from_qr(context: &Context, qr: &str) -> Result<(), Error> { + match check_qr(context, &qr).await.state { + LotState::QrAccount => set_account_from_qr(context, qr).await, + LotState::QrWebrtcInstance => { + let val = decode_webrtc_instance(context, qr).text2; + context + .set_config(Config::WebrtcInstance, val.as_ref().map(|x| x.as_str())) + .await?; + Ok(()) + } + _ => bail!("qr code does not contain config: {}", qr), + } +} + /// Extract address for the mailto scheme. /// /// Scheme: `mailto:addr...?subject=...&body=..` @@ -618,6 +661,25 @@ mod tests { assert_eq!(res.get_text1().unwrap(), "example.org"); } + #[async_std::test] + async fn test_decode_webrtc_instance() { + let ctx = TestContext::new().await; + + let res = check_qr(&ctx.ctx, "DCWEBRTC:basicwebrtc:https://basicurl.com/$ROOM").await; + assert_eq!(res.get_state(), LotState::QrWebrtcInstance); + assert_eq!(res.get_text1().unwrap(), "basicurl.com"); + assert_eq!( + res.get_text2().unwrap(), + "basicwebrtc:https://basicurl.com/$ROOM" + ); + + // Test it again with mixcased "dcWebRTC:" uri scheme + let res = check_qr(&ctx.ctx, "dcWebRTC:https://example.org/").await; + assert_eq!(res.get_state(), LotState::QrWebrtcInstance); + assert_eq!(res.get_text1().unwrap(), "example.org"); + assert_eq!(res.get_text2().unwrap(), "https://example.org/"); + } + #[async_std::test] async fn test_decode_account_bad_scheme() { let ctx = TestContext::new().await; @@ -638,4 +700,34 @@ mod tests { assert_eq!(res.get_state(), LotState::QrError); assert!(res.get_text1().is_some()); } + + #[async_std::test] + async fn test_set_config_from_qr() { + let ctx = TestContext::new().await; + + assert!(ctx.ctx.get_config(Config::WebrtcInstance).await.is_none()); + + let res = set_config_from_qr(&ctx.ctx, "badqr:https://example.org/").await; + assert!(!res.is_ok()); + assert!(ctx.ctx.get_config(Config::WebrtcInstance).await.is_none()); + + let res = set_config_from_qr(&ctx.ctx, "https://no.qr").await; + assert!(!res.is_ok()); + assert!(ctx.ctx.get_config(Config::WebrtcInstance).await.is_none()); + + let res = set_config_from_qr(&ctx.ctx, "dcwebrtc:https://example.org/").await; + assert!(res.is_ok()); + assert_eq!( + ctx.ctx.get_config(Config::WebrtcInstance).await.unwrap(), + "https://example.org/" + ); + + let res = + set_config_from_qr(&ctx.ctx, "DCWEBRTC:basicwebrtc:https://foo.bar/?$ROOM&test").await; + assert!(res.is_ok()); + assert_eq!( + ctx.ctx.get_config(Config::WebrtcInstance).await.unwrap(), + "basicwebrtc:https://foo.bar/?$ROOM&test" + ); + } } From 799c56b492c25b6df8698e97e331b4acebf3fa5f Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Thu, 30 Jul 2020 12:54:04 +0200 Subject: [PATCH 2/2] make clippy happy --- src/qr.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qr.rs b/src/qr.rs index 37004690e..ba4eae45e 100644 --- a/src/qr.rs +++ b/src/qr.rs @@ -275,7 +275,7 @@ pub async fn set_config_from_qr(context: &Context, qr: &str) -> Result<(), Error LotState::QrWebrtcInstance => { let val = decode_webrtc_instance(context, qr).text2; context - .set_config(Config::WebrtcInstance, val.as_ref().map(|x| x.as_str())) + .set_config(Config::WebrtcInstance, val.as_deref()) .await?; Ok(()) }