diff --git a/payjoin/src/uri/mod.rs b/payjoin/src/uri/mod.rs index aa8cc693..954e421b 100644 --- a/payjoin/src/uri/mod.rs +++ b/payjoin/src/uri/mod.rs @@ -41,7 +41,26 @@ impl PayjoinExtras { } pub type Uri<'a, NetworkValidation> = bip21::Uri<'a, NetworkValidation, MaybePayjoinExtras>; -pub type PjUri<'a> = bip21::Uri<'a, NetworkChecked, PayjoinExtras>; +#[derive(Clone)] +pub struct PjUri<'a>(bip21::Uri<'a, NetworkChecked, PayjoinExtras>); + +impl<'a> std::fmt::Display for PjUri<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + // FIXME bip21 does not escape # in pj parameter + // work around this by overriding + let malformed_uri = format!("{}", self.0); + let escaped = malformed_uri.replacen("#", "%23", 1); + write!(f, "{}", escaped) + } +} + +impl<'a> std::ops::Deref for PjUri<'a> { + type Target = bip21::Uri<'a, NetworkChecked, PayjoinExtras>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} mod sealed { use bitcoin::address::NetworkChecked; @@ -65,7 +84,7 @@ impl<'a> UriExt<'a> for Uri<'a, NetworkChecked> { uri.label = self.label; uri.message = self.message; - Ok(uri) + Ok(PjUri(uri)) } MaybePayjoinExtras::Unsupported => { let mut uri = bip21::Uri::new(self.address); @@ -157,7 +176,7 @@ impl PjUriBuilder { pj_uri.amount = self.amount; pj_uri.label = self.label.map(Into::into); pj_uri.message = self.message.map(Into::into); - pj_uri + PjUri(pj_uri) } } diff --git a/payjoin/src/uri/url_ext.rs b/payjoin/src/uri/url_ext.rs index c0743ca9..4cdbb145 100644 --- a/payjoin/src/uri/url_ext.rs +++ b/payjoin/src/uri/url_ext.rs @@ -129,9 +129,20 @@ mod tests { #[test] fn test_valid_v2_url_fragment_on_bip21() { let uri = "bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?amount=0.01\ - &pj=https://example.com\ - #ohttp%3DAQO6SMScPUqSo60A7MY6Ak2hDO0CGAxz7BLYp60syRu0gw"; - let uri = Uri::try_from(uri).unwrap().assume_checked().check_pj_supported().unwrap(); - assert!(uri.extras.endpoint().ohttp().is_some()); + &pj=https://example.com/\ + %23ohttp%3DAQO6SMScPUqSo60A7MY6Ak2hDO0CGAxz7BLYp60syRu0gw\ + &pjos=0"; + let pjuri = Uri::try_from(uri).unwrap().assume_checked().check_pj_supported().unwrap(); + + assert!(pjuri.extras.endpoint().ohttp().is_some()); + assert_eq!(format!("{}", pjuri), uri); + + let reordered = "bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?amount=0.01\ + &pjos=0&pj=https://example.com/\ + %23ohttp%3DAQO6SMScPUqSo60A7MY6Ak2hDO0CGAxz7BLYp60syRu0gw"; + let pjuri = + Uri::try_from(reordered).unwrap().assume_checked().check_pj_supported().unwrap(); + assert!(pjuri.extras.endpoint().ohttp().is_some()); + assert_eq!(format!("{}", pjuri), uri); } }