diff --git a/pubky-homeserver/src/database/tables/entries.rs b/pubky-homeserver/src/database/tables/entries.rs index 22a3aa4..e82c309 100644 --- a/pubky-homeserver/src/database/tables/entries.rs +++ b/pubky-homeserver/src/database/tables/entries.rs @@ -1,7 +1,7 @@ use pkarr::PublicKey; use postcard::{from_bytes, to_allocvec}; use serde::{Deserialize, Serialize}; -use tracing::{debug, instrument}; +use tracing::instrument; use heed::{ types::{Bytes, Str}, @@ -169,8 +169,6 @@ fn next_threshold( reverse: bool, shallow: bool, ) -> String { - debug!("Fuck me!"); - format!( "{path}{file_or_directory}{}", if file_or_directory.is_empty() { diff --git a/pubky/src/shared/pkarr.rs b/pubky/src/shared/pkarr.rs index 01cd0fb..e624a2a 100644 --- a/pubky/src/shared/pkarr.rs +++ b/pubky/src/shared/pkarr.rs @@ -10,7 +10,7 @@ use crate::{ PubkyClient, }; -const MAX_RECURSIVE_PUBKY_HOMESERVER_RESOLUTION: u8 = 3; +const MAX_ENDPOINT_RESOLUTION_RECURSION: u8 = 3; impl PubkyClient { /// Publish the SVCB record for `_pubky.`. @@ -56,24 +56,28 @@ impl PubkyClient { .map_err(|_| Error::Generic("Could not resolve homeserver".to_string())) } - /// Resolve a service's public_key and clearnet url from a Pubky domain + /// Resolve a service's public_key and "non-pkarr url" from a Pubky domain + /// + /// "non-pkarr" url is any URL where the hostname isn't a 52 z-base32 character, + /// usually an IPv4, IPv6 or ICANN domain, but could also be any other unknown hostname. + /// + /// Recursively resolve SVCB and HTTPS endpoints, with [MAX_ENDPOINT_RESOLUTION_RECURSION] limit. pub(crate) async fn resolve_endpoint(&self, target: &str) -> Result { let original_target = target; // TODO: cache the result of this function? let mut target = target.to_string(); - let mut homeserver_public_key = None; + let mut endpoint_public_key = None; let mut origin = target.clone(); let mut step = 0; // PublicKey is very good at extracting the Pkarr TLD from a string. while let Ok(public_key) = PublicKey::try_from(target.clone()) { - if step >= MAX_RECURSIVE_PUBKY_HOMESERVER_RESOLUTION { + if step >= MAX_ENDPOINT_RESOLUTION_RECURSION { break; }; - step += 1; if let Some(signed_packet) = self @@ -85,8 +89,12 @@ impl PubkyClient { let svcb = signed_packet.resource_records(&target).fold( None, |prev: Option, answer| { - if let pkarr::dns::rdata::RData::SVCB(curr) = &answer.rdata { - let curr = curr.clone(); + if let Some(svcb) = match &answer.rdata { + pkarr::dns::rdata::RData::SVCB(svcb) => Some(svcb), + pkarr::dns::rdata::RData::HTTPS(curr) => Some(&curr.0), + _ => None, + } { + let curr = svcb.clone(); if curr.priority == 0 { return Some(curr); @@ -106,7 +114,7 @@ impl PubkyClient { ); if let Some(svcb) = svcb { - homeserver_public_key = Some(public_key.clone()); + endpoint_public_key = Some(public_key.clone()); target = svcb.target.to_string(); if let Some(port) = svcb.get_param(pkarr::dns::rdata::SVCB::PORT) { @@ -120,14 +128,14 @@ impl PubkyClient { origin.clone_from(&target); }; - if step >= MAX_RECURSIVE_PUBKY_HOMESERVER_RESOLUTION { + if step >= MAX_ENDPOINT_RESOLUTION_RECURSION { continue; }; } } } - if let Some(public_key) = homeserver_public_key { + if let Some(public_key) = endpoint_public_key { let url = Url::parse(&format!( "{}://{}", if origin.starts_with("localhost") { @@ -145,6 +153,7 @@ impl PubkyClient { } } +#[derive(Debug)] pub(crate) struct Endpoint { pub public_key: PublicKey, pub url: Url, @@ -155,12 +164,104 @@ mod tests { use super::*; use pkarr::{ - dns::{rdata::SVCB, Packet}, + dns::{ + rdata::{HTTPS, SVCB}, + Packet, + }, mainline::{dht::DhtSettings, Testnet}, Keypair, PkarrClient, Settings, SignedPacket, }; use pubky_homeserver::Homeserver; + #[tokio::test] + async fn resolve_endpoint_https() { + let testnet = Testnet::new(10); + + let pkarr_client = PkarrClient::new(Settings { + dht: DhtSettings { + bootstrap: Some(testnet.bootstrap.clone()), + ..Default::default() + }, + ..Default::default() + }) + .unwrap() + .as_async(); + + let domain = "example.com"; + let mut target; + + // Server + { + let keypair = Keypair::random(); + + let https = HTTPS(SVCB::new(0, domain.try_into().unwrap())); + + let mut packet = Packet::new_reply(0); + + packet.answers.push(pkarr::dns::ResourceRecord::new( + "foo".try_into().unwrap(), + pkarr::dns::CLASS::IN, + 60 * 60, + pkarr::dns::rdata::RData::HTTPS(https), + )); + + let signed_packet = SignedPacket::from_packet(&keypair, &packet).unwrap(); + + pkarr_client.publish(&signed_packet).await.unwrap(); + + target = format!("foo.{}", keypair.public_key()); + } + + // intermediate + { + let keypair = Keypair::random(); + + let svcb = SVCB::new(0, target.as_str().try_into().unwrap()); + + let mut packet = Packet::new_reply(0); + + packet.answers.push(pkarr::dns::ResourceRecord::new( + "bar".try_into().unwrap(), + pkarr::dns::CLASS::IN, + 60 * 60, + pkarr::dns::rdata::RData::SVCB(svcb), + )); + + let signed_packet = SignedPacket::from_packet(&keypair, &packet).unwrap(); + + pkarr_client.publish(&signed_packet).await.unwrap(); + + target = format!("bar.{}", keypair.public_key()) + } + + { + let keypair = Keypair::random(); + + let svcb = SVCB::new(0, target.as_str().try_into().unwrap()); + + let mut packet = Packet::new_reply(0); + + packet.answers.push(pkarr::dns::ResourceRecord::new( + "pubky".try_into().unwrap(), + pkarr::dns::CLASS::IN, + 60 * 60, + pkarr::dns::rdata::RData::SVCB(svcb), + )); + + let signed_packet = SignedPacket::from_packet(&keypair, &packet).unwrap(); + + pkarr_client.publish(&signed_packet).await.unwrap(); + + target = format!("pubky.{}", keypair.public_key()) + } + + let client = PubkyClient::test(&testnet); + + let endpoint = client.resolve_endpoint(&target).await.unwrap(); + + assert_eq!(endpoint.url.host_str().unwrap(), domain); + } + #[tokio::test] async fn resolve_homeserver() { let testnet = Testnet::new(10); diff --git a/pubky/src/shared/public.rs b/pubky/src/shared/public.rs index 7dbb18e..dabfdd8 100644 --- a/pubky/src/shared/public.rs +++ b/pubky/src/shared/public.rs @@ -63,32 +63,31 @@ impl PubkyClient { pub(crate) async fn pubky_to_http>(&self, url: T) -> Result { let original_url: Url = url.try_into().map_err(|_| Error::InvalidUrl)?; - if original_url.scheme() != "pubky" { - return Ok(original_url); - } - let pubky = original_url .host_str() - .ok_or(Error::Generic("Missing Pubky Url host".to_string()))? - .to_string(); + .ok_or(Error::Generic("Missing Pubky Url host".to_string()))?; - let Endpoint { mut url, .. } = self - .resolve_pubky_homeserver(&PublicKey::try_from(pubky.clone())?) - .await?; + if let Ok(public_key) = PublicKey::try_from(pubky) { + let Endpoint { mut url, .. } = self.resolve_pubky_homeserver(&public_key).await?; - let path = original_url.path_segments(); + // TODO: remove if we move to subdomains instead of paths. + if original_url.scheme() == "pubky" { + let path = original_url.path_segments(); - // TODO: replace if we move to subdomains instead of paths. - let mut split = url.path_segments_mut().unwrap(); - split.push(&pubky); - if let Some(segments) = path { - for segment in segments { - split.push(segment); + let mut split = url.path_segments_mut().unwrap(); + split.push(pubky); + if let Some(segments) = path { + for segment in segments { + split.push(segment); + } + } + drop(split); } - } - drop(split); - Ok(url) + return Ok(url); + } + + Ok(original_url) } }