Compare commits
6 commits
main
...
url-wrappe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5cec6cef6 | ||
|
|
d066cc9e13 | ||
|
|
b6e461a51b | ||
|
|
bff0567c27 | ||
|
|
d0f37c699f | ||
|
|
62b7543299 |
32 changed files with 183 additions and 99 deletions
|
|
@ -40,7 +40,7 @@ Based on this we can define the following minimal struct to (de)serialize a `Per
|
||||||
# use activitypub_federation::fetch::object_id::ObjectId;
|
# use activitypub_federation::fetch::object_id::ObjectId;
|
||||||
# use serde::{Deserialize, Serialize};
|
# use serde::{Deserialize, Serialize};
|
||||||
# use activitystreams_kinds::actor::PersonType;
|
# use activitystreams_kinds::actor::PersonType;
|
||||||
# use url::Url;
|
# use activitypub_federation::url::Url;
|
||||||
# use activitypub_federation::traits::tests::DbUser;
|
# use activitypub_federation::traits::tests::DbUser;
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
|
|
@ -64,7 +64,7 @@ pub struct Person {
|
||||||
Besides we also need a second struct to represent the data which gets stored in our local database (for example PostgreSQL). This is necessary because the data format used by SQL is very different from that used by that from Activitypub. It is organized by an integer primary key instead of a link id. Nested structs are complicated to represent and easier if flattened. Some fields like `type` don't need to be stored at all. On the other hand, the database contains fields which can't be federated, such as the private key and a boolean indicating if the item is local or remote.
|
Besides we also need a second struct to represent the data which gets stored in our local database (for example PostgreSQL). This is necessary because the data format used by SQL is very different from that used by that from Activitypub. It is organized by an integer primary key instead of a link id. Nested structs are complicated to represent and easier if flattened. Some fields like `type` don't need to be stored at all. On the other hand, the database contains fields which can't be federated, such as the private key and a boolean indicating if the item is local or remote.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
# use url::Url;
|
# use activitypub_federation::url::Url;
|
||||||
# use chrono::{DateTime, Utc};
|
# use chrono::{DateTime, Utc};
|
||||||
|
|
||||||
pub struct DbUser {
|
pub struct DbUser {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ Activitypub propagates actions across servers using `Activities`. For this each
|
||||||
|
|
||||||
```
|
```
|
||||||
# use serde::{Deserialize, Serialize};
|
# use serde::{Deserialize, Serialize};
|
||||||
# use url::Url;
|
# use activitypub_federation::url::Url;
|
||||||
# use anyhow::Error;
|
# use anyhow::Error;
|
||||||
# use async_trait::async_trait;
|
# use async_trait::async_trait;
|
||||||
# use activitypub_federation::fetch::object_id::ObjectId;
|
# use activitypub_federation::fetch::object_id::ObjectId;
|
||||||
|
|
@ -62,7 +62,7 @@ Next its time to setup the actual HTTP handler for the inbox. For this we first
|
||||||
# use activitypub_federation::traits::ActivityHandler;
|
# use activitypub_federation::traits::ActivityHandler;
|
||||||
# use activitypub_federation::traits::tests::{DbConnection, DbUser, Follow};
|
# use activitypub_federation::traits::tests::{DbConnection, DbUser, Follow};
|
||||||
# use serde::{Deserialize, Serialize};
|
# use serde::{Deserialize, Serialize};
|
||||||
# use url::Url;
|
# use activitypub_federation::url::Url;
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ let activity = Follow {
|
||||||
actor: ObjectId::parse("https://lemmy.ml/u/nutomic")?,
|
actor: ObjectId::parse("https://lemmy.ml/u/nutomic")?,
|
||||||
object: recipient.federation_id.clone().into(),
|
object: recipient.federation_id.clone().into(),
|
||||||
kind: Default::default(),
|
kind: Default::default(),
|
||||||
id: "https://lemmy.ml/activities/321".try_into()?
|
id: "https://lemmy.ml/activities/321".parse()?
|
||||||
};
|
};
|
||||||
let inboxes = vec![recipient.shared_inbox_or_inbox()];
|
let inboxes = vec![recipient.shared_inbox_or_inbox()];
|
||||||
|
|
||||||
|
|
@ -62,7 +62,7 @@ let activity = Follow {
|
||||||
actor: ObjectId::parse("https://lemmy.ml/u/nutomic")?,
|
actor: ObjectId::parse("https://lemmy.ml/u/nutomic")?,
|
||||||
object: recipient.federation_id.clone().into(),
|
object: recipient.federation_id.clone().into(),
|
||||||
kind: Default::default(),
|
kind: Default::default(),
|
||||||
id: "https://lemmy.ml/activities/321".try_into()?
|
id: "https://lemmy.ml/activities/321".parse()?
|
||||||
};
|
};
|
||||||
let inboxes = vec![recipient.shared_inbox_or_inbox()];
|
let inboxes = vec![recipient.shared_inbox_or_inbox()];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ It is sometimes necessary to fetch from a URL, but we don't know the exact type
|
||||||
# use serde::{Deserialize, Serialize};
|
# use serde::{Deserialize, Serialize};
|
||||||
# use activitypub_federation::traits::tests::DbConnection;
|
# use activitypub_federation::traits::tests::DbConnection;
|
||||||
# use activitypub_federation::config::Data;
|
# use activitypub_federation::config::Data;
|
||||||
# use url::Url;
|
# use activitypub_federation::url::Url;
|
||||||
# use activitypub_federation::traits::tests::{Person, Note};
|
# use activitypub_federation::traits::tests::{Person, Note};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,9 @@ use activitypub_federation::{
|
||||||
kinds::activity::CreateType,
|
kinds::activity::CreateType,
|
||||||
protocol::{context::WithContext, helpers::deserialize_one_or_many},
|
protocol::{context::WithContext, helpers::deserialize_one_or_many},
|
||||||
traits::{ActivityHandler, Object},
|
traits::{ActivityHandler, Object},
|
||||||
|
url::Url,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,11 @@ use activitypub_federation::{
|
||||||
kinds::actor::PersonType,
|
kinds::actor::PersonType,
|
||||||
protocol::{public_key::PublicKey, verification::verify_domains_match},
|
protocol::{public_key::PublicKey, verification::verify_domains_match},
|
||||||
traits::{ActivityHandler, Actor, Object},
|
traits::{ActivityHandler, Actor, Object},
|
||||||
|
url::Url,
|
||||||
};
|
};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt::Debug;
|
use std::{fmt::Debug, str::FromStr};
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct DbUser {
|
pub struct DbUser {
|
||||||
|
|
@ -36,8 +36,8 @@ pub enum PersonAcceptedActivities {
|
||||||
|
|
||||||
impl DbUser {
|
impl DbUser {
|
||||||
pub fn new(hostname: &str, name: &str) -> Result<DbUser, Error> {
|
pub fn new(hostname: &str, name: &str) -> Result<DbUser, Error> {
|
||||||
let ap_id = Url::parse(&format!("https://{}/{}", hostname, &name))?.into();
|
let ap_id = Url::from_str(&format!("https://{}/{}", hostname, &name))?.into();
|
||||||
let inbox = Url::parse(&format!("https://{}/{}/inbox", hostname, &name))?;
|
let inbox = Url::from_str(&format!("https://{}/{}/inbox", hostname, &name))?;
|
||||||
let keypair = generate_actor_keypair()?;
|
let keypair = generate_actor_keypair()?;
|
||||||
Ok(DbUser {
|
Ok(DbUser {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,10 @@ use activitypub_federation::{
|
||||||
kinds::{object::NoteType, public},
|
kinds::{object::NoteType, public},
|
||||||
protocol::{helpers::deserialize_one_or_many, verification::verify_domains_match},
|
protocol::{helpers::deserialize_one_or_many, verification::verify_domains_match},
|
||||||
traits::{Actor, Object},
|
traits::{Actor, Object},
|
||||||
|
url::Url,
|
||||||
};
|
};
|
||||||
use activitystreams_kinds::link::MentionType;
|
use activitystreams_kinds::link::MentionType;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct DbPost {
|
pub struct DbPost {
|
||||||
|
|
@ -63,7 +63,7 @@ impl Object for DbPost {
|
||||||
id: self.ap_id,
|
id: self.ap_id,
|
||||||
content: self.text,
|
content: self.text,
|
||||||
attributed_to: self.creator,
|
attributed_to: self.creator,
|
||||||
to: vec![public()],
|
to: vec![public().try_into()?],
|
||||||
tag: vec![],
|
tag: vec![],
|
||||||
in_reply_to: None,
|
in_reply_to: None,
|
||||||
})
|
})
|
||||||
|
|
@ -98,7 +98,7 @@ impl Object for DbPost {
|
||||||
kind: Default::default(),
|
kind: Default::default(),
|
||||||
id: generate_object_id(data.domain())?.into(),
|
id: generate_object_id(data.domain())?.into(),
|
||||||
attributed_to: data.local_user().ap_id,
|
attributed_to: data.local_user().ap_id,
|
||||||
to: vec![public()],
|
to: vec![public().try_into()?],
|
||||||
content: format!("Hello {}", creator.name),
|
content: format!("Hello {}", creator.name),
|
||||||
in_reply_to: Some(json.id.clone()),
|
in_reply_to: Some(json.id.clone()),
|
||||||
tag: vec![mention],
|
tag: vec![mention],
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use activitypub_federation::url::Url;
|
||||||
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
||||||
use url::{ParseError, Url};
|
use url::ParseError;
|
||||||
|
|
||||||
/// Just generate random url as object id. In a real project, you probably want to use
|
/// Just generate random url as object id. In a real project, you probably want to use
|
||||||
/// an url which contains the database id for easy retrieval (or store the random id in db).
|
/// an url which contains the database id for easy retrieval (or store the random id in db).
|
||||||
|
|
@ -9,5 +12,5 @@ pub fn generate_object_id(domain: &str) -> Result<Url, ParseError> {
|
||||||
.take(7)
|
.take(7)
|
||||||
.map(char::from)
|
.map(char::from)
|
||||||
.collect();
|
.collect();
|
||||||
Url::parse(&format!("https://{}/objects/{}", domain, id))
|
Url::from_str(&format!("https://{}/objects/{}", domain, id))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@ use activitypub_federation::{
|
||||||
fetch::object_id::ObjectId,
|
fetch::object_id::ObjectId,
|
||||||
kinds::activity::AcceptType,
|
kinds::activity::AcceptType,
|
||||||
traits::ActivityHandler,
|
traits::ActivityHandler,
|
||||||
|
url::Url,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,9 @@ use activitypub_federation::{
|
||||||
kinds::activity::CreateType,
|
kinds::activity::CreateType,
|
||||||
protocol::helpers::deserialize_one_or_many,
|
protocol::helpers::deserialize_one_or_many,
|
||||||
traits::{ActivityHandler, Object},
|
traits::{ActivityHandler, Object},
|
||||||
|
url::Url,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,9 @@ use activitypub_federation::{
|
||||||
fetch::object_id::ObjectId,
|
fetch::object_id::ObjectId,
|
||||||
kinds::activity::FollowType,
|
kinds::activity::FollowType,
|
||||||
traits::{ActivityHandler, Actor},
|
traits::{ActivityHandler, Actor},
|
||||||
|
url::Url,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,23 @@ use crate::{
|
||||||
objects::{person::DbUser, post::DbPost},
|
objects::{person::DbUser, post::DbPost},
|
||||||
Error,
|
Error,
|
||||||
};
|
};
|
||||||
use activitypub_federation::config::{FederationConfig, UrlVerifier};
|
use activitypub_federation::{
|
||||||
|
config::{FederationConfig, UrlVerifier},
|
||||||
|
url::Url,
|
||||||
|
};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use std::{
|
use std::{
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
};
|
};
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
pub async fn new_instance(
|
pub async fn new_instance(
|
||||||
hostname: &str,
|
hostname: &str,
|
||||||
name: String,
|
name: String,
|
||||||
) -> Result<FederationConfig<DatabaseHandle>, Error> {
|
) -> Result<FederationConfig<DatabaseHandle>, Error> {
|
||||||
let mut system_user = DbUser::new(hostname, "system".into())?;
|
let mut system_user = DbUser::new(hostname, "system".into())?;
|
||||||
system_user.ap_id = Url::parse(&format!("http://{}/", hostname))?.into();
|
system_user.ap_id = Url::from_str(&format!("http://{}/", hostname))?.into();
|
||||||
|
|
||||||
let local_user = DbUser::new(hostname, name)?;
|
let local_user = DbUser::new(hostname, name)?;
|
||||||
let database = Arc::new(Database {
|
let database = Arc::new(Database {
|
||||||
|
|
@ -51,7 +53,7 @@ struct MyUrlVerifier();
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl UrlVerifier for MyUrlVerifier {
|
impl UrlVerifier for MyUrlVerifier {
|
||||||
async fn verify(&self, url: &Url) -> Result<(), activitypub_federation::error::Error> {
|
async fn verify(&self, url: &Url) -> Result<(), activitypub_federation::error::Error> {
|
||||||
if url.domain() == Some("malicious.com") {
|
if url.domain() == "malicious.com" {
|
||||||
Err(activitypub_federation::error::Error::Other(
|
Err(activitypub_federation::error::Error::Other(
|
||||||
"malicious domain".into(),
|
"malicious domain".into(),
|
||||||
))
|
))
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,11 @@ use activitypub_federation::{
|
||||||
kinds::actor::PersonType,
|
kinds::actor::PersonType,
|
||||||
protocol::{context::WithContext, public_key::PublicKey, verification::verify_domains_match},
|
protocol::{context::WithContext, public_key::PublicKey, verification::verify_domains_match},
|
||||||
traits::{ActivityHandler, Actor, Object},
|
traits::{ActivityHandler, Actor, Object},
|
||||||
|
url::Url,
|
||||||
};
|
};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt::Debug;
|
use std::{fmt::Debug, str::FromStr};
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct DbUser {
|
pub struct DbUser {
|
||||||
|
|
@ -46,8 +46,8 @@ pub enum PersonAcceptedActivities {
|
||||||
|
|
||||||
impl DbUser {
|
impl DbUser {
|
||||||
pub fn new(hostname: &str, name: String) -> Result<DbUser, Error> {
|
pub fn new(hostname: &str, name: String) -> Result<DbUser, Error> {
|
||||||
let ap_id = Url::parse(&format!("http://{}/{}", hostname, &name))?.into();
|
let ap_id = Url::from_str(&format!("http://{}/{}", hostname, &name))?.into();
|
||||||
let inbox = Url::parse(&format!("http://{}/{}/inbox", hostname, &name))?;
|
let inbox = Url::from_str(&format!("http://{}/{}/inbox", hostname, &name))?;
|
||||||
let keypair = generate_actor_keypair()?;
|
let keypair = generate_actor_keypair()?;
|
||||||
Ok(DbUser {
|
Ok(DbUser {
|
||||||
name,
|
name,
|
||||||
|
|
@ -79,7 +79,7 @@ impl DbUser {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn followers_url(&self) -> Result<Url, Error> {
|
pub fn followers_url(&self) -> Result<Url, Error> {
|
||||||
Ok(Url::parse(&format!("{}/followers", self.ap_id.inner()))?)
|
Ok(Url::from_str(&format!("{}/followers", self.ap_id.inner()))?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn follow(&self, other: &str, data: &Data<DatabaseHandle>) -> Result<(), Error> {
|
pub async fn follow(&self, other: &str, data: &Data<DatabaseHandle>) -> Result<(), Error> {
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@ use activitypub_federation::{
|
||||||
kinds::{object::NoteType, public},
|
kinds::{object::NoteType, public},
|
||||||
protocol::{helpers::deserialize_one_or_many, verification::verify_domains_match},
|
protocol::{helpers::deserialize_one_or_many, verification::verify_domains_match},
|
||||||
traits::Object,
|
traits::Object,
|
||||||
|
url::Url,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct DbPost {
|
pub struct DbPost {
|
||||||
|
|
@ -19,7 +19,7 @@ pub struct DbPost {
|
||||||
|
|
||||||
impl DbPost {
|
impl DbPost {
|
||||||
pub fn new(text: String, creator: ObjectId<DbUser>) -> Result<DbPost, Error> {
|
pub fn new(text: String, creator: ObjectId<DbUser>) -> Result<DbPost, Error> {
|
||||||
let ap_id = generate_object_id(creator.inner().domain().unwrap())?.into();
|
let ap_id = generate_object_id(creator.inner().domain())?.into();
|
||||||
Ok(DbPost {
|
Ok(DbPost {
|
||||||
text,
|
text,
|
||||||
ap_id,
|
ap_id,
|
||||||
|
|
@ -65,7 +65,7 @@ impl Object for DbPost {
|
||||||
kind: Default::default(),
|
kind: Default::default(),
|
||||||
id: self.ap_id,
|
id: self.ap_id,
|
||||||
attributed_to: self.creator,
|
attributed_to: self.creator,
|
||||||
to: vec![public(), creator.followers_url()?],
|
to: vec![public().try_into()?, creator.followers_url()?],
|
||||||
content: self.text,
|
content: self.text,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use activitypub_federation::url::Url;
|
||||||
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
||||||
use url::{ParseError, Url};
|
use url::ParseError;
|
||||||
|
|
||||||
/// Just generate random url as object id. In a real project, you probably want to use
|
/// Just generate random url as object id. In a real project, you probably want to use
|
||||||
/// an url which contains the database id for easy retrieval (or store the random id in db).
|
/// an url which contains the database id for easy retrieval (or store the random id in db).
|
||||||
|
|
@ -9,5 +12,5 @@ pub fn generate_object_id(domain: &str) -> Result<Url, ParseError> {
|
||||||
.take(7)
|
.take(7)
|
||||||
.map(char::from)
|
.map(char::from)
|
||||||
.collect();
|
.collect();
|
||||||
Url::parse(&format!("http://{}/objects/{}", domain, id))
|
Url::from_str(&format!("http://{}/objects/{}", domain, id))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ use crate::{
|
||||||
|
|
||||||
use futures_core::Future;
|
use futures_core::Future;
|
||||||
|
|
||||||
|
use crate::url::Url;
|
||||||
use reqwest_middleware::ClientWithMiddleware;
|
use reqwest_middleware::ClientWithMiddleware;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::{
|
use std::{
|
||||||
|
|
@ -26,7 +27,6 @@ use tokio::{
|
||||||
task::{JoinHandle, JoinSet},
|
task::{JoinHandle, JoinSet},
|
||||||
};
|
};
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
/// Send a new activity to the given inboxes with automatic retry on failure. Alternatively you
|
/// Send a new activity to the given inboxes with automatic retry on failure. Alternatively you
|
||||||
/// can implement your own queue and then send activities using [[crate::activity_sending::SendActivityTask]].
|
/// can implement your own queue and then send activities using [[crate::activity_sending::SendActivityTask]].
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use crate::{
|
||||||
http_signatures::sign_request,
|
http_signatures::sign_request,
|
||||||
reqwest_shim::ResponseExt,
|
reqwest_shim::ResponseExt,
|
||||||
traits::{ActivityHandler, Actor},
|
traits::{ActivityHandler, Actor},
|
||||||
|
url::Url,
|
||||||
FEDERATION_CONTENT_TYPE,
|
FEDERATION_CONTENT_TYPE,
|
||||||
};
|
};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
|
|
@ -27,7 +28,6 @@ use std::{
|
||||||
time::{Duration, SystemTime},
|
time::{Duration, SystemTime},
|
||||||
};
|
};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
/// All info needed to sign and send one activity to one inbox. You should generally use
|
/// All info needed to sign and send one activity to one inbox. You should generally use
|
||||||
|
|
@ -202,7 +202,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn generate_request_headers(inbox_url: &Url) -> HeaderMap {
|
pub(crate) fn generate_request_headers(inbox_url: &Url) -> HeaderMap {
|
||||||
let mut host = inbox_url.domain().expect("read inbox domain").to_string();
|
let mut host = inbox_url.domain().to_string();
|
||||||
if let Some(port) = inbox_url.port() {
|
if let Some(port) = inbox_url.port() {
|
||||||
host = format!("{}:{}", host, port);
|
host = format!("{}:{}", host, port);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,8 @@ where
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[allow(clippy::unwrap_used)]
|
#[allow(clippy::unwrap_used)]
|
||||||
mod test {
|
mod test {
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
activity_sending::generate_request_headers,
|
activity_sending::generate_request_headers,
|
||||||
|
|
@ -54,12 +56,12 @@ mod test {
|
||||||
fetch::object_id::ObjectId,
|
fetch::object_id::ObjectId,
|
||||||
http_signatures::sign_request,
|
http_signatures::sign_request,
|
||||||
traits::tests::{DbConnection, DbUser, Follow, DB_USER_KEYPAIR},
|
traits::tests::{DbConnection, DbUser, Follow, DB_USER_KEYPAIR},
|
||||||
|
url::Url,
|
||||||
};
|
};
|
||||||
use actix_web::test::TestRequest;
|
use actix_web::test::TestRequest;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use reqwest_middleware::ClientWithMiddleware;
|
use reqwest_middleware::ClientWithMiddleware;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_receive_activity() {
|
async fn test_receive_activity() {
|
||||||
|
|
@ -108,7 +110,7 @@ mod test {
|
||||||
async fn test_receive_unparseable_activity() {
|
async fn test_receive_unparseable_activity() {
|
||||||
let (_, _, config) = setup_receive_test().await;
|
let (_, _, config) = setup_receive_test().await;
|
||||||
|
|
||||||
let actor = Url::parse("http://ds9.lemmy.ml/u/lemmy_alpha").unwrap();
|
let actor = Url::from_str("http://ds9.lemmy.ml/u/lemmy_alpha").unwrap();
|
||||||
let id = "http://localhost:123/1";
|
let id = "http://localhost:123/1";
|
||||||
let activity = json!({
|
let activity = json!({
|
||||||
"actor": actor.as_str(),
|
"actor": actor.as_str(),
|
||||||
|
|
@ -140,7 +142,7 @@ mod test {
|
||||||
|
|
||||||
async fn construct_request(body: &Bytes, actor: &Url) -> TestRequest {
|
async fn construct_request(body: &Bytes, actor: &Url) -> TestRequest {
|
||||||
let inbox = "https://example.com/inbox";
|
let inbox = "https://example.com/inbox";
|
||||||
let headers = generate_request_headers(&Url::parse(inbox).unwrap());
|
let headers = generate_request_headers(&Url::from_str(inbox).unwrap());
|
||||||
let request_builder = ClientWithMiddleware::from(Client::default())
|
let request_builder = ClientWithMiddleware::from(Client::default())
|
||||||
.post(inbox)
|
.post(inbox)
|
||||||
.headers(headers);
|
.headers(headers);
|
||||||
|
|
@ -165,7 +167,7 @@ mod test {
|
||||||
actor: ObjectId::parse("http://localhost:123").unwrap(),
|
actor: ObjectId::parse("http://localhost:123").unwrap(),
|
||||||
object: ObjectId::parse("http://localhost:124").unwrap(),
|
object: ObjectId::parse("http://localhost:124").unwrap(),
|
||||||
kind: Default::default(),
|
kind: Default::default(),
|
||||||
id: "http://localhost:123/1".try_into().unwrap(),
|
id: "http://localhost:123/1".parse().unwrap(),
|
||||||
};
|
};
|
||||||
let body: Bytes = serde_json::to_vec(&activity).unwrap().into();
|
let body: Bytes = serde_json::to_vec(&activity).unwrap().into();
|
||||||
let incoming_request = construct_request(&body, activity.actor.inner()).await;
|
let incoming_request = construct_request(&body, activity.actor.inner()).await;
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ use crate::{
|
||||||
error::Error,
|
error::Error,
|
||||||
protocol::verification::verify_domains_match,
|
protocol::verification::verify_domains_match,
|
||||||
traits::{ActivityHandler, Actor},
|
traits::{ActivityHandler, Actor},
|
||||||
|
url::Url,
|
||||||
};
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use derive_builder::Builder;
|
use derive_builder::Builder;
|
||||||
|
|
@ -35,7 +36,6 @@ use std::{
|
||||||
},
|
},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
/// Configuration for this library, with various federation related settings
|
/// Configuration for this library, with various federation related settings
|
||||||
#[derive(Builder, Clone)]
|
#[derive(Builder, Clone)]
|
||||||
|
|
@ -156,11 +156,7 @@ impl<T: Clone> FederationConfig<T> {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
if url.domain().is_none() {
|
if url.domain() == "localhost" && !self.debug {
|
||||||
return Err(Error::UrlVerificationError("Url must have a domain"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if url.domain() == Some("localhost") && !self.debug {
|
|
||||||
return Err(Error::UrlVerificationError(
|
return Err(Error::UrlVerificationError(
|
||||||
"Localhost is only allowed in debug mode",
|
"Localhost is only allowed in debug mode",
|
||||||
));
|
));
|
||||||
|
|
@ -247,7 +243,7 @@ impl<T: Clone> Deref for FederationConfig<T> {
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use async_trait::async_trait;
|
/// # use async_trait::async_trait;
|
||||||
/// # use url::Url;
|
/// # use activitypub_federation::url::Url;
|
||||||
/// # use activitypub_federation::config::UrlVerifier;
|
/// # use activitypub_federation::config::UrlVerifier;
|
||||||
/// # use activitypub_federation::error::Error;
|
/// # use activitypub_federation::error::Error;
|
||||||
/// # #[derive(Clone)]
|
/// # #[derive(Clone)]
|
||||||
|
|
@ -264,7 +260,7 @@ impl<T: Clone> Deref for FederationConfig<T> {
|
||||||
/// impl UrlVerifier for Verifier {
|
/// impl UrlVerifier for Verifier {
|
||||||
/// async fn verify(&self, url: &Url) -> Result<(), Error> {
|
/// async fn verify(&self, url: &Url) -> Result<(), Error> {
|
||||||
/// let blocklist = get_blocklist(&self.db_connection).await;
|
/// let blocklist = get_blocklist(&self.db_connection).await;
|
||||||
/// let domain = url.domain().unwrap().to_string();
|
/// let domain = url.domain().to_string();
|
||||||
/// if blocklist.contains(&domain) {
|
/// if blocklist.contains(&domain) {
|
||||||
/// Err(Error::Other("Domain is blocked".into()))
|
/// Err(Error::Other("Domain is blocked".into()))
|
||||||
/// } else {
|
/// } else {
|
||||||
|
|
@ -351,6 +347,8 @@ impl<T: Clone> FederationMiddleware<T> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[allow(clippy::unwrap_used)]
|
#[allow(clippy::unwrap_used)]
|
||||||
mod test {
|
mod test {
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
async fn config() -> FederationConfig<i32> {
|
async fn config() -> FederationConfig<i32> {
|
||||||
|
|
@ -365,10 +363,8 @@ mod test {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_url_is_local() -> Result<(), Error> {
|
async fn test_url_is_local() -> Result<(), Error> {
|
||||||
let config = config().await;
|
let config = config().await;
|
||||||
assert!(config.is_local_url(&Url::parse("http://example.com")?));
|
assert!(config.is_local_url(&Url::from_str("http://example.com")?));
|
||||||
assert!(!config.is_local_url(&Url::parse("http://other.com")?));
|
assert!(!config.is_local_url(&Url::from_str("http://other.com")?));
|
||||||
// ensure that missing domain doesnt cause crash
|
|
||||||
assert!(!config.is_local_url(&Url::parse("http://127.0.0.1")?));
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
//! Error messages returned by this library
|
//! Error messages returned by this library
|
||||||
|
|
||||||
use crate::fetch::webfinger::WebFingerError;
|
use crate::{fetch::webfinger::WebFingerError, url::Url};
|
||||||
use http_signature_normalization_reqwest::SignError;
|
use http_signature_normalization_reqwest::SignError;
|
||||||
use rsa::{
|
use rsa::{
|
||||||
errors::Error as RsaError,
|
errors::Error as RsaError,
|
||||||
|
|
@ -8,7 +8,6 @@ use rsa::{
|
||||||
};
|
};
|
||||||
use std::string::FromUtf8Error;
|
use std::string::FromUtf8Error;
|
||||||
use tokio::task::JoinError;
|
use tokio::task::JoinError;
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
/// Error messages returned by this library
|
/// Error messages returned by this library
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
use crate::{config::Data, error::Error, fetch::fetch_object_http, traits::Collection};
|
use crate::{config::Data, error::Error, fetch::fetch_object_http, traits::Collection, url::Url};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
fmt::{Debug, Display, Formatter},
|
fmt::{Debug, Display, Formatter},
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
|
str::FromStr,
|
||||||
};
|
};
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
/// Typed wrapper for Activitypub Collection ID which helps with dereferencing.
|
/// Typed wrapper for Activitypub Collection ID which helps with dereferencing.
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
|
@ -21,7 +21,7 @@ where
|
||||||
{
|
{
|
||||||
/// Construct a new CollectionId instance
|
/// Construct a new CollectionId instance
|
||||||
pub fn parse(url: &str) -> Result<Self, url::ParseError> {
|
pub fn parse(url: &str) -> Result<Self, url::ParseError> {
|
||||||
Ok(Self(Box::new(Url::parse(url)?), PhantomData::<Kind>))
|
Ok(Self(Box::new(Url::from_str(url)?), PhantomData::<Kind>))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fetches collection over HTTP
|
/// Fetches collection over HTTP
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use crate::{
|
||||||
extract_id,
|
extract_id,
|
||||||
http_signatures::sign_request,
|
http_signatures::sign_request,
|
||||||
reqwest_shim::ResponseExt,
|
reqwest_shim::ResponseExt,
|
||||||
|
url::Url,
|
||||||
FEDERATION_CONTENT_TYPE,
|
FEDERATION_CONTENT_TYPE,
|
||||||
};
|
};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
|
|
@ -15,7 +16,6 @@ use http::{HeaderValue, StatusCode};
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
/// Typed wrapper for collection IDs
|
/// Typed wrapper for collection IDs
|
||||||
pub mod collection_id;
|
pub mod collection_id;
|
||||||
|
|
@ -135,13 +135,13 @@ async fn fetch_object_http_with_accept<T: Clone, Kind: DeserializeOwned>(
|
||||||
match serde_json::from_slice(&text) {
|
match serde_json::from_slice(&text) {
|
||||||
Ok(object) => Ok(FetchObjectResponse {
|
Ok(object) => Ok(FetchObjectResponse {
|
||||||
object,
|
object,
|
||||||
url,
|
url: url.try_into()?,
|
||||||
content_type,
|
content_type,
|
||||||
object_id,
|
object_id,
|
||||||
}),
|
}),
|
||||||
Err(e) => Err(ParseFetchedObject(
|
Err(e) => Err(ParseFetchedObject(
|
||||||
e,
|
e,
|
||||||
url,
|
url.try_into()?,
|
||||||
String::from_utf8(Vec::from(text))?,
|
String::from_utf8(Vec::from(text))?,
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{config::Data, error::Error, fetch::fetch_object_http, traits::Object};
|
use crate::{config::Data, error::Error, fetch::fetch_object_http, traits::Object, url::Url};
|
||||||
use chrono::{DateTime, Duration as ChronoDuration, Utc};
|
use chrono::{DateTime, Duration as ChronoDuration, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
|
|
@ -6,7 +6,6 @@ use std::{
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
};
|
};
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
impl<T> FromStr for ObjectId<T>
|
impl<T> FromStr for ObjectId<T>
|
||||||
where
|
where
|
||||||
|
|
@ -66,7 +65,7 @@ where
|
||||||
{
|
{
|
||||||
/// Construct a new objectid instance
|
/// Construct a new objectid instance
|
||||||
pub fn parse(url: &str) -> Result<Self, url::ParseError> {
|
pub fn parse(url: &str) -> Result<Self, url::ParseError> {
|
||||||
Ok(Self(Box::new(Url::parse(url)?), PhantomData::<Kind>))
|
Ok(Self(Box::new(Url::from_str(url)?), PhantomData::<Kind>))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the wrapped URL value
|
/// Returns a reference to the wrapped URL value
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ use crate::{
|
||||||
error::Error,
|
error::Error,
|
||||||
fetch::{fetch_object_http_with_accept, object_id::ObjectId},
|
fetch::{fetch_object_http_with_accept, object_id::ObjectId},
|
||||||
traits::{Actor, Object},
|
traits::{Actor, Object},
|
||||||
|
url::Url,
|
||||||
FEDERATION_CONTENT_TYPE,
|
FEDERATION_CONTENT_TYPE,
|
||||||
};
|
};
|
||||||
use http::HeaderValue;
|
use http::HeaderValue;
|
||||||
|
|
@ -10,9 +11,8 @@ use itertools::Itertools;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{collections::HashMap, fmt::Display};
|
use std::{collections::HashMap, fmt::Display, str::FromStr};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
/// Errors relative to webfinger handling
|
/// Errors relative to webfinger handling
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
|
@ -60,7 +60,7 @@ where
|
||||||
debug!("Fetching webfinger url: {}", &fetch_url);
|
debug!("Fetching webfinger url: {}", &fetch_url);
|
||||||
|
|
||||||
let res: Webfinger = fetch_object_http_with_accept(
|
let res: Webfinger = fetch_object_http_with_accept(
|
||||||
&Url::parse(&fetch_url).map_err(Error::UrlParse)?,
|
&Url::from_str(&fetch_url).map_err(Error::UrlParse)?,
|
||||||
data,
|
data,
|
||||||
&WEBFINGER_CONTENT_TYPE,
|
&WEBFINGER_CONTENT_TYPE,
|
||||||
)
|
)
|
||||||
|
|
@ -143,10 +143,10 @@ where
|
||||||
/// of discovery.
|
/// of discovery.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use url::Url;
|
/// # use activitypub_federation::url::Url;
|
||||||
/// # use activitypub_federation::fetch::webfinger::build_webfinger_response;
|
/// # use activitypub_federation::fetch::webfinger::build_webfinger_response;
|
||||||
/// let subject = "acct:nutomic@lemmy.ml".to_string();
|
/// let subject = "acct:nutomic@lemmy.ml".to_string();
|
||||||
/// let url = Url::parse("https://lemmy.ml/u/nutomic")?;
|
/// let url = "https://lemmy.ml/u/nutomic".parse()?;
|
||||||
/// build_webfinger_response(subject, url);
|
/// build_webfinger_response(subject, url);
|
||||||
/// # Ok::<(), anyhow::Error>(())
|
/// # Ok::<(), anyhow::Error>(())
|
||||||
/// ```
|
/// ```
|
||||||
|
|
@ -162,11 +162,11 @@ pub fn build_webfinger_response(subject: String, url: Url) -> Webfinger {
|
||||||
/// will be empty.
|
/// will be empty.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use url::Url;
|
/// # use activitypub_federation::url::Url;
|
||||||
/// # use activitypub_federation::fetch::webfinger::build_webfinger_response_with_type;
|
/// # use activitypub_federation::fetch::webfinger::build_webfinger_response_with_type;
|
||||||
/// let subject = "acct:nutomic@lemmy.ml".to_string();
|
/// let subject = "acct:nutomic@lemmy.ml".to_string();
|
||||||
/// let user = Url::parse("https://lemmy.ml/u/nutomic")?;
|
/// let user = "https://lemmy.ml/u/nutomic".parse()?;
|
||||||
/// let group = Url::parse("https://lemmy.ml/c/asklemmy")?;
|
/// let group = "https://lemmy.ml/c/asklemmy".parse()?;
|
||||||
/// build_webfinger_response_with_type(subject, vec![
|
/// build_webfinger_response_with_type(subject, vec![
|
||||||
/// (user, Some("Person")),
|
/// (user, Some("Person")),
|
||||||
/// (group, Some("Group"))]);
|
/// (group, Some("Group"))]);
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ use crate::{
|
||||||
fetch::object_id::ObjectId,
|
fetch::object_id::ObjectId,
|
||||||
protocol::public_key::main_key_id,
|
protocol::public_key::main_key_id,
|
||||||
traits::{Actor, Object},
|
traits::{Actor, Object},
|
||||||
|
url::Url,
|
||||||
};
|
};
|
||||||
use base64::{engine::general_purpose::STANDARD as Base64, Engine};
|
use base64::{engine::general_purpose::STANDARD as Base64, Engine};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
|
|
@ -30,9 +31,8 @@ use rsa::{
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
use std::{collections::BTreeMap, fmt::Debug, time::Duration};
|
use std::{collections::BTreeMap, fmt::Debug, str::FromStr, time::Duration};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
/// A private/public key pair used for HTTP signatures
|
/// A private/public key pair used for HTTP signatures
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -166,7 +166,7 @@ where
|
||||||
None => return Err(Error::ActivitySignatureInvalid.into()),
|
None => return Err(Error::ActivitySignatureInvalid.into()),
|
||||||
Some(caps) => caps.get(1).expect("regex error").as_str(),
|
Some(caps) => caps.get(1).expect("regex error").as_str(),
|
||||||
};
|
};
|
||||||
let actor_url = Url::parse(actor_id).map_err(|_| Error::ActivitySignatureInvalid)?;
|
let actor_url = Url::from_str(actor_id).map_err(|_| Error::ActivitySignatureInvalid)?;
|
||||||
let actor_id: ObjectId<A> = actor_url.into();
|
let actor_id: ObjectId<A> = actor_url.into();
|
||||||
|
|
||||||
let actor = actor_id.dereference(data).await?;
|
let actor = actor_id.dereference(data).await?;
|
||||||
|
|
@ -287,9 +287,10 @@ pub mod test {
|
||||||
use rsa::{pkcs1::DecodeRsaPrivateKey, pkcs8::DecodePrivateKey};
|
use rsa::{pkcs1::DecodeRsaPrivateKey, pkcs8::DecodePrivateKey};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
static ACTOR_ID: Lazy<Url> = Lazy::new(|| Url::parse("https://example.com/u/alice").unwrap());
|
static ACTOR_ID: Lazy<Url> =
|
||||||
|
Lazy::new(|| Url::from_str("https://example.com/u/alice").unwrap());
|
||||||
static INBOX_URL: Lazy<Url> =
|
static INBOX_URL: Lazy<Url> =
|
||||||
Lazy::new(|| Url::parse("https://example.com/u/alice/inbox").unwrap());
|
Lazy::new(|| Url::from_str("https://example.com/u/alice/inbox").unwrap());
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_sign() {
|
async fn test_sign() {
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ pub mod http_signatures;
|
||||||
pub mod protocol;
|
pub mod protocol;
|
||||||
pub(crate) mod reqwest_shim;
|
pub(crate) mod reqwest_shim;
|
||||||
pub mod traits;
|
pub mod traits;
|
||||||
|
pub mod url;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::Data,
|
config::Data,
|
||||||
|
|
@ -32,8 +33,8 @@ use crate::{
|
||||||
};
|
};
|
||||||
pub use activitystreams_kinds as kinds;
|
pub use activitystreams_kinds as kinds;
|
||||||
|
|
||||||
|
use crate::url::Url;
|
||||||
use serde::{de::DeserializeOwned, Deserialize};
|
use serde::{de::DeserializeOwned, Deserialize};
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
/// Mime type for Activitypub data, used for `Accept` and `Content-Type` HTTP headers
|
/// Mime type for Activitypub data, used for `Accept` and `Content-Type` HTTP headers
|
||||||
pub const FEDERATION_CONTENT_TYPE: &str = "application/activity+json";
|
pub const FEDERATION_CONTENT_TYPE: &str = "application/activity+json";
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,9 @@
|
||||||
//! Ok::<(), serde_json::error::Error>(())
|
//! Ok::<(), serde_json::error::Error>(())
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use crate::{config::Data, traits::ActivityHandler};
|
use crate::{config::Data, traits::ActivityHandler, url::Url};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
/// Default context used in Activitypub
|
/// Default context used in Activitypub
|
||||||
const DEFAULT_CONTEXT: &str = "https://www.w3.org/ns/activitystreams";
|
const DEFAULT_CONTEXT: &str = "https://www.w3.org/ns/activitystreams";
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use serde::{Deserialize, Deserializer};
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use activitypub_federation::protocol::helpers::deserialize_one_or_many;
|
/// # use activitypub_federation::protocol::helpers::deserialize_one_or_many;
|
||||||
/// # use url::Url;
|
/// # use activitypub_federation::url::Url;
|
||||||
/// #[derive(serde::Deserialize)]
|
/// #[derive(serde::Deserialize)]
|
||||||
/// struct Note {
|
/// struct Note {
|
||||||
/// #[serde(deserialize_with = "deserialize_one_or_many")]
|
/// #[serde(deserialize_with = "deserialize_one_or_many")]
|
||||||
|
|
@ -52,7 +52,7 @@ where
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use activitypub_federation::protocol::helpers::deserialize_one;
|
/// # use activitypub_federation::protocol::helpers::deserialize_one;
|
||||||
/// # use url::Url;
|
/// # use activitypub_federation::url::Url;
|
||||||
/// #[derive(serde::Deserialize)]
|
/// #[derive(serde::Deserialize)]
|
||||||
/// struct Note {
|
/// struct Note {
|
||||||
/// #[serde(deserialize_with = "deserialize_one")]
|
/// #[serde(deserialize_with = "deserialize_one")]
|
||||||
|
|
@ -88,7 +88,7 @@ where
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use activitypub_federation::protocol::helpers::deserialize_skip_error;
|
/// # use activitypub_federation::protocol::helpers::deserialize_skip_error;
|
||||||
/// # use url::Url;
|
/// # use activitypub_federation::url::Url;
|
||||||
/// #[derive(serde::Deserialize)]
|
/// #[derive(serde::Deserialize)]
|
||||||
/// struct Note {
|
/// struct Note {
|
||||||
/// content: String,
|
/// content: String,
|
||||||
|
|
@ -120,8 +120,7 @@ where
|
||||||
mod tests {
|
mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn deserialize_one_multiple_values() {
|
fn deserialize_one_multiple_values() {
|
||||||
use crate::protocol::helpers::deserialize_one;
|
use crate::{protocol::helpers::deserialize_one, url::Url};
|
||||||
use url::Url;
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
struct Note {
|
struct Note {
|
||||||
#[serde(deserialize_with = "deserialize_one")]
|
#[serde(deserialize_with = "deserialize_one")]
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
//! Struct which is used to federate actor key for HTTP signatures
|
//! Struct which is used to federate actor key for HTTP signatures
|
||||||
|
|
||||||
|
use crate::url::Url;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
/// Public key of actors which is used for HTTP signatures.
|
/// Public key of actors which is used for HTTP signatures.
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
//! Verify that received data is valid
|
//! Verify that received data is valid
|
||||||
|
|
||||||
use crate::error::Error;
|
use crate::{error::Error, url::Url};
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
/// Check that both urls have the same domain. If not, return UrlVerificationError.
|
/// Check that both urls have the same domain. If not, return UrlVerificationError.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use url::Url;
|
/// # use activitypub_federation::url::Url;
|
||||||
/// # use activitypub_federation::protocol::verification::verify_domains_match;
|
/// # use activitypub_federation::protocol::verification::verify_domains_match;
|
||||||
/// let a = Url::parse("https://example.com/abc")?;
|
/// let a = "https://example.com/abc".parse()?;
|
||||||
/// let b = Url::parse("https://sample.net/abc")?;
|
/// let b = "https://sample.net/abc".parse()?;
|
||||||
/// assert!(verify_domains_match(&a, &b).is_err());
|
/// assert!(verify_domains_match(&a, &b).is_err());
|
||||||
/// # Ok::<(), url::ParseError>(())
|
/// # Ok::<(), url::ParseError>(())
|
||||||
/// ```
|
/// ```
|
||||||
|
|
@ -23,10 +22,10 @@ pub fn verify_domains_match(a: &Url, b: &Url) -> Result<(), Error> {
|
||||||
/// Check that both urls are identical. If not, return UrlVerificationError.
|
/// Check that both urls are identical. If not, return UrlVerificationError.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use url::Url;
|
/// # use activitypub_federation::url::Url;
|
||||||
/// # use activitypub_federation::protocol::verification::verify_urls_match;
|
/// # use activitypub_federation::protocol::verification::verify_urls_match;
|
||||||
/// let a = Url::parse("https://example.com/abc")?;
|
/// let a = "https://example.com/abc".parse()?;
|
||||||
/// let b = Url::parse("https://example.com/123")?;
|
/// let b = "https://example.com/123".parse()?;
|
||||||
/// assert!(verify_urls_match(&a, &b).is_err());
|
/// assert!(verify_urls_match(&a, &b).is_err());
|
||||||
/// # Ok::<(), url::ParseError>(())
|
/// # Ok::<(), url::ParseError>(())
|
||||||
/// ```
|
/// ```
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
//! Traits which need to be implemented for federated data types
|
//! Traits which need to be implemented for federated data types
|
||||||
|
|
||||||
use crate::{config::Data, protocol::public_key::PublicKey};
|
use crate::{config::Data, protocol::public_key::PublicKey, url::Url};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{fmt::Debug, ops::Deref};
|
use std::{fmt::Debug, ops::Deref};
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
/// Helper for converting between database structs and federated protocol structs.
|
/// Helper for converting between database structs and federated protocol structs.
|
||||||
///
|
///
|
||||||
|
|
@ -13,7 +12,7 @@ use url::Url;
|
||||||
/// # use activitystreams_kinds::{object::NoteType, public};
|
/// # use activitystreams_kinds::{object::NoteType, public};
|
||||||
/// # use chrono::{Local, DateTime, Utc};
|
/// # use chrono::{Local, DateTime, Utc};
|
||||||
/// # use serde::{Deserialize, Serialize};
|
/// # use serde::{Deserialize, Serialize};
|
||||||
/// # use url::Url;
|
/// # use activitypub_federation::url::Url;
|
||||||
/// # use activitypub_federation::protocol::{public_key::PublicKey, helpers::deserialize_one_or_many};
|
/// # use activitypub_federation::protocol::{public_key::PublicKey, helpers::deserialize_one_or_many};
|
||||||
/// # use activitypub_federation::config::Data;
|
/// # use activitypub_federation::config::Data;
|
||||||
/// # use activitypub_federation::fetch::object_id::ObjectId;
|
/// # use activitypub_federation::fetch::object_id::ObjectId;
|
||||||
|
|
@ -62,7 +61,7 @@ use url::Url;
|
||||||
/// kind: Default::default(),
|
/// kind: Default::default(),
|
||||||
/// id: self.ap_id.clone().into(),
|
/// id: self.ap_id.clone().into(),
|
||||||
/// attributed_to: self.creator,
|
/// attributed_to: self.creator,
|
||||||
/// to: vec![public()],
|
/// to: vec![public().try_into()?],
|
||||||
/// content: self.text,
|
/// content: self.text,
|
||||||
/// })
|
/// })
|
||||||
/// }
|
/// }
|
||||||
|
|
@ -162,7 +161,7 @@ pub trait Object: Sized + Debug {
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use activitystreams_kinds::activity::FollowType;
|
/// # use activitystreams_kinds::activity::FollowType;
|
||||||
/// # use url::Url;
|
/// # use activitypub_federation::url::Url;
|
||||||
/// # use activitypub_federation::fetch::object_id::ObjectId;
|
/// # use activitypub_federation::fetch::object_id::ObjectId;
|
||||||
/// # use activitypub_federation::config::Data;
|
/// # use activitypub_federation::config::Data;
|
||||||
/// # use activitypub_federation::traits::ActivityHandler;
|
/// # use activitypub_federation::traits::ActivityHandler;
|
||||||
|
|
|
||||||
82
src/url.rs
Normal file
82
src/url.rs
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
//! Wrapper for `url::Url` type.
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::{
|
||||||
|
fmt::{Display, Formatter},
|
||||||
|
ops::Deref,
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Wrapper for `url::Url` type. Has `domain` as mandatory field, and prints plain
|
||||||
|
/// string for debugging.
|
||||||
|
#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Debug)]
|
||||||
|
pub struct Url(url::Url);
|
||||||
|
|
||||||
|
impl Deref for Url {
|
||||||
|
type Target = url::Url;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Url {
|
||||||
|
#[allow(clippy::to_string_in_format_args)]
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.0.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Url {
|
||||||
|
/// Returns domain of the url
|
||||||
|
pub fn domain(&self) -> &str {
|
||||||
|
self.0.domain().expect("has domain")
|
||||||
|
}
|
||||||
|
fn new(value: url::Url) -> Result<Self, url::ParseError> {
|
||||||
|
if value.domain().is_none() {
|
||||||
|
return Err(url::ParseError::EmptyHost);
|
||||||
|
}
|
||||||
|
Ok(Url(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<url::Url> for Url {
|
||||||
|
type Error = url::ParseError;
|
||||||
|
fn try_from(value: url::Url) -> Result<Self, Self::Error> {
|
||||||
|
Self::new(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::from_over_into)]
|
||||||
|
impl Into<url::Url> for Url {
|
||||||
|
fn into(self) -> url::Url {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Url {
|
||||||
|
type Err = url::ParseError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let value = url::Url::from_str(s)?;
|
||||||
|
Self::new(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use crate::error::Error;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_url() -> Result<(), Error> {
|
||||||
|
assert!(Url::from_str("http://example.com").is_ok());
|
||||||
|
assert!(Url::try_from(url::Url::from_str("http://example.com")?).is_ok());
|
||||||
|
|
||||||
|
assert!(Url::from_str("http://127.0.0.1").is_err());
|
||||||
|
assert!(Url::try_from(url::Url::from_str("http://127.0.0.1")?).is_err());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue