Update to latest activitystreams

This commit is contained in:
Aode (Lion) 2022-01-17 16:54:45 -06:00
parent 6b0d3298cc
commit 8893895c71
27 changed files with 338 additions and 264 deletions

27
Cargo.lock generated
View file

@ -4,17 +4,16 @@ version = 3
[[package]] [[package]]
name = "activitystreams" name = "activitystreams"
version = "0.7.0-alpha.14" version = "0.7.0-alpha.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bcc3fbb392890a1942b1e5cca76cba93c8ed24b5ff50004cc3289afaab3f92c" checksum = "31bca51dcfddda6551570371a1c065528050eea2151a29897ac2dad67b5624cb"
dependencies = [ dependencies = [
"activitystreams-kinds", "activitystreams-kinds",
"chrono", "iri-string",
"mime", "mime",
"serde 1.0.133", "serde 1.0.133",
"serde_json", "serde_json",
"thiserror", "time 0.3.5",
"url",
] ]
[[package]] [[package]]
@ -30,12 +29,12 @@ dependencies = [
[[package]] [[package]]
name = "activitystreams-kinds" name = "activitystreams-kinds"
version = "0.1.2" version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0784e99afd032199d3ed70cefb8eb3a8d1aef15f7f2c4e68d033c4e12bb6079e" checksum = "4a762e3441050b51cd0695c1413735cd04195950f50dba8f5da5d7201628fcfc"
dependencies = [ dependencies = [
"iri-string",
"serde 1.0.133", "serde 1.0.133",
"url",
] ]
[[package]] [[package]]
@ -318,7 +317,6 @@ dependencies = [
"awc", "awc",
"background-jobs", "background-jobs",
"base64", "base64",
"chrono",
"config", "config",
"console-subscriber", "console-subscriber",
"dashmap", "dashmap",
@ -339,6 +337,7 @@ dependencies = [
"sled", "sled",
"structopt", "structopt",
"thiserror", "thiserror",
"time 0.3.5",
"toml", "toml",
"tracing", "tracing",
"tracing-actix-web", "tracing-actix-web",
@ -1256,6 +1255,15 @@ dependencies = [
"cfg-if", "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]] [[package]]
name = "itertools" name = "itertools"
version = "0.10.3" version = "0.10.3"
@ -3045,7 +3053,6 @@ dependencies = [
"idna", "idna",
"matches", "matches",
"percent-encoding", "percent-encoding",
"serde 1.0.133",
] ]
[[package]] [[package]]

View file

@ -21,13 +21,12 @@ anyhow = "1.0"
actix-rt = "2.0.2" actix-rt = "2.0.2"
actix-web = { version = "4.0.0-beta.7", default-features = false } actix-web = { version = "4.0.0-beta.7", default-features = false }
actix-webfinger = "0.4.0-beta.5" 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" activitystreams-ext = "0.1.0-alpha.2"
ammonia = "3.1.0" ammonia = "3.1.0"
async-rwlock = "1.3.0" async-rwlock = "1.3.0"
awc = { version = "3.0.0-beta.6", default-features = false, features = ["rustls"] } awc = { version = "3.0.0-beta.6", default-features = false, features = ["rustls"] }
base64 = "0.13" base64 = "0.13"
chrono = "0.4.19"
config = "0.11.0" config = "0.11.0"
console-subscriber = "0.1" console-subscriber = "0.1"
dashmap = "5.0.0" dashmap = "5.0.0"
@ -46,6 +45,7 @@ sha2 = "0.10"
sled = "0.34.6" sled = "0.34.6"
structopt = "0.3.12" structopt = "0.3.12"
thiserror = "1.0" thiserror = "1.0"
time = "0.3.5"
tracing = "0.1" tracing = "0.1"
tracing-awc = { version = "0.1.0-beta.10" } tracing-awc = { version = "0.1.0-beta.10" }
tracing-error = "0.2" tracing-error = "0.2"

View file

@ -2,15 +2,15 @@ use activitystreams::{
activity::ActorAndObject, activity::ActorAndObject,
actor::{Actor, ApActor}, actor::{Actor, ApActor},
unparsed::UnparsedMutExt, unparsed::UnparsedMutExt,
url::Url, iri_string::types::IriString,
}; };
use activitystreams_ext::{Ext1, UnparsedExtension}; use activitystreams_ext::{Ext1, UnparsedExtension};
#[derive(Clone, serde::Deserialize, serde::Serialize)] #[derive(Clone, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct PublicKeyInner { pub struct PublicKeyInner {
pub id: Url, pub id: IriString,
pub owner: Url, pub owner: IriString,
pub public_key_pem: String, pub public_key_pem: String,
} }

View file

