From 734d25de36f63abc3e335a1f0ba3235700c7449c Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Tue, 26 Nov 2024 15:28:37 +0100 Subject: [PATCH] nostr: add trailing slash handling to `RelayUrl` Signed-off-by: Yuki Kishimoto --- crates/nostr/src/event/tag/standard.rs | 36 ++++++++++++------------- crates/nostr/src/nips/nip19.rs | 7 +++-- crates/nostr/src/nips/nip47.rs | 5 ++-- crates/nostr/src/types/url.rs | 37 +++++++++++++++++++++++--- 4 files changed, 58 insertions(+), 27 deletions(-) diff --git a/crates/nostr/src/event/tag/standard.rs b/crates/nostr/src/event/tag/standard.rs index 65cf6d7b2..01146e631 100644 --- a/crates/nostr/src/event/tag/standard.rs +++ b/crates/nostr/src/event/tag/standard.rs @@ -1348,7 +1348,7 @@ mod tests { vec![ "q", "378f145897eea948952674269945e88612420db35791784abf0616b4fed56ef7", - "wss://relay.damus.io/" + "wss://relay.damus.io" ], TagStandard::Quote { event_id: EventId::from_hex( @@ -1411,7 +1411,7 @@ mod tests { vec![ "p", "13adc511de7e1cfcf1c6b7f6365fb5a03442d7bcacf565ea57fa7770912c023d", - "wss://relay.damus.io/" + "wss://relay.damus.io" ], TagStandard::PublicKey { public_key: PublicKey::from_str( @@ -1447,7 +1447,7 @@ mod tests { vec![ "e", "378f145897eea948952674269945e88612420db35791784abf0616b4fed56ef7", - "wss://relay.damus.io/" + "wss://relay.damus.io" ], TagStandard::Event { event_id: EventId::from_hex( @@ -1527,7 +1527,7 @@ mod tests { vec![ "a", "30023:a695f6b60119d9521934a691347d9f78e8770b56da16bb255ee286ddf9fda919:ipsum", - "wss://relay.nostr.org/" + "wss://relay.nostr.org" ], TagStandard::Coordinate { coordinate: Coordinate::new( @@ -1548,7 +1548,7 @@ mod tests { vec![ "p", "13adc511de7e1cfcf1c6b7f6365fb5a03442d7bcacf565ea57fa7770912c023d", - "wss://relay.damus.io/", + "wss://relay.damus.io", "Speaker", ], TagStandard::PublicKeyLiveEvent { @@ -1586,7 +1586,7 @@ mod tests { vec![ "p", "13adc511de7e1cfcf1c6b7f6365fb5a03442d7bcacf565ea57fa7770912c023d", - "wss://relay.damus.io/", + "wss://relay.damus.io", "alias", ], TagStandard::PublicKey { @@ -1694,7 +1694,7 @@ mod tests { vec![ "p", "13adc511de7e1cfcf1c6b7f6365fb5a03442d7bcacf565ea57fa7770912c023d", - "wss://relay.damus.io/", + "wss://relay.damus.io", "Host", "a5d9290ef9659083c490b303eb7ee41356d8778ff19f2f91776c8dc4443388a64ffcf336e61af4c25c05ac3ae952d1ced889ed655b67790891222aaa15b99fdd" ], @@ -1726,7 +1726,7 @@ mod tests { assert_eq!( vec!["r", "wss://atlas.nostr.land/"], TagStandard::RelayMetadata { - relay_url: RelayUrl::parse("wss://atlas.nostr.land").unwrap(), + relay_url: RelayUrl::parse("wss://atlas.nostr.land/").unwrap(), metadata: None } .to_vec() @@ -1735,14 +1735,14 @@ mod tests { assert_eq!( vec!["r", "wss://atlas.nostr.land/", "read"], TagStandard::RelayMetadata { - relay_url: RelayUrl::parse("wss://atlas.nostr.land").unwrap(), + relay_url: RelayUrl::parse("wss://atlas.nostr.land/").unwrap(), metadata: Some(RelayMetadata::Read) } .to_vec() ); assert_eq!( - vec!["r", "wss://atlas.nostr.land/", "write"], + vec!["r", "wss://atlas.nostr.land", "write"], TagStandard::RelayMetadata { relay_url: RelayUrl::parse("wss://atlas.nostr.land").unwrap(), metadata: Some(RelayMetadata::Write) @@ -1861,7 +1861,7 @@ mod tests { TagStandard::parse(&[ "q", "378f145897eea948952674269945e88612420db35791784abf0616b4fed56ef7", - "wss://relay.damus.io/" + "wss://relay.damus.io" ]) .unwrap(), TagStandard::Quote { @@ -1967,7 +1967,7 @@ mod tests { TagStandard::parse(&[ "p", "13adc511de7e1cfcf1c6b7f6365fb5a03442d7bcacf565ea57fa7770912c023d", - "wss://relay.damus.io/" + "wss://relay.damus.io" ]) .unwrap(), TagStandard::PublicKey { @@ -2004,7 +2004,7 @@ mod tests { TagStandard::parse(&[ "e", "378f145897eea948952674269945e88612420db35791784abf0616b4fed56ef7", - "wss://relay.damus.io/" + "wss://relay.damus.io" ]) .unwrap(), TagStandard::Event { @@ -2095,7 +2095,7 @@ mod tests { TagStandard::parse(&[ "a", "30023:a695f6b60119d9521934a691347d9f78e8770b56da16bb255ee286ddf9fda919:ipsum", - "wss://relay.nostr.org/" + "wss://relay.nostr.org" ]) .unwrap(), TagStandard::Coordinate { @@ -2115,13 +2115,13 @@ mod tests { assert_eq!( TagStandard::parse(&["r", "wss://atlas.nostr.land/"]).unwrap(), TagStandard::RelayMetadata { - relay_url: RelayUrl::parse("wss://atlas.nostr.land").unwrap(), + relay_url: RelayUrl::parse("wss://atlas.nostr.land/").unwrap(), metadata: None } ); assert_eq!( - TagStandard::parse(&["r", "wss://atlas.nostr.land/", "read"]).unwrap(), + TagStandard::parse(&["r", "wss://atlas.nostr.land", "read"]).unwrap(), TagStandard::RelayMetadata { relay_url: RelayUrl::parse("wss://atlas.nostr.land").unwrap(), metadata: Some(RelayMetadata::Read) @@ -2129,7 +2129,7 @@ mod tests { ); assert_eq!( - TagStandard::parse(&["r", "wss://atlas.nostr.land/", "write"]).unwrap(), + TagStandard::parse(&["r", "wss://atlas.nostr.land", "write"]).unwrap(), TagStandard::RelayMetadata { relay_url: RelayUrl::parse("wss://atlas.nostr.land").unwrap(), metadata: Some(RelayMetadata::Write) @@ -2149,7 +2149,7 @@ mod tests { "13adc511de7e1cfcf1c6b7f6365fb5a03442d7bcacf565ea57fa7770912c023d" ) .unwrap(), - relay_url: Some(RelayUrl::parse("wss://relay.damus.io").unwrap()), + relay_url: Some(RelayUrl::parse("wss://relay.damus.io/").unwrap()), alias: Some(String::from("alias")), uppercase: false, } diff --git a/crates/nostr/src/nips/nip19.rs b/crates/nostr/src/nips/nip19.rs index 365556bd0..7bef4bd88 100644 --- a/crates/nostr/src/nips/nip19.rs +++ b/crates/nostr/src/nips/nip19.rs @@ -822,8 +822,11 @@ mod tests { assert_eq!( Nip19::Profile( - Nip19Profile::new(expected_pubkey, ["wss://r.x.com", "wss://djbas.sadkb.com"]) - .unwrap() + Nip19Profile::new( + expected_pubkey, + ["wss://r.x.com/", "wss://djbas.sadkb.com/"] + ) + .unwrap() ), nip19 ); diff --git a/crates/nostr/src/nips/nip47.rs b/crates/nostr/src/nips/nip47.rs index ea49c038a..1c231293c 100644 --- a/crates/nostr/src/nips/nip47.rs +++ b/crates/nostr/src/nips/nip47.rs @@ -977,8 +977,7 @@ impl FromStr for NostrWalletConnectURI { impl fmt::Display for NostrWalletConnectURI { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // trailing slash is removed, this breaks some clients - let relay_url = self.relay_url.to_string(); - let relay_url = relay_url.strip_suffix('/').unwrap_or(&relay_url); + let relay_url: &str = self.relay_url.as_str_without_trailing_slash(); write!( f, "{NOSTR_WALLET_CONNECT_URI_SCHEME}://{}?relay={}&secret={}", @@ -1041,7 +1040,7 @@ mod tests { #[test] fn test_parse_uri() { - let uri = "nostr+walletconnect://b889ff5b1513b641e2a139f661a661364979c5beee91842f8f0ef42ab558e9d4?relay=wss%3A%2F%2Frelay.damus.io%2F&secret=71a8c14c1407c113601079c4302dab36460f0ccd0ad506f1f2dc73b5100e4f3c&lud16=nostr%40nostr.com"; + let uri = "nostr+walletconnect://b889ff5b1513b641e2a139f661a661364979c5beee91842f8f0ef42ab558e9d4?relay=wss%3A%2F%2Frelay.damus.io&secret=71a8c14c1407c113601079c4302dab36460f0ccd0ad506f1f2dc73b5100e4f3c&lud16=nostr%40nostr.com"; let uri = NostrWalletConnectURI::from_str(uri).unwrap(); let pubkey = diff --git a/crates/nostr/src/types/url.rs b/crates/nostr/src/types/url.rs index 51eb8584f..fcea3e5f0 100644 --- a/crates/nostr/src/types/url.rs +++ b/crates/nostr/src/types/url.rs @@ -49,6 +49,7 @@ impl From for Error { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct RelayUrl { url: Url, + has_trailing_slash: bool, } impl RelayUrl { @@ -65,12 +66,18 @@ impl RelayUrl { return Err(Error::MultipleSchemeSeparators); } + // Check if has trailing slash + let has_trailing_slash: bool = url.ends_with('/'); + // Parse URL let url: Url = Url::parse(url)?; // Check scheme match url.scheme() { - "ws" | "wss" => Ok(Self { url }), + "ws" | "wss" => Ok(Self { + url, + has_trailing_slash, + }), scheme => Err(Error::UnsupportedScheme(scheme.to_string())), } } @@ -83,16 +90,29 @@ impl RelayUrl { .map_or(false, |host| host.ends_with(".onion")) } + /// Return the serialization of this relay URL without the trailing slash. + /// + /// This method will always remove the trailing slash. + #[inline] + pub fn as_str_without_trailing_slash(&self) -> &str { + self.url.as_str().trim_end_matches('/') + } + /// Return the serialization of this relay URL. + /// + /// The trailing slash will be removed only if the parsed URL hadn't it. #[inline] pub fn as_str(&self) -> &str { + if !self.has_trailing_slash { + return self.as_str_without_trailing_slash(); + } + self.url.as_str() } } impl fmt::Display for RelayUrl { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // TODO: if no path, remove last "/" write!(f, "{}", self.as_str()) } } @@ -245,9 +265,18 @@ mod tests { } #[test] - fn test_relay_url_display() { + fn test_relay_url_as_str() { let relay_url = RelayUrl::parse("ws://example.com").unwrap(); - assert_eq!(relay_url.to_string(), "ws://example.com/"); + assert_eq!(relay_url.as_str(), "ws://example.com"); + + let relay_url = RelayUrl::parse("ws://example.com/").unwrap(); + assert_eq!(relay_url.as_str(), "ws://example.com/"); + + let relay_url = RelayUrl::parse("ws://example.com/").unwrap(); + assert_eq!( + relay_url.as_str_without_trailing_slash(), + "ws://example.com" + ); } #[test]