Compare commits
11 commits
upgrade-re
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 754b2a0f3d | |||
|
|
588f431266 | ||
|
|
838dd9e501 | ||
|
|
279d29d350 | ||
|
|
fcb69ebffe | ||
|
|
5e8e918003 | ||
|
|
4ae8532b17 | ||
|
|
f47fe58285 | ||
|
|
f60afae428 | ||
|
|
11f95ff384 | ||
|
|
9d7bd965a4 |
16 changed files with 310 additions and 81 deletions
101
Cargo.lock
generated
101
Cargo.lock
generated
|
|
@ -4,7 +4,7 @@ version = 4
|
|||
|
||||
[[package]]
|
||||
name = "activitypub_federation"
|
||||
version = "0.7.0-beta.8"
|
||||
version = "0.7.0-beta.11"
|
||||
dependencies = [
|
||||
"activitystreams-kinds",
|
||||
"actix-web",
|
||||
|
|
@ -313,9 +313,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
|||
|
||||
[[package]]
|
||||
name = "aws-lc-rs"
|
||||
version = "1.15.2"
|
||||
version = "1.15.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a88aab2464f1f25453baa7a07c84c5b7684e274054ba06817f382357f77a288"
|
||||
checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256"
|
||||
dependencies = [
|
||||
"aws-lc-sys",
|
||||
"zeroize",
|
||||
|
|
@ -323,9 +323,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "aws-lc-sys"
|
||||
version = "0.35.0"
|
||||
version = "0.37.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b45afffdee1e7c9126814751f88dddc747f41d91da16c9551a0f1e8a11e788a1"
|
||||
checksum = "5c34dda4df7017c8db52132f0f8a2e0f8161649d15723ed63fc00c82d0f2081a"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cmake",
|
||||
|
|
@ -557,6 +557,16 @@ dependencies = [
|
|||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.10.1"
|
||||
|
|
@ -1017,6 +1027,25 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"http 1.4.0",
|
||||
"indexmap",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.16.1"
|
||||
|
|
@ -1139,6 +1168,7 @@ dependencies = [
|
|||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"h2",
|
||||
"http 1.4.0",
|
||||
"http-body",
|
||||
"httparse",
|
||||
|
|
@ -1186,9 +1216,11 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"socket2 0.6.1",
|
||||
"system-configuration",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
"windows-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1618,9 +1650,9 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
|||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391"
|
||||
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
|
||||
|
||||
[[package]]
|
||||
name = "parking"
|
||||
|
|
@ -1836,7 +1868,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
||||
dependencies = [
|
||||
"rand_chacha 0.9.0",
|
||||
"rand_core 0.9.3",
|
||||
"rand_core 0.9.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1856,7 +1888,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core 0.9.3",
|
||||
"rand_core 0.9.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1870,9 +1902,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.9.3"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
|
||||
dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
]
|
||||
|
|
@ -1929,8 +1961,10 @@ checksum = "04e9018c9d814e5f30cc16a0f03271aeab3571e609612d9fe78c1aa8d11c2f62"
|
|||
dependencies = [
|
||||
"base64",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http 1.4.0",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
|
|
@ -1939,6 +1973,7 @@ dependencies = [
|
|||
"hyper-util",
|
||||
"js-sys",
|
||||
"log",
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"quinn",
|
||||
|
|
@ -2052,9 +2087,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.13.2"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282"
|
||||
checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd"
|
||||
dependencies = [
|
||||
"web-time",
|
||||
"zeroize",
|
||||
|
|
@ -2066,7 +2101,7 @@ version = "0.6.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784"
|
||||
dependencies = [
|
||||
"core-foundation",
|
||||
"core-foundation 0.10.1",
|
||||
"core-foundation-sys",
|
||||
"jni",
|
||||
"log",
|
||||
|
|
@ -2089,9 +2124,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
|
|||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.103.8"
|
||||
version = "0.103.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52"
|
||||
checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"ring",
|
||||
|
|
@ -2142,7 +2177,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"core-foundation",
|
||||
"core-foundation 0.10.1",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"security-framework-sys",
|
||||
|
|
@ -2387,6 +2422,27 @@ dependencies = [
|
|||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"core-foundation 0.9.4",
|
||||
"system-configuration-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration-sys"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tagptr"
|
||||
version = "0.2.0"
|
||||
|
|
@ -2880,6 +2936,17 @@ version = "0.2.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-registry"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.4.1"
|
||||
|
|
|
|||
10
Cargo.toml
10
Cargo.toml
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "activitypub_federation"
|
||||
version = "0.7.0-beta.8"
|
||||
version = "0.7.0-beta.11"
|
||||
edition = "2021"
|
||||
description = "High-level Activitypub framework"
|
||||
keywords = ["activitypub", "activitystreams", "federation", "fediverse"]
|
||||
|
|
@ -14,10 +14,6 @@ actix-web = ["dep:actix-web", "dep:http02"]
|
|||
axum = ["dep:axum", "dep:tower"]
|
||||
axum-original-uri = ["dep:axum", "axum/original-uri"]
|
||||
|
||||
[lints.rust]
|
||||
warnings = "deny"
|
||||
deprecated = "deny"
|
||||
|
||||
[lints.clippy]
|
||||
perf = { level = "deny", priority = -1 }
|
||||
complexity = { level = "deny", priority = -1 }
|
||||
|
|
@ -40,7 +36,6 @@ serde_json = { version = "1.0.149", features = ["preserve_order"] }
|
|||
reqwest = { version = "0.13.1", default-features = false, features = [
|
||||
"json",
|
||||
"stream",
|
||||
"rustls",
|
||||
] }
|
||||
reqwest-middleware = "0.5.0"
|
||||
tracing = "0.1.44"
|
||||
|
|
@ -95,6 +90,9 @@ axum = { version = "0.8.8", features = ["macros"] }
|
|||
axum-extra = { version = "0.12.5", features = ["typed-header"] }
|
||||
env_logger = "0.11.8"
|
||||
tokio = { version = "1.49.0", features = ["full"] }
|
||||
reqwest = { version = "0.13.1",features = [
|
||||
"rustls"
|
||||
] }
|
||||
|
||||
[profile.dev]
|
||||
strip = "symbols"
|
||||
|
|
|
|||
|
|
@ -32,10 +32,10 @@ impl Object for SearchableDbObjects {
|
|||
type Kind = SearchableObjects;
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn id(&self) -> &Url {
|
||||
fn id(&self) -> Url {
|
||||
match self {
|
||||
SearchableDbObjects::User(p) => &p.federation_id,
|
||||
SearchableDbObjects::Post(n) => &n.federation_id,
|
||||
SearchableDbObjects::User(p) => p.federation_id.clone(),
|
||||
SearchableDbObjects::Post(n) => n.federation_id.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -69,8 +69,8 @@ impl Object for DbUser {
|
|||
type Kind = Person;
|
||||
type Error = Error;
|
||||
|
||||
fn id(&self) -> &Url {
|
||||
self.ap_id.inner()
|
||||
fn id(&self) -> Url {
|
||||
self.ap_id.inner().clone()
|
||||
}
|
||||
|
||||
fn last_refreshed_at(&self) -> Option<DateTime<Utc>> {
|
||||
|
|
|
|||
|
|
@ -50,8 +50,8 @@ impl Object for DbPost {
|
|||
type Kind = Note;
|
||||
type Error = Error;
|
||||
|
||||
fn id(&self) -> &Url {
|
||||
self.ap_id.inner()
|
||||
fn id(&self) -> Url {
|
||||
self.ap_id.inner().clone()
|
||||
}
|
||||
|
||||
async fn read_from_id(
|
||||
|
|
|
|||
|
|
@ -134,8 +134,8 @@ impl Object for DbUser {
|
|||
type Kind = Person;
|
||||
type Error = Error;
|
||||
|
||||
fn id(&self) -> &Url {
|
||||
self.ap_id.inner()
|
||||
fn id(&self) -> Url {
|
||||
self.ap_id.inner().clone()
|
||||
}
|
||||
|
||||
fn last_refreshed_at(&self) -> Option<DateTime<Utc>> {
|
||||
|
|
|
|||
|
|
@ -47,8 +47,8 @@ impl Object for DbPost {
|
|||
type Kind = Note;
|
||||
type Error = Error;
|
||||
|
||||
fn id(&self) -> &Url {
|
||||
self.ap_id.inner()
|
||||
fn id(&self) -> Url {
|
||||
self.ap_id.inner().clone()
|
||||
}
|
||||
|
||||
async fn read_from_id(
|
||||
|
|
|
|||
|
|
@ -190,7 +190,7 @@ where
|
|||
// PKey is internally like an Arc<>, so cloning is ok
|
||||
data.config
|
||||
.actor_pkey_cache
|
||||
.try_get_with_by_ref(actor_id, async {
|
||||
.try_get_with_by_ref(&actor_id, async {
|
||||
let private_key_pem = actor.private_key_pem().ok_or_else(|| {
|
||||
Error::Other(format!(
|
||||
"Actor {actor_id} does not contain a private key for signing"
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ use crate::{
|
|||
http_signatures::sign_request,
|
||||
protocol::verification::verify_domains_match,
|
||||
traits::{Activity, Actor},
|
||||
utils::validate_ip,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use bytes::Bytes;
|
||||
|
|
@ -32,7 +33,6 @@ use reqwest_middleware::{ClientWithMiddleware, RequestBuilder};
|
|||
use rsa::{pkcs8::DecodePrivateKey, RsaPrivateKey};
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::{
|
||||
net::IpAddr,
|
||||
ops::Deref,
|
||||
sync::{
|
||||
atomic::{AtomicU32, Ordering},
|
||||
|
|
@ -41,7 +41,6 @@ use std::{
|
|||
},
|
||||
time::Duration,
|
||||
};
|
||||
use tokio::net::lookup_host;
|
||||
use url::Url;
|
||||
|
||||
/// Configuration for this library, with various federation related settings
|
||||
|
|
@ -183,30 +182,9 @@ impl<T: Clone> FederationConfig<T> {
|
|||
return Err(Error::UrlVerificationError("Explicit port is not allowed"));
|
||||
}
|
||||
|
||||
// Resolve domain and see if it points to private IP
|
||||
// TODO: Use is_global() once stabilized
|
||||
// https://doc.rust-lang.org/std/net/enum.IpAddr.html#method.is_global
|
||||
let invalid_ip =
|
||||
lookup_host((domain.to_owned(), 80))
|
||||
.await?
|
||||
.any(|addr| match addr.ip() {
|
||||
IpAddr::V4(addr) => {
|
||||
addr.is_private()
|
||||
|| addr.is_link_local()
|
||||
|| addr.is_loopback()
|
||||
|| addr.is_multicast()
|
||||
}
|
||||
IpAddr::V6(addr) => {
|
||||
addr.is_loopback()
|
||||
|| addr.is_multicast()
|
||||
|| ((addr.segments()[0] & 0xfe00) == 0xfc00) // is_unique_local
|
||||
|| ((addr.segments()[0] & 0xffc0) == 0xfe80) // is_unicast_link_local
|
||||
}
|
||||
});
|
||||
if invalid_ip {
|
||||
return Err(Error::UrlVerificationError(
|
||||
"Localhost is only allowed in debug mode",
|
||||
));
|
||||
let allow_local = std::env::var("DANGER_FEDERATION_ALLOW_LOCAL_IP").is_ok();
|
||||
if !allow_local && validate_ip(&url).await.is_err() {
|
||||
return Err(Error::DomainResolveError(domain.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -400,6 +378,15 @@ impl<T: Clone> Data<T> {
|
|||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Resolve domain of the url and throw error if it points to local/private IP.
|
||||
pub async fn is_valid_ip(&self, url: &Url) -> Result<(), Error> {
|
||||
if self.config.debug {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
validate_ip(url).await
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> Deref for Data<T> {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,9 @@ pub enum Error {
|
|||
/// url verification error
|
||||
#[error("URL failed verification: {0}")]
|
||||
UrlVerificationError(&'static str),
|
||||
/// Resolving domain points to local IP.
|
||||
#[error("Resolving domain {0} points to local IP address. This may indicate an attacker attempting to access internal services. If intentional, you can ignore this error by setting DANGER_FEDERATION_ALLOW_LOCAL_IP=1")]
|
||||
DomainResolveError(String),
|
||||
/// Incoming activity has invalid digest for body
|
||||
#[error("Incoming activity has invalid digest for body")]
|
||||
ActivityBodyDigestInvalid,
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ pub mod http_signatures;
|
|||
pub mod protocol;
|
||||
pub(crate) mod reqwest_shim;
|
||||
pub mod traits;
|
||||
mod utils;
|
||||
|
||||
use crate::{
|
||||
config::Data,
|
||||
|
|
|
|||
|
|
@ -1,13 +1,22 @@
|
|||
//! Serde deserialization functions which help to receive differently shaped data
|
||||
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use activitystreams_kinds::public;
|
||||
use itertools::Itertools;
|
||||
use serde::{de::Error, Deserialize, Deserializer};
|
||||
use serde_json::Value;
|
||||
use url::Url;
|
||||
|
||||
/// Deserialize JSON single value or array into Vec.
|
||||
/// Deserialize JSON single value or array into `Vec<Url>`.
|
||||
///
|
||||
/// Useful if your application can handle multiple values for a field, but another federated
|
||||
/// platform only sends a single one.
|
||||
///
|
||||
/// Also accepts common `Public` aliases for recipient fields. Some implementations send `Public`
|
||||
/// or `as:Public` instead of the canonical `https://www.w3.org/ns/activitystreams#Public` URL
|
||||
/// in fields such as `to` and `cc`.
|
||||
///
|
||||
/// ```
|
||||
/// # use activitypub_federation::kinds::public;
|
||||
/// # use activitypub_federation::protocol::helpers::deserialize_one_or_many;
|
||||
/// # use url::Url;
|
||||
/// #[derive(serde::Deserialize)]
|
||||
|
|
@ -25,24 +34,39 @@ use serde::{Deserialize, Deserializer};
|
|||
/// "https://lemmy.ml/u/bob"
|
||||
/// ]}"#)?;
|
||||
/// assert_eq!(multiple.to.len(), 2);
|
||||
/// Ok::<(), anyhow::Error>(())
|
||||
pub fn deserialize_one_or_many<'de, T, D>(deserializer: D) -> Result<Vec<T>, D::Error>
|
||||
///
|
||||
/// let note: Note = serde_json::from_str(r#"{"to": ["Public", "as:Public"]}"#)?;
|
||||
/// assert_eq!(note.to, vec![public()]);
|
||||
/// # Ok::<(), anyhow::Error>(())
|
||||
/// ```
|
||||
pub fn deserialize_one_or_many<'de, D>(deserializer: D) -> Result<Vec<Url>, D::Error>
|
||||
where
|
||||
T: Deserialize<'de>,
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum OneOrMany<T> {
|
||||
One(T),
|
||||
Many(Vec<T>),
|
||||
enum OneOrMany {
|
||||
Many(Vec<Value>),
|
||||
One(Value),
|
||||
}
|
||||
|
||||
let result: OneOrMany<T> = Deserialize::deserialize(deserializer)?;
|
||||
Ok(match result {
|
||||
OneOrMany::Many(list) => list,
|
||||
let result: OneOrMany = Deserialize::deserialize(deserializer)?;
|
||||
let values = match result {
|
||||
OneOrMany::One(value) => vec![value],
|
||||
OneOrMany::Many(values) => values,
|
||||
};
|
||||
|
||||
values
|
||||
.into_iter()
|
||||
.map(|value| match value {
|
||||
Value::String(value) if matches!(value.as_str(), "Public" | "as:Public") => {
|
||||
Ok(public())
|
||||
}
|
||||
Value::String(value) => Url::parse(&value).map_err(D::Error::custom),
|
||||
value => Url::deserialize(value).map_err(D::Error::custom),
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map(|values| values.into_iter().unique().collect())
|
||||
}
|
||||
|
||||
/// Deserialize JSON single value or single element array into single value.
|
||||
|
|
@ -140,6 +164,11 @@ where
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::deserialize_one_or_many;
|
||||
use activitystreams_kinds::public;
|
||||
use anyhow::Result;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[test]
|
||||
fn deserialize_one_multiple_values() {
|
||||
use crate::protocol::helpers::deserialize_one;
|
||||
|
|
@ -155,4 +184,70 @@ mod tests {
|
|||
);
|
||||
assert!(note.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_one_or_many_single_public_aliases() -> Result<()> {
|
||||
use url::Url;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Note {
|
||||
#[serde(deserialize_with = "deserialize_one_or_many")]
|
||||
to: Vec<Url>,
|
||||
}
|
||||
|
||||
for alias in ["Public", "as:Public"] {
|
||||
let note = serde_json::from_str::<Note>(&format!(r#"{{"to": "{alias}"}}"#))?;
|
||||
assert_eq!(note.to, vec![public()]);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_one_or_many_array() -> Result<()> {
|
||||
use url::Url;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Note {
|
||||
#[serde(deserialize_with = "deserialize_one_or_many")]
|
||||
to: Vec<Url>,
|
||||
}
|
||||
|
||||
let note = serde_json::from_str::<Note>(
|
||||
r#"{
|
||||
"to": [
|
||||
"https://example.com/c/main",
|
||||
"Public",
|
||||
"as:Public",
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
]
|
||||
}"#,
|
||||
)?;
|
||||
|
||||
assert_eq!(
|
||||
note.to,
|
||||
vec![Url::parse("https://example.com/c/main")?, public(),]
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_one_or_many_leaves_other_strings_unchanged() -> Result<()> {
|
||||
use url::Url;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Note {
|
||||
#[serde(deserialize_with = "deserialize_one_or_many")]
|
||||
to: Vec<Url>,
|
||||
content: String,
|
||||
}
|
||||
|
||||
let note = serde_json::from_str::<Note>(r#"{"to": "Public", "content": "Public"}"#)?;
|
||||
|
||||
assert_eq!(note.to, vec![public()]);
|
||||
assert_eq!(note.content, "Public");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ where
|
|||
type Error = E;
|
||||
|
||||
/// `id` field of the object
|
||||
fn id(&self) -> &Url {
|
||||
fn id(&self) -> Url {
|
||||
match self {
|
||||
Either::Left(l) => l.id(),
|
||||
Either::Right(r) => r.id(),
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ pub mod tests;
|
|||
/// type Kind = Note;
|
||||
/// type Error = anyhow::Error;
|
||||
///
|
||||
/// fn id(&self) -> &Url { self.ap_id.inner() }
|
||||
/// fn id(&self) -> Url { self.ap_id.inner().clone() }
|
||||
///
|
||||
/// async fn read_from_id(object_id: Url, data: &Data<Self::DataType>) -> Result<Option<Self>, Self::Error> {
|
||||
/// // Attempt to read object from local database. Return Ok(None) if not found.
|
||||
|
|
@ -110,7 +110,7 @@ pub trait Object: Sized + Debug {
|
|||
type Error;
|
||||
|
||||
/// `id` field of the object
|
||||
fn id(&self) -> &Url;
|
||||
fn id(&self) -> Url;
|
||||
|
||||
/// Returns the last time this object was updated.
|
||||
///
|
||||
|
|
@ -194,8 +194,8 @@ pub trait Object: Sized + Debug {
|
|||
redirect_remote_object,
|
||||
};
|
||||
let id = self.id();
|
||||
let res = if !data.config.is_local_url(id) {
|
||||
redirect_remote_object(id)
|
||||
let res = if !data.config.is_local_url(&id) {
|
||||
redirect_remote_object(&id)
|
||||
} else if !self.is_deleted() {
|
||||
let json = self.into_json(data).await?;
|
||||
create_http_response(json, federation_context)?
|
||||
|
|
|
|||
|
|
@ -73,8 +73,8 @@ impl Object for DbUser {
|
|||
type Kind = Person;
|
||||
type Error = Error;
|
||||
|
||||
fn id(&self) -> &Url {
|
||||
&self.federation_id
|
||||
fn id(&self) -> Url {
|
||||
self.federation_id.clone()
|
||||
}
|
||||
|
||||
async fn read_from_id(
|
||||
|
|
@ -179,7 +179,7 @@ impl Object for DbPost {
|
|||
type Kind = Note;
|
||||
type Error = Error;
|
||||
|
||||
fn id(&self) -> &Url {
|
||||
fn id(&self) -> Url {
|
||||
todo!()
|
||||
}
|
||||
|
||||
|
|
|
|||
78
src/utils.rs
Normal file
78
src/utils.rs
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
|
||||
use crate::error::Error;
|
||||
use tokio::net::lookup_host;
|
||||
use url::{Host, Url};
|
||||
|
||||
// TODO: Use is_global() once stabilized
|
||||
// https://doc.rust-lang.org/std/net/enum.IpAddr.html#method.is_global
|
||||
pub(crate) async fn validate_ip(url: &Url) -> Result<(), Error> {
|
||||
let mut ip = vec![];
|
||||
let host = url
|
||||
.host()
|
||||
.ok_or(Error::UrlVerificationError("Url must have a domain"))?;
|
||||
match host {
|
||||
Host::Domain(domain) => ip.extend(
|
||||
lookup_host((domain.to_owned(), 80))
|
||||
.await?
|
||||
.map(|s| s.ip().to_canonical()),
|
||||
),
|
||||
Host::Ipv4(ipv4) => ip.push(ipv4.into()),
|
||||
Host::Ipv6(ipv6) => ip.push(ipv6.into()),
|
||||
};
|
||||
|
||||
let invalid_ip = ip.into_iter().any(|addr| match addr {
|
||||
IpAddr::V4(addr) => v4_is_invalid(addr),
|
||||
IpAddr::V6(addr) => v6_is_invalid(addr),
|
||||
});
|
||||
if invalid_ip {
|
||||
return Err(Error::DomainResolveError(host.to_string()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn v4_is_invalid(v4: Ipv4Addr) -> bool {
|
||||
v4.is_private()
|
||||
|| v4.is_loopback()
|
||||
|| v4.is_link_local()
|
||||
|| v4.is_multicast()
|
||||
|| v4.is_documentation()
|
||||
|| v4.is_unspecified()
|
||||
|| v4.is_broadcast()
|
||||
}
|
||||
|
||||
fn v6_is_invalid(v6: Ipv6Addr) -> bool {
|
||||
v6.is_loopback()
|
||||
|| v6.is_multicast()
|
||||
|| v6.is_unique_local()
|
||||
|| v6.is_unicast_link_local()
|
||||
|| v6.is_unspecified()
|
||||
|| v6_is_documentation(v6)
|
||||
|| v6.to_ipv4_mapped().is_some_and(v4_is_invalid)
|
||||
}
|
||||
|
||||
fn v6_is_documentation(v6: std::net::Ipv6Addr) -> bool {
|
||||
matches!(
|
||||
v6.segments(),
|
||||
[0x2001, 0xdb8, ..] | [0x3fff, 0..=0x0fff, ..]
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_is_valid_ip() -> Result<(), Error> {
|
||||
assert!(validate_ip(&Url::parse("http://example.com")?)
|
||||
.await
|
||||
.is_ok());
|
||||
assert!(validate_ip(&Url::parse("http://172.66.147.243")?)
|
||||
.await
|
||||
.is_ok());
|
||||
assert!(validate_ip(&Url::parse("http://localhost")?).await.is_err());
|
||||
assert!(validate_ip(&Url::parse("http://127.0.0.1")?).await.is_err());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue