Add support for SOCKS proxy
This commit is contained in:
parent
36730be03b
commit
f92428e509
12 changed files with 85 additions and 16 deletions
19
Cargo.lock
generated
19
Cargo.lock
generated
|
@ -792,6 +792,12 @@ dependencies = [
|
|||
"signature",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
|
||||
|
||||
[[package]]
|
||||
name = "elliptic-curve"
|
||||
version = "0.10.6"
|
||||
|
@ -2674,6 +2680,7 @@ dependencies = [
|
|||
"serde_urlencoded",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tokio-socks",
|
||||
"url 2.2.2",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
|
@ -3280,6 +3287,18 @@ dependencies = [
|
|||
"tokio-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-socks"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51165dfa029d2a65969413a6cc96f354b86b464498702f174a4efa13608fd8c0"
|
||||
dependencies = [
|
||||
"either",
|
||||
"futures-util",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.1"
|
||||
|
|
|
@ -51,7 +51,7 @@ rand = "0.8.4"
|
|||
# Used for managing database migrations
|
||||
refinery = { version = "0.8.4", features = ["tokio-postgres"] }
|
||||
# Used for making async HTTP requests
|
||||
reqwest = { version = "0.11.10", features = ["json", "multipart"] }
|
||||
reqwest = { version = "0.11.10", features = ["json", "multipart", "socks"] }
|
||||
# Used for working with RSA keys
|
||||
rsa = "0.5.0"
|
||||
pem = "1.0.2"
|
||||
|
|
|
@ -23,6 +23,9 @@ registrations_open: false
|
|||
|
||||
#post_character_limit: 2000
|
||||
|
||||
# Proxy for outgoing requests
|
||||
#proxy_url: 'socks5h://127.0.0.1:9050'
|
||||
|
||||
# List of blocked domains
|
||||
#blocked_instances: []
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ use std::collections::{BTreeMap, HashMap};
|
|||
use std::time::Duration;
|
||||
|
||||
use actix_web::http::Method;
|
||||
use reqwest::{Client, Proxy};
|
||||
use rsa::RsaPrivateKey;
|
||||
use serde::Serialize;
|
||||
use tokio::time::sleep;
|
||||
|
@ -33,6 +34,15 @@ pub enum DelivererError {
|
|||
HttpError(reqwest::StatusCode),
|
||||
}
|
||||
|
||||
fn build_client(instance: &Instance) -> reqwest::Result<Client> {
|
||||
let mut client_builder = Client::builder();
|
||||
if let Some(ref proxy_url) = instance.proxy_url {
|
||||
let proxy = Proxy::all(proxy_url)?;
|
||||
client_builder = client_builder.proxy(proxy);
|
||||
};
|
||||
client_builder.build()
|
||||
}
|
||||
|
||||
async fn send_activity(
|
||||
instance: &Instance,
|
||||
actor_key: &RsaPrivateKey,
|
||||
|
@ -48,7 +58,7 @@ async fn send_activity(
|
|||
actor_key_id,
|
||||
)?;
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let client = build_client(instance)?;
|
||||
let request = client.post(inbox_url)
|
||||
.header("Host", headers.host)
|
||||
.header("Date", headers.date)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
|
||||
use reqwest::{Client, Method};
|
||||
use reqwest::{Client, Method, Proxy};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::activitypub::activity::Object;
|
||||
|
@ -33,9 +33,14 @@ pub enum FetchError {
|
|||
OtherError(&'static str),
|
||||
}
|
||||
|
||||
fn build_client() -> reqwest::Result<Client> {
|
||||
fn build_client(instance: &Instance) -> reqwest::Result<Client> {
|
||||
let mut client_builder = Client::builder();
|
||||
let connect_timeout = Duration::from_secs(FETCHER_CONNECTION_TIMEOUT);
|
||||
Client::builder()
|
||||
if let Some(ref proxy_url) = instance.proxy_url {
|
||||
let proxy = Proxy::all(proxy_url)?;
|
||||
client_builder = client_builder.proxy(proxy);
|
||||
};
|
||||
client_builder
|
||||
.connect_timeout(connect_timeout)
|
||||
.build()
|
||||
}
|
||||
|
@ -46,7 +51,7 @@ async fn send_request(
|
|||
url: &str,
|
||||
query_params: &[(&str, &str)],
|
||||
) -> Result<String, FetchError> {
|
||||
let client = build_client()?;
|
||||
let client = build_client(instance)?;
|
||||
let mut request_builder = client.get(url);
|
||||
if !query_params.is_empty() {
|
||||
request_builder = request_builder.query(query_params);
|
||||
|
@ -83,10 +88,11 @@ async fn send_request(
|
|||
const FILE_MAX_SIZE: u64 = 1024 * 1024 * 20;
|
||||
|
||||
pub async fn fetch_file(
|
||||
instance: &Instance,
|
||||
url: &str,
|
||||
output_dir: &Path,
|
||||
) -> Result<(String, Option<String>), FetchError> {
|
||||
let client = build_client()?;
|
||||
let client = build_client(instance)?;
|
||||
let response = client.get(url).send().await?;
|
||||
if let Some(file_size) = response.content_length() {
|
||||
if file_size > FILE_MAX_SIZE {
|
||||
|
@ -111,7 +117,7 @@ pub async fn perform_webfinger_query(
|
|||
guess_protocol(&actor_address.hostname),
|
||||
actor_address.hostname,
|
||||
);
|
||||
let client = build_client()?;
|
||||
let client = build_client(instance)?;
|
||||
let mut request_builder = client.get(&webfinger_url);
|
||||
if !instance.is_private {
|
||||
// Public instance should set User-Agent header
|
||||
|
@ -145,13 +151,14 @@ pub async fn fetch_actor(
|
|||
}
|
||||
|
||||
pub async fn fetch_actor_images(
|
||||
instance: &Instance,
|
||||
actor: &Actor,
|
||||
media_dir: &Path,
|
||||
default_avatar: Option<String>,
|
||||
default_banner: Option<String>,
|
||||
) -> (Option<String>, Option<String>) {
|
||||
let maybe_avatar = if let Some(icon) = &actor.icon {
|
||||
match fetch_file(&icon.url, media_dir).await {
|
||||
match fetch_file(instance, &icon.url, media_dir).await {
|
||||
Ok((file_name, _)) => Some(file_name),
|
||||
Err(error) => {
|
||||
log::warn!("failed to fetch avatar ({})", error);
|
||||
|
@ -162,7 +169,7 @@ pub async fn fetch_actor_images(
|
|||
None
|
||||
};
|
||||
let maybe_banner = if let Some(image) = &actor.image {
|
||||
match fetch_file(&image.url, media_dir).await {
|
||||
match fetch_file(instance, &image.url, media_dir).await {
|
||||
Ok((file_name, _)) => Some(file_name),
|
||||
Err(error) => {
|
||||
log::warn!("failed to fetch banner ({})", error);
|
||||
|
|
|
@ -70,6 +70,7 @@ async fn create_remote_profile(
|
|||
return Err(ImportError::LocalObject);
|
||||
};
|
||||
let (maybe_avatar, maybe_banner) = fetch_actor_images(
|
||||
instance,
|
||||
&actor,
|
||||
media_dir,
|
||||
None,
|
||||
|
@ -115,6 +116,7 @@ pub async fn get_or_import_profile_by_actor_id(
|
|||
log::info!("re-fetched profile {}", profile.acct);
|
||||
let profile_updated = update_remote_profile(
|
||||
db_client,
|
||||
instance,
|
||||
media_dir,
|
||||
profile,
|
||||
actor,
|
||||
|
@ -143,6 +145,7 @@ pub async fn get_or_import_profile_by_actor_id(
|
|||
log::info!("re-fetched profile {}", profile.acct);
|
||||
let profile_updated = update_remote_profile(
|
||||
db_client,
|
||||
instance,
|
||||
media_dir,
|
||||
profile,
|
||||
actor,
|
||||
|
|
|
@ -161,7 +161,11 @@ pub async fn handle_note(
|
|||
};
|
||||
let attachment_url = attachment.url
|
||||
.ok_or(ValidationError("attachment URL is missing"))?;
|
||||
let (file_name, media_type) = fetch_file(&attachment_url, media_dir).await
|
||||
let (file_name, media_type) = fetch_file(
|
||||
instance,
|
||||
&attachment_url,
|
||||
media_dir,
|
||||
).await
|
||||
.map_err(|err| {
|
||||
log::warn!("{}", err);
|
||||
ValidationError("failed to fetch attachment")
|
||||
|
|
|
@ -9,6 +9,7 @@ use crate::activitypub::{
|
|||
fetcher::helpers::ImportError,
|
||||
vocabulary::PERSON,
|
||||
};
|
||||
use crate::config::{Config, Instance};
|
||||
use crate::errors::ValidationError;
|
||||
use crate::models::profiles::queries::{
|
||||
get_profile_by_remote_actor_id,
|
||||
|
@ -18,8 +19,8 @@ use crate::models::profiles::types::{DbActorProfile, ProfileUpdateData};
|
|||
use super::HandlerResult;
|
||||
|
||||
pub async fn handle_update_person(
|
||||
config: &Config,
|
||||
db_client: &impl GenericClient,
|
||||
media_dir: &Path,
|
||||
activity: Activity,
|
||||
) -> HandlerResult {
|
||||
let actor: Actor = serde_json::from_value(activity.object)
|
||||
|
@ -31,13 +32,20 @@ pub async fn handle_update_person(
|
|||
db_client,
|
||||
&actor.id,
|
||||
).await?;
|
||||
update_remote_profile(db_client, media_dir, profile, actor).await?;
|
||||
update_remote_profile(
|
||||
db_client,
|
||||
&config.instance(),
|
||||
&config.media_dir(),
|
||||
profile,
|
||||
actor,
|
||||
).await?;
|
||||
Ok(Some(PERSON))
|
||||
}
|
||||
|
||||
/// Updates remote actor's profile
|
||||
pub async fn update_remote_profile(
|
||||
db_client: &impl GenericClient,
|
||||
instance: &Instance,
|
||||
media_dir: &Path,
|
||||
profile: DbActorProfile,
|
||||
actor: Actor,
|
||||
|
@ -58,6 +66,7 @@ pub async fn update_remote_profile(
|
|||
);
|
||||
};
|
||||
let (maybe_avatar, maybe_banner) = fetch_actor_images(
|
||||
instance,
|
||||
&actor,
|
||||
media_dir,
|
||||
profile.avatar_file_name,
|
||||
|
|
|
@ -195,7 +195,7 @@ pub async fn receive_activity(
|
|||
},
|
||||
(UPDATE, PERSON) => {
|
||||
require_actor_signature(&activity.actor, &signer_id)?;
|
||||
handle_update_person(db_client, &config.media_dir(), activity).await?
|
||||
handle_update_person(config, db_client, activity).await?
|
||||
},
|
||||
(ADD, _) => {
|
||||
require_actor_signature(&activity.actor, &signer_id)?;
|
||||
|
|
|
@ -135,7 +135,13 @@ impl RefetchActor {
|
|||
&self.id,
|
||||
).await?;
|
||||
let actor = fetch_actor(&config.instance(), &self.id).await?;
|
||||
update_remote_profile(db_client, &config.media_dir(), profile, actor).await?;
|
||||
update_remote_profile(
|
||||
db_client,
|
||||
&config.instance(),
|
||||
&config.media_dir(),
|
||||
profile,
|
||||
actor,
|
||||
).await?;
|
||||
println!("profile updated");
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -98,6 +98,8 @@ pub struct Config {
|
|||
#[serde(default = "default_post_character_limit")]
|
||||
pub post_character_limit: usize,
|
||||
|
||||
proxy_url: Option<String>,
|
||||
|
||||
#[serde(default)]
|
||||
pub blocked_instances: Vec<String>,
|
||||
|
||||
|
@ -129,6 +131,7 @@ impl Config {
|
|||
_url: self.try_instance_url().unwrap(),
|
||||
_version: self.version.clone(),
|
||||
actor_key: self.instance_rsa_key.clone().unwrap(),
|
||||
proxy_url: self.proxy_url.clone(),
|
||||
is_private: matches!(self.environment, Environment::Development),
|
||||
}
|
||||
}
|
||||
|
@ -160,6 +163,8 @@ pub struct Instance {
|
|||
_version: String,
|
||||
// Instance actor
|
||||
pub actor_key: RsaPrivateKey,
|
||||
// Proxy for outgoing requests
|
||||
pub proxy_url: Option<String>,
|
||||
// Private instance won't send signed HTTP requests
|
||||
pub is_private: bool,
|
||||
}
|
||||
|
@ -171,6 +176,7 @@ impl Instance {
|
|||
_url: url,
|
||||
_version: "0.0.0".to_string(),
|
||||
actor_key,
|
||||
proxy_url: None,
|
||||
is_private: true,
|
||||
}
|
||||
}
|
||||
|
@ -289,6 +295,7 @@ mod tests {
|
|||
_url: instance_url,
|
||||
_version: "1.0.0".to_string(),
|
||||
actor_key: instance_rsa_key,
|
||||
proxy_url: None,
|
||||
is_private: true,
|
||||
};
|
||||
|
||||
|
@ -305,6 +312,7 @@ mod tests {
|
|||
_url: instance_url,
|
||||
_version: "1.0.0".to_string(),
|
||||
actor_key: instance_rsa_key,
|
||||
proxy_url: None,
|
||||
is_private: true,
|
||||
};
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ pub fn guess_protocol(hostname: &str) -> &'static str {
|
|||
let maybe_ipv6_address = hostname.parse::<Ipv6Addr>();
|
||||
if let Ok(ipv6_address) = maybe_ipv6_address {
|
||||
let prefix = ipv6_address.segments()[0];
|
||||
if prefix >= 0x0200 && prefix <= 0x03ff {
|
||||
if (0x0200..=0x03ff).contains(&prefix) {
|
||||
// Yggdrasil
|
||||
return "http";
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue