1use std::collections::BTreeMap;
2
3use apb::{Shortcuts, Activity, Actor, ActorMut, Base, Collection, CollectionPage, Object};
4use reqwest::{header::{ACCEPT, CONTENT_TYPE, USER_AGENT}, Method, Response};
5use sea_orm::{ActiveValue::Set, ColumnTrait, ConnectionTrait, DbErr, EntityTrait, IntoActiveModel, NotSet, QueryFilter, ActiveModelTrait};
6
7use super::{Addresser, Cloaker, Normalizer};
8use httpsign::HttpSignature;
9
10use crate::AP;
11
12#[derive(Debug, Clone)]
13pub enum Pull<T> {
14 Actor(T),
15 Activity(T),
16 Object(T),
17}
18
19#[derive(Debug, thiserror::Error)]
20pub enum RequestError {
21 #[error("dereferenced resource ({1:?}) doesn't match requested type ({0:?})")]
22 Mismatch(apb::ObjectType, apb::ObjectType),
23
24 #[error("error fetching resource: {0:?}")]
25 Reqwest(#[from] reqwest::Error),
26
27 #[error("fetch failed with status {0}: {1}")]
28 Fetch(reqwest::StatusCode, String),
29
30 #[error("database error while fetching resource: {0:?}")]
31 Database(#[from] sea_orm::DbErr),
32
33 #[error("dereferenced resource is malformed: {0:?}")]
34 Malformed(#[from] apb::FieldErr),
35
36 #[error("error normalizing resource: {0:?}")]
37 Normalization(#[from] crate::traits::normalize::NormalizerError),
38
39 #[error("too many redirects while resolving resource id, aborting")]
40 TooManyRedirects,
41
42 #[error("resource no longer exists")]
43 Tombstone,
44
45 #[error("request aborted due to configured policies")]
46 AbortedForPolicy,
47
48 #[error("error constructing http signature: {0:?}")]
49 HttpSignature(#[from] httpsign::HttpSignatureError),
50}
51
52impl RequestError {
53 fn mismatch(expected: apb::ObjectType, found: apb::ObjectType) -> Self {
54 RequestError::Mismatch(expected, found)
55 }
56}
57
58impl Pull<serde_json::Value> {
59 pub fn actor(self) -> Result<serde_json::Value, RequestError> {
60 match self {
61 Self::Actor(x) => Ok(x),
62 Self::Activity(x) => Err(RequestError::mismatch(apb::ObjectType::Actor(apb::ActorType::Person), x.object_type().unwrap_or(apb::ObjectType::Activity(apb::ActivityType::Activity)))),
63 Self::Object(x) => Err(RequestError::mismatch(apb::ObjectType::Actor(apb::ActorType::Person), x.object_type().unwrap_or(apb::ObjectType::Object))),
64 }
65 }
66
67 pub fn activity(self) -> Result<serde_json::Value, RequestError> {
68 match self {
69 Self::Actor(x) => Err(RequestError::mismatch(apb::ObjectType::Activity(apb::ActivityType::Activity), x.object_type().unwrap_or(apb::ObjectType::Actor(apb::ActorType::Person)))),
70 Self::Activity(x) => Ok(x),
71 Self::Object(x) => Err(RequestError::mismatch(apb::ObjectType::Activity(apb::ActivityType::Activity), x.object_type().unwrap_or(apb::ObjectType::Object))),
72 }
73 }
74
75 pub fn object(self) -> Result<serde_json::Value, RequestError> {
76 match self {
77 Self::Actor(x) => Err(RequestError::mismatch(apb::ObjectType::Object, x.object_type().unwrap_or(apb::ObjectType::Actor(apb::ActorType::Person)))),
78 Self::Activity(x) => Err(RequestError::mismatch(apb::ObjectType::Object, x.object_type().unwrap_or(apb::ObjectType::Activity(apb::ActivityType::Activity)))),
79 Self::Object(x) => Ok(x),
80 }
81 }
82}
83
84#[async_trait::async_trait]
85pub trait Fetcher {
86 async fn pull(&self, id: &str) -> Result<Pull<serde_json::Value>, RequestError> { self.pull_r(id, 0).await }
87 async fn pull_r(&self, id: &str, depth: u32) -> Result<Pull<serde_json::Value>, RequestError>;
88
89
90 async fn webfinger(&self, user: &str, host: &str) -> Result<Option<String>, RequestError>;
91
92 async fn fetch_domain(&self, domain: &str, tx: &impl ConnectionTrait) -> Result<crate::model::instance::Model, RequestError>;
93
94 async fn fetch_user(&self, id: &str, tx: &impl ConnectionTrait) -> Result<crate::model::actor::Model, RequestError>;
95 async fn resolve_user(&self, actor: serde_json::Value, tx: &impl ConnectionTrait) -> Result<crate::model::actor::ActiveModel, RequestError>;
96
97 async fn fetch_activity(&self, id: &str, tx: &impl ConnectionTrait) -> Result<crate::model::activity::Model, RequestError>;
98 async fn resolve_activity(&self, activity: serde_json::Value, tx: &impl ConnectionTrait) -> Result<crate::model::activity::Model, RequestError>;
99
100 async fn fetch_object(&self, id: &str, tx: &impl ConnectionTrait) -> Result<crate::model::object::Model, RequestError>;
101 async fn resolve_object(&self, object: serde_json::Value, tx: &impl ConnectionTrait) -> Result<crate::model::object::Model, RequestError>;
102
103 async fn fetch_thread(&self, id: &str, tx: &impl ConnectionTrait) -> Result<(), RequestError>;
104 async fn fetch_outbox(&self, id: &str, tx: &impl ConnectionTrait) -> Result<(), RequestError>;
105
106 fn client(domain: &str) -> reqwest::Client {
107 reqwest::Client::builder()
108 .user_agent(format!("upub+{} ({domain})", crate::VERSION))
109 .connect_timeout(std::time::Duration::from_secs(30)) .read_timeout(std::time::Duration::from_secs(30)) .timeout(std::time::Duration::from_secs(300)) .build()
113 .expect("failed building http client, check system tls or resolver")
114 }
115
116 async fn request(
117 method: reqwest::Method,
118 url: &str,
119 payload: Option<&str>,
120 from: &str,
121 key: &str,
122 domain: &str,
123 ) -> Result<Response, RequestError> {
124 let host = crate::Context::server(url);
125 let date = chrono::Utc::now().format("%a, %d %b %Y %H:%M:%S GMT").to_string(); let path = url.replace("https://", "").replace("http://", "").replace(&host, "");
127 let digest = httpsign::digest(payload.unwrap_or_default());
128
129 let headers = vec!["(request-target)", "host", "date", "digest"];
130 let headers_map : BTreeMap<String, String> = [
131 ("host".to_string(), host.clone()),
132 ("date".to_string(), date.clone()),
133 ("digest".to_string(), digest.clone()),
134 ].into();
135
136 let mut signer = HttpSignature::new(
137 format!("{from}#main-key"), "rsa-sha256".to_string(),
140 &headers,
141 );
142
143 signer
144 .build_manually(&method.to_string().to_lowercase(), &path, headers_map)
145 .sign(key)?;
146
147 let response = Self::client(domain)
148 .request(method, url)
149 .header(ACCEPT, apb::jsonld::CONTENT_TYPE_LD_JSON_ACTIVITYPUB)
150 .header(CONTENT_TYPE, apb::jsonld::CONTENT_TYPE_LD_JSON_ACTIVITYPUB)
151 .header("Host", host.clone())
152 .header("Date", date.clone())
153 .header("Digest", digest)
154 .header("Signature", signer.header())
155 .body(payload.unwrap_or("").to_string())
156 .send()
157 .await?;
158
159 match response.error_for_status_ref() {
161 Ok(_) => Ok(response),
162 Err(e) =>
163 Err(RequestError::Fetch(
164 e.status().unwrap_or_default(),
165 response.text().await?,
166 )),
167 }
168 }
169}
170
171
172#[async_trait::async_trait]
173impl Fetcher for crate::Context {
174 async fn pull_r(&self, id: &str, depth: u32) -> Result<Pull<serde_json::Value>, RequestError> {
175 if crate::ext::is_blacklisted(id, &self.cfg().reject.fetch) {
176 return Err(RequestError::AbortedForPolicy);
177 }
178
179 tracing::debug!("fetching {id}");
180 let document = Self::request(
183 Method::GET, id, None,
184 self.base(), self.pkey(), self.domain(),
185 )
186 .await?
187 .json::<serde_json::Value>()
188 .await?;
189
190 let doc_id = document.id()?;
191 if id != doc_id {
192 if depth >= self.cfg().security.max_id_redirects {
193 return Err(RequestError::TooManyRedirects);
194 }
195 return self.pull(&doc_id).await;
196 }
197
198 crate::downtime::unset(self.db(), &crate::Context::server(id)).await?;
199
200 match document.object_type()? {
201 apb::ObjectType::Collection(x) => Err(RequestError::mismatch(apb::ObjectType::Object, apb::ObjectType::Collection(x))),
202 apb::ObjectType::Tombstone => Err(RequestError::Tombstone),
203 apb::ObjectType::Activity(_) => Ok(Pull::Activity(document)),
204 apb::ObjectType::Actor(_) => Ok(Pull::Actor(document)),
205 _ => Ok(Pull::Object(document)),
206 }
207 }
208
209
210 async fn webfinger(&self, user: &str, host: &str) -> Result<Option<String>, RequestError> {
211 if let Some(usr) = crate::model::actor::Entity::find()
214 .filter(crate::model::actor::Column::PreferredUsername.eq(user))
215 .filter(crate::model::actor::Column::Domain.eq(host))
216 .one(self.db())
217 .await?
218 {
219 return Ok(Some(usr.id));
220 }
221
222 let subject = format!("acct:{user}@{host}");
223 let webfinger_uri = format!("https://{host}/.well-known/webfinger?resource={subject}");
224 let resource = reqwest::Client::new()
225 .get(webfinger_uri)
226 .header(ACCEPT, "application/jrd+json")
227 .header(USER_AGENT, format!("upub+{} ({})", crate::VERSION, self.domain()))
228 .send()
229 .await?
230 .json::<jrd::JsonResourceDescriptor>()
231 .await?;
232
233 if resource.subject != subject {
234 tracing::error!("webfinger result ({}) differs from expected subject ({})", resource.subject, subject);
235 return Ok(None);
236 }
237
238 for link in resource.links {
239 if link.rel == "self" {
240 if let Some(href) = link.href {
241 return Ok(Some(href));
242 }
243 }
244 }
245
246 if let Some(alias) = resource.aliases.into_iter().next() {
247 return Ok(Some(alias));
248 }
249
250 Ok(None)
251 }
252
253 async fn fetch_domain(&self, domain: &str, tx: &impl ConnectionTrait) -> Result<crate::model::instance::Model, RequestError> {
254 if let Some(x) = crate::model::instance::Entity::find_by_domain(domain).one(tx).await? {
255 return Ok(x); }
257
258 let mut instance_model = crate::model::instance::Model {
259 internal: 0,
260 domain: domain.to_string(),
261 name: None,
262 software: None,
263 down_since: None,
264 icon: None,
265 version: None,
266 users: None,
267 posts: None,
268 published: chrono::Utc::now(),
269 updated: chrono::Utc::now(),
270 };
271
272 if let Ok(res) = Self::request(
273 Method::GET, &format!("https://{domain}"), None,
274 self.base(), self.pkey(), self.domain(),
275 ).await {
276 if let Ok(actor) = res.json::<serde_json::Value>().await {
277 if let Ok(name) = actor.name() {
278 instance_model.name = Some(name.to_string());
279 }
280 if let Ok(icon) = actor.icon().id() {
281 instance_model.icon = Some(icon.to_string());
282 }
283 }
284 }
285
286 if let Ok(nodeinfo) = crate::model::instance::Entity::nodeinfo(domain).await {
287 instance_model.software = Some(nodeinfo.software.name);
288 instance_model.version = nodeinfo.software.version;
289 instance_model.users = nodeinfo.usage.users.and_then(|x| x.total);
290 instance_model.posts = nodeinfo.usage.local_posts;
291 }
292
293 let mut active_model = instance_model.clone().into_active_model();
294 active_model.internal = NotSet;
295 crate::model::instance::Entity::insert(active_model).exec(tx).await?;
296 let internal = crate::model::instance::Entity::domain_to_internal(domain, tx)
297 .await?
298 .ok_or_else(|| DbErr::RecordNotFound(domain.to_string()))?;
299 instance_model.internal = internal;
300
301 Ok(instance_model)
302 }
303
304 async fn resolve_user(&self, mut document: serde_json::Value, tx: &impl ConnectionTrait) -> Result<crate::model::actor::ActiveModel, RequestError> {
305 let id = document.id()?.to_string();
306
307 let _domain = self.fetch_domain(&crate::Context::server(&id), tx).await?;
308
309 if document.followers_count().is_err() {
311 if let Ok(followers) = document.followers().resolve(self).await {
312 if let Ok(total) = followers.total_items() {
313 document = document.set_followers_count(Some(total));
314 }
315 }
316 }
317
318 if document.following_count().is_err() {
319 if let Ok(following) = document.following().resolve(self).await {
320 if let Ok(total) = following.total_items() {
321 document = document.set_following_count(Some(total));
322 }
323 }
324 }
325
326 if document.statuses_count().is_err() {
327 if let Ok(outbox) = document.outbox().resolve(self).await {
328 if let Ok(total) = outbox.total_items() {
329 document = document.set_statuses_count(Some(total));
330 }
331 }
332 }
333
334 let mut user_model = AP::actor_q(&document, None)?;
335
336 if let Set(Some(ref image)) = user_model.image {
338 if !image.starts_with(self.base()) {
339 user_model.image = Set(Some(self.cloaked(image)));
340 }
341 }
342
343 if let Set(Some(ref icon)) = user_model.icon {
344 if !icon.starts_with(self.base()) {
345 user_model.icon = Set(Some(self.cloaked(icon)));
346 }
347 }
348
349 Ok(user_model)
350 }
351
352 async fn fetch_user(&self, id: &str, tx: &impl ConnectionTrait) -> Result<crate::model::actor::Model, RequestError> {
353 if let Some(x) = crate::model::actor::Entity::find_by_ap_id(id).one(tx).await? {
354 return Ok(x); }
356
357 let document = self.pull(id).await?.actor()?;
358
359 if document.id()? != id {
360 if let Some(x) = crate::model::actor::Entity::find_by_ap_id(&document.id()?).one(tx).await? {
361 return Ok(x); }
363 }
364
365 let active_model = self.resolve_user(document, tx).await?;
366
367 crate::model::actor::Entity::insert(active_model).exec(tx).await?;
371
372 Ok(
374 crate::model::actor::Entity::find_by_ap_id(id)
375 .one(tx)
376 .await?
377 .ok_or_else(|| DbErr::RecordNotFound(id.to_string()))?
378 )
379 }
380
381 async fn fetch_activity(&self, id: &str, tx: &impl ConnectionTrait) -> Result<crate::model::activity::Model, RequestError> {
382 if let Some(x) = crate::model::activity::Entity::find_by_ap_id(id).one(tx).await? {
383 return Ok(x); }
385
386 let activity = self.pull(id).await?.activity()?;
387
388 self.resolve_activity(activity, tx).await
389 }
390
391 async fn resolve_activity(&self, activity: serde_json::Value, tx: &impl ConnectionTrait) -> Result<crate::model::activity::Model, RequestError> {
392 let _domain = self.fetch_domain(&crate::Context::server(&activity.id()?), tx).await?;
393
394 if let Ok(activity_actor) = activity.actor().id() {
395 if let Err(e) = self.fetch_user(&activity_actor, tx).await {
396 tracing::warn!("could not get actor of fetched activity: {e}");
397 }
398 }
399
400 if let Ok(activity_object) = activity.object().id() {
401 if let Err(e) = self.fetch_object(&activity_object, tx).await {
402 tracing::warn!("could not get object of fetched activity: {e}");
403 }
404 }
405
406 let activity_model = self.insert_activity(activity, tx).await?;
407 self.address(Some(&activity_model), None, tx).await?;
408
409 Ok(activity_model)
410 }
411
412 async fn fetch_thread(&self, id: &str, tx: &impl ConnectionTrait) -> Result<(), RequestError> {
413 let object = self.pull(id).await?.object()?;
414
415 let model = self.fetch_object(id, tx).await?;
417 let mut active = model.clone().into_active_model();
418 let mut changed = false;
419
420 let new_like_count = object.likes_count().unwrap_or_default();
421 if new_like_count > model.likes {
422 active.likes = Set(new_like_count);
423 changed = true;
424 }
425
426 let new_share_count = object.shares_count().unwrap_or_default();
427 if new_share_count > model.announces {
428 active.announces = Set(new_share_count);
429 changed = true;
430 }
431
432 if changed {
433 active.update(tx).await?;
434 }
435
436 let replies = object.replies().resolve(self).await?;
438
439 for obj in replies.items().flat() {
441 if let Err(e) = self.fetch_object(&obj.id()?, tx).await {
442 tracing::warn!("error fetching reply: {e}");
443 }
444 }
445
446 for obj in replies.ordered_items().flat() {
447 if let Err(e) = self.fetch_object(&obj.id()?, tx).await {
448 tracing::warn!("error fetching reply: {e}");
449 }
450 }
451
452 let mut page;
454 let mut next = replies.first();
455
456 loop {
457 page = next.resolve(self).await?;
458
459 if matches!(page.object_type()?, apb::ObjectType::Collection(apb::CollectionType::Collection)) {
462 page = page.first().into_inner()?;
463 }
464
465 for obj in page.items().flat() {
468 if let Err(e) = self.fetch_object(&obj.id()?, tx).await {
469 tracing::warn!("error fetching reply: {e}");
470 }
471 }
472
473 for obj in page.ordered_items().flat() {
474 if let Err(e) = self.fetch_object(&obj.id()?, tx).await {
475 tracing::warn!("error fetching reply: {e}");
476 }
477 }
478
479 next = page.next();
480 if next.is_empty() { break };
481 }
482
483 Ok(())
484 }
485
486 async fn fetch_outbox(&self, id: &str, tx: &impl ConnectionTrait) -> Result<(), RequestError> {
487 let actor = self.pull(id).await?.actor()?;
488 let outbox = actor
489 .outbox().resolve(self).await?
490 .first().resolve(self).await?;
491
492 for node in outbox.ordered_items().flat() {
498 if let Ok(activity) = node.into_inner() {
499 if matches!(activity.activity_type(), Ok(apb::ActivityType::Create)) {
500 if let Ok(oid) = activity.object().id() {
501 self.fetch_object(&oid, tx).await?;
502 }
503 }
504 }
505 }
506
507 Ok(())
508 }
509
510 async fn fetch_object(&self, id: &str, tx: &impl ConnectionTrait) -> Result<crate::model::object::Model, RequestError> {
511 fetch_object_r(self, id, 0, tx).await
512 }
513
514 async fn resolve_object(&self, object: serde_json::Value, tx: &impl ConnectionTrait) -> Result<crate::model::object::Model, RequestError> {
515 resolve_object_r(self, object, 0, tx).await
516 }
517}
518
519#[async_recursion::async_recursion]
520async fn fetch_object_r(ctx: &crate::Context, id: &str, depth: u32, tx: &impl ConnectionTrait) -> Result<crate::model::object::Model, RequestError> {
521 if let Some(x) = crate::model::object::Entity::find_by_ap_id(id).one(tx).await? {
522 return Ok(x); }
524
525 let object = ctx.pull(id).await?.object()?;
526
527 if object.id()? != id {
528 if let Some(x) = crate::model::object::Entity::find_by_ap_id(&object.id()?).one(tx).await? {
529 return Ok(x); }
531 }
532
533 resolve_object_r(ctx, object, depth, tx).await
534}
535
536async fn resolve_object_r(ctx: &crate::Context, object: serde_json::Value, depth: u32, tx: &impl ConnectionTrait) -> Result<crate::model::object::Model, RequestError> {
537 let id = object.id()?.to_string();
538
539 if let Ok(oid) = object.id() {
540 if oid != id {
541 if let Some(x) = crate::model::object::Entity::find_by_ap_id(&oid).one(tx).await? {
542 return Ok(x); }
544 }
545 }
546
547 if let Ok(attributed_to) = object.attributed_to().id() {
548 if let Err(e) = ctx.fetch_user(&attributed_to, tx).await {
549 tracing::warn!("could not get actor of fetched object: {e}");
550 }
551 }
552
553 if let Ok(reply) = object.in_reply_to().id() {
554 if depth <= ctx.cfg().security.thread_crawl_depth {
555 fetch_object_r(ctx, &reply, depth + 1, tx).await?;
556 } else {
557 tracing::warn!("thread deeper than {}, giving up fetching more replies", ctx.cfg().security.thread_crawl_depth);
558 }
559 }
560
561 let object_model = ctx.insert_object(object, tx).await?;
562 ctx.address(None, Some(&object_model), tx).await?;
563
564 Ok(object_model)
565}
566
567#[allow(async_fn_in_trait)]
568pub trait Dereferenceable<T> : Sync + Send {
569 async fn resolve(self, ctx: &crate::Context) -> Result<T, RequestError>;
570}
571
572impl Dereferenceable<serde_json::Value> for apb::Node<serde_json::Value> {
573 async fn resolve(self, ctx: &crate::Context) -> Result<serde_json::Value, RequestError> {
574 match self {
575 apb::Node::Link(uri) => {
576 let href = uri.href()?;
577 if crate::ext::is_blacklisted(&href, &ctx.cfg().reject.fetch) {
578 return Err(RequestError::AbortedForPolicy);
579 }
580 tracing::info!("dereferencing {href}");
581 let res = crate::Context::request(Method::GET, &href, None, ctx.base(), ctx.pkey(), ctx.domain())
582 .await?
583 .json::<serde_json::Value>()
584 .await?;
585 Ok(res)
586 },
587 apb::Node::Object(x) => Ok(*x),
588 apb::Node::Empty => Err(RequestError::Tombstone),
589 apb::Node::Array(_) => Err(RequestError::Malformed(apb::FieldErr("id"))), }
591 }
592}