mirror of
https://github.com/LemmyNet/activitypub-federation-rust.git
synced 2024-06-03 05:49:28 +00:00
Compare commits
6 commits
Author | SHA1 | Date | |
---|---|---|---|
16844f048a | |||
cf1f84993b | |||
24afad7abc | |||
c48de9e944 | |||
be69efdee3 | |||
ddc455510b |
42
Cargo.toml
42
Cargo.toml
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "activitypub_federation"
|
name = "activitypub_federation"
|
||||||
version = "0.5.4"
|
version = "0.5.6"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "High-level Activitypub framework"
|
description = "High-level Activitypub framework"
|
||||||
keywords = ["activitypub", "activitystreams", "federation", "fediverse"]
|
keywords = ["activitypub", "activitystreams", "federation", "fediverse"]
|
||||||
|
@ -32,21 +32,21 @@ redundant_closure_for_method_calls = "deny"
|
||||||
unwrap_used = "deny"
|
unwrap_used = "deny"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = { version = "0.4.34", features = ["clock"], default-features = false }
|
chrono = { version = "0.4.38", features = ["clock"], default-features = false }
|
||||||
serde = { version = "1.0.197", features = ["derive"] }
|
serde = { version = "1.0.200", features = ["derive"] }
|
||||||
async-trait = "0.1.77"
|
async-trait = "0.1.80"
|
||||||
url = { version = "2.5.0", features = ["serde"] }
|
url = { version = "2.5.0", features = ["serde"] }
|
||||||
serde_json = { version = "1.0.114", features = ["preserve_order"] }
|
serde_json = { version = "1.0.116", features = ["preserve_order"] }
|
||||||
reqwest = { version = "0.11.24", features = ["json", "stream"] }
|
reqwest = { version = "0.11.27", features = ["json", "stream"] }
|
||||||
reqwest-middleware = "0.2.4"
|
reqwest-middleware = "0.2.5"
|
||||||
tracing = "0.1.40"
|
tracing = "0.1.40"
|
||||||
base64 = "0.21.7"
|
base64 = "0.22.1"
|
||||||
openssl = "0.10.64"
|
openssl = "0.10.64"
|
||||||
once_cell = "1.19.0"
|
once_cell = "1.19.0"
|
||||||
http = "0.2.11"
|
http = "0.2.12"
|
||||||
sha2 = "0.10.8"
|
sha2 = "0.10.8"
|
||||||
thiserror = "1.0.57"
|
thiserror = "1.0.59"
|
||||||
derive_builder = "0.12.0"
|
derive_builder = "0.20.0"
|
||||||
itertools = "0.12.1"
|
itertools = "0.12.1"
|
||||||
dyn-clone = "1.0.17"
|
dyn-clone = "1.0.17"
|
||||||
enum_delegate = "0.2.0"
|
enum_delegate = "0.2.0"
|
||||||
|
@ -57,20 +57,20 @@ http-signature-normalization-reqwest = { version = "0.10.0", default-features =
|
||||||
"default-spawner",
|
"default-spawner",
|
||||||
] }
|
] }
|
||||||
http-signature-normalization = "0.7.0"
|
http-signature-normalization = "0.7.0"
|
||||||
bytes = "1.5.0"
|
bytes = "1.6.0"
|
||||||
futures-core = { version = "0.3.30", default-features = false }
|
futures-core = { version = "0.3.30", default-features = false }
|
||||||
pin-project-lite = "0.2.13"
|
pin-project-lite = "0.2.14"
|
||||||
activitystreams-kinds = "0.3.0"
|
activitystreams-kinds = "0.3.0"
|
||||||
regex = { version = "1.10.3", default-features = false, features = ["std", "unicode-case"] }
|
regex = { version = "1.10.4", default-features = false, features = ["std", "unicode-case"] }
|
||||||
tokio = { version = "1.36.0", features = [
|
tokio = { version = "1.37.0", features = [
|
||||||
"sync",
|
"sync",
|
||||||
"rt",
|
"rt",
|
||||||
"rt-multi-thread",
|
"rt-multi-thread",
|
||||||
"time",
|
"time",
|
||||||
] }
|
] }
|
||||||
diesel = { version = "2.1.4", features = ["postgres"], default-features = false, optional = true }
|
diesel = { version = "2.1.6", features = ["postgres"], default-features = false, optional = true }
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
moka = { version = "0.12.5", features = ["future"] }
|
moka = { version = "0.12.7", features = ["future"] }
|
||||||
|
|
||||||
# Actix-web
|
# Actix-web
|
||||||
actix-web = { version = "4.5.1", default-features = false, optional = true }
|
actix-web = { version = "4.5.1", default-features = false, optional = true }
|
||||||
|
@ -82,12 +82,12 @@ axum = { version = "0.6.20", features = [
|
||||||
], default-features = false, optional = true }
|
], default-features = false, optional = true }
|
||||||
tower = { version = "0.4.13", optional = true }
|
tower = { version = "0.4.13", optional = true }
|
||||||
hyper = { version = "0.14", optional = true }
|
hyper = { version = "0.14", optional = true }
|
||||||
http-body-util = {version = "0.1.0", optional = true }
|
http-body-util = {version = "0.1.1", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
anyhow = "1.0.80"
|
anyhow = "1.0.82"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
env_logger = "0.10.2"
|
env_logger = "0.11.3"
|
||||||
tower-http = { version = "0.5.2", features = ["map-request-body", "util"] }
|
tower-http = { version = "0.5.2", features = ["map-request-body", "util"] }
|
||||||
axum = { version = "0.6.20", features = [
|
axum = { version = "0.6.20", features = [
|
||||||
"http1",
|
"http1",
|
||||||
|
@ -95,7 +95,7 @@ axum = { version = "0.6.20", features = [
|
||||||
"query",
|
"query",
|
||||||
], default-features = false }
|
], default-features = false }
|
||||||
axum-macros = "0.3.8"
|
axum-macros = "0.3.8"
|
||||||
tokio = { version = "1.36.0", features = ["full"] }
|
tokio = { version = "1.37.0", features = ["full"] }
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
strip = "symbols"
|
strip = "symbols"
|
||||||
|
|
|
@ -174,11 +174,17 @@ impl<T: Clone> FederationConfig<T> {
|
||||||
/// Returns true if the url refers to this instance. Handles hostnames like `localhost:8540` for
|
/// Returns true if the url refers to this instance. Handles hostnames like `localhost:8540` for
|
||||||
/// local debugging.
|
/// local debugging.
|
||||||
pub(crate) fn is_local_url(&self, url: &Url) -> bool {
|
pub(crate) fn is_local_url(&self, url: &Url) -> bool {
|
||||||
let mut domain = url.host_str().expect("id has domain").to_string();
|
match url.host_str() {
|
||||||
if let Some(port) = url.port() {
|
Some(domain) => {
|
||||||
domain = format!("{}:{}", domain, port);
|
let domain = if let Some(port) = url.port() {
|
||||||
|
format!("{}:{}", domain, port)
|
||||||
|
} else {
|
||||||
|
domain.to_string()
|
||||||
|
};
|
||||||
|
domain == self.domain
|
||||||
|
}
|
||||||
|
None => false,
|
||||||
}
|
}
|
||||||
domain == self.domain
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the local domain
|
/// Returns the local domain
|
||||||
|
@ -355,13 +361,17 @@ mod test {
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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::parse("http://example.com")?));
|
||||||
assert!(!config.is_local_url(&Url::parse("http://other.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")?));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_get_domain() {
|
async fn test_get_domain() {
|
||||||
let config = config().await;
|
let config = config().await;
|
||||||
|
|
|
@ -53,19 +53,21 @@ pub async fn fetch_object_http<T: Clone, Kind: DeserializeOwned>(
|
||||||
url: &Url,
|
url: &Url,
|
||||||
data: &Data<T>,
|
data: &Data<T>,
|
||||||
) -> Result<FetchObjectResponse<Kind>, Error> {
|
) -> Result<FetchObjectResponse<Kind>, Error> {
|
||||||
static CONTENT_TYPE: HeaderValue = HeaderValue::from_static(FEDERATION_CONTENT_TYPE);
|
static FETCH_CONTENT_TYPE: HeaderValue = HeaderValue::from_static(FEDERATION_CONTENT_TYPE);
|
||||||
static ALT_CONTENT_TYPE: HeaderValue = HeaderValue::from_static(
|
const VALID_RESPONSE_CONTENT_TYPES: [&str; 3] = [
|
||||||
r#"application/ld+json; profile="https://www.w3.org/ns/activitystreams""#,
|
FEDERATION_CONTENT_TYPE, // lemmy
|
||||||
);
|
r#"application/ld+json; profile="https://www.w3.org/ns/activitystreams""#, // activitypub standard
|
||||||
static ALT_CONTENT_TYPE_MASTODON: HeaderValue =
|
r#"application/activity+json; charset=utf-8"#, // mastodon
|
||||||
HeaderValue::from_static(r#"application/activity+json; charset=utf-8"#);
|
];
|
||||||
let res = fetch_object_http_with_accept(url, data, &CONTENT_TYPE).await?;
|
let res = fetch_object_http_with_accept(url, data, &FETCH_CONTENT_TYPE).await?;
|
||||||
|
|
||||||
// Ensure correct content-type to prevent vulnerabilities.
|
// Ensure correct content-type to prevent vulnerabilities, with case insensitive comparison.
|
||||||
if res.content_type.as_ref() != Some(&CONTENT_TYPE)
|
let content_type = res
|
||||||
&& res.content_type.as_ref() != Some(&ALT_CONTENT_TYPE)
|
.content_type
|
||||||
&& res.content_type.as_ref() != Some(&ALT_CONTENT_TYPE_MASTODON)
|
.as_ref()
|
||||||
{
|
.and_then(|c| c.to_str().ok())
|
||||||
|
.ok_or(Error::FetchInvalidContentType(res.url.clone()))?;
|
||||||
|
if !VALID_RESPONSE_CONTENT_TYPES.contains(&content_type) {
|
||||||
return Err(Error::FetchInvalidContentType(res.url));
|
return Err(Error::FetchInvalidContentType(res.url));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -189,8 +189,11 @@ fn verify_signature_inner(
|
||||||
uri: &Uri,
|
uri: &Uri,
|
||||||
public_key: &str,
|
public_key: &str,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
static CONFIG: Lazy<http_signature_normalization::Config> =
|
static CONFIG: Lazy<http_signature_normalization::Config> = Lazy::new(|| {
|
||||||
Lazy::new(|| http_signature_normalization::Config::new().set_expiration(EXPIRES_AFTER));
|
http_signature_normalization::Config::new()
|
||||||
|
.set_expiration(EXPIRES_AFTER)
|
||||||
|
.require_digest()
|
||||||
|
});
|
||||||
|
|
||||||
let path_and_query = uri.path_and_query().map(PathAndQuery::as_str).unwrap_or("");
|
let path_and_query = uri.path_and_query().map(PathAndQuery::as_str).unwrap_or("");
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue