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 serde::{Deserialize, Serialize};
|
||||
# use activitystreams_kinds::actor::PersonType;
|
||||
# use url::Url;
|
||||
# use activitypub_federation::url::Url;
|
||||
# use activitypub_federation::traits::tests::DbUser;
|
||||
|
||||
#[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.
|
||||
|
||||
```rust
|
||||
# use url::Url;
|
||||
# use activitypub_federation::url::Url;
|
||||
# use chrono::{DateTime, Utc};
|
||||
|
||||
pub struct DbUser {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Activitypub propagates actions across servers using `Activities`. For this each
|
|||
|
||||
```
|
||||
# use serde::{Deserialize, Serialize};
|
||||
# use url::Url;
|
||||
# use activitypub_federation::url::Url;
|
||||
# use anyhow::Error;
|
||||
# use async_trait::async_trait;
|
||||
# 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::tests::{DbConnection, DbUser, Follow};
|
||||
# use serde::{Deserialize, Serialize};
|
||||
# use url::Url;
|
||||
# use activitypub_federation::url::Url;
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
#[serde(untagged)]
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ let activity = Follow {
|
|||
actor: ObjectId::parse("https://lemmy.ml/u/nutomic")?,
|
||||
object: recipient.federation_id.clone().into(),
|
||||
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()];
|
||||
|
||||
|
|
@ -62,7 +62,7 @@ let activity = Follow {
|
|||
actor: ObjectId::parse("https://lemmy.ml/u/nutomic")?,
|
||||
object: recipient.federation_id.clone().into(),
|
||||
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()];
|
||||
|
||||
|
|
|
|||
|
|
@ -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 activitypub_federation::traits::tests::DbConnection;
|
||||
# use activitypub_federation::config::Data;
|
||||
# use url::Url;
|
||||
# use activitypub_federation::url::Url;
|
||||
# use activitypub_federation::traits::tests::{Person, Note};
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ use activitypub_federation::{
|
|||
kinds::activity::CreateType,
|
||||
protocol::{context::WithContext, helpers::deserialize_one_or_many},
|
||||
traits::{ActivityHandler, Object},
|
||||
url::Url,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@ use activitypub_federation::{
|
|||
kinds::actor::PersonType,
|
||||
protocol::{public_key::PublicKey, verification::verify_domains_match},
|
||||
traits::{ActivityHandler, Actor, Object},
|
||||
url::Url,
|
||||
};
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Debug;
|
||||
use url::Url;
|
||||
use std::{fmt::Debug, str::FromStr};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DbUser {
|
||||
|
|
@ -36,8 +36,8 @@ pub enum PersonAcceptedActivities {
|
|||
|
||||
impl DbUser {
|
||||
pub fn new(hostname: &str, name: &str) -> Result<DbUser, Error> {
|
||||
let ap_id = Url::parse(&format!("https://{}/{}", hostname, &name))?.into();
|
||||
let inbox = Url::parse(&format!("https://{}/{}/inbox", hostname, &name))?;
|
||||
let ap_id = Url::from_str(&format!("https://{}/{}", hostname, &name))?.into();
|
||||
let inbox = Url::from_str(&format!("https://{}/{}/inbox", hostname, &name))?;
|
||||
let keypair = generate_actor_keypair()?;
|
||||
Ok(DbUser {
|
||||
name: name.to_string(),
|
||||
|
|
|
|||
|
|
@ -11,10 +11,10 @@ use activitypub_federation::{
|
|||
kinds::{object::NoteType, public},
|
||||
protocol::{helpers::deserialize_one_or_many, verification::verify_domains_match},
|
||||
traits::{Actor, Object},
|
||||
url::Url,
|
||||
};
|
||||
use activitystreams_kinds::link::MentionType;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DbPost {
|
||||
|
|
@ -63,7 +63,7 @@ impl Object for DbPost {
|
|||
id: self.ap_id,
|
||||
content: self.text,
|
||||
attributed_to: self.creator,
|
||||
to: vec![public()],
|
||||
to: vec![public().try_into()?],
|
||||
tag: vec![],
|
||||
in_reply_to: None,
|
||||
})
|
||||
|
|
@ -98,7 +98,7 @@ impl Object for DbPost {
|
|||
kind: Default::default(),
|
||||
id: generate_object_id(data.domain())?.into(),
|
||||
attributed_to: data.local_user().ap_id,
|
||||
to: vec![public()],
|
||||
to: vec![public().try_into()?],
|
||||
content: format!("Hello {}", creator.name),
|
||||
in_reply_to: Some(json.id.clone()),
|
||||
tag: vec![mention],
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use activitypub_federation::url::Url;
|
||||
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
|
||||
/// 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)
|
||||
.map(char::from)
|
||||
.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,
|
||||
kinds::activity::AcceptType,
|
||||
traits::ActivityHandler,
|
||||
url::Url,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ use activitypub_federation::{
|
|||
kinds::activity::CreateType,
|
||||
protocol::helpers::deserialize_one_or_many,
|
||||
traits::{ActivityHandler, Object},
|
||||
url::Url,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ use activitypub_federation::{
|
|||
fetch::object_id::ObjectId,
|
||||
kinds::activity::FollowType,
|
||||
traits::{ActivityHandler, Actor},
|
||||
url::Url,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
|
|
|||
|
|
@ -2,21 +2,23 @@ use crate::{
|
|||
objects::{person::DbUser, post::DbPost},
|
||||
Error,
|
||||
};
|
||||
use activitypub_federation::config::{FederationConfig, UrlVerifier};
|
||||
use activitypub_federation::{
|
||||
config::{FederationConfig, UrlVerifier},
|
||||
url::Url,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use async_trait::async_trait;
|
||||
use std::{
|
||||
str::FromStr,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
pub async fn new_instance(
|
||||
hostname: &str,
|
||||
name: String,
|
||||
) -> Result<FederationConfig<DatabaseHandle>, Error> {
|
||||
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 database = Arc::new(Database {
|
||||
|
|
@ -51,7 +53,7 @@ struct MyUrlVerifier();
|
|||
#[async_trait]
|
||||
impl UrlVerifier for MyUrlVerifier {
|
||||
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(
|
||||
"malicious domain".into(),
|
||||
))
|
||||
|
|
|
|||
|
|
@ -14,11 +14,11 @@ use activitypub_federation::{
|
|||
kinds::actor::PersonType,
|
||||
protocol::{context::WithContext, public_key::PublicKey, verification::verify_domains_match},
|
||||
traits::{ActivityHandler, Actor, Object},
|
||||
url::Url,
|
||||
};
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Debug;
|
||||
use url::Url;
|
||||
use std::{fmt::Debug, str::FromStr};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DbUser {
|
||||
|
|
@ -46,8 +46,8 @@ pub enum PersonAcceptedActivities {
|
|||
|
||||
impl DbUser {
|
||||
pub fn new(hostname: &str, name: String) -> Result<DbUser, Error> {
|
||||
let ap_id = Url::parse(&format!("http://{}/{}", hostname, &name))?.into();
|
||||
let inbox = Url::parse(&format!("http://{}/{}/inbox", hostname, &name))?;
|
||||
let ap_id = Url::from_str(&format!("http://{}/{}", hostname, &name))?.into();
|
||||
let inbox = Url::from_str(&format!("http://{}/{}/inbox", hostname, &name))?;
|
||||
let keypair = generate_actor_keypair()?;
|
||||
Ok(DbUser {
|
||||
name,
|
||||
|
|
@ -79,7 +79,7 @@ impl DbUser {
|
|||
}
|
||||
|
||||
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> {
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ use activitypub_federation::{
|
|||
kinds::{object::NoteType, public},
|
||||
protocol::{helpers::deserialize_one_or_many, verification::verify_domains_match},
|
||||
traits::Object,
|
||||
url::Url,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DbPost {
|
||||
|
|
@ -19,7 +19,7 @@ pub struct DbPost {
|
|||
|
||||
impl DbPost {
|
||||
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 {
|
||||
text,
|
||||
ap_id,
|
||||
|
|
@ -65,7 +65,7 @@ impl Object for DbPost {
|
|||
kind: Default::default(),
|
||||
id: self.ap_id,
|
||||
attributed_to: self.creator,
|
||||
to: vec![public(), creator.followers_url()?],
|
||||
to: vec![public().try_into()?, creator.followers_url()?],
|
||||
content: self.text,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use activitypub_federation::url::Url;
|
||||
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
|
||||
/// 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)
|
||||
.map(char::from)
|
||||
.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 crate::url::Url;
|
||||
use reqwest_middleware::ClientWithMiddleware;
|
||||
use serde::Serialize;
|
||||
use std::{
|
||||
|
|
@ -26,7 +27,6 @@ use tokio::{
|
|||
task::{JoinHandle, JoinSet},
|
||||
};
|
||||
use tracing::{info, warn};
|
||||
use url::Url;
|
||||
|
||||
/// 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]].
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ use crate::{
|
|||
http_signatures::sign_request,
|
||||
reqwest_shim::ResponseExt,
|
||||
traits::{ActivityHandler, Actor},
|
||||
url::Url,
|
||||
FEDERATION_CONTENT_TYPE,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
|
|
@ -27,7 +28,6 @@ use std::{
|
|||
time::{Duration, SystemTime},
|
||||
};
|
||||
use tracing::debug;
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
/// 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 {
|
||||
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() {
|
||||
host = format!("{}:{}", host, port);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,6 +47,8 @@ where
|
|||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
mod test {
|
||||
use std::str::FromStr;
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
activity_sending::generate_request_headers,
|
||||
|
|
@ -54,12 +56,12 @@ mod test {
|
|||
fetch::object_id::ObjectId,
|
||||
http_signatures::sign_request,
|
||||
traits::tests::{DbConnection, DbUser, Follow, DB_USER_KEYPAIR},
|
||||
url::Url,
|
||||
};
|
||||
use actix_web::test::TestRequest;
|
||||
use reqwest::Client;
|
||||
use reqwest_middleware::ClientWithMiddleware;
|
||||
use serde_json::json;
|
||||
use url::Url;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_receive_activity() {
|
||||
|
|
@ -108,7 +110,7 @@ mod test {
|
|||
async fn test_receive_unparseable_activity() {
|
||||
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 activity = json!({
|
||||
"actor": actor.as_str(),
|
||||
|
|
@ -140,7 +142,7 @@ mod test {
|
|||
|
||||
async fn construct_request(body: &Bytes, actor: &Url) -> TestRequest {
|
||||
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())
|
||||
.post(inbox)
|
||||
.headers(headers);
|
||||
|
|
@ -165,7 +167,7 @@ mod test {
|
|||
actor: ObjectId::parse("http://localhost:123").unwrap(),
|
||||
object: ObjectId::parse("http://localhost:124").unwrap(),
|
||||
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 incoming_request = construct_request(&body, activity.actor.inner()).await;
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ use crate::{
|
|||
error::Error,
|
||||
protocol::verification::verify_domains_match,
|
||||
traits::{ActivityHandler, Actor},
|
||||
url::Url,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use derive_builder::Builder;
|
||||
|
|
@ -35,7 +36,6 @@ use std::{
|
|||
},
|
||||
time::Duration,
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
/// Configuration for this library, with various federation related settings
|
||||
#[derive(Builder, Clone)]
|
||||
|
|
@ -156,11 +156,7 @@ impl<T: Clone> FederationConfig<T> {
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
if url.domain().is_none() {
|
||||
return Err(Error::UrlVerificationError("Url must have a domain"));
|
||||
}
|
||||
|
||||
if url.domain() == Some("localhost") && !self.debug {
|
||||
if url.domain() == "localhost" && !self.debug {
|
||||
return Err(Error::UrlVerificationError(
|
||||
"Localhost is only allowed in debug mode",
|
||||
));
|
||||
|
|
@ -247,7 +243,7 @@ impl<T: Clone> Deref for FederationConfig<T> {
|
|||
///
|
||||
/// ```
|
||||
/// # use async_trait::async_trait;
|
||||
/// # use url::Url;
|
||||
/// # use activitypub_federation::url::Url;
|
||||
/// # use activitypub_federation::config::UrlVerifier;
|
||||
/// # use activitypub_federation::error::Error;
|
||||
/// # #[derive(Clone)]
|
||||
|
|
@ -264,7 +260,7 @@ impl<T: Clone> Deref for FederationConfig<T> {
|
|||
/// impl UrlVerifier for Verifier {
|
||||
/// async fn verify(&self, url: &Url) -> Result<(), Error> {
|
||||
/// 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) {
|
||||
/// Err(Error::Other("Domain is blocked".into()))
|
||||
/// } else {
|
||||
|
|
@ -351,6 +347,8 @@ impl<T: Clone> FederationMiddleware<T> {
|
|||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
mod test {
|
||||
use std::str::FromStr;
|
||||
|
||||
use super::*;
|
||||
|
||||
async fn config() -> FederationConfig<i32> {
|
||||
|
|
@ -365,10 +363,8 @@ mod test {
|
|||
#[tokio::test]
|
||||
async fn test_url_is_local() -> Result<(), Error> {
|
||||
let config = config().await;
|
||||
assert!(config.is_local_url(&Url::parse("http://example.com")?));
|
||||
assert!(!config.is_local_url(&Url::parse("http://other.com")?));
|
||||
// ensure that missing domain doesnt cause crash
|
||||
assert!(!config.is_local_url(&Url::parse("http://127.0.0.1")?));
|
||||
assert!(config.is_local_url(&Url::from_str("http://example.com")?));
|
||||
assert!(!config.is_local_url(&Url::from_str("http://other.com")?));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
//! 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 rsa::{
|
||||
errors::Error as RsaError,
|
||||
|
|
@ -8,7 +8,6 @@ use rsa::{
|
|||
};
|
||||
use std::string::FromUtf8Error;
|
||||
use tokio::task::JoinError;
|
||||
use url::Url;
|
||||
|
||||
/// Error messages returned by this library
|
||||
#[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 std::{
|
||||
fmt::{Debug, Display, Formatter},
|
||||
marker::PhantomData,
|
||||
str::FromStr,
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
/// Typed wrapper for Activitypub Collection ID which helps with dereferencing.
|
||||
#[derive(Serialize, Deserialize)]
|
||||
|
|
@ -21,7 +21,7 @@ where
|
|||
{
|
||||
/// Construct a new CollectionId instance
|
||||
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
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ use crate::{
|
|||
extract_id,
|
||||
http_signatures::sign_request,
|
||||
reqwest_shim::ResponseExt,
|
||||
url::Url,
|
||||
FEDERATION_CONTENT_TYPE,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
|
|
@ -15,7 +16,6 @@ use http::{HeaderValue, StatusCode};
|
|||
use serde::de::DeserializeOwned;
|
||||
use std::sync::atomic::Ordering;
|
||||
use tracing::info;
|
||||
use url::Url;
|
||||
|
||||
/// Typed wrapper for collection IDs
|
||||
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) {
|
||||
Ok(object) => Ok(FetchObjectResponse {
|
||||
object,
|
||||
url,
|
||||
url: url.try_into()?,
|
||||
content_type,
|
||||
object_id,
|
||||
}),
|
||||
Err(e) => Err(ParseFetchedObject(
|
||||
e,
|
||||
url,
|
||||
url.try_into()?,
|
||||
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 serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
|
|
@ -6,7 +6,6 @@ use std::{
|
|||
marker::PhantomData,
|
||||
str::FromStr,
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
impl<T> FromStr for ObjectId<T>
|
||||
where
|
||||
|
|
@ -66,7 +65,7 @@ where
|
|||
{
|
||||
/// Construct a new objectid instance
|
||||
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
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use crate::{
|
|||
error::Error,
|
||||
fetch::{fetch_object_http_with_accept, object_id::ObjectId},
|
||||
traits::{Actor, Object},
|
||||
url::Url,
|
||||
FEDERATION_CONTENT_TYPE,
|
||||
};
|
||||
use http::HeaderValue;
|
||||
|
|
@ -10,9 +11,8 @@ use itertools::Itertools;
|
|||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::HashMap, fmt::Display};
|
||||
use std::{collections::HashMap, fmt::Display, str::FromStr};
|
||||
use tracing::debug;
|
||||
use url::Url;
|
||||
|
||||
/// Errors relative to webfinger handling
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
|
|
@ -60,7 +60,7 @@ where
|
|||
debug!("Fetching webfinger url: {}", &fetch_url);
|
||||
|
||||
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,
|
||||
&WEBFINGER_CONTENT_TYPE,
|
||||
)
|
||||
|
|
@ -143,10 +143,10 @@ where
|
|||
/// of discovery.
|
||||
///
|
||||
/// ```
|
||||
/// # use url::Url;
|
||||
/// # use activitypub_federation::url::Url;
|
||||
/// # use activitypub_federation::fetch::webfinger::build_webfinger_response;
|
||||
/// 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);
|
||||
/// # Ok::<(), anyhow::Error>(())
|
||||
/// ```
|
||||
|
|
@ -162,11 +162,11 @@ pub fn build_webfinger_response(subject: String, url: Url) -> Webfinger {
|
|||
/// will be empty.
|
||||
///
|
||||
/// ```
|
||||
/// # use url::Url;
|
||||
/// # use activitypub_federation::url::Url;
|
||||
/// # use activitypub_federation::fetch::webfinger::build_webfinger_response_with_type;
|
||||
/// let subject = "acct:nutomic@lemmy.ml".to_string();
|
||||
/// let user = Url::parse("https://lemmy.ml/u/nutomic")?;
|
||||
/// let group = Url::parse("https://lemmy.ml/c/asklemmy")?;
|
||||
/// let user = "https://lemmy.ml/u/nutomic".parse()?;
|
||||
/// let group = "https://lemmy.ml/c/asklemmy".parse()?;
|
||||
/// build_webfinger_response_with_type(subject, vec![
|
||||
/// (user, Some("Person")),
|
||||
/// (group, Some("Group"))]);
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ use crate::{
|
|||
fetch::object_id::ObjectId,
|
||||
protocol::public_key::main_key_id,
|
||||
traits::{Actor, Object},
|
||||
url::Url,
|
||||
};
|
||||
use base64::{engine::general_purpose::STANDARD as Base64, Engine};
|
||||
use bytes::Bytes;
|
||||
|
|
@ -30,9 +31,8 @@ use rsa::{
|
|||
};
|
||||
use serde::Deserialize;
|
||||
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 url::Url;
|
||||
|
||||
/// A private/public key pair used for HTTP signatures
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -166,7 +166,7 @@ where
|
|||
None => return Err(Error::ActivitySignatureInvalid.into()),
|
||||
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 = actor_id.dereference(data).await?;
|
||||
|
|
@ -287,9 +287,10 @@ pub mod test {
|
|||
use rsa::{pkcs1::DecodeRsaPrivateKey, pkcs8::DecodePrivateKey};
|
||||
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> =
|
||||
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]
|
||||
async fn test_sign() {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ pub mod http_signatures;
|
|||
pub mod protocol;
|
||||
pub(crate) mod reqwest_shim;
|
||||
pub mod traits;
|
||||
pub mod url;
|
||||
|
||||
use crate::{
|
||||
config::Data,
|
||||
|
|
@ -32,8 +33,8 @@ use crate::{
|
|||
};
|
||||
pub use activitystreams_kinds as kinds;
|
||||
|
||||
use crate::url::Url;
|
||||
use serde::{de::DeserializeOwned, Deserialize};
|
||||
use url::Url;
|
||||
|
||||
/// Mime type for Activitypub data, used for `Accept` and `Content-Type` HTTP headers
|
||||
pub const FEDERATION_CONTENT_TYPE: &str = "application/activity+json";
|
||||
|
|
|
|||
|
|
@ -19,10 +19,9 @@
|
|||
//! 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_json::Value;
|
||||
use url::Url;
|
||||
|
||||
/// Default context used in Activitypub
|
||||
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 url::Url;
|
||||
/// # use activitypub_federation::url::Url;
|
||||
/// #[derive(serde::Deserialize)]
|
||||
/// struct Note {
|
||||
/// #[serde(deserialize_with = "deserialize_one_or_many")]
|
||||
|
|
@ -52,7 +52,7 @@ where
|
|||
///
|
||||
/// ```
|
||||
/// # use activitypub_federation::protocol::helpers::deserialize_one;
|
||||
/// # use url::Url;
|
||||
/// # use activitypub_federation::url::Url;
|
||||
/// #[derive(serde::Deserialize)]
|
||||
/// struct Note {
|
||||
/// #[serde(deserialize_with = "deserialize_one")]
|
||||
|
|
@ -88,7 +88,7 @@ where
|
|||
///
|
||||
/// ```
|
||||
/// # use activitypub_federation::protocol::helpers::deserialize_skip_error;
|
||||
/// # use url::Url;
|
||||
/// # use activitypub_federation::url::Url;
|
||||
/// #[derive(serde::Deserialize)]
|
||||
/// struct Note {
|
||||
/// content: String,
|
||||
|
|
@ -120,8 +120,7 @@ where
|
|||
mod tests {
|
||||
#[test]
|
||||
fn deserialize_one_multiple_values() {
|
||||
use crate::protocol::helpers::deserialize_one;
|
||||
use url::Url;
|
||||
use crate::{protocol::helpers::deserialize_one, url::Url};
|
||||
#[derive(serde::Deserialize)]
|
||||
struct Note {
|
||||
#[serde(deserialize_with = "deserialize_one")]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
//! Struct which is used to federate actor key for HTTP signatures
|
||||
|
||||
use crate::url::Url;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
/// Public key of actors which is used for HTTP signatures.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
//! Verify that received data is valid
|
||||
|
||||
use crate::error::Error;
|
||||
use url::Url;
|
||||
use crate::{error::Error, url::Url};
|
||||
|
||||
/// 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;
|
||||
/// let a = Url::parse("https://example.com/abc")?;
|
||||
/// let b = Url::parse("https://sample.net/abc")?;
|
||||
/// let a = "https://example.com/abc".parse()?;
|
||||
/// let b = "https://sample.net/abc".parse()?;
|
||||
/// assert!(verify_domains_match(&a, &b).is_err());
|
||||
/// # 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.
|
||||
///
|
||||
/// ```
|
||||
/// # use url::Url;
|
||||
/// # use activitypub_federation::url::Url;
|
||||
/// # use activitypub_federation::protocol::verification::verify_urls_match;
|
||||
/// let a = Url::parse("https://example.com/abc")?;
|
||||
/// let b = Url::parse("https://example.com/123")?;
|
||||
/// let a = "https://example.com/abc".parse()?;
|
||||
/// let b = "https://example.com/123".parse()?;
|
||||
/// assert!(verify_urls_match(&a, &b).is_err());
|
||||
/// # Ok::<(), url::ParseError>(())
|
||||
/// ```
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
//! 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 chrono::{DateTime, Utc};
|
||||
use serde::Deserialize;
|
||||
use std::{fmt::Debug, ops::Deref};
|
||||
use url::Url;
|
||||
|
||||
/// Helper for converting between database structs and federated protocol structs.
|
||||
///
|
||||
|
|
@ -13,7 +12,7 @@ use url::Url;
|
|||
/// # use activitystreams_kinds::{object::NoteType, public};
|
||||
/// # use chrono::{Local, DateTime, Utc};
|
||||
/// # 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::config::Data;
|
||||
/// # use activitypub_federation::fetch::object_id::ObjectId;
|
||||
|
|
@ -62,7 +61,7 @@ use url::Url;
|
|||
/// kind: Default::default(),
|
||||
/// id: self.ap_id.clone().into(),
|
||||
/// attributed_to: self.creator,
|
||||
/// to: vec![public()],
|
||||
/// to: vec![public().try_into()?],
|
||||
/// content: self.text,
|
||||
/// })
|
||||
/// }
|
||||
|
|
@ -162,7 +161,7 @@ pub trait Object: Sized + Debug {
|
|||
///
|
||||
/// ```
|
||||
/// # use activitystreams_kinds::activity::FollowType;
|
||||
/// # use url::Url;
|
||||
/// # use activitypub_federation::url::Url;
|
||||
/// # use activitypub_federation::fetch::object_id::ObjectId;
|
||||
/// # use activitypub_federation::config::Data;
|
||||
/// # 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