]> Untitled Git - lemmy.git/blob - lemmy_apub/src/fetcher/fetch.rs
Merge pull request #1328 from LemmyNet/move_views_to_diesel
[lemmy.git] / lemmy_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 reqwest::{Client, StatusCode};
5 use serde::Deserialize;
6 use std::time::Duration;
7 use thiserror::Error;
8 use url::Url;
9
10 /// Maximum number of HTTP requests allowed to handle a single incoming activity (or a single object
11 /// fetch through the search).
12 ///
13 /// Tests are passing with a value of 5, so 10 should be safe for production.
14 static MAX_REQUEST_NUMBER: i32 = 10;
15
16 #[derive(Debug, Error)]
17 pub(in crate::fetcher) struct FetchError {
18   pub inner: anyhow::Error,
19   pub status_code: Option<StatusCode>,
20 }
21
22 impl From<LemmyError> for FetchError {
23   fn from(t: LemmyError) -> Self {
24     FetchError {
25       inner: t.inner,
26       status_code: None,
27     }
28   }
29 }
30
31 impl From<reqwest::Error> for FetchError {
32   fn from(t: reqwest::Error) -> Self {
33     let status = t.status();
34     FetchError {
35       inner: t.into(),
36       status_code: status,
37     }
38   }
39 }
40
41 impl std::fmt::Display for FetchError {
42   fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
43     std::fmt::Display::fmt(&self, f)
44   }
45 }
46
47 /// Fetch any type of ActivityPub object, handling things like HTTP headers, deserialisation,
48 /// timeouts etc.
49 pub(in crate::fetcher) async fn fetch_remote_object<Response>(
50   client: &Client,
51   url: &Url,
52   recursion_counter: &mut i32,
53 ) -> Result<Response, FetchError>
54 where
55   Response: for<'de> Deserialize<'de> + std::fmt::Debug,
56 {
57   *recursion_counter += 1;
58   if *recursion_counter > MAX_REQUEST_NUMBER {
59     return Err(LemmyError::from(anyhow!("Maximum recursion depth reached")).into());
60   }
61   check_is_apub_id_valid(&url)?;
62
63   let timeout = Duration::from_secs(60);
64
65   let res = retry(|| {
66     client
67       .get(url.as_str())
68       .header("Accept", APUB_JSON_CONTENT_TYPE)
69       .timeout(timeout)
70       .send()
71   })
72   .await?;
73
74   if res.status() == StatusCode::GONE {
75     return Err(FetchError {
76       inner: anyhow!("Remote object {} was deleted", url),
77       status_code: Some(res.status()),
78     });
79   }
80
81   Ok(res.json().await?)
82 }