2023-02-11 12:32:35 +00:00
|
|
|
use crate::{
|
|
|
|
request_data::RequestData,
|
|
|
|
utils::reqwest_shim::ResponseExt,
|
|
|
|
Error,
|
|
|
|
APUB_JSON_CONTENT_TYPE,
|
|
|
|
};
|
2022-11-28 21:19:56 +00:00
|
|
|
use http::{header::HeaderName, HeaderValue, StatusCode};
|
2022-06-02 11:17:12 +00:00
|
|
|
use serde::de::DeserializeOwned;
|
2023-02-11 12:32:35 +00:00
|
|
|
use std::{collections::BTreeMap, sync::atomic::Ordering};
|
2022-11-28 21:19:56 +00:00
|
|
|
use tracing::info;
|
2022-06-02 11:17:12 +00:00
|
|
|
use url::Url;
|
|
|
|
|
2023-01-28 03:45:11 +00:00
|
|
|
pub(crate) mod reqwest_shim;
|
|
|
|
|
2023-02-19 12:26:01 +00:00
|
|
|
/// Fetch a remote object over HTTP and convert to `Kind`.
|
|
|
|
///
|
|
|
|
/// [crate::core::object_id::ObjectId::dereference] wraps this function to add caching and
|
|
|
|
/// conversion to database type. Only use this function directly in exceptional cases where that
|
|
|
|
/// behaviour is undesired.
|
|
|
|
///
|
|
|
|
/// Every time an object is fetched via HTTP, [RequestData.request_counter] is incremented by one.
|
|
|
|
/// If the value exceeds [FederationSettings.http_fetch_limit], the request is aborted with
|
|
|
|
/// [Error::RequestLimit]. This prevents denial of service attacks where an attack triggers
|
|
|
|
/// infinite, recursive fetching of data.
|
|
|
|
pub async fn fetch_object_http<T: Clone, Kind: DeserializeOwned>(
|
2022-06-02 11:17:12 +00:00
|
|
|
url: &Url,
|
2023-02-11 12:32:35 +00:00
|
|
|
data: &RequestData<T>,
|
2022-06-02 11:17:12 +00:00
|
|
|
) -> Result<Kind, Error> {
|
2023-02-19 12:26:01 +00:00
|
|
|
let config = &data.config;
|
2022-06-02 11:17:12 +00:00
|
|
|
// dont fetch local objects this way
|
2023-02-19 12:26:01 +00:00
|
|
|
debug_assert!(url.domain() != Some(&config.hostname));
|
|
|
|
config.verify_url_valid(url).await?;
|
2022-06-02 11:17:12 +00:00
|
|
|
info!("Fetching remote object {}", url.to_string());
|
|
|
|
|
2023-02-11 12:32:35 +00:00
|
|
|
let counter = data.request_counter.fetch_add(1, Ordering::SeqCst);
|
2023-02-19 12:26:01 +00:00
|
|
|
if counter > config.http_fetch_limit {
|
2022-06-02 11:17:12 +00:00
|
|
|
return Err(Error::RequestLimit);
|
|
|
|
}
|
|
|
|
|
2023-02-19 12:26:01 +00:00
|
|
|
let res = config
|
2022-06-02 11:17:12 +00:00
|
|
|
.client
|
|
|
|
.get(url.as_str())
|
|
|
|
.header("Accept", APUB_JSON_CONTENT_TYPE)
|
2023-02-19 12:26:01 +00:00
|
|
|
.timeout(config.request_timeout)
|
2022-06-02 11:17:12 +00:00
|
|
|
.send()
|
|
|
|
.await
|
|
|
|
.map_err(Error::conv)?;
|
|
|
|
|
|
|
|
if res.status() == StatusCode::GONE {
|
|
|
|
return Err(Error::ObjectDeleted);
|
|
|
|
}
|
|
|
|
|
2023-01-28 03:45:11 +00:00
|
|
|
res.json_limited().await
|
2022-06-02 11:17:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Check that both urls have the same domain. If not, return UrlVerificationError.
|
2023-02-19 12:26:01 +00:00
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// # use url::Url;
|
|
|
|
/// # use activitypub_federation::utils::verify_domains_match;
|
|
|
|
/// let a = Url::parse("https://example.com/abc")?;
|
|
|
|
/// let b = Url::parse("https://sample.net/abc")?;
|
|
|
|
/// assert!(verify_domains_match(&a, &b).is_err());
|
|
|
|
/// # Ok::<(), url::ParseError>(())
|
|
|
|
/// ```
|
2022-06-02 11:17:12 +00:00
|
|
|
pub fn verify_domains_match(a: &Url, b: &Url) -> Result<(), Error> {
|
|
|
|
if a.domain() != b.domain() {
|
|
|
|
return Err(Error::UrlVerificationError("Domains do not match"));
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Check that both urls are identical. If not, return UrlVerificationError.
|
2023-02-19 12:26:01 +00:00
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// # use url::Url;
|
|
|
|
/// # use activitypub_federation::utils::verify_urls_match;
|
|
|
|
/// let a = Url::parse("https://example.com/abc")?;
|
|
|
|
/// let b = Url::parse("https://example.com/123")?;
|
|
|
|
/// assert!(verify_urls_match(&a, &b).is_err());
|
|
|
|
/// # Ok::<(), url::ParseError>(())
|
|
|
|
/// ```
|
2022-06-02 11:17:12 +00:00
|
|
|
pub fn verify_urls_match(a: &Url, b: &Url) -> Result<(), Error> {
|
|
|
|
if a != b {
|
|
|
|
return Err(Error::UrlVerificationError("Urls do not match"));
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
2022-06-02 12:30:08 +00:00
|
|
|
|
2022-11-28 21:19:56 +00:00
|
|
|
/// Utility to converts either actix or axum headermap to a BTreeMap
|
|
|
|
pub fn header_to_map<'a, H>(headers: H) -> BTreeMap<String, String>
|
|
|
|
where
|
|
|
|
H: IntoIterator<Item = (&'a HeaderName, &'a HeaderValue)>,
|
|
|
|
{
|
|
|
|
let mut header_map = BTreeMap::new();
|
2022-06-02 12:30:08 +00:00
|
|
|
|
2022-11-28 21:19:56 +00:00
|
|
|
for (name, value) in headers {
|
|
|
|
if let Ok(value) = value.to_str() {
|
|
|
|
header_map.insert(name.to_string(), value.to_string());
|
|
|
|
}
|
2022-06-02 12:30:08 +00:00
|
|
|
}
|
|
|
|
|
2022-11-28 21:19:56 +00:00
|
|
|
header_map
|
2022-06-02 12:30:08 +00:00
|
|
|
}
|