mirror of
https://github.com/LemmyNet/activitypub-federation-rust.git
synced 2025-03-13 18:52:39 +00:00
Add wrapper type for url (fixes #58)
This commit is contained in:
parent
83a156394e
commit
62b7543299
17 changed files with 102 additions and 57 deletions
|
@ -26,7 +26,7 @@ use tokio::{
|
|||
task::{JoinHandle, JoinSet},
|
||||
};
|
||||
use tracing::{info, warn};
|
||||
use url::Url;
|
||||
use crate::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]].
|
||||
|
|
|
@ -27,7 +27,7 @@ use std::{
|
|||
time::{Duration, SystemTime},
|
||||
};
|
||||
use tracing::debug;
|
||||
use url::Url;
|
||||
use crate::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);
|
||||
}
|
||||
|
|
|
@ -59,7 +59,7 @@ mod test {
|
|||
use reqwest::Client;
|
||||
use reqwest_middleware::ClientWithMiddleware;
|
||||
use serde_json::json;
|
||||
use url::Url;
|
||||
use crate::url::Url;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_receive_activity() {
|
||||
|
@ -108,7 +108,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 +140,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);
|
||||
|
|
|
@ -35,7 +35,7 @@ use std::{
|
|||
},
|
||||
time::Duration,
|
||||
};
|
||||
use url::Url;
|
||||
use crate::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 crate::url::Url;
|
||||
/// # use activitypub_federation::config::UrlVerifier;
|
||||
/// # use activitypub_federation::error::Error;
|
||||
/// # #[derive(Clone)]
|
||||
|
@ -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,10 @@ 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")?));
|
||||
assert!(config.is_local_url(&Url::from_str("http://example.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")?));
|
||||
assert!(!config.is_local_url(&Url::from_str("http://127.0.0.1")?));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ use rsa::{
|
|||
};
|
||||
use std::string::FromUtf8Error;
|
||||
use tokio::task::JoinError;
|
||||
use url::Url;
|
||||
use crate::url::Url;
|
||||
|
||||
/// Error messages returned by this library
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
|
|
|
@ -2,9 +2,9 @@ use crate::{config::Data, error::Error, fetch::fetch_object_http, traits::Collec
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
fmt::{Debug, Display, Formatter},
|
||||
marker::PhantomData,
|
||||
marker::PhantomData, str::FromStr,
|
||||
};
|
||||
use url::Url;
|
||||
use crate::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
|
||||
|
|
|
@ -15,7 +15,7 @@ use http::{HeaderValue, StatusCode};
|
|||
use serde::de::DeserializeOwned;
|
||||
use std::sync::atomic::Ordering;
|
||||
use tracing::info;
|
||||
use url::Url;
|
||||
use crate::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.into(),
|
||||
content_type,
|
||||
object_id,
|
||||
}),
|
||||
Err(e) => Err(ParseFetchedObject(
|
||||
e,
|
||||
url,
|
||||
url.into(),
|
||||
String::from_utf8(Vec::from(text))?,
|
||||
)),
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ use std::{
|
|||
marker::PhantomData,
|
||||
str::FromStr,
|
||||
};
|
||||
use url::Url;
|
||||
use crate::url::Url;
|
||||
|
||||
impl<T> FromStr for ObjectId<T>
|
||||
where
|
||||
|
@ -66,7 +66,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
|
||||
|
|
|
@ -10,9 +10,9 @@ 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;
|
||||
use crate::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 crate::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 = Url::from_str("https://lemmy.ml/u/nutomic")?;
|
||||
/// 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 crate::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 = Url::from_str("https://lemmy.ml/u/nutomic")?;
|
||||
/// let group = Url::from_str("https://lemmy.ml/c/asklemmy")?;
|
||||
/// build_webfinger_response_with_type(subject, vec![
|
||||
/// (user, Some("Person")),
|
||||
/// (group, Some("Group"))]);
|
||||
|
|
|
@ -30,9 +30,9 @@ 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;
|
||||
use crate::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,9 @@ 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,
|
||||
|
@ -33,7 +34,7 @@ use crate::{
|
|||
pub use activitystreams_kinds as kinds;
|
||||
|
||||
use serde::{de::DeserializeOwned, Deserialize};
|
||||
use url::Url;
|
||||
use crate::url::Url;
|
||||
|
||||
/// Mime type for Activitypub data, used for `Accept` and `Content-Type` HTTP headers
|
||||
pub const FEDERATION_CONTENT_TYPE: &str = "application/activity+json";
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
use crate::{config::Data, traits::ActivityHandler};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use url::Url;
|
||||
use crate::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 crate::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 crate::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 crate::url::Url;
|
||||
/// #[derive(serde::Deserialize)]
|
||||
/// struct Note {
|
||||
/// content: String,
|
||||
|
@ -121,7 +121,7 @@ mod tests {
|
|||
#[test]
|
||||
fn deserialize_one_multiple_values() {
|
||||
use crate::protocol::helpers::deserialize_one;
|
||||
use url::Url;
|
||||
use crate::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 serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
use crate::url::Url;
|
||||
|
||||
/// Public key of actors which is used for HTTP signatures.
|
||||
///
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
//! Verify that received data is valid
|
||||
|
||||
use crate::error::Error;
|
||||
use url::Url;
|
||||
use crate::url::Url;
|
||||
|
||||
/// Check that both urls have the same domain. If not, return UrlVerificationError.
|
||||
///
|
||||
/// ```
|
||||
/// # use url::Url;
|
||||
/// # use crate::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 = Url::from_str("https://example.com/abc")?;
|
||||
/// let b = Url::from_str("https://sample.net/abc")?;
|
||||
/// assert!(verify_domains_match(&a, &b).is_err());
|
||||
/// # Ok::<(), url::ParseError>(())
|
||||
/// # Ok::<(), Url::from_strError>(())
|
||||
/// ```
|
||||
pub fn verify_domains_match(a: &Url, b: &Url) -> Result<(), Error> {
|
||||
if a.domain() != b.domain() {
|
||||
|
@ -23,12 +23,12 @@ 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 crate::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 = Url::from_str("https://example.com/abc")?;
|
||||
/// let b = Url::from_str("https://example.com/123")?;
|
||||
/// assert!(verify_urls_match(&a, &b).is_err());
|
||||
/// # Ok::<(), url::ParseError>(())
|
||||
/// # Ok::<(), Url::from_strError>(())
|
||||
/// ```
|
||||
pub fn verify_urls_match(a: &Url, b: &Url) -> Result<(), Error> {
|
||||
if a != b {
|
||||
|
|
|
@ -5,7 +5,7 @@ use async_trait::async_trait;
|
|||
use chrono::{DateTime, Utc};
|
||||
use serde::Deserialize;
|
||||
use std::{fmt::Debug, ops::Deref};
|
||||
use url::Url;
|
||||
use crate::url::Url;
|
||||
|
||||
/// Helper for converting between database structs and federated protocol structs.
|
||||
///
|
||||
|
@ -13,7 +13,7 @@ use url::Url;
|
|||
/// # use activitystreams_kinds::{object::NoteType, public};
|
||||
/// # use chrono::{Local, DateTime, Utc};
|
||||
/// # use serde::{Deserialize, Serialize};
|
||||
/// # use url::Url;
|
||||
/// # use crate::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;
|
||||
|
@ -162,7 +162,7 @@ pub trait Object: Sized + Debug {
|
|||
///
|
||||
/// ```
|
||||
/// # use activitystreams_kinds::activity::FollowType;
|
||||
/// # use url::Url;
|
||||
/// # use crate::url::Url;
|
||||
/// # use activitypub_federation::fetch::object_id::ObjectId;
|
||||
/// # use activitypub_federation::config::Data;
|
||||
/// # use activitypub_federation::traits::ActivityHandler;
|
||||
|
|
46
src/url.rs
Normal file
46
src/url.rs
Normal file
|
@ -0,0 +1,46 @@
|
|||
//! Wrapper for `url::Url` type.
|
||||
|
||||
use std::{fmt::{Display, Formatter}, ops::Deref, str::FromStr};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// 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 {
|
||||
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 {
|
||||
// TODO: must have error handling, or ensure at creation that it has domain
|
||||
self.0.domain().expect("has domain")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<url::Url> for Url {
|
||||
fn from(value: url::Url) -> Self {
|
||||
Url(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Url {
|
||||
type Err = url::ParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(url::Url::from_str(s).map(Url).unwrap())
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue