diff --git a/Cargo.lock b/Cargo.lock index d5f67cc..c505c83 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,17 +4,16 @@ version = 3 [[package]] name = "activitystreams" -version = "0.7.0-alpha.14" +version = "0.7.0-alpha.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bcc3fbb392890a1942b1e5cca76cba93c8ed24b5ff50004cc3289afaab3f92c" +checksum = "31bca51dcfddda6551570371a1c065528050eea2151a29897ac2dad67b5624cb" dependencies = [ "activitystreams-kinds", - "chrono", + "iri-string", "mime", "serde 1.0.133", "serde_json", - "thiserror", - "url", + "time 0.3.5", ] [[package]] @@ -30,12 +29,12 @@ dependencies = [ [[package]] name = "activitystreams-kinds" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0784e99afd032199d3ed70cefb8eb3a8d1aef15f7f2c4e68d033c4e12bb6079e" +checksum = "4a762e3441050b51cd0695c1413735cd04195950f50dba8f5da5d7201628fcfc" dependencies = [ + "iri-string", "serde 1.0.133", - "url", ] [[package]] @@ -318,7 +317,6 @@ dependencies = [ "awc", "background-jobs", "base64", - "chrono", "config", "console-subscriber", "dashmap", @@ -339,6 +337,7 @@ dependencies = [ "sled", "structopt", "thiserror", + "time 0.3.5", "toml", "tracing", "tracing-actix-web", @@ -1256,6 +1255,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "iri-string" +version = "0.5.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08b57553d1be311c9e979117451952d55f1b33366abe5a07f07899dc2f11a4d9" +dependencies = [ + "serde 1.0.133", +] + [[package]] name = "itertools" version = "0.10.3" @@ -3045,7 +3053,6 @@ dependencies = [ "idna", "matches", "percent-encoding", - "serde 1.0.133", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index dd1935a..aa4845c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,13 +21,12 @@ anyhow = "1.0" actix-rt = "2.0.2" actix-web = { version = "4.0.0-beta.7", default-features = false } actix-webfinger = "0.4.0-beta.5" -activitystreams = "0.7.0-alpha.10" +activitystreams = "0.7.0-alpha.16" activitystreams-ext = "0.1.0-alpha.2" ammonia = "3.1.0" async-rwlock = "1.3.0" awc = { version = "3.0.0-beta.6", default-features = false, features = ["rustls"] } base64 = "0.13" -chrono = "0.4.19" config = "0.11.0" console-subscriber = "0.1" dashmap = "5.0.0" @@ -46,6 +45,7 @@ sha2 = "0.10" sled = "0.34.6" structopt = "0.3.12" thiserror = "1.0" +time = "0.3.5" tracing = "0.1" tracing-awc = { version = "0.1.0-beta.10" } tracing-error = "0.2" diff --git a/src/apub.rs b/src/apub.rs index 11eff3e..7fe3e9c 100644 --- a/src/apub.rs +++ b/src/apub.rs @@ -2,15 +2,15 @@ use activitystreams::{ activity::ActorAndObject, actor::{Actor, ApActor}, unparsed::UnparsedMutExt, - url::Url, + iri_string::types::IriString, }; use activitystreams_ext::{Ext1, UnparsedExtension}; #[derive(Clone, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "camelCase")] pub struct PublicKeyInner { - pub id: Url, - pub owner: Url, + pub id: IriString, + pub owner: IriString, pub public_key_pem: String, } diff --git a/src/config.rs b/src/config.rs index 43088be..0b15e57 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,7 +4,13 @@ use crate::{ middleware::MyVerify, requests::Requests, }; -use activitystreams::{uri, url::Url}; +use activitystreams::{ + iri, + iri_string::{ + resolve::FixedBaseResolver, + types::{IriAbsoluteString, IriFragmentStr, IriRelativeStr, IriString}, + }, +}; use config::Environment; use http_signature_normalization_actix::prelude::{VerifyDigest, VerifySignature}; use sha2::{Digest, Sha256}; @@ -22,8 +28,8 @@ pub(crate) struct ParsedConfig { https: bool, publish_blocks: bool, sled_path: PathBuf, - source_repo: Url, - opentelemetry_url: Option, + source_repo: IriString, + opentelemetry_url: Option, } #[derive(Clone)] @@ -35,12 +41,13 @@ pub struct Config { restricted_mode: bool, validate_signatures: bool, publish_blocks: bool, - base_uri: Url, + base_uri: IriAbsoluteString, sled_path: PathBuf, - source_repo: Url, - opentelemetry_url: Option, + source_repo: IriString, + opentelemetry_url: Option, } +#[derive(Debug)] pub enum UrlKind { Activity, Actor, @@ -95,7 +102,7 @@ impl Config { let config: ParsedConfig = config.try_into()?; let scheme = if config.https { "https" } else { "http" }; - let base_uri = uri!(format!("{}://{}", scheme, config.hostname)); + let base_uri = iri!(format!("{}://{}", scheme, config.hostname)).into_absolute(); Ok(Config { hostname: config.hostname, @@ -210,33 +217,50 @@ impl Config { ) } - pub(crate) fn source_code(&self) -> &Url { + pub(crate) fn source_code(&self) -> &IriString { &self.source_repo } - pub(crate) fn opentelemetry_url(&self) -> Option<&Url> { + pub(crate) fn opentelemetry_url(&self) -> Option<&IriString> { self.opentelemetry_url.as_ref() } - pub(crate) fn generate_url(&self, kind: UrlKind) -> Url { - let mut url = self.base_uri.clone(); + pub(crate) fn generate_url(&self, kind: UrlKind) -> IriString { + self.do_generate_url(kind).expect("Generated valid IRI") + } - match kind { - UrlKind::Activity => url.set_path(&format!("activity/{}", Uuid::new_v4())), - UrlKind::Actor => url.set_path("actor"), - UrlKind::Followers => url.set_path("followers"), - UrlKind::Following => url.set_path("following"), - UrlKind::Inbox => url.set_path("inbox"), - UrlKind::Index => (), + #[tracing::instrument(fields(base_uri = tracing::field::debug(&self.base_uri), kind = tracing::field::debug(&kind)))] + fn do_generate_url(&self, kind: UrlKind) -> Result { + let iri = match kind { + UrlKind::Activity => FixedBaseResolver::new(self.base_uri.as_ref()) + .resolve(IriRelativeStr::new(&format!("activity/{}", Uuid::new_v4()))?.as_ref())?, + UrlKind::Actor => FixedBaseResolver::new(self.base_uri.as_ref()) + .resolve(IriRelativeStr::new("actor")?.as_ref())?, + UrlKind::Followers => FixedBaseResolver::new(self.base_uri.as_ref()) + .resolve(IriRelativeStr::new("followers")?.as_ref())?, + UrlKind::Following => FixedBaseResolver::new(self.base_uri.as_ref()) + .resolve(IriRelativeStr::new("following")?.as_ref())?, + UrlKind::Inbox => FixedBaseResolver::new(self.base_uri.as_ref()) + .resolve(IriRelativeStr::new("inbox")?.as_ref())?, + UrlKind::Index => self.base_uri.clone().into(), UrlKind::MainKey => { - url.set_path("actor"); - url.set_fragment(Some("main-key")); + let actor = IriRelativeStr::new("actor")?; + let fragment = IriFragmentStr::new("main-key")?; + + let mut resolved = + FixedBaseResolver::new(self.base_uri.as_ref()).resolve(actor.as_ref())?; + + resolved.set_fragment(Some(fragment)); + resolved } - UrlKind::Media(uuid) => url.set_path(&format!("media/{}", uuid)), - UrlKind::NodeInfo => url.set_path("nodeinfo/2.0.json"), - UrlKind::Outbox => url.set_path("outbox"), + UrlKind::Media(uuid) => FixedBaseResolver::new(self.base_uri.as_ref()) + .resolve(IriRelativeStr::new(&format!("media/{}", uuid))?.as_ref())?, + UrlKind::NodeInfo => FixedBaseResolver::new(self.base_uri.as_ref()) + .resolve(IriRelativeStr::new("nodeinfo/2.0.json")?.as_ref())?, + UrlKind::Outbox => FixedBaseResolver::new(self.base_uri.as_ref()) + .resolve(IriRelativeStr::new("outbox")?.as_ref())?, }; - url + Ok(iri) } } diff --git a/src/data/actor.rs b/src/data/actor.rs index c9000fb..59c5c2f 100644 --- a/src/data/actor.rs +++ b/src/data/actor.rs @@ -4,7 +4,7 @@ use crate::{ error::{Error, ErrorKind}, requests::Requests, }; -use activitystreams::{prelude::*, url::Url}; +use activitystreams::{iri_string::types::IriString, prelude::*}; use std::time::{Duration, SystemTime}; const REFETCH_DURATION: Duration = Duration::from_secs(60 * 30); @@ -40,7 +40,7 @@ impl ActorCache { #[tracing::instrument(name = "Get Actor", fields(id = id.to_string().as_str(), requests))] pub(crate) async fn get( &self, - id: &Url, + id: &IriString, requests: &Requests, ) -> Result, Error> { if let Some(actor) = self.db.actor(id.clone()).await? { @@ -66,12 +66,16 @@ impl ActorCache { } #[tracing::instrument(name = "Fetch remote actor", fields(id = id.to_string().as_str(), requests))] - pub(crate) async fn get_no_cache(&self, id: &Url, requests: &Requests) -> Result { + pub(crate) async fn get_no_cache( + &self, + id: &IriString, + requests: &Requests, + ) -> Result { let accepted_actor = requests.fetch::(id.as_str()).await?; - let input_domain = id.domain().ok_or(ErrorKind::MissingDomain)?; + let input_authority = id.authority_components().ok_or(ErrorKind::MissingDomain)?; let accepted_actor_id = accepted_actor - .id(input_domain)? + .id(input_authority.host(), input_authority.port())? .ok_or(ErrorKind::MissingId)?; let inbox = get_inbox(&accepted_actor)?.clone(); @@ -90,7 +94,7 @@ impl ActorCache { } } -fn get_inbox(actor: &AcceptedActors) -> Result<&Url, Error> { +fn get_inbox(actor: &AcceptedActors) -> Result<&IriString, Error> { Ok(actor .endpoints()? .and_then(|e| e.shared_inbox) diff --git a/src/data/media.rs b/src/data/media.rs index 5ca71d1..f504a03 100644 --- a/src/data/media.rs +++ b/src/data/media.rs @@ -2,7 +2,7 @@ use crate::{ db::{Db, MediaMeta}, error::Error, }; -use activitystreams::url::Url; +use activitystreams::iri_string::types::IriString; use actix_web::web::Bytes; use std::time::{Duration, SystemTime}; use uuid::Uuid; @@ -20,12 +20,12 @@ impl MediaCache { } #[tracing::instrument(name = "Get media uuid", fields(url = url.to_string().as_str()))] - pub(crate) async fn get_uuid(&self, url: Url) -> Result, Error> { + pub(crate) async fn get_uuid(&self, url: IriString) -> Result, Error> { self.db.media_id(url).await } #[tracing::instrument(name = "Get media url")] - pub(crate) async fn get_url(&self, uuid: Uuid) -> Result, Error> { + pub(crate) async fn get_url(&self, uuid: Uuid) -> Result, Error> { self.db.media_url(uuid).await } @@ -56,7 +56,7 @@ impl MediaCache { } #[tracing::instrument(name = "Store media url", fields(url = url.to_string().as_str()))] - pub(crate) async fn store_url(&self, url: Url) -> Result { + pub(crate) async fn store_url(&self, url: IriString) -> Result { let uuid = Uuid::new_v4(); self.db.save_url(url, uuid).await?; diff --git a/src/data/node.rs b/src/data/node.rs index 6808468..42a2260 100644 --- a/src/data/node.rs +++ b/src/data/node.rs @@ -1,8 +1,8 @@ use crate::{ db::{Contact, Db, Info, Instance}, - error::Error, + error::{Error, ErrorKind}, }; -use activitystreams::url::Url; +use activitystreams::{iri, iri_string::types::IriString}; use std::time::{Duration, SystemTime}; #[derive(Clone, Debug)] @@ -12,7 +12,7 @@ pub struct NodeCache { #[derive(Clone, serde::Deserialize, serde::Serialize)] pub struct Node { - pub(crate) base: Url, + pub(crate) base: IriString, pub(crate) info: Option, pub(crate) instance: Option, pub(crate) contact: Option, @@ -50,18 +50,15 @@ impl NodeCache { let instance = instances.get(&actor_id).cloned(); let contact = contacts.get(&actor_id).cloned(); - Node::new(actor_id) - .info(info) - .instance(instance) - .contact(contact) + Node::new(actor_id).map(|node| node.info(info).instance(instance).contact(contact)) }) - .collect(); + .collect::, Error>>()?; Ok(vec) } #[tracing::instrument(name = "Is NodeInfo Outdated", fields(actor_id = actor_id.to_string().as_str()))] - pub(crate) async fn is_nodeinfo_outdated(&self, actor_id: Url) -> bool { + pub(crate) async fn is_nodeinfo_outdated(&self, actor_id: IriString) -> bool { self.db .info(actor_id) .await @@ -70,7 +67,7 @@ impl NodeCache { } #[tracing::instrument(name = "Is Contact Outdated", fields(actor_id = actor_id.to_string().as_str()))] - pub(crate) async fn is_contact_outdated(&self, actor_id: Url) -> bool { + pub(crate) async fn is_contact_outdated(&self, actor_id: IriString) -> bool { self.db .contact(actor_id) .await @@ -79,7 +76,7 @@ impl NodeCache { } #[tracing::instrument(name = "Is Instance Outdated", fields(actor_id = actor_id.to_string().as_str()))] - pub(crate) async fn is_instance_outdated(&self, actor_id: Url) -> bool { + pub(crate) async fn is_instance_outdated(&self, actor_id: IriString) -> bool { self.db .instance(actor_id) .await @@ -90,7 +87,7 @@ impl NodeCache { #[tracing::instrument(name = "Save node info", fields(actor_id = actor_id.to_string().as_str(), software, version, reg))] pub(crate) async fn set_info( &self, - actor_id: Url, + actor_id: IriString, software: String, version: String, reg: bool, @@ -121,7 +118,7 @@ impl NodeCache { )] pub(crate) async fn set_instance( &self, - actor_id: Url, + actor_id: IriString, title: String, description: String, version: String, @@ -155,11 +152,11 @@ impl NodeCache { )] pub(crate) async fn set_contact( &self, - actor_id: Url, + actor_id: IriString, username: String, display_name: String, - url: Url, - avatar: Url, + url: IriString, + avatar: IriString, ) -> Result<(), Error> { self.db .save_contact( @@ -177,17 +174,18 @@ impl NodeCache { } impl Node { - fn new(mut url: Url) -> Self { - url.set_fragment(None); - url.set_query(None); - url.set_path(""); + fn new(url: IriString) -> Result { + let authority = url.authority_str().ok_or(ErrorKind::MissingDomain)?; + let scheme = url.scheme_str(); - Node { - base: url, + let base = iri!(format!("{}://{}", scheme, authority)); + + Ok(Node { + base, info: None, instance: None, contact: None, - } + }) } fn info(mut self, info: Option) -> Self { diff --git a/src/data/state.rs b/src/data/state.rs index 2f3df71..b2e9820 100644 --- a/src/data/state.rs +++ b/src/data/state.rs @@ -5,7 +5,7 @@ use crate::{ error::Error, requests::{Breakers, Requests}, }; -use activitystreams::url::Url; +use activitystreams::iri_string::types::IriString; use actix_web::web; use async_rwlock::RwLock; use lru::LruCache; @@ -18,7 +18,7 @@ use tracing::info; pub struct State { pub(crate) public_key: RsaPublicKey, private_key: RsaPrivateKey, - object_cache: Arc>>, + object_cache: Arc>>, node_cache: NodeCache, breakers: Breakers, pub(crate) db: Db, @@ -52,22 +52,22 @@ impl State { name = "Get inboxes for other domains", fields( existing_inbox = existing_inbox.to_string().as_str(), - domain + authority ) )] pub(crate) async fn inboxes_without( &self, - existing_inbox: &Url, - domain: &str, - ) -> Result, Error> { + existing_inbox: &IriString, + authority: &str, + ) -> Result, Error> { Ok(self .db .inboxes() .await? .iter() .filter_map(|inbox| { - if let Some(dom) = inbox.domain() { - if inbox != existing_inbox && dom != domain { + if let Some(authority_str) = inbox.authority_str() { + if inbox != existing_inbox && authority_str != authority { return Some(inbox.clone()); } } @@ -77,11 +77,11 @@ impl State { .collect()) } - pub(crate) async fn is_cached(&self, object_id: &Url) -> bool { + pub(crate) async fn is_cached(&self, object_id: &IriString) -> bool { self.object_cache.read().await.contains(object_id) } - pub(crate) async fn cache(&self, object_id: Url, actor_id: Url) { + pub(crate) async fn cache(&self, object_id: IriString, actor_id: IriString) { self.object_cache.write().await.put(object_id, actor_id); } diff --git a/src/db.rs b/src/db.rs index 2da98b1..e8f5038 100644 --- a/src/db.rs +++ b/src/db.rs @@ -1,5 +1,8 @@ -use crate::{config::Config, error::Error}; -use activitystreams::url::Url; +use crate::{ + config::Config, + error::{Error, ErrorKind}, +}; +use activitystreams::iri_string::types::IriString; use actix_web::web::Bytes; use rsa::{ pkcs8::{FromPrivateKey, ToPrivateKey}, @@ -41,10 +44,10 @@ impl std::fmt::Debug for Inner { #[derive(Clone, serde::Deserialize, serde::Serialize)] pub struct Actor { - pub(crate) id: Url, + pub(crate) id: IriString, pub(crate) public_key: String, - pub(crate) public_key_id: Url, - pub(crate) inbox: Url, + pub(crate) public_key_id: IriString, + pub(crate) inbox: IriString, pub(crate) saved_at: SystemTime, } @@ -88,8 +91,8 @@ pub struct Instance { pub struct Contact { pub(crate) username: String, pub(crate) display_name: String, - pub(crate) url: Url, - pub(crate) avatar: Url, + pub(crate) url: IriString, + pub(crate) avatar: IriString, pub(crate) updated: SystemTime, } @@ -106,7 +109,10 @@ impl std::fmt::Debug for Contact { } impl Inner { - fn connected_by_domain(&self, domains: &[String]) -> impl DoubleEndedIterator { + fn connected_by_domain( + &self, + domains: &[String], + ) -> impl DoubleEndedIterator { let reversed: Vec<_> = domains.iter().map(|s| domain_key(s.as_str())).collect(); self.connected_actor_ids @@ -115,7 +121,7 @@ impl Inner { .filter_map(|res| res.ok()) .filter_map(url_from_ivec) .filter_map(move |url| { - let connected_domain = url.domain()?; + let connected_domain = url.authority_str()?; let connected_rdnn = domain_key(connected_domain); for rdnn in &reversed { @@ -136,7 +142,7 @@ impl Inner { .map(|s| String::from_utf8_lossy(&s).to_string()) } - fn connected(&self) -> impl DoubleEndedIterator { + fn connected(&self) -> impl DoubleEndedIterator { self.connected_actor_ids .iter() .values() @@ -156,7 +162,7 @@ impl Inner { }) } - fn connected_info(&self) -> impl DoubleEndedIterator + '_ { + fn connected_info(&self) -> impl DoubleEndedIterator + '_ { self.connected_actor_ids .iter() .values() @@ -170,7 +176,7 @@ impl Inner { }) } - fn connected_instance(&self) -> impl DoubleEndedIterator + '_ { + fn connected_instance(&self) -> impl DoubleEndedIterator + '_ { self.connected_actor_ids .iter() .values() @@ -184,7 +190,7 @@ impl Inner { }) } - fn connected_contact(&self) -> impl DoubleEndedIterator + '_ { + fn connected_contact(&self) -> impl DoubleEndedIterator + '_ { self.connected_actor_ids .iter() .values() @@ -198,9 +204,9 @@ impl Inner { }) } - fn is_allowed(&self, domain: &str) -> bool { - let prefix = domain_prefix(domain); - let reverse_domain = domain_key(domain); + fn is_allowed(&self, authority: &str) -> bool { + let prefix = domain_prefix(authority); + let reverse_domain = domain_key(authority); if self.restricted_mode { self.allowed_domains @@ -263,11 +269,11 @@ impl Db { Ok(t) } - pub(crate) async fn connected_ids(&self) -> Result, Error> { + pub(crate) async fn connected_ids(&self) -> Result, Error> { self.unblock(|inner| Ok(inner.connected().collect())).await } - pub(crate) async fn save_info(&self, actor_id: Url, info: Info) -> Result<(), Error> { + pub(crate) async fn save_info(&self, actor_id: IriString, info: Info) -> Result<(), Error> { self.unblock(move |inner| { let vec = serde_json::to_vec(&info)?; @@ -280,7 +286,7 @@ impl Db { .await } - pub(crate) async fn info(&self, actor_id: Url) -> Result, Error> { + pub(crate) async fn info(&self, actor_id: IriString) -> Result, Error> { self.unblock(move |inner| { if let Some(ivec) = inner.actor_id_info.get(actor_id.as_str().as_bytes())? { let info = serde_json::from_slice(&ivec)?; @@ -292,14 +298,14 @@ impl Db { .await } - pub(crate) async fn connected_info(&self) -> Result, Error> { + pub(crate) async fn connected_info(&self) -> Result, Error> { self.unblock(|inner| Ok(inner.connected_info().collect())) .await } pub(crate) async fn save_instance( &self, - actor_id: Url, + actor_id: IriString, instance: Instance, ) -> Result<(), Error> { self.unblock(move |inner| { @@ -314,7 +320,7 @@ impl Db { .await } - pub(crate) async fn instance(&self, actor_id: Url) -> Result, Error> { + pub(crate) async fn instance(&self, actor_id: IriString) -> Result, Error> { self.unblock(move |inner| { if let Some(ivec) = inner.actor_id_instance.get(actor_id.as_str().as_bytes())? { let instance = serde_json::from_slice(&ivec)?; @@ -326,12 +332,16 @@ impl Db { .await } - pub(crate) async fn connected_instance(&self) -> Result, Error> { + pub(crate) async fn connected_instance(&self) -> Result, Error> { self.unblock(|inner| Ok(inner.connected_instance().collect())) .await } - pub(crate) async fn save_contact(&self, actor_id: Url, contact: Contact) -> Result<(), Error> { + pub(crate) async fn save_contact( + &self, + actor_id: IriString, + contact: Contact, + ) -> Result<(), Error> { self.unblock(move |inner| { let vec = serde_json::to_vec(&contact)?; @@ -344,7 +354,7 @@ impl Db { .await } - pub(crate) async fn contact(&self, actor_id: Url) -> Result, Error> { + pub(crate) async fn contact(&self, actor_id: IriString) -> Result, Error> { self.unblock(move |inner| { if let Some(ivec) = inner.actor_id_contact.get(actor_id.as_str().as_bytes())? { let contact = serde_json::from_slice(&ivec)?; @@ -356,12 +366,12 @@ impl Db { .await } - pub(crate) async fn connected_contact(&self) -> Result, Error> { + pub(crate) async fn connected_contact(&self) -> Result, Error> { self.unblock(|inner| Ok(inner.connected_contact().collect())) .await } - pub(crate) async fn save_url(&self, url: Url, id: Uuid) -> Result<(), Error> { + pub(crate) async fn save_url(&self, url: IriString, id: Uuid) -> Result<(), Error> { self.unblock(move |inner| { inner .media_id_media_url @@ -393,7 +403,7 @@ impl Db { .await } - pub(crate) async fn media_id(&self, url: Url) -> Result, Error> { + pub(crate) async fn media_id(&self, url: IriString) -> Result, Error> { self.unblock(move |inner| { if let Some(ivec) = inner.media_url_media_id.get(url.as_str().as_bytes())? { Ok(uuid_from_ivec(ivec)) @@ -404,7 +414,7 @@ impl Db { .await } - pub(crate) async fn media_url(&self, id: Uuid) -> Result, Error> { + pub(crate) async fn media_url(&self, id: Uuid) -> Result, Error> { self.unblock(move |inner| { if let Some(ivec) = inner.media_id_media_url.get(id.as_bytes())? { Ok(url_from_ivec(ivec)) @@ -442,24 +452,22 @@ impl Db { self.unblock(|inner| Ok(inner.blocks().collect())).await } - pub(crate) async fn inboxes(&self) -> Result, Error> { + pub(crate) async fn inboxes(&self) -> Result, Error> { self.unblock(|inner| Ok(inner.connected_actors().map(|actor| actor.inbox).collect())) .await } - pub(crate) async fn is_connected(&self, mut id: Url) -> Result { - id.set_path(""); - id.set_query(None); - id.set_fragment(None); + pub(crate) async fn is_connected(&self, base_id: IriString) -> Result { + let scheme = base_id.scheme_str(); + let authority = base_id.authority_str().ok_or(ErrorKind::MissingDomain)?; + let prefix = format!("{}://{}", scheme, authority); self.unblock(move |inner| { let connected = inner .connected_actor_ids - .scan_prefix(id.as_str().as_bytes()) + .scan_prefix(prefix.as_bytes()) .values() - .filter_map(|res| res.ok()) - .next() - .is_some(); + .any(|res| res.is_ok()); Ok(connected) }) @@ -468,8 +476,8 @@ impl Db { pub(crate) async fn actor_id_from_public_key_id( &self, - public_key_id: Url, - ) -> Result, Error> { + public_key_id: IriString, + ) -> Result, Error> { self.unblock(move |inner| { if let Some(ivec) = inner .public_key_id_actor_id @@ -483,7 +491,7 @@ impl Db { .await } - pub(crate) async fn actor(&self, actor_id: Url) -> Result, Error> { + pub(crate) async fn actor(&self, actor_id: IriString) -> Result, Error> { self.unblock(move |inner| { if let Some(ivec) = inner.actor_id_actor.get(actor_id.as_str().as_bytes())? { let actor = serde_json::from_slice(&ivec)?; @@ -511,7 +519,7 @@ impl Db { .await } - pub(crate) async fn remove_connection(&self, actor_id: Url) -> Result<(), Error> { + pub(crate) async fn remove_connection(&self, actor_id: IriString) -> Result<(), Error> { tracing::debug!("Removing Connection: {}", actor_id); self.unblock(move |inner| { inner @@ -523,7 +531,7 @@ impl Db { .await } - pub(crate) async fn add_connection(&self, actor_id: Url) -> Result<(), Error> { + pub(crate) async fn add_connection(&self, actor_id: IriString) -> Result<(), Error> { tracing::debug!("Adding Connection: {}", actor_id); self.unblock(move |inner| { inner @@ -543,11 +551,11 @@ impl Db { .remove(connected.as_str().as_bytes())?; } - for domain in &domains { + for authority in &domains { inner .blocked_domains - .insert(domain_key(domain), domain.as_bytes())?; - inner.allowed_domains.remove(domain_key(domain))?; + .insert(domain_key(authority), authority.as_bytes())?; + inner.allowed_domains.remove(domain_key(authority))?; } Ok(()) @@ -557,8 +565,8 @@ impl Db { pub(crate) async fn remove_blocks(&self, domains: Vec) -> Result<(), Error> { self.unblock(move |inner| { - for domain in &domains { - inner.blocked_domains.remove(domain_key(domain))?; + for authority in &domains { + inner.blocked_domains.remove(domain_key(authority))?; } Ok(()) @@ -568,10 +576,10 @@ impl Db { pub(crate) async fn add_allows(&self, domains: Vec) -> Result<(), Error> { self.unblock(move |inner| { - for domain in &domains { + for authority in &domains { inner .allowed_domains - .insert(domain_key(domain), domain.as_bytes())?; + .insert(domain_key(authority), authority.as_bytes())?; } Ok(()) @@ -589,8 +597,8 @@ impl Db { } } - for domain in &domains { - inner.allowed_domains.remove(domain_key(domain))?; + for authority in &domains { + inner.allowed_domains.remove(domain_key(authority))?; } Ok(()) @@ -598,10 +606,10 @@ impl Db { .await } - pub(crate) async fn is_allowed(&self, url: Url) -> Result { + pub(crate) async fn is_allowed(&self, url: IriString) -> Result { self.unblock(move |inner| { - if let Some(domain) = url.domain() { - Ok(inner.is_allowed(domain)) + if let Some(authority) = url.authority_str() { + Ok(inner.is_allowed(authority)) } else { Ok(false) } @@ -639,12 +647,12 @@ impl Db { } } -fn domain_key(domain: &str) -> String { - domain.split('.').rev().collect::>().join(".") + "." +fn domain_key(authority: &str) -> String { + authority.split('.').rev().collect::>().join(".") + "." } -fn domain_prefix(domain: &str) -> String { - domain +fn domain_prefix(authority: &str) -> String { + authority .split('.') .rev() .take(2) @@ -653,8 +661,8 @@ fn domain_prefix(domain: &str) -> String { + "." } -fn url_from_ivec(ivec: sled::IVec) -> Option { - String::from_utf8_lossy(&ivec).parse::().ok() +fn url_from_ivec(ivec: sled::IVec) -> Option { + String::from_utf8_lossy(&ivec).parse::().ok() } fn uuid_from_ivec(ivec: sled::IVec) -> Option { @@ -664,14 +672,14 @@ fn uuid_from_ivec(ivec: sled::IVec) -> Option { #[cfg(test)] mod tests { use super::Db; - use activitystreams::url::Url; + use activitystreams::iri_string::types::IriString; use std::future::Future; #[test] fn connect_and_verify() { run(|db| async move { - let example_actor: Url = "http://example.com/actor".parse().unwrap(); - let example_sub_actor: Url = "http://example.com/users/fake".parse().unwrap(); + let example_actor: IriString = "http://example.com/actor".parse().unwrap(); + let example_sub_actor: IriString = "http://example.com/users/fake".parse().unwrap(); db.add_connection(example_actor.clone()).await.unwrap(); assert!(db.is_connected(example_sub_actor).await.unwrap()); }) @@ -680,8 +688,8 @@ mod tests { #[test] fn disconnect_and_verify() { run(|db| async move { - let example_actor: Url = "http://example.com/actor".parse().unwrap(); - let example_sub_actor: Url = "http://example.com/users/fake".parse().unwrap(); + let example_actor: IriString = "http://example.com/actor".parse().unwrap(); + let example_sub_actor: IriString = "http://example.com/users/fake".parse().unwrap(); db.add_connection(example_actor.clone()).await.unwrap(); assert!(db.is_connected(example_sub_actor.clone()).await.unwrap()); @@ -693,7 +701,7 @@ mod tests { #[test] fn connected_actor_in_connected_list() { run(|db| async move { - let example_actor: Url = "http://example.com/actor".parse().unwrap(); + let example_actor: IriString = "http://example.com/actor".parse().unwrap(); db.add_connection(example_actor.clone()).await.unwrap(); assert!(db.connected_ids().await.unwrap().contains(&example_actor)); @@ -703,7 +711,7 @@ mod tests { #[test] fn disconnected_actor_not_in_connected_list() { run(|db| async move { - let example_actor: Url = "http://example.com/actor".parse().unwrap(); + let example_actor: IriString = "http://example.com/actor".parse().unwrap(); db.add_connection(example_actor.clone()).await.unwrap(); db.remove_connection(example_actor.clone()).await.unwrap(); diff --git a/src/error.rs b/src/error.rs index ef6f73d..1cf9bd5 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,4 @@ -use activitystreams::{error::DomainError, url::ParseError}; +use activitystreams::checked::CheckError; use actix_web::{ error::{BlockingError, ResponseError}, http::StatusCode, @@ -56,8 +56,11 @@ pub(crate) enum ErrorKind { #[error("Couldn't parse key, {0}")] Pkcs8(#[from] rsa::pkcs8::Error), - #[error("Couldn't parse URI, {0}")] - Uri(#[from] ParseError), + #[error("Couldn't parse IRI, {0}")] + ParseIri(#[from] activitystreams::iri_string::validate::Error), + + #[error("Couldn't resolve IRI, {0}")] + ResolveIri(#[from] activitystreams::iri_string::resolve::Error), #[error("Couldn't perform IO, {0}")] Io(#[from] io::Error), @@ -102,7 +105,7 @@ pub(crate) enum ErrorKind { CpuCount(#[from] std::num::TryFromIntError), #[error("{0}")] - HostMismatch(#[from] DomainError), + HostMismatch(#[from] CheckError), #[error("Invalid or missing content type")] ContentType, @@ -137,7 +140,7 @@ pub(crate) enum ErrorKind { #[error("Input is missing a 'id' field")] MissingId, - #[error("Url is missing a domain")] + #[error("IriString is missing a domain")] MissingDomain, #[error("URI is missing domain field")] diff --git a/src/jobs/apub/announce.rs b/src/jobs/apub/announce.rs index f5d88ba..69b3e72 100644 --- a/src/jobs/apub/announce.rs +++ b/src/jobs/apub/announce.rs @@ -7,13 +7,13 @@ use crate::{ DeliverMany, JobState, }, }; -use activitystreams::{activity::Announce as AsAnnounce, url::Url}; +use activitystreams::{activity::Announce as AsAnnounce, iri_string::types::IriString}; use background_jobs::ActixJob; use std::{future::Future, pin::Pin}; #[derive(Clone, serde::Deserialize, serde::Serialize)] pub(crate) struct Announce { - object_id: Url, + object_id: IriString, actor: Actor, } @@ -27,7 +27,7 @@ impl std::fmt::Debug for Announce { } impl Announce { - pub fn new(object_id: Url, actor: Actor) -> Self { + pub fn new(object_id: IriString, actor: Actor) -> Self { Announce { object_id, actor } } @@ -49,8 +49,8 @@ impl Announce { // Generate a type that says "Look at this object" fn generate_announce( config: &Config, - activity_id: &Url, - object_id: &Url, + activity_id: &IriString, + object_id: &IriString, ) -> Result { let announce = AsAnnounce::new(config.generate_url(UrlKind::Actor), object_id.clone()); diff --git a/src/jobs/apub/follow.rs b/src/jobs/apub/follow.rs index 33094bd..3663276 100644 --- a/src/jobs/apub/follow.rs +++ b/src/jobs/apub/follow.rs @@ -8,7 +8,7 @@ use crate::{ use activitystreams::{ activity::{Accept as AsAccept, Follow as AsFollow}, prelude::*, - url::Url, + iri_string::types::IriString, }; use background_jobs::ActixJob; use std::{future::Future, pin::Pin}; @@ -62,7 +62,7 @@ impl Follow { } // Generate a type that says "I want to follow you" -fn generate_follow(config: &Config, actor_id: &Url, my_id: &Url) -> Result { +fn generate_follow(config: &Config, actor_id: &IriString, my_id: &IriString) -> Result { let follow = AsFollow::new(my_id.clone(), actor_id.clone()); prepare_activity( @@ -75,9 +75,9 @@ fn generate_follow(config: &Config, actor_id: &Url, my_id: &Url) -> Result Result { let mut follow = AsFollow::new(actor_id.clone(), my_id.clone()); diff --git a/src/jobs/apub/forward.rs b/src/jobs/apub/forward.rs index 1104b24..1a6c613 100644 --- a/src/jobs/apub/forward.rs +++ b/src/jobs/apub/forward.rs @@ -23,7 +23,7 @@ impl Forward { async fn perform(self, state: JobState) -> Result<(), Error> { let object_id = self .input - .object() + .object_unchecked() .as_single_id() .ok_or(ErrorKind::MissingId)?; @@ -31,7 +31,8 @@ impl Forward { state .job_server - .queue(DeliverMany::new(inboxes, self.input)?).await?; + .queue(DeliverMany::new(inboxes, self.input)?) + .await?; Ok(()) } diff --git a/src/jobs/apub/mod.rs b/src/jobs/apub/mod.rs index b2900fb..d857b46 100644 --- a/src/jobs/apub/mod.rs +++ b/src/jobs/apub/mod.rs @@ -7,9 +7,9 @@ use crate::{ use activitystreams::{ activity::{Follow as AsFollow, Undo as AsUndo}, context, + iri_string::types::IriString, prelude::*, security, - url::Url, }; use std::convert::TryInto; @@ -23,16 +23,23 @@ pub(crate) use self::{ announce::Announce, follow::Follow, forward::Forward, reject::Reject, undo::Undo, }; -async fn get_inboxes(state: &State, actor: &Actor, object_id: &Url) -> Result, Error> { - let domain = object_id.host().ok_or(ErrorKind::Domain)?.to_string(); +async fn get_inboxes( + state: &State, + actor: &Actor, + object_id: &IriString, +) -> Result, Error> { + let authority = object_id + .authority_str() + .ok_or(ErrorKind::Domain)? + .to_string(); - state.inboxes_without(&actor.inbox, &domain).await + state.inboxes_without(&actor.inbox, &authority).await } fn prepare_activity( mut t: T, - id: impl TryInto, - to: impl TryInto, + id: impl TryInto, + to: impl TryInto, ) -> Result where T: ObjectExt + BaseExt, @@ -45,7 +52,11 @@ where } // Generate a type that says "I want to stop following you" -fn generate_undo_follow(config: &Config, actor_id: &Url, my_id: &Url) -> Result { +fn generate_undo_follow( + config: &Config, + actor_id: &IriString, + my_id: &IriString, +) -> Result { let mut follow = AsFollow::new(my_id.clone(), actor_id.clone()); follow.set_id(config.generate_url(UrlKind::Activity)); diff --git a/src/jobs/contact.rs b/src/jobs/contact.rs index 62714f3..2144b14 100644 --- a/src/jobs/contact.rs +++ b/src/jobs/contact.rs @@ -3,14 +3,14 @@ use crate::{ error::{Error, ErrorKind}, jobs::JobState, }; -use activitystreams::{object::Image, prelude::*, url::Url}; +use activitystreams::{object::Image, prelude::*, iri_string::types::IriString}; use background_jobs::ActixJob; use std::{future::Future, pin::Pin}; #[derive(Clone, serde::Deserialize, serde::Serialize)] pub(crate) struct QueryContact { - actor_id: Url, - contact_id: Url, + actor_id: IriString, + contact_id: IriString, } impl std::fmt::Debug for QueryContact { @@ -23,7 +23,7 @@ impl std::fmt::Debug for QueryContact { } impl QueryContact { - pub(crate) fn new(actor_id: Url, contact_id: Url) -> Self { + pub(crate) fn new(actor_id: IriString, contact_id: IriString) -> Self { QueryContact { actor_id, contact_id, @@ -57,7 +57,7 @@ impl QueryContact { } } -fn to_contact(contact: AcceptedActors) -> Option<(String, String, Url, Url)> { +fn to_contact(contact: AcceptedActors) -> Option<(String, String, IriString, IriString)> { let username = contact.preferred_username()?.to_owned(); let display_name = contact.name()?.as_one()?.as_xsd_string()?.to_owned(); diff --git a/src/jobs/deliver.rs b/src/jobs/deliver.rs index 4f0eff5..ee3ce48 100644 --- a/src/jobs/deliver.rs +++ b/src/jobs/deliver.rs @@ -1,11 +1,11 @@ use crate::{error::Error, jobs::JobState}; -use activitystreams::url::Url; +use activitystreams::iri_string::types::IriString; use background_jobs::{ActixJob, Backoff}; use std::{future::Future, pin::Pin}; #[derive(Clone, serde::Deserialize, serde::Serialize)] pub(crate) struct Deliver { - to: Url, + to: IriString, data: serde_json::Value, } @@ -19,7 +19,7 @@ impl std::fmt::Debug for Deliver { } impl Deliver { - pub(crate) fn new(to: Url, data: T) -> Result + pub(crate) fn new(to: IriString, data: T) -> Result where T: serde::ser::Serialize, { diff --git a/src/jobs/deliver_many.rs b/src/jobs/deliver_many.rs index ab8be0e..c131168 100644 --- a/src/jobs/deliver_many.rs +++ b/src/jobs/deliver_many.rs @@ -2,13 +2,13 @@ use crate::{ error::Error, jobs::{Deliver, JobState}, }; -use activitystreams::url::Url; +use activitystreams::iri_string::types::IriString; use background_jobs::ActixJob; use futures_util::future::LocalBoxFuture; #[derive(Clone, serde::Deserialize, serde::Serialize)] pub(crate) struct DeliverMany { - to: Vec, + to: Vec, data: serde_json::Value, } @@ -30,7 +30,7 @@ impl std::fmt::Debug for DeliverMany { } impl DeliverMany { - pub(crate) fn new(to: Vec, data: T) -> Result + pub(crate) fn new(to: Vec, data: T) -> Result where T: serde::ser::Serialize, { diff --git a/src/jobs/instance.rs b/src/jobs/instance.rs index 5721423..dd143bf 100644 --- a/src/jobs/instance.rs +++ b/src/jobs/instance.rs @@ -1,15 +1,15 @@ use crate::{ config::UrlKind, - error::Error, + error::{Error, ErrorKind}, jobs::{cache_media::CacheMedia, JobState}, }; -use activitystreams::url::Url; +use activitystreams::{iri, iri_string::types::IriString}; use background_jobs::ActixJob; use std::{future::Future, pin::Pin}; #[derive(Clone, serde::Deserialize, serde::Serialize)] pub(crate) struct QueryInstance { - actor_id: Url, + actor_id: IriString, } impl std::fmt::Debug for QueryInstance { @@ -21,7 +21,7 @@ impl std::fmt::Debug for QueryInstance { } impl QueryInstance { - pub(crate) fn new(actor_id: Url) -> Self { + pub(crate) fn new(actor_id: IriString) -> Self { QueryInstance { actor_id } } @@ -40,10 +40,12 @@ impl QueryInstance { return Ok(()); } - let mut instance_uri = self.actor_id.clone(); - instance_uri.set_fragment(None); - instance_uri.set_query(None); - instance_uri.set_path("api/v1/instance"); + let authority = self + .actor_id + .authority_str() + .ok_or(ErrorKind::MissingDomain)?; + let scheme = self.actor_id.scheme_str(); + let instance_uri = iri!(format!("{}://{}/api/v1/instance", scheme, authority)); let instance = state .requests @@ -132,6 +134,6 @@ struct Instance { struct Contact { username: String, display_name: String, - url: Url, - avatar: Url, + url: IriString, + avatar: IriString, } diff --git a/src/jobs/nodeinfo.rs b/src/jobs/nodeinfo.rs index 7ffd732..1f06400 100644 --- a/src/jobs/nodeinfo.rs +++ b/src/jobs/nodeinfo.rs @@ -1,14 +1,14 @@ use crate::{ - error::Error, + error::{Error, ErrorKind}, jobs::{JobState, QueryContact}, }; -use activitystreams::url::Url; +use activitystreams::{iri, iri_string::types::IriString}; use background_jobs::ActixJob; use std::{fmt::Debug, future::Future, pin::Pin}; #[derive(Clone, serde::Deserialize, serde::Serialize)] pub(crate) struct QueryNodeinfo { - actor_id: Url, + actor_id: IriString, } impl Debug for QueryNodeinfo { @@ -20,7 +20,7 @@ impl Debug for QueryNodeinfo { } impl QueryNodeinfo { - pub(crate) fn new(actor_id: Url) -> Self { + pub(crate) fn new(actor_id: IriString) -> Self { QueryNodeinfo { actor_id } } @@ -34,10 +34,12 @@ impl QueryNodeinfo { return Ok(()); } - let mut well_known_uri = self.actor_id.clone(); - well_known_uri.set_fragment(None); - well_known_uri.set_query(None); - well_known_uri.set_path(".well-known/nodeinfo"); + let authority = self + .actor_id + .authority_str() + .ok_or(ErrorKind::MissingDomain)?; + let scheme = self.actor_id.scheme_str(); + let well_known_uri = iri!(format!("{}://{}/.well-known/nodeinfo", scheme, authority)); let well_known = state .requests @@ -100,7 +102,7 @@ struct Nodeinfo { #[derive(serde::Deserialize)] #[serde(rename_all = "camelCase")] struct Metadata { - staff_accounts: Option>, + staff_accounts: Option>, } #[derive(serde::Deserialize)] @@ -202,7 +204,7 @@ impl<'de> serde::de::Deserialize<'de> for SupportedNodeinfo { #[cfg(test)] mod tests { use super::{Nodeinfo, WellKnown}; - use activitystreams::url::Url; + use activitystreams::iri_string::types::IriString; const BANANA_DOG: &'static str = r#"{"links":[{"rel":"http://nodeinfo.diaspora.software/ns/schema/2.0","href":"https://banana.dog/nodeinfo/2.0"},{"rel":"http://nodeinfo.diaspora.software/ns/schema/2.1","href":"https://banana.dog/nodeinfo/2.1"}]}"#; const ASONIX_DOG: &'static str = r#"{"links":[{"rel":"http://nodeinfo.diaspora.software/ns/schema/2.0","href":"https://asonix.dog/nodeinfo/2.0"}]}"#; @@ -224,7 +226,7 @@ mod tests { assert_eq!( accounts[0], "https://soc.hyena.network/users/HyNET" - .parse::() + .parse::() .unwrap() ); } diff --git a/src/main.rs b/src/main.rs index 38291c3..89c1802 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ // need this for ructe #![allow(clippy::needless_borrow)] -use activitystreams::url::Url; +use activitystreams::iri_string::types::IriString; use actix_web::{web, App, HttpServer}; use console_subscriber::ConsoleLayer; use opentelemetry::{sdk::Resource, KeyValue}; @@ -34,7 +34,7 @@ use self::{ fn init_subscriber( software_name: &'static str, - opentelemetry_url: Option<&Url>, + opentelemetry_url: Option<&IriString>, ) -> Result<(), anyhow::Error> { LogTracer::init()?; diff --git a/src/middleware/verifier.rs b/src/middleware/verifier.rs index 6ac45f8..4a56ff1 100644 --- a/src/middleware/verifier.rs +++ b/src/middleware/verifier.rs @@ -4,7 +4,7 @@ use crate::{ error::{Error, ErrorKind}, requests::Requests, }; -use activitystreams::{base::BaseExt, uri, url::Url}; +use activitystreams::{base::BaseExt, iri, iri_string::types::IriString}; use actix_web::web; use http_signature_normalization_actix::{prelude::*, verify::DeprecatedAlgorithm}; use rsa::{hash::Hash, padding::PaddingScheme, pkcs8::FromPublicKey, PublicKey, RsaPublicKey}; @@ -23,7 +23,7 @@ impl MyVerify { signature: String, signing_string: String, ) -> Result { - let public_key_id = uri!(key_id); + let public_key_id = iri!(key_id); let actor_id = if let Some(mut actor_id) = self .2 @@ -85,8 +85,8 @@ impl MyVerify { enum PublicKeyResponse { PublicKey { #[allow(dead_code)] - id: Url, - owner: Url, + id: IriString, + owner: IriString, #[allow(dead_code)] public_key_pem: String, }, @@ -94,7 +94,7 @@ enum PublicKeyResponse { } impl PublicKeyResponse { - fn actor_id(&self) -> Option { + fn actor_id(&self) -> Option { match self { PublicKeyResponse::PublicKey { owner, .. } => Some(owner.clone()), PublicKeyResponse::Actor(actor) => actor.id_unchecked().cloned(), diff --git a/src/requests.rs b/src/requests.rs index b04b301..83f68d6 100644 --- a/src/requests.rs +++ b/src/requests.rs @@ -1,8 +1,7 @@ use crate::error::{Error, ErrorKind}; -use activitystreams::url::Url; +use activitystreams::iri_string::types::IriString; use actix_web::{http::header::Date, web::Bytes}; use awc::Client; -use chrono::{DateTime, Utc}; use dashmap::DashMap; use http_signature_normalization_actix::prelude::*; use rsa::{hash::Hash, padding::PaddingScheme, RsaPrivateKey}; @@ -16,6 +15,7 @@ use std::{ }, time::SystemTime, }; +use time::OffsetDateTime; use tracing::{debug, info, warn}; use tracing_awc::Tracing; @@ -31,9 +31,9 @@ impl std::fmt::Debug for Breakers { } impl Breakers { - fn should_try(&self, url: &Url) -> bool { - if let Some(domain) = url.domain() { - if let Some(breaker) = self.inner.get(domain) { + fn should_try(&self, url: &IriString) -> bool { + if let Some(authority) = url.authority_str() { + if let Some(breaker) = self.inner.get(authority) { breaker.should_try() } else { true @@ -43,10 +43,10 @@ impl Breakers { } } - fn fail(&self, url: &Url) { - if let Some(domain) = url.domain() { + fn fail(&self, url: &IriString) { + if let Some(authority) = url.authority_str() { let should_write = { - if let Some(mut breaker) = self.inner.get_mut(domain) { + if let Some(mut breaker) = self.inner.get_mut(authority) { breaker.fail(); false } else { @@ -55,16 +55,16 @@ impl Breakers { }; if should_write { - let mut breaker = self.inner.entry(domain.to_owned()).or_default(); + let mut breaker = self.inner.entry(authority.to_owned()).or_default(); breaker.fail(); } } } - fn succeed(&self, url: &Url) { - if let Some(domain) = url.domain() { + fn succeed(&self, url: &IriString) { + if let Some(authority) = url.authority_str() { let should_write = { - if let Some(mut breaker) = self.inner.get_mut(domain) { + if let Some(mut breaker) = self.inner.get_mut(authority) { breaker.succeed(); false } else { @@ -73,7 +73,7 @@ impl Breakers { }; if should_write { - let mut breaker = self.inner.entry(domain.to_owned()).or_default(); + let mut breaker = self.inner.entry(authority.to_owned()).or_default(); breaker.succeed(); } } @@ -91,8 +91,8 @@ impl Default for Breakers { #[derive(Debug)] struct Breaker { failures: usize, - last_attempt: DateTime, - last_success: DateTime, + last_attempt: OffsetDateTime, + last_success: OffsetDateTime, } impl Breaker { @@ -100,30 +100,30 @@ impl Breaker { 10 } - fn failure_wait() -> chrono::Duration { - chrono::Duration::days(1) + fn failure_wait() -> time::Duration { + time::Duration::days(1) } fn should_try(&self) -> bool { self.failures < Self::failure_threshold() - || self.last_attempt + Self::failure_wait() < Utc::now() + || self.last_attempt + Self::failure_wait() < OffsetDateTime::now_utc() } fn fail(&mut self) { self.failures += 1; - self.last_attempt = Utc::now(); + self.last_attempt = OffsetDateTime::now_utc(); } fn succeed(&mut self) { self.failures = 0; - self.last_attempt = Utc::now(); - self.last_success = Utc::now(); + self.last_attempt = OffsetDateTime::now_utc(); + self.last_success = OffsetDateTime::now_utc(); } } impl Default for Breaker { fn default() -> Self { - let now = Utc::now(); + let now = OffsetDateTime::now_utc(); Breaker { failures: 0, @@ -217,7 +217,7 @@ impl Requests { where T: serde::de::DeserializeOwned, { - let parsed_url = url.parse::()?; + let parsed_url = url.parse::()?; if !self.breakers.should_try(&parsed_url) { return Err(ErrorKind::Breaker.into()); @@ -274,7 +274,7 @@ impl Requests { #[tracing::instrument(name = "Fetch Bytes")] pub(crate) async fn fetch_bytes(&self, url: &str) -> Result<(String, Bytes), Error> { - let parsed_url = url.parse::()?; + let parsed_url = url.parse::()?; if !self.breakers.should_try(&parsed_url) { return Err(ErrorKind::Breaker.into()); @@ -346,7 +346,7 @@ impl Requests { "Deliver to Inbox", fields(self, inbox = inbox.to_string().as_str(), item) )] - pub(crate) async fn deliver(&self, inbox: Url, item: &T) -> Result<(), Error> + pub(crate) async fn deliver(&self, inbox: IriString, item: &T) -> Result<(), Error> where T: serde::ser::Serialize + std::fmt::Debug, { diff --git a/src/routes/inbox.rs b/src/routes/inbox.rs index 88d3d47..e75f8f5 100644 --- a/src/routes/inbox.rs +++ b/src/routes/inbox.rs @@ -10,7 +10,8 @@ use crate::{ routes::accepted, }; use activitystreams::{ - activity, base::AnyBase, prelude::*, primitives::OneOrMany, public, url::Url, + activity, base::AnyBase, iri_string::types::IriString, prelude::*, primitives::OneOrMany, + public, }; use actix_web::{web, HttpResponse}; use http_signature_normalization_actix::prelude::{DigestVerified, SignatureVerified}; @@ -79,7 +80,7 @@ pub(crate) async fn route( fn valid_without_listener(input: &AcceptedActivities) -> Result { match input.kind() { Some(ValidTypes::Follow) => Ok(true), - Some(ValidTypes::Undo) => Ok(single_object(input.object())?.is_kind("Follow")), + Some(ValidTypes::Undo) => Ok(single_object(input.object_unchecked())?.is_kind("Follow")), _ => Ok(false), } } @@ -90,7 +91,7 @@ fn kind_str(base: &AnyBase) -> Result<&str, Error> { .map_err(Into::into) } -fn id_string(id: Option<&Url>) -> Result { +fn id_string(id: Option<&IriString>) -> Result { id.map(|s| s.to_string()) .ok_or(ErrorKind::MissingId) .map_err(Into::into) @@ -101,11 +102,14 @@ fn single_object(o: &OneOrMany) -> Result<&AnyBase, Error> { } async fn handle_accept(config: &Config, input: AcceptedActivities) -> Result<(), Error> { - let base = single_object(input.object())?.clone(); + let base = single_object(input.object_unchecked())?.clone(); let follow = if let Some(follow) = activity::Follow::from_any_base(base)? { follow } else { - return Err(ErrorKind::Kind(kind_str(single_object(input.object())?)?.to_owned()).into()); + return Err(ErrorKind::Kind( + kind_str(single_object(input.object_unchecked())?)?.to_owned(), + ) + .into()); }; if !follow.actor_is(&config.generate_url(UrlKind::Actor)) { @@ -121,11 +125,14 @@ async fn handle_reject( input: AcceptedActivities, actor: Actor, ) -> Result<(), Error> { - let base = single_object(input.object())?.clone(); + let base = single_object(input.object_unchecked())?.clone(); let follow = if let Some(follow) = activity::Follow::from_any_base(base)? { follow } else { - return Err(ErrorKind::Kind(kind_str(single_object(input.object())?)?.to_owned()).into()); + return Err(ErrorKind::Kind( + kind_str(single_object(input.object_unchecked())?)?.to_owned(), + ) + .into()); }; if !follow.actor_is(&config.generate_url(UrlKind::Actor)) { @@ -144,7 +151,7 @@ async fn handle_undo( actor: Actor, is_listener: bool, ) -> Result<(), Error> { - let any_base = single_object(input.object())?.clone(); + let any_base = single_object(input.object_unchecked())?.clone(); let undone_object = AcceptedUndoObjects::from_any_base(any_base)?.ok_or(ErrorKind::ObjectFormat)?; @@ -157,12 +164,13 @@ async fn handle_undo( } } - let my_id: Url = config.generate_url(UrlKind::Actor); + let my_id: IriString = config.generate_url(UrlKind::Actor); if !undone_object.object_is(&my_id) && !undone_object.object_is(&public()) { - return Err( - ErrorKind::WrongActor(id_string(undone_object.object().as_single_id())?).into(), - ); + return Err(ErrorKind::WrongActor(id_string( + undone_object.object_unchecked().as_single_id(), + )?) + .into()); } if !is_listener { @@ -189,13 +197,17 @@ async fn handle_announce( input: AcceptedActivities, actor: Actor, ) -> Result<(), Error> { - let object_id = input.object().as_single_id().ok_or(ErrorKind::MissingId)?; + let object_id = input + .object_unchecked() + .as_single_id() + .ok_or(ErrorKind::MissingId)?; if state.is_cached(object_id).await { return Err(ErrorKind::Duplicate.into()); } - jobs.queue(Announce::new(object_id.to_owned(), actor)).await?; + jobs.queue(Announce::new(object_id.to_owned(), actor)) + .await?; Ok(()) } @@ -206,10 +218,12 @@ async fn handle_follow( input: AcceptedActivities, actor: Actor, ) -> Result<(), Error> { - let my_id: Url = config.generate_url(UrlKind::Actor); + let my_id: IriString = config.generate_url(UrlKind::Actor); if !input.object_is(&my_id) && !input.object_is(&public()) { - return Err(ErrorKind::WrongActor(id_string(input.object().as_single_id())?).into()); + return Err( + ErrorKind::WrongActor(id_string(input.object_unchecked().as_single_id())?).into(), + ); } jobs.queue(Follow::new(input, actor)).await?; diff --git a/src/routes/nodeinfo.rs b/src/routes/nodeinfo.rs index b1d27cb..005f9d8 100644 --- a/src/routes/nodeinfo.rs +++ b/src/routes/nodeinfo.rs @@ -57,7 +57,7 @@ pub(crate) async fn route( .await .unwrap_or_default() .iter() - .filter_map(|listener| listener.domain()) + .filter_map(|listener| listener.authority_str()) .map(|s| s.to_owned()) .collect(), blocks: if config.publish_blocks() { diff --git a/templates/admin.rs.html b/templates/admin.rs.html index 21b8c42..e9d89aa 100644 --- a/templates/admin.rs.html +++ b/templates/admin.rs.html @@ -1,7 +1,7 @@ @use crate::db::Contact; -@use activitystreams::url::Url; +@use activitystreams::iri_string::types::IriString; -@(contact: &Contact, base: &Url) +@(contact: &Contact, base: &IriString)
@@ -12,7 +12,7 @@

@contact.display_name

- @@@contact.username@if let Some(domain) = base.domain() {@@@domain} + @@@contact.username@if let Some(authority) = base.authority_str() {@@@authority}

diff --git a/templates/info.rs.html b/templates/info.rs.html index 6120c57..7614398 100644 --- a/templates/info.rs.html +++ b/templates/info.rs.html @@ -1,11 +1,11 @@ @use crate::db::Info; -@use activitystreams::url::Url; +@use activitystreams::iri_string::types::IriString; -@(info: &Info, base: &Url) +@(info: &Info, base: &IriString)
- @if let Some(domain) = base.domain() { -

@domain

+ @if let Some(authority) = base.authority_str() { +

@authority

}

Running @info.software, version @info.version. diff --git a/templates/instance.rs.html b/templates/instance.rs.html index d08bcfc..0699147 100644 --- a/templates/instance.rs.html +++ b/templates/instance.rs.html @@ -1,7 +1,7 @@ @use crate::{db::{Contact, Instance}, templates::admin}; -@use activitystreams::url::Url; +@use activitystreams::iri_string::types::IriString; -@(instance: &Instance, software: Option<&str>, contact: Option<&Contact>, base: &Url) +@(instance: &Instance, software: Option<&str>, contact: Option<&Contact>, base: &IriString)