diff --git a/src/config.rs b/src/config.rs index 71e0fae..47319fa 100644 --- a/src/config.rs +++ b/src/config.rs @@ -28,7 +28,7 @@ use dyn_clone::{clone_trait_object, DynClone}; use moka::future::Cache; use once_cell::sync::Lazy; use regex::Regex; -use reqwest::Request; +use reqwest::{redirect::Policy, Client, Request}; use reqwest_middleware::{ClientWithMiddleware, RequestBuilder}; use rsa::{pkcs8::DecodePrivateKey, RsaPrivateKey}; use serde::de::DeserializeOwned; @@ -58,9 +58,10 @@ pub struct FederationConfig { /// [crate::fetch::object_id::ObjectId] for more details. #[builder(default = "20")] pub(crate) http_fetch_limit: u32, - #[builder(default = "reqwest::Client::default().into()")] + #[builder(default = "default_client()")] /// HTTP client used for all outgoing requests. Middleware can be used to add functionality /// like log tracing or retry of failed requests. + /// Redirects should be disabled to prevent an attacker from accessing local addresses. pub(crate) client: ClientWithMiddleware, /// Run library in debug mode. This allows usage of http and localhost urls. It also sends /// outgoing activities synchronously, not in background thread. This helps to make tests @@ -416,6 +417,14 @@ impl FederationMiddleware { } } +fn default_client() -> ClientWithMiddleware { + Client::builder() + .redirect(Policy::none()) + .build() + .unwrap_or_else(|_| Client::default()) + .into() +} + #[cfg(test)] #[allow(clippy::unwrap_used)] mod test { diff --git a/src/fetch/mod.rs b/src/fetch/mod.rs index cb23319..1964530 100644 --- a/src/fetch/mod.rs +++ b/src/fetch/mod.rs @@ -11,7 +11,7 @@ use crate::{ FEDERATION_CONTENT_TYPE, }; use bytes::Bytes; -use http::{HeaderValue, StatusCode}; +use http::{header::LOCATION, HeaderValue, StatusCode}; use serde::de::DeserializeOwned; use std::sync::atomic::Ordering; use tracing::info; @@ -132,6 +132,11 @@ async fn fetch_object_http_with_accept( req.send().await? }; + if let Some(location) = res.headers().get(LOCATION) { + let location: Url = location.to_str().unwrap().parse()?; + return Box::pin(fetch_object_http_with_accept(&location, data, content_type)).await; + } + if res.status() == StatusCode::GONE { return Err(Error::ObjectDeleted(url.clone())); }