@ -4,7 +4,13 @@ use crate::{
middleware::MyVerify, middleware::MyVerify,
requests::Requests, requests::Requests,
}; };
use activitystreams::{uri, url::Url}; use activitystreams::{
iri,
iri_string::{
resolve::FixedBaseResolver,
types::{IriAbsoluteString, IriFragmentStr, IriRelativeStr, IriString},
},
};
use config::Environment; use config::Environment;
use http_signature_normalization_actix::prelude::{VerifyDigest, VerifySignature}; use http_signature_normalization_actix::prelude::{VerifyDigest, VerifySignature};
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
@ -22,8 +28,8 @@ pub(crate) struct ParsedConfig {
https: bool, https: bool,
publish_blocks: bool, publish_blocks: bool,
sled_path: PathBuf, sled_path: PathBuf,
source_repo: Url, source_repo: IriString,
opentelemetry_url: Option<Url>, opentelemetry_url: Option<IriString>,
} }
#[derive(Clone)] #[derive(Clone)]
@ -35,12 +41,13 @@ pub struct Config {
restricted_mode: bool, restricted_mode: bool,
validate_signatures: bool, validate_signatures: bool,
publish_blocks: bool, publish_blocks: bool,
base_uri: Url, base_uri: IriAbsoluteString,
sled_path: PathBuf, sled_path: PathBuf,
source_repo: Url, source_repo: IriString,
opentelemetry_url: Option<Url>, opentelemetry_url: Option<IriString>,
} }
#[derive(Debug)]
pub enum UrlKind { pub enum UrlKind {
Activity, Activity,
Actor, Actor,
@ -95,7 +102,7 @@ impl Config {
let config: ParsedConfig = config.try_into()?; let config: ParsedConfig = config.try_into()?;
let scheme = if config.https { "https" } else { "http" }; 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 { Ok(Config {
hostname: config.hostname, 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 &self.source_repo
} }
pub(crate) fn opentelemetry_url(&self) -> Option<&Url> { pub(crate) fn opentelemetry_url(&self) -> Option<&IriString> {
self.opentelemetry_url.as_ref() self.opentelemetry_url.as_ref()
} }
pub(crate) fn generate_url(&self, kind: UrlKind) -> Url { pub(crate) fn generate_url(&self, kind: UrlKind) -> IriString {
let mut url = self.base_uri.clone(); self.do_generate_url(kind).expect("Generated valid IRI")
}
match kind { #[tracing::instrument(fields(base_uri = tracing::field::debug(&self.base_uri), kind = tracing::field::debug(&kind)))]
UrlKind::Activity => url.set_path(&format!("activity/{}", Uuid::new_v4())), fn do_generate_url(&self, kind: UrlKind) -> Result<IriString, Error> {
UrlKind::Actor => url.set_path("actor"), let iri = match kind {
UrlKind::Followers => url.set_path("followers"), UrlKind::Activity => FixedBaseResolver::new(self.base_uri.as_ref())
UrlKind::Following => url.set_path("following"), .resolve(IriRelativeStr::new(&format!("activity/{}", Uuid::new_v4()))?.as_ref())?,
UrlKind::Inbox => url.set_path("inbox"), UrlKind::Actor => FixedBaseResolver::new(self.base_uri.as_ref())
UrlKind::Index => (), .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 => { UrlKind::MainKey => {
url.set_path("actor"); let actor = IriRelativeStr::new("actor")?;
url.set_fragment(Some("main-key")); 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::Media(uuid) => FixedBaseResolver::new(self.base_uri.as_ref())
UrlKind::NodeInfo => url.set_path("nodeinfo/2.0.json"), .resolve(IriRelativeStr::new(&format!("media/{}", uuid))?.as_ref())?,
UrlKind::Outbox => url.set_path("outbox"), 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)
} }
} }

View file

@ -4,7 +4,7 @@ use crate::{
error::{Error, ErrorKind}, error::{Error, ErrorKind},
requests::Requests, requests::Requests,
}; };
use activitystreams::{prelude::*, url::Url}; use activitystreams::{iri_string::types::IriString, prelude::*};
use std::time::{Duration, SystemTime}; use std::time::{Duration, SystemTime};
const REFETCH_DURATION: Duration = Duration::from_secs(60 * 30); 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))] #[tracing::instrument(name = "Get Actor", fields(id = id.to_string().as_str(), requests))]
pub(crate) async fn get( pub(crate) async fn get(
&self, &self,
id: &Url, id: &IriString,
requests: &Requests, requests: &Requests,
) -> Result<MaybeCached<Actor>, Error> { ) -> Result<MaybeCached<Actor>, Error> {
if let Some(actor) = self.db.actor(id.clone()).await? { 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))] #[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<Actor, Error> { pub(crate) async fn get_no_cache(
&self,
id: &IriString,
requests: &Requests,
) -> Result<Actor, Error> {
let accepted_actor = requests.fetch::<AcceptedActors>(id.as_str()).await?; let accepted_actor = requests.fetch::<AcceptedActors>(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 let accepted_actor_id = accepted_actor
.id(input_domain)? .id(input_authority.host(), input_authority.port())?
.ok_or(ErrorKind::MissingId)?; .ok_or(ErrorKind::MissingId)?;
let inbox = get_inbox(&accepted_actor)?.clone(); 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 Ok(actor
.endpoints()? .endpoints()?
.and_then(|e| e.shared_inbox) .and_then(|e| e.shared_inbox)

View file

@ -2,7 +2,7 @@ use crate::{
db::{Db, MediaMeta}, db::{Db, MediaMeta},
error::Error, error::Error,
}; };
use activitystreams::url::Url; use activitystreams::iri_string::types::IriString;
use actix_web::web::Bytes; use actix_web::web::Bytes;
use std::time::{Duration, SystemTime}; use std::time::{Duration, SystemTime};
use uuid::Uuid; use uuid::Uuid;
@ -20,12 +20,12 @@ impl MediaCache {
} }
#[tracing::instrument(name = "Get media uuid", fields(url = url.to_string().as_str()))] #[tracing::instrument(name = "Get media uuid", fields(url = url.to_string().as_str()))]
pub(crate) async fn get_uuid(&self, url: Url) -> Result<Option<Uuid>, Error> { pub(crate) async fn get_uuid(&self, url: IriString) -> Result<Option<Uuid>, Error> {
self.db.media_id(url).await self.db.media_id(url).await
} }
#[tracing::instrument(name = "Get media url")] #[tracing::instrument(name = "Get media url")]
pub(crate) async fn get_url(&self, uuid: Uuid) -> Result<Option<Url>, Error> { pub(crate) async fn get_url(&self, uuid: Uuid) -> Result<Option<IriString>, Error> {
self.db.media_url(uuid).await 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()))] #[tracing::instrument(name = "Store media url", fields(url = url.to_string().as_str()))]
pub(crate) async fn store_url(&self, url: Url) -> Result<Uuid, Error> { pub(crate) async fn store_url(&self, url: IriString) -> Result<Uuid, Error> {
let uuid = Uuid::new_v4(); let uuid = Uuid::new_v4();
self.db.save_url(url, uuid).await?; self.db.save_url(url, uuid).await?;

View file

@ -1,8 +1,8 @@
use crate::{ use crate::{
db::{Contact, Db, Info, Instance}, 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}; use std::time::{Duration, SystemTime};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -12,7 +12,7 @@ pub struct NodeCache {
#[derive(Clone, serde::Deserialize, serde::Serialize)] #[derive(Clone, serde::Deserialize, serde::Serialize)]
pub struct Node { pub struct Node {
pub(crate) base: Url, pub(crate) base: IriString,
pub(crate) info: Option<Info>, pub(crate) info: Option<Info>,
pub(crate) instance: Option<Instance>, pub(crate) instance: Option<Instance>,
pub(crate) contact: Option<Contact>, pub(crate) contact: Option<Contact>,
@ -50,18 +50,15 @@ impl NodeCache {
let instance = instances.get(&actor_id).cloned(); let instance = instances.get(&actor_id).cloned();
let contact = contacts.get(&actor_id).cloned(); let contact = contacts.get(&actor_id).cloned();
Node::new(actor_id) Node::new(actor_id).map(|node| node.info(info).instance(instance).contact(contact))
.info(info)
.instance(instance)
.contact(contact)
}) })
.collect(); .collect::<Result<Vec<Node>, Error>>()?;
Ok(vec) Ok(vec)
} }
#[tracing::instrument(name = "Is NodeInfo Outdated", fields(actor_id = actor_id.to_string().as_str()))] #[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 self.db
.info(actor_id) .info(actor_id)
.await .await
@ -70,7 +67,7 @@ impl NodeCache {
} }
#[tracing::instrument(name = "Is Contact Outdated", fields(actor_id = actor_id.to_string().as_str()))] #[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 self.db
.contact(actor_id) .contact(actor_id)
.await .await
@ -79,7 +76,7 @@ impl NodeCache {
} }
#[tracing::instrument(name = "Is Instance Outdated", fields(actor_id = actor_id.to_string().as_str()))] #[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 self.db
.instance(actor_id) .instance(actor_id)
.await .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))] #[tracing::instrument(name = "Save node info", fields(actor_id = actor_id.to_string().as_str(), software, version, reg))]
pub(crate) async fn set_info( pub(crate) async fn set_info(
&self, &self,
actor_id: Url, actor_id: IriString,
software: String, software: String,
version: String, version: String,
reg: bool, reg: bool,
@ -121,7 +118,7 @@ impl NodeCache {
)] )]
pub(crate) async fn set_instance( pub(crate) async fn set_instance(
&self, &self,
actor_id: Url, actor_id: IriString,
title: String, title: String,
description: String, description: String,
version: String, version: String,
@ -155,11 +152,11 @@ impl NodeCache {
)] )]
pub(crate) async fn set_contact( pub(crate) async fn set_contact(
&self, &self,
actor_id: Url, actor_id: IriString,
username: String, username: String,
display_name: String, display_name: String,
url: Url, url: IriString,
avatar: Url, avatar: IriString,
) -> Result<(), Error> { ) -> Result<(), Error> {
self.db self.db
.save_contact( .save_contact(
@ -177,17 +174,18 @@ impl NodeCache {
} }
impl Node { impl Node {
fn new(mut url: Url) -> Self { fn new(url: IriString) -> Result<Self, Error> {
url.set_fragment(None); let authority = url.authority_str().ok_or(ErrorKind::MissingDomain)?;
url.set_query(None); let scheme = url.scheme_str();
url.set_path("");
Node { let base = iri!(format!("{}://{}", scheme, authority));
base: url,
Ok(Node {
base,
info: None, info: None,
instance: None, instance: None,
contact: None, contact: None,
} })
} }
fn info(mut self, info: Option<Info>) -> Self { fn info(mut self, info: Option<Info>) -> Self {

View file

@ -5,7 +5,7 @@ use crate::{
error::Error, error::Error,
requests::{Breakers, Requests}, requests::{Breakers, Requests},
}; };
use activitystreams::url::Url; use activitystreams::iri_string::types::IriString;
use actix_web::web; use actix_web::web;
use async_rwlock::RwLock; use async_rwlock::RwLock;
use lru::LruCache; use lru::LruCache;
@ -18,7 +18,7 @@ use tracing::info;
pub struct State { pub struct State {
pub(crate) public_key: RsaPublicKey, pub(crate) public_key: RsaPublicKey,
private_key: RsaPrivateKey, private_key: RsaPrivateKey,
object_cache: Arc<RwLock<LruCache<Url, Url>>>, object_cache: Arc<RwLock<LruCache<IriString, IriString>>>,
node_cache: NodeCache, node_cache: NodeCache,
breakers: Breakers, breakers: Breakers,
pub(crate) db: Db, pub(crate) db: Db,
@ -52,22 +52,22 @@ impl State {
name = "Get inboxes for other domains", name = "Get inboxes for other domains",
fields( fields(
existing_inbox = existing_inbox.to_string().as_str(), existing_inbox = existing_inbox.to_string().as_str(),
domain authority
) )
)] )]
pub(crate) async fn inboxes_without( pub(crate) async fn inboxes_without(
&self, &self,
existing_inbox: &Url, existing_inbox: &IriString,
domain: &str, authority: &str,
) -> Result<Vec<Url>, Error> { ) -> Result<Vec<IriString>, Error> {
Ok(self Ok(self
.db .db
.inboxes() .inboxes()
.await? .await?
.iter() .iter()
.filter_map(|inbox| { .filter_map(|inbox| {
if let Some(dom) = inbox.domain() { if let Some(authority_str) = inbox.authority_str() {
if inbox != existing_inbox && dom != domain { if inbox != existing_inbox && authority_str != authority {
return Some(inbox.clone()); return Some(inbox.clone());
} }
} }
@ -77,11 +77,11 @@ impl State {
.collect()) .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) 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); self.object_cache.write().await.put(object_id, actor_id);
} }

144
src/db.rs
View file

@ -1,5 +1,8 @@
use crate::{config::Config, error::Error}; use crate::{
use activitystreams::url::Url; config::Config,
error::{Error, ErrorKind},
};
use activitystreams::iri_string::types::IriString;
use actix_web::web::Bytes; use actix_web::web::Bytes;
use rsa::{ use rsa::{
pkcs8::{FromPrivateKey, ToPrivateKey}, pkcs8::{FromPrivateKey, ToPrivateKey},
@ -41,10 +44,10 @@ impl std::fmt::Debug for Inner {
#[derive(Clone, serde::Deserialize, serde::Serialize)] #[derive(Clone, serde::Deserialize, serde::Serialize)]
pub struct Actor { pub struct Actor {
pub(crate) id: Url, pub(crate) id: IriString,
pub(crate) public_key: String, pub(crate) public_key: String,
pub(crate) public_key_id: Url, pub(crate) public_key_id: IriString,
pub(crate) inbox: Url, pub(crate) inbox: IriString,
pub(crate) saved_at: SystemTime, pub(crate) saved_at: SystemTime,
} }
@ -88,8 +91,8 @@ pub struct Instance {
pub struct Contact { pub struct Contact {
pub(crate) username: String, pub(crate) username: String,
pub(crate) display_name: String, pub(crate) display_name: String,
pub(crate) url: Url, pub(crate) url: IriString,
pub(crate) avatar: Url, pub(crate) avatar: IriString,
pub(crate) updated: SystemTime, pub(crate) updated: SystemTime,
} }
@ -106,7 +109,10 @@ impl std::fmt::Debug for Contact {
} }
impl Inner { impl Inner {
fn connected_by_domain(&self, domains: &[String]) -> impl DoubleEndedIterator<Item = Url> { fn connected_by_domain(
&self,
domains: &[String],
) -> impl DoubleEndedIterator<Item = IriString> {
let reversed: Vec<_> = domains.iter().map(|s| domain_key(s.as_str())).collect(); let reversed: Vec<_> = domains.iter().map(|s| domain_key(s.as_str())).collect();
self.connected_actor_ids self.connected_actor_ids
@ -115,7 +121,7 @@ impl Inner {
.filter_map(|res| res.ok()) .filter_map(|res| res.ok())
.filter_map(url_from_ivec) .filter_map(url_from_ivec)
.filter_map(move |url| { .filter_map(move |url| {
let connected_domain = url.domain()?; let connected_domain = url.authority_str()?;
let connected_rdnn = domain_key(connected_domain); let connected_rdnn = domain_key(connected_domain);
for rdnn in &reversed { for rdnn in &reversed {
@ -136,7 +142,7 @@ impl Inner {
.map(|s| String::from_utf8_lossy(&s).to_string()) .map(|s| String::from_utf8_lossy(&s).to_string())
} }
fn connected(&self) -> impl DoubleEndedIterator<Item = Url> { fn connected(&self) -> impl DoubleEndedIterator<Item = IriString> {
self.connected_actor_ids self.connected_actor_ids
.iter() .iter()
.values() .values()
@ -156,7 +162,7 @@ impl Inner {
}) })
} }
fn connected_info(&self) -> impl DoubleEndedIterator<Item = (Url, Info)> + '_ { fn connected_info(&self) -> impl DoubleEndedIterator<Item = (IriString, Info)> + '_ {
self.connected_actor_ids self.connected_actor_ids
.iter() .iter()
.values() .values()
@ -170,7 +176,7 @@ impl Inner {
}) })
} }
fn connected_instance(&self) -> impl DoubleEndedIterator<Item = (Url, Instance)> + '_ { fn connected_instance(&self) -> impl DoubleEndedIterator<Item = (IriString, Instance)> + '_ {
self.connected_actor_ids self.connected_actor_ids
.iter() .iter()
.values() .values()
@ -184,7 +190,7 @@ impl Inner {
}) })
} }
fn connected_contact(&self) -> impl DoubleEndedIterator<Item = (Url, Contact)> + '_ { fn connected_contact(&self) -> impl DoubleEndedIterator<Item = (IriString, Contact)> + '_ {
self.connected_actor_ids self.connected_actor_ids
.iter() .iter()
.values() .values()
@ -198,9 +204,9 @@ impl Inner {
}) })
} }
fn is_allowed(&self, domain: &str) -> bool { fn is_allowed(&self, authority: &str) -> bool {
let prefix = domain_prefix(domain); let prefix = domain_prefix(authority);
let reverse_domain = domain_key(domain); let reverse_domain = domain_key(authority);
if self.restricted_mode { if self.restricted_mode {
self.allowed_domains self.allowed_domains
@ -263,11 +269,11 @@ impl Db {
Ok(t) Ok(t)
} }
pub(crate) async fn connected_ids(&self) -> Result<Vec<Url>, Error> { pub(crate) async fn connected_ids(&self) -> Result<Vec<IriString>, Error> {
self.unblock(|inner| Ok(inner.connected().collect())).await 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| { self.unblock(move |inner| {
let vec = serde_json::to_vec(&info)?; let vec = serde_json::to_vec(&info)?;
@ -280,7 +286,7 @@ impl Db {
.await .await
} }
pub(crate) async fn info(&self, actor_id: Url) -> Result<Option<Info>, Error> { pub(crate) async fn info(&self, actor_id: IriString) -> Result<Option<Info>, Error> {
self.unblock(move |inner| { self.unblock(move |inner| {
if let Some(ivec) = inner.actor_id_info.get(actor_id.as_str().as_bytes())? { if let Some(ivec) = inner.actor_id_info.get(actor_id.as_str().as_bytes())? {
let info = serde_json::from_slice(&ivec)?; let info = serde_json::from_slice(&ivec)?;
@ -292,14 +298,14 @@ impl Db {
.await .await
} }
pub(crate) async fn connected_info(&self) -> Result<HashMap<Url, Info>, Error> { pub(crate) async fn connected_info(&self) -> Result<HashMap<IriString, Info>, Error> {
self.unblock(|inner| Ok(inner.connected_info().collect())) self.unblock(|inner| Ok(inner.connected_info().collect()))
.await .await
} }
pub(crate) async fn save_instance( pub(crate) async fn save_instance(
&self, &self,
actor_id: Url, actor_id: IriString,
instance: Instance, instance: Instance,
) -> Result<(), Error> { ) -> Result<(), Error> {
self.unblock(move |inner| { self.unblock(move |inner| {
@ -314,7 +320,7 @@ impl Db {
.await .await
} }
pub(crate) async fn instance(&self, actor_id: Url) -> Result<Option<Instance>, Error> { pub(crate) async fn instance(&self, actor_id: IriString) -> Result<Option<Instance>, Error> {
self.unblock(move |inner| { self.unblock(move |inner| {
if let Some(ivec) = inner.actor_id_instance.get(actor_id.as_str().as_bytes())? { if let Some(ivec) = inner.actor_id_instance.get(actor_id.as_str().as_bytes())? {
let instance = serde_json::from_slice(&ivec)?; let instance = serde_json::from_slice(&ivec)?;
@ -326,12 +332,16 @@ impl Db {
.await .await
} }
pub(crate) async fn connected_instance(&self) -> Result<HashMap<Url, Instance>, Error> { pub(crate) async fn connected_instance(&self) -> Result<HashMap<IriString, Instance>, Error> {
self.unblock(|inner| Ok(inner.connected_instance().collect())) self.unblock(|inner| Ok(inner.connected_instance().collect()))
.await .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| { self.unblock(move |inner| {
let vec = serde_json::to_vec(&contact)?; let vec = serde_json::to_vec(&contact)?;
@ -344,7 +354,7 @@ impl Db {
.await .await
} }
pub(crate) async fn contact(&self, actor_id: Url) -> Result<Option<Contact>, Error> { pub(crate) async fn contact(&self, actor_id: IriString) -> Result<Option<Contact>, Error> {
self.unblock(move |inner| { self.unblock(move |inner| {
if let Some(ivec) = inner.actor_id_contact.get(actor_id.as_str().as_bytes())? { if let Some(ivec) = inner.actor_id_contact.get(actor_id.as_str().as_bytes())? {
let contact = serde_json::from_slice(&ivec)?; let contact = serde_json::from_slice(&ivec)?;
@ -356,12 +366,12 @@ impl Db {
.await .await
} }
pub(crate) async fn connected_contact(&self) -> Result<HashMap<Url, Contact>, Error> { pub(crate) async fn connected_contact(&self) -> Result<HashMap<IriString, Contact>, Error> {
self.unblock(|inner| Ok(inner.connected_contact().collect())) self.unblock(|inner| Ok(inner.connected_contact().collect()))
.await .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| { self.unblock(move |inner| {
inner inner
.media_id_media_url .media_id_media_url
@ -393,7 +403,7 @@ impl Db {
.await .await
} }
pub(crate) async fn media_id(&self, url: Url) -> Result<Option<Uuid>, Error> { pub(crate) async fn media_id(&self, url: IriString) -> Result<Option<Uuid>, Error> {
self.unblock(move |inner| { self.unblock(move |inner| {
if let Some(ivec) = inner.media_url_media_id.get(url.as_str().as_bytes())? { if let Some(ivec) = inner.media_url_media_id.get(url.as_str().as_bytes())? {
Ok(uuid_from_ivec(ivec)) Ok(uuid_from_ivec(ivec))
@ -404,7 +414,7 @@ impl Db {
.await .await
} }
pub(crate) async fn media_url(&self, id: Uuid) -> Result<Option<Url>, Error> { pub(crate) async fn media_url(&self, id: Uuid) -> Result<Option<IriString>, Error> {
self.unblock(move |inner| { self.unblock(move |inner| {
if let Some(ivec) = inner.media_id_media_url.get(id.as_bytes())? { if let Some(ivec) = inner.media_id_media_url.get(id.as_bytes())? {
Ok(url_from_ivec(ivec)) Ok(url_from_ivec(ivec))
@ -442,24 +452,22 @@ impl Db {
self.unblock(|inner| Ok(inner.blocks().collect())).await self.unblock(|inner| Ok(inner.blocks().collect())).await
} }
pub(crate) async fn inboxes(&self) -> Result<Vec<Url>, Error> { pub(crate) async fn inboxes(&self) -> Result<Vec<IriString>, Error> {
self.unblock(|inner| Ok(inner.connected_actors().map(|actor| actor.inbox).collect())) self.unblock(|inner| Ok(inner.connected_actors().map(|actor| actor.inbox).collect()))
.await .await
} }
pub(crate) async fn is_connected(&self, mut id: Url) -> Result<bool, Error> { pub(crate) async fn is_connected(&self, base_id: IriString) -> Result<bool, Error> {
id.set_path(""); let scheme = base_id.scheme_str();
id.set_query(None); let authority = base_id.authority_str().ok_or(ErrorKind::MissingDomain)?;
id.set_fragment(None); let prefix = format!("{}://{}", scheme, authority);
self.unblock(move |inner| { self.unblock(move |inner| {
let connected = inner let connected = inner
.connected_actor_ids .connected_actor_ids
.scan_prefix(id.as_str().as_bytes()) .scan_prefix(prefix.as_bytes())
.values() .values()
.filter_map(|res| res.ok()) .any(|res| res.is_ok());
.next()
.is_some();
Ok(connected) Ok(connected)
}) })
@ -468,8 +476,8 @@ impl Db {
pub(crate) async fn actor_id_from_public_key_id( pub(crate) async fn actor_id_from_public_key_id(
&self, &self,
public_key_id: Url, public_key_id: IriString,
) -> Result<Option<Url>, Error> { ) -> Result<Option<IriString>, Error> {
self.unblock(move |inner| { self.unblock(move |inner| {
if let Some(ivec) = inner if let Some(ivec) = inner
.public_key_id_actor_id .public_key_id_actor_id
@ -483,7 +491,7 @@ impl Db {
.await .await
} }
pub(crate) async fn actor(&self, actor_id: Url) -> Result<Option<Actor>, Error> { pub(crate) async fn actor(&self, actor_id: IriString) -> Result<Option<Actor>, Error> {
self.unblock(move |inner| { self.unblock(move |inner| {
if let Some(ivec) = inner.actor_id_actor.get(actor_id.as_str().as_bytes())? { if let Some(ivec) = inner.actor_id_actor.get(actor_id.as_str().as_bytes())? {
let actor = serde_json::from_slice(&ivec)?; let actor = serde_json::from_slice(&ivec)?;
@ -511,7 +519,7 @@ impl Db {
.await .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); tracing::debug!("Removing Connection: {}", actor_id);
self.unblock(move |inner| { self.unblock(move |inner| {
inner inner
@ -523,7 +531,7 @@ impl Db {
.await .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); tracing::debug!("Adding Connection: {}", actor_id);
self.unblock(move |inner| { self.unblock(move |inner| {
inner inner
@ -543,11 +551,11 @@ impl Db {
.remove(connected.as_str().as_bytes())?; .remove(connected.as_str().as_bytes())?;
} }
for domain in &domains { for authority in &domains {
inner inner
.blocked_domains .blocked_domains
.insert(domain_key(domain), domain.as_bytes())?; .insert(domain_key(authority), authority.as_bytes())?;
inner.allowed_domains.remove(domain_key(domain))?; inner.allowed_domains.remove(domain_key(authority))?;
} }
Ok(()) Ok(())
@ -557,8 +565,8 @@ impl Db {
pub(crate) async fn remove_blocks(&self, domains: Vec<String>) -> Result<(), Error> { pub(crate) async fn remove_blocks(&self, domains: Vec<String>) -> Result<(), Error> {
self.unblock(move |inner| { self.unblock(move |inner| {
for domain in &domains { for authority in &domains {
inner.blocked_domains.remove(domain_key(domain))?; inner.blocked_domains.remove(domain_key(authority))?;
} }
Ok(()) Ok(())
@ -568,10 +576,10 @@ impl Db {
pub(crate) async fn add_allows(&self, domains: Vec<String>) -> Result<(), Error> { pub(crate) async fn add_allows(&self, domains: Vec<String>) -> Result<(), Error> {
self.unblock(move |inner| { self.unblock(move |inner| {
for domain in &domains { for authority in &domains {
inner inner
.allowed_domains .allowed_domains
.insert(domain_key(domain), domain.as_bytes())?; .insert(domain_key(authority), authority.as_bytes())?;
} }
Ok(()) Ok(())
@ -589,8 +597,8 @@ impl Db {
} }
} }
for domain in &domains { for authority in &domains {
inner.allowed_domains.remove(domain_key(domain))?; inner.allowed_domains.remove(domain_key(authority))?;
} }
Ok(()) Ok(())
@ -598,10 +606,10 @@ impl Db {
.await .await
} }
pub(crate) async fn is_allowed(&self, url: Url) -> Result<bool, Error> { pub(crate) async fn is_allowed(&self, url: IriString) -> Result<bool, Error> {
self.unblock(move |inner| { self.unblock(move |inner| {
if let Some(domain) = url.domain() { if let Some(authority) = url.authority_str() {
Ok(inner.is_allowed(domain)) Ok(inner.is_allowed(authority))
} else { } else {
Ok(false) Ok(false)
} }
@ -639,12 +647,12 @@ impl Db {
} }
} }
fn domain_key(domain: &str) -> String { fn domain_key(authority: &str) -> String {
domain.split('.').rev().collect::<Vec<_>>().join(".") + "." authority.split('.').rev().collect::<Vec<_>>().join(".") + "."
} }
fn domain_prefix(domain: &str) -> String { fn domain_prefix(authority: &str) -> String {
domain authority
.split('.') .split('.')
.rev() .rev()
.take(2) .take(2)
@ -653,8 +661,8 @@ fn domain_prefix(domain: &str) -> String {
+ "." + "."
} }
fn url_from_ivec(ivec: sled::IVec) -> Option<Url> { fn url_from_ivec(ivec: sled::IVec) -> Option<IriString> {
String::from_utf8_lossy(&ivec).parse::<Url>().ok() String::from_utf8_lossy(&ivec).parse::<IriString>().ok()
} }
fn uuid_from_ivec(ivec: sled::IVec) -> Option<Uuid> { fn uuid_from_ivec(ivec: sled::IVec) -> Option<Uuid> {
@ -664,14 +672,14 @@ fn uuid_from_ivec(ivec: sled::IVec) -> Option<Uuid> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::Db; use super::Db;
use activitystreams::url::Url; use activitystreams::iri_string::types::IriString;
use std::future::Future; use std::future::Future;
#[test] #[test]
fn connect_and_verify() { fn connect_and_verify() {
run(|db| async move { run(|db| async move {
let example_actor: Url = "http://example.com/actor".parse().unwrap(); let example_actor: IriString = "http://example.com/actor".parse().unwrap();
let example_sub_actor: Url = "http://example.com/users/fake".parse().unwrap(); let example_sub_actor: IriString = "http://example.com/users/fake".parse().unwrap();
db.add_connection(example_actor.clone()).await.unwrap(); db.add_connection(example_actor.clone()).await.unwrap();
assert!(db.is_connected(example_sub_actor).await.unwrap()); assert!(db.is_connected(example_sub_actor).await.unwrap());
}) })
@ -680,8 +688,8 @@ mod tests {
#[test] #[test]
fn disconnect_and_verify() { fn disconnect_and_verify() {
run(|db| async move { run(|db| async move {
let example_actor: Url = "http://example.com/actor".parse().unwrap(); let example_actor: IriString = "http://example.com/actor".parse().unwrap();
let example_sub_actor: Url = "http://example.com/users/fake".parse().unwrap(); let example_sub_actor: IriString = "http://example.com/users/fake".parse().unwrap();
db.add_connection(example_actor.clone()).await.unwrap(); db.add_connection(example_actor.clone()).await.unwrap();
assert!(db.is_connected(example_sub_actor.clone()).await.unwrap()); assert!(db.is_connected(example_sub_actor.clone()).await.unwrap());
@ -693,7 +701,7 @@ mod tests {
#[test] #[test]
fn connected_actor_in_connected_list() { fn connected_actor_in_connected_list() {
run(|db| async move { 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.add_connection(example_actor.clone()).await.unwrap();
assert!(db.connected_ids().await.unwrap().contains(&example_actor)); assert!(db.connected_ids().await.unwrap().contains(&example_actor));
@ -703,7 +711,7 @@ mod tests {
#[test] #[test]
fn disconnected_actor_not_in_connected_list() { fn disconnected_actor_not_in_connected_list() {
run(|db| async move { 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.add_connection(example_actor.clone()).await.unwrap();
db.remove_connection(example_actor.clone()).await.unwrap(); db.remove_connection(example_actor.clone()).await.unwrap();

View file

@ -1,4 +1,4 @@
use activitystreams::{error::DomainError, url::ParseError}; use activitystreams::checked::CheckError;
use actix_web::{ use actix_web::{
error::{BlockingError, ResponseError}, error::{BlockingError, ResponseError},
http::StatusCode, http::StatusCode,
@ -56,8 +56,11 @@ pub(crate) enum ErrorKind {
#[error("Couldn't parse key, {0}")] #[error("Couldn't parse key, {0}")]
Pkcs8(#[from] rsa::pkcs8::Error), Pkcs8(#[from] rsa::pkcs8::Error),
#[error("Couldn't parse URI, {0}")] #[error("Couldn't parse IRI, {0}")]
Uri(#[from] ParseError), 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}")] #[error("Couldn't perform IO, {0}")]
Io(#[from] io::Error), Io(#[from] io::Error),
@ -102,7 +105,7 @@ pub(crate) enum ErrorKind {
CpuCount(#[from] std::num::TryFromIntError), CpuCount(#[from] std::num::TryFromIntError),
#[error("{0}")] #[error("{0}")]
HostMismatch(#[from] DomainError), HostMismatch(#[from] CheckError),
#[error("Invalid or missing content type")] #[error("Invalid or missing content type")]
ContentType, ContentType,
@ -137,7 +140,7 @@ pub(crate) enum ErrorKind {
#[error("Input is missing a 'id' field")] #[error("Input is missing a 'id' field")]
MissingId, MissingId,
#[error("Url is missing a domain")] #[error("IriString is missing a domain")]
MissingDomain, MissingDomain,
#[error("URI is missing domain field")] #[error("URI is missing domain field")]

View file

@ -7,13 +7,13 @@ use crate::{
DeliverMany, JobState, 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 background_jobs::ActixJob;
use std::{future::Future, pin::Pin}; use std::{future::Future, pin::Pin};
#[derive(Clone, serde::Deserialize, serde::Serialize)] #[derive(Clone, serde::Deserialize, serde::Serialize)]
pub(crate) struct Announce { pub(crate) struct Announce {
object_id: Url, object_id: IriString,
actor: Actor, actor: Actor,
} }
@ -27,7 +27,7 @@ impl std::fmt::Debug for Announce {
} }
impl 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 } Announce { object_id, actor }
} }
@ -49,8 +49,8 @@ impl Announce {
// Generate a type that says "Look at this object" // Generate a type that says "Look at this object"
fn generate_announce( fn generate_announce(
config: &Config, config: &Config,
activity_id: &Url, activity_id: &IriString,
object_id: &Url, object_id: &IriString,
) -> Result<AsAnnounce, Error> { ) -> Result<AsAnnounce, Error> {
let announce = AsAnnounce::new(config.generate_url(UrlKind::Actor), object_id.clone()); let announce = AsAnnounce::new(config.generate_url(UrlKind::Actor), object_id.clone());

View file

@ -8,7 +8,7 @@ use crate::{
use activitystreams::{ use activitystreams::{
activity::{Accept as AsAccept, Follow as AsFollow}, activity::{Accept as AsAccept, Follow as AsFollow},
prelude::*, prelude::*,
url::Url, iri_string::types::IriString,
}; };
use background_jobs::ActixJob; use background_jobs::ActixJob;
use std::{future::Future, pin::Pin}; use std::{future::Future, pin::Pin};
@ -62,7 +62,7 @@ impl Follow {
} }
// Generate a type that says "I want to follow you" // Generate a type that says "I want to follow you"
fn generate_follow(config: &Config, actor_id: &Url, my_id: &Url) -> Result<AsFollow, Error> { fn generate_follow(config: &Config, actor_id: &IriString, my_id: &IriString) -> Result<AsFollow, Error> {
let follow = AsFollow::new(my_id.clone(), actor_id.clone()); let follow = AsFollow::new(my_id.clone(), actor_id.clone());
prepare_activity( prepare_activity(
@ -75,9 +75,9 @@ fn generate_follow(config: &Config, actor_id: &Url, my_id: &Url) -> Result<AsFol
// Generate a type that says "I accept your follow request" // Generate a type that says "I accept your follow request"
fn generate_accept_follow( fn generate_accept_follow(
config: &Config, config: &Config,
actor_id: &Url, actor_id: &IriString,
input_id: &Url, input_id: &IriString,
my_id: &Url, my_id: &IriString,
) -> Result<AsAccept, Error> { ) -> Result<AsAccept, Error> {
let mut follow = AsFollow::new(actor_id.clone(), my_id.clone()); let mut follow = AsFollow::new(actor_id.clone(), my_id.clone());

View file

@ -23,7 +23,7 @@ impl Forward {
async fn perform(self, state: JobState) -> Result<(), Error> { async fn perform(self, state: JobState) -> Result<(), Error> {
let object_id = self let object_id = self
.input .input
.object() .object_unchecked()
.as_single_id() .as_single_id()
.ok_or(ErrorKind::MissingId)?; .ok_or(ErrorKind::MissingId)?;
@ -31,7 +31,8 @@ impl Forward {
state state
.job_server .job_server
.queue(DeliverMany::new(inboxes, self.input)?).await?; .queue(DeliverMany::new(inboxes, self.input)?)
.await?;
Ok(()) Ok(())
} }

View file

@ -7,9 +7,9 @@ use crate::{
use activitystreams::{ use activitystreams::{
activity::{Follow as AsFollow, Undo as AsUndo}, activity::{Follow as AsFollow, Undo as AsUndo},
context, context,
iri_string::types::IriString,
prelude::*, prelude::*,
security, security,
url::Url,
}; };
use std::convert::TryInto; use std::convert::TryInto;
@ -23,16 +23,23 @@ pub(crate) use self::{
announce::Announce, follow::Follow, forward::Forward, reject::Reject, undo::Undo, announce::Announce, follow::Follow, forward::Forward, reject::Reject, undo::Undo,
}; };
async fn get_inboxes(state: &State, actor: &Actor, object_id: &Url) -> Result<Vec<Url>, Error> { async fn get_inboxes(
let domain = object_id.host().ok_or(ErrorKind::Domain)?.to_string(); state: &State,
actor: &Actor,
object_id: &IriString,
) -> Result<Vec<IriString>, 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<T, U, V, Kind>( fn prepare_activity<T, U, V, Kind>(
mut t: T, mut t: T,
id: impl TryInto<Url, Error = U>, id: impl TryInto<IriString, Error = U>,
to: impl TryInto<Url, Error = V>, to: impl TryInto<IriString, Error = V>,
) -> Result<T, Error> ) -> Result<T, Error>
where where
T: ObjectExt<Kind> + BaseExt<Kind>, T: ObjectExt<Kind> + BaseExt<Kind>,
@ -45,7 +52,11 @@ where
} }
// Generate a type that says "I want to stop following you" // Generate a type that says "I want to stop following you"
fn generate_undo_follow(config: &Config, actor_id: &Url, my_id: &Url) -> Result<AsUndo, Error> { fn generate_undo_follow(
config: &Config,
actor_id: &IriString,
my_id: &IriString,
) -> Result<AsUndo, Error> {
let mut follow = AsFollow::new(my_id.clone(), actor_id.clone()); let mut follow = AsFollow::new(my_id.clone(), actor_id.clone());
follow.set_id(config.generate_url(UrlKind::Activity)); follow.set_id(config.generate_url(UrlKind::Activity));

View file

@ -3,14 +3,14 @@ use crate::{
error::{Error, ErrorKind}, error::{Error, ErrorKind},
jobs::JobState, jobs::JobState,
}; };
use activitystreams::{object::Image, prelude::*, url::Url}; use activitystreams::{object::Image, prelude::*, iri_string::types::IriString};
use background_jobs::ActixJob; use background_jobs::ActixJob;
use std::{future::Future, pin::Pin}; use std::{future::Future, pin::Pin};
#[derive(Clone, serde::Deserialize, serde::Serialize)] #[derive(Clone, serde::Deserialize, serde::Serialize)]
pub(crate) struct QueryContact { pub(crate) struct QueryContact {
actor_id: Url, actor_id: IriString,
contact_id: Url, contact_id: IriString,
} }
impl std::fmt::Debug for QueryContact { impl std::fmt::Debug for QueryContact {
@ -23,7 +23,7 @@ impl std::fmt::Debug for QueryContact {
} }
impl 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 { QueryContact {
actor_id, actor_id,
contact_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 username = contact.preferred_username()?.to_owned();
let display_name = contact.name()?.as_one()?.as_xsd_string()?.to_owned(); let display_name = contact.name()?.as_one()?.as_xsd_string()?.to_owned();

View file

@ -1,11 +1,11 @@
use crate::{error::Error, jobs::JobState}; use crate::{error::Error, jobs::JobState};
use activitystreams::url::Url; use activitystreams::iri_string::types::IriString;
use background_jobs::{ActixJob, Backoff}; use background_jobs::{ActixJob, Backoff};
use std::{future::Future, pin::Pin}; use std::{future::Future, pin::Pin};
#[derive(Clone, serde::Deserialize, serde::Serialize)] #[derive(Clone, serde::Deserialize, serde::Serialize)]
pub(crate) struct Deliver { pub(crate) struct Deliver {
to: Url, to: IriString,
data: serde_json::Value, data: serde_json::Value,
} }
@ -19,7 +19,7 @@ impl std::fmt::Debug for Deliver {
} }
impl Deliver { impl Deliver {
pub(crate) fn new<T>(to: Url, data: T) -> Result<Self, Error> pub(crate) fn new<T>(to: IriString, data: T) -> Result<Self, Error>
where where
T: serde::ser::Serialize, T: serde::ser::Serialize,
{ {

View file

@ -2,13 +2,13 @@ use crate::{
error::Error, error::Error,
jobs::{Deliver, JobState}, jobs::{Deliver, JobState},
}; };
use activitystreams::url::Url; use activitystreams::iri_string::types::IriString;
use background_jobs::ActixJob; use background_jobs::ActixJob;
use futures_util::future::LocalBoxFuture; use futures_util::future::LocalBoxFuture;
#[derive(Clone, serde::Deserialize, serde::Serialize)] #[derive(Clone, serde::Deserialize, serde::Serialize)]
pub(crate) struct DeliverMany { pub(crate) struct DeliverMany {
to: Vec<Url>, to: Vec<IriString>,
data: serde_json::Value, data: serde_json::Value,
} }
@ -30,7 +30,7 @@ impl std::fmt::Debug for DeliverMany {
} }
impl DeliverMany { impl DeliverMany {
pub(crate) fn new<T>(to: Vec<Url>, data: T) -> Result<Self, Error> pub(crate) fn new<T>(to: Vec<IriString>, data: T) -> Result<Self, Error>
where where
T: serde::ser::Serialize, T: serde::ser::Serialize,
{ {

View file

@ -1,15 +1,15 @@
use crate::{ use crate::{
config::UrlKind, config::UrlKind,
error::Error, error::{Error, ErrorKind},
jobs::{cache_media::CacheMedia, JobState}, jobs::{cache_media::CacheMedia, JobState},
}; };
use activitystreams::url::Url; use activitystreams::{iri, iri_string::types::IriString};
use background_jobs::ActixJob; use background_jobs::ActixJob;
use std::{future::Future, pin::Pin}; use std::{future::Future, pin::Pin};
#[derive(Clone, serde::Deserialize, serde::Serialize)] #[derive(Clone, serde::Deserialize, serde::Serialize)]
pub(crate) struct QueryInstance { pub(crate) struct QueryInstance {
actor_id: Url, actor_id: IriString,
} }
impl std::fmt::Debug for QueryInstance { impl std::fmt::Debug for QueryInstance {
@ -21,7 +21,7 @@ impl std::fmt::Debug for QueryInstance {
} }
impl QueryInstance { impl QueryInstance {
pub(crate) fn new(actor_id: Url) -> Self { pub(crate) fn new(actor_id: IriString) -> Self {
QueryInstance { actor_id } QueryInstance { actor_id }
} }
@ -40,10 +40,12 @@ impl QueryInstance {
return Ok(()); return Ok(());
} }
let mut instance_uri = self.actor_id.clone(); let authority = self
instance_uri.set_fragment(None); .actor_id
instance_uri.set_query(None); .authority_str()
instance_uri.set_path("api/v1/instance"); .ok_or(ErrorKind::MissingDomain)?;
let scheme = self.actor_id.scheme_str();
let instance_uri = iri!(format!("{}://{}/api/v1/instance", scheme, authority));
let instance = state let instance = state
.requests .requests
@ -132,6 +134,6 @@ struct Instance {
struct Contact { struct Contact {
username: String, username: String,
display_name: String, display_name: String,
url: Url, url: IriString,
avatar: Url, avatar: IriString,
} }

View file

@ -1,14 +1,14 @@
use crate::{ use crate::{
error::Error, error::{Error, ErrorKind},
jobs::{JobState, QueryContact}, jobs::{JobState, QueryContact},
}; };
use activitystreams::url::Url; use activitystreams::{iri, iri_string::types::IriString};
use background_jobs::ActixJob; use background_jobs::ActixJob;
use std::{fmt::Debug, future::Future, pin::Pin}; use std::{fmt::Debug, future::Future, pin::Pin};
#[derive(Clone, serde::Deserialize, serde::Serialize)] #[derive(Clone, serde::Deserialize, serde::Serialize)]
pub(crate) struct QueryNodeinfo { pub(crate) struct QueryNodeinfo {
actor_id: Url, actor_id: IriString,
} }
impl Debug for QueryNodeinfo { impl Debug for QueryNodeinfo {
@ -20,7 +20,7 @@ impl Debug for QueryNodeinfo {
} }
impl QueryNodeinfo { impl QueryNodeinfo {
pub(crate) fn new(actor_id: Url) -> Self { pub(crate) fn new(actor_id: IriString) -> Self {
QueryNodeinfo { actor_id } QueryNodeinfo { actor_id }
} }
@ -34,10 +34,12 @@ impl QueryNodeinfo {
return Ok(()); return Ok(());
} }
let mut well_known_uri = self.actor_id.clone(); let authority = self
well_known_uri.set_fragment(None); .actor_id
well_known_uri.set_query(None); .authority_str()
well_known_uri.set_path(".well-known/nodeinfo"); .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 let well_known = state
.requests .requests
@ -100,7 +102,7 @@ struct Nodeinfo {
#[derive(serde::Deserialize)] #[derive(serde::Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct Metadata { struct Metadata {
staff_accounts: Option<Vec<Url>>, staff_accounts: Option<Vec<IriString>>,
} }
#[derive(serde::Deserialize)] #[derive(serde::Deserialize)]
@ -202,7 +204,7 @@ impl<'de> serde::de::Deserialize<'de> for SupportedNodeinfo {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{Nodeinfo, WellKnown}; 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 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"}]}"#; 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!( assert_eq!(
accounts[0], accounts[0],
"https://soc.hyena.network/users/HyNET" "https://soc.hyena.network/users/HyNET"
.parse::<Url>() .parse::<IriString>()
.unwrap() .unwrap()
); );
} }

View file

@ -1,7 +1,7 @@
// need this for ructe // need this for ructe
#![allow(clippy::needless_borrow)] #![allow(clippy::needless_borrow)]
use activitystreams::url::Url; use activitystreams::iri_string::types::IriString;
use actix_web::{web, App, HttpServer}; use actix_web::{web, App, HttpServer};
use console_subscriber::ConsoleLayer; use console_subscriber::ConsoleLayer;
use opentelemetry::{sdk::Resource, KeyValue}; use opentelemetry::{sdk::Resource, KeyValue};
@ -34,7 +34,7 @@ use self::{
fn init_subscriber( fn init_subscriber(
software_name: &'static str, software_name: &'static str,
opentelemetry_url: Option<&Url>, opentelemetry_url: Option<&IriString>,
) -> Result<(), anyhow::Error> { ) -> Result<(), anyhow::Error> {
LogTracer::init()?; LogTracer::init()?;

View file

@ -4,7 +4,7 @@ use crate::{
error::{Error, ErrorKind}, error::{Error, ErrorKind},
requests::Requests, requests::Requests,
}; };
use activitystreams::{base::BaseExt, uri, url::Url}; use activitystreams::{base::BaseExt, iri, iri_string::types::IriString};
use actix_web::web; use actix_web::web;
use http_signature_normalization_actix::{prelude::*, verify::DeprecatedAlgorithm}; use http_signature_normalization_actix::{prelude::*, verify::DeprecatedAlgorithm};
use rsa::{hash::Hash, padding::PaddingScheme, pkcs8::FromPublicKey, PublicKey, RsaPublicKey}; use rsa::{hash::Hash, padding::PaddingScheme, pkcs8::FromPublicKey, PublicKey, RsaPublicKey};
@ -23,7 +23,7 @@ impl MyVerify {
signature: String, signature: String,
signing_string: String, signing_string: String,
) -> Result<bool, Error> { ) -> Result<bool, Error> {
let public_key_id = uri!(key_id); let public_key_id = iri!(key_id);
let actor_id = if let Some(mut actor_id) = self let actor_id = if let Some(mut actor_id) = self
.2 .2
@ -85,8 +85,8 @@ impl MyVerify {
enum PublicKeyResponse { enum PublicKeyResponse {
PublicKey { PublicKey {
#[allow(dead_code)] #[allow(dead_code)]
id: Url, id: IriString,
owner: Url, owner: IriString,
#[allow(dead_code)] #[allow(dead_code)]
public_key_pem: String, public_key_pem: String,
}, },
@ -94,7 +94,7 @@ enum PublicKeyResponse {
} }
impl PublicKeyResponse { impl PublicKeyResponse {
fn actor_id(&self) -> Option<Url> { fn actor_id(&self) -> Option<IriString> {
match self { match self {
PublicKeyResponse::PublicKey { owner, .. } => Some(owner.clone()), PublicKeyResponse::PublicKey { owner, .. } => Some(owner.clone()),
PublicKeyResponse::Actor(actor) => actor.id_unchecked().cloned(), PublicKeyResponse::Actor(actor) => actor.id_unchecked().cloned(),

View file

@ -1,8 +1,7 @@
use crate::error::{Error, ErrorKind}; use crate::error::{Error, ErrorKind};
use activitystreams::url::Url; use activitystreams::iri_string::types::IriString;
use actix_web::{http::header::Date, web::Bytes}; use actix_web::{http::header::Date, web::Bytes};
use awc::Client; use awc::Client;
use chrono::{DateTime, Utc};
use dashmap::DashMap; use dashmap::DashMap;
use http_signature_normalization_actix::prelude::*; use http_signature_normalization_actix::prelude::*;
use rsa::{hash::Hash, padding::PaddingScheme, RsaPrivateKey}; use rsa::{hash::Hash, padding::PaddingScheme, RsaPrivateKey};
@ -16,6 +15,7 @@ use std::{
}, },
time::SystemTime, time::SystemTime,
}; };
use time::OffsetDateTime;
use tracing::{debug, info, warn}; use tracing::{debug, info, warn};
use tracing_awc::Tracing; use tracing_awc::Tracing;
@ -31,9 +31,9 @@ impl std::fmt::Debug for Breakers {
} }
impl Breakers { impl Breakers {
fn should_try(&self, url: &Url) -> bool { fn should_try(&self, url: &IriString) -> bool {
if let Some(domain) = url.domain() { if let Some(authority) = url.authority_str() {
if let Some(breaker) = self.inner.get(domain) { if let Some(breaker) = self.inner.get(authority) {
breaker.should_try() breaker.should_try()
} else { } else {
true true
@ -43,10 +43,10 @@ impl Breakers {
} }
} }
fn fail(&self, url: &Url) { fn fail(&self, url: &IriString) {
if let Some(domain) = url.domain() { if let Some(authority) = url.authority_str() {
let should_write = { 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(); breaker.fail();
false false
} else { } else {
@ -55,16 +55,16 @@ impl Breakers {
}; };
if should_write { 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(); breaker.fail();
} }
} }
} }
fn succeed(&self, url: &Url) { fn succeed(&self, url: &IriString) {
if let Some(domain) = url.domain() { if let Some(authority) = url.authority_str() {
let should_write = { 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(); breaker.succeed();
false false
} else { } else {
@ -73,7 +73,7 @@ impl Breakers {
}; };
if should_write { 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(); breaker.succeed();
} }
} }
@ -91,8 +91,8 @@ impl Default for Breakers {
#[derive(Debug)] #[derive(Debug)]
struct Breaker { struct Breaker {
failures: usize, failures: usize,
last_attempt: DateTime<Utc>, last_attempt: OffsetDateTime,
last_success: DateTime<Utc>, last_success: OffsetDateTime,
} }
impl Breaker { impl Breaker {
@ -100,30 +100,30 @@ impl Breaker {
10 10
} }
fn failure_wait() -> chrono::Duration { fn failure_wait() -> time::Duration {
chrono::Duration::days(1) time::Duration::days(1)
} }
fn should_try(&self) -> bool { fn should_try(&self) -> bool {
self.failures < Self::failure_threshold() 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) { fn fail(&mut self) {
self.failures += 1; self.failures += 1;
self.last_attempt = Utc::now(); self.last_attempt = OffsetDateTime::now_utc();
} }
fn succeed(&mut self) { fn succeed(&mut self) {
self.failures = 0; self.failures = 0;
self.last_attempt = Utc::now(); self.last_attempt = OffsetDateTime::now_utc();
self.last_success = Utc::now(); self.last_success = OffsetDateTime::now_utc();
} }
} }
impl Default for Breaker { impl Default for Breaker {
fn default() -> Self { fn default() -> Self {
let now = Utc::now(); let now = OffsetDateTime::now_utc();
Breaker { Breaker {
failures: 0, failures: 0,
@ -217,7 +217,7 @@ impl Requests {
where where
T: serde::de::DeserializeOwned, T: serde::de::DeserializeOwned,
{ {
let parsed_url = url.parse::<Url>()?; let parsed_url = url.parse::<IriString>()?;
if !self.breakers.should_try(&parsed_url) { if !self.breakers.should_try(&parsed_url) {
return Err(ErrorKind::Breaker.into()); return Err(ErrorKind::Breaker.into());
@ -274,7 +274,7 @@ impl Requests {
#[tracing::instrument(name = "Fetch Bytes")] #[tracing::instrument(name = "Fetch Bytes")]
pub(crate) async fn fetch_bytes(&self, url: &str) -> Result<(String, Bytes), Error> { pub(crate) async fn fetch_bytes(&self, url: &str) -> Result<(String, Bytes), Error> {
let parsed_url = url.parse::<Url>()?; let parsed_url = url.parse::<IriString>()?;
if !self.breakers.should_try(&parsed_url) { if !self.breakers.should_try(&parsed_url) {
return Err(ErrorKind::Breaker.into()); return Err(ErrorKind::Breaker.into());
@ -346,7 +346,7 @@ impl Requests {
"Deliver to Inbox", "Deliver to Inbox",
fields(self, inbox = inbox.to_string().as_str(), item) fields(self, inbox = inbox.to_string().as_str(), item)
)] )]
pub(crate) async fn deliver<T>(&self, inbox: Url, item: &T) -> Result<(), Error> pub(crate) async fn deliver<T>(&self, inbox: IriString, item: &T) -> Result<(), Error>
where where
T: serde::ser::Serialize + std::fmt::Debug, T: serde::ser::Serialize + std::fmt::Debug,
{ {

View file

@ -10,7 +10,8 @@ use crate::{
routes::accepted, routes::accepted,
}; };
use activitystreams::{ 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 actix_web::{web, HttpResponse};
use http_signature_normalization_actix::prelude::{DigestVerified, SignatureVerified}; use http_signature_normalization_actix::prelude::{DigestVerified, SignatureVerified};
@ -79,7 +80,7 @@ pub(crate) async fn route(
fn valid_without_listener(input: &AcceptedActivities) -> Result<bool, Error> { fn valid_without_listener(input: &AcceptedActivities) -> Result<bool, Error> {
match input.kind() { match input.kind() {
Some(ValidTypes::Follow) => Ok(true), 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), _ => Ok(false),
} }
} }
@ -90,7 +91,7 @@ fn kind_str(base: &AnyBase) -> Result<&str, Error> {
.map_err(Into::into) .map_err(Into::into)
} }
fn id_string(id: Option<&Url>) -> Result<String, Error> { fn id_string(id: Option<&IriString>) -> Result<String, Error> {
id.map(|s| s.to_string()) id.map(|s| s.to_string())
.ok_or(ErrorKind::MissingId) .ok_or(ErrorKind::MissingId)
.map_err(Into::into) .map_err(Into::into)
@ -101,11 +102,14 @@ fn single_object(o: &OneOrMany<AnyBase>) -> Result<&AnyBase, Error> {
} }
async fn handle_accept(config: &Config, input: AcceptedActivities) -> Result<(), 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)? { let follow = if let Some(follow) = activity::Follow::from_any_base(base)? {
follow follow
} else { } 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)) { if !follow.actor_is(&config.generate_url(UrlKind::Actor)) {
@ -121,11 +125,14 @@ async fn handle_reject(
input: AcceptedActivities, input: AcceptedActivities,
actor: Actor, actor: Actor,
) -> Result<(), Error> { ) -> 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)? { let follow = if let Some(follow) = activity::Follow::from_any_base(base)? {
follow follow
} else { } 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)) { if !follow.actor_is(&config.generate_url(UrlKind::Actor)) {
@ -144,7 +151,7 @@ async fn handle_undo(
actor: Actor, actor: Actor,
is_listener: bool, is_listener: bool,
) -> Result<(), Error> { ) -> Result<(), Error> {
let any_base = single_object(input.object())?.clone(); let any_base = single_object(input.object_unchecked())?.clone();
let undone_object = let undone_object =
AcceptedUndoObjects::from_any_base(any_base)?.ok_or(ErrorKind::ObjectFormat)?; 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()) { if !undone_object.object_is(&my_id) && !undone_object.object_is(&public()) {
return Err( return Err(ErrorKind::WrongActor(id_string(
ErrorKind::WrongActor(id_string(undone_object.object().as_single_id())?).into(), undone_object.object_unchecked().as_single_id(),
); )?)
.into());
} }
if !is_listener { if !is_listener {
@ -189,13 +197,17 @@ async fn handle_announce(
input: AcceptedActivities, input: AcceptedActivities,
actor: Actor, actor: Actor,
) -> Result<(), Error> { ) -> 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 { if state.is_cached(object_id).await {
return Err(ErrorKind::Duplicate.into()); 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(()) Ok(())
} }
@ -206,10 +218,12 @@ async fn handle_follow(
input: AcceptedActivities, input: AcceptedActivities,
actor: Actor, actor: Actor,
) -> Result<(), Error> { ) -> 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()) { 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?; jobs.queue(Follow::new(input, actor)).await?;

View file

@ -57,7 +57,7 @@ pub(crate) async fn route(
.await .await
.unwrap_or_default() .unwrap_or_default()
.iter() .iter()
.filter_map(|listener| listener.domain()) .filter_map(|listener| listener.authority_str())
.map(|s| s.to_owned()) .map(|s| s.to_owned())
.collect(), .collect(),
blocks: if config.publish_blocks() { blocks: if config.publish_blocks() {

View file

@ -1,7 +1,7 @@
@use crate::db::Contact; @use crate::db::Contact;
@use activitystreams::url::Url; @use activitystreams::iri_string::types::IriString;
@(contact: &Contact, base: &Url) @(contact: &Contact, base: &IriString)
<div class="admin"> <div class="admin">
<div class="left"> <div class="left">
@ -12,7 +12,7 @@
<div class="right"> <div class="right">
<p class="display-name"><a href="@contact.url">@contact.display_name</a></p> <p class="display-name"><a href="@contact.url">@contact.display_name</a></p>
<p class="username"> <p class="username">
@@@contact.username@if let Some(domain) = base.domain() {@@@domain} @@@contact.username@if let Some(authority) = base.authority_str() {@@@authority}
</p> </p>
</div> </div>
</div> </div>

View file

@ -1,11 +1,11 @@
@use crate::db::Info; @use crate::db::Info;
@use activitystreams::url::Url; @use activitystreams::iri_string::types::IriString;
@(info: &Info, base: &Url) @(info: &Info, base: &IriString)
<article class="info"> <article class="info">
@if let Some(domain) = base.domain() { @if let Some(authority) = base.authority_str() {
<h4 class="padded"><a href="@base">@domain</a></h4> <h4 class="padded"><a href="@base">@authority</a></h4>
} }
<p class="padded"> <p class="padded">
Running @info.software, version @info.version. Running @info.software, version @info.version.

View file

@ -1,7 +1,7 @@
@use crate::{db::{Contact, Instance}, templates::admin}; @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)
<article class="instance"> <article class="instance">
<h4 class="padded"><a href="@base">@instance.title</a></h4> <h4 class="padded"><a href="@base">@instance.title</a></h4>