]> Untitled Git - lemmy.git/blob - crates/apub/src/fetcher/fetch.rs
Running clippy --fix (#1647)
[lemmy.git] / crates / apub / src / fetcher / fetch.rs
1 use crate::{check_is_apub_id_valid, APUB_JSON_CONTENT_TYPE};
2 use anyhow::anyhow;
3 use lemmy_utils::{request::retry, LemmyError};
4 use log::info;
5 use reqwest::{Client, StatusCode};
6 use serde::Deserialize;
7 use std::time::Duration;
8 use thiserror::Error;
9 use url::Url;
10
11 /// Maximum number of HTTP requests allowed to handle a single incoming activity (or a single object
12 /// fetch through the search).
13 ///
14 /// A community fetch will load the outbox with up to 20 items, and fetch the creator for each item.
15 /// So we are looking at a maximum of 22 requests (rounded up just to be safe).
16 static MAX_REQUEST_NUMBER: i32 = 25;
17
18 #[derive(Debug, Error)]
19 pub(in crate::fetcher) struct FetchError {
20   pub inner: anyhow::Error,
21   pub status_code: Option<StatusCode>,
22 }
23
24 impl From<LemmyError> for FetchError {
25   fn from(t: LemmyError) -> Self {
26     FetchError {
27       inner: t.inner,
28       status_code: None,
29     }
30   }
31 }
32
33 impl From<reqwest::Error> for FetchError {
34   fn from(t: reqwest::Error) -> Self {
35     let status = t.status();
36     FetchError {
37       inner: t.into(),
38       status_code: status,
39     }
40   }
41 }
42
43 impl std::fmt::Display for FetchError {
44   fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
45     std::fmt::Display::fmt(&self, f)
46   }
47 }
48
49 /// Fetch any type of ActivityPub object, handling things like HTTP headers, deserialisation,
50 /// timeouts etc.
51 pub(in crate::fetcher) async fn fetch_remote_object<Response>(
52   client: &Client,
53   url: &Url,
54   recursion_counter: &mut i32,
55 ) -> Result<Response, FetchError>
56 where
57   Response: for<'de> Deserialize<'de> + std::fmt::Debug,
58 {
59   *recursion_counter += 1;
60   if *recursion_counter > MAX_REQUEST_NUMBER {
61     return Err(LemmyError::from(anyhow!("Maximum recursion depth reached")).into());
62   }
63   check_is_apub_id_valid(url, false)?;
64
65   let timeout = Duration::from_secs(60);
66
67   let res = retry(|| {
68     client
69       .get(url.as_str())
70       .header("Accept", APUB_JSON_CONTENT_TYPE)
71       .timeout(timeout)
72       .send()
73   })
74   .await?;
75
76   if res.status() == StatusCode::GONE {
77     info!("Fetched remote object {} which was deleted", url);
78     return Err(FetchError {
79       inner: anyhow!("Fetched remote object {} which was deleted", url),
80       status_code: Some(res.status()),
81     });
82   }
83
84   let object = res.json().await?;
85   info!("Fetched remote object {}", url);
86   Ok(object)
87 }