1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//! Types and helpers for using the [Activity Streams 2.0 media type] when
//! sending and receiving ActivityPub objects over HTTP.
//!
//! [Activity Streams 2.0 media type]:
//!     https://www.w3.org/TR/activitystreams-core/#media-type

use crate::activitypub;
use actix_web::guard::GuardContext;
use actix_web::http::header::{Accept, ContentType};
use actix_web::HttpResponse;
use mime::Mime;
use once_cell::sync::Lazy;
use std::str::FromStr;

/// The long media type string for Activity Streams objects, recommended by the
/// ActivityPub specification.
pub const CT_ACTIVITY_JSON: &str = r#"application/ld+json; profile="https://www.w3.org/ns/activitystreams""#;
/// The short media type string for Activity Streams objects.
pub const CT_ACTIVITY_JSON_SHORT: &str = "application/activity+json";
/// The strongly typed version of [`CT_ACTIVITY_JSON`], for Content-Type headers.
static CONTENT_TYPE: Lazy<ContentType> = Lazy::new(|| ContentType(Mime::from_str(CT_ACTIVITY_JSON).unwrap()));

/// A guard to filter out requests that have neither [`CT_ACTIVITY_JSON`] nor
/// [`CT_ACTIVITY_JSON_SHORT`] in their Content-Type header.
pub fn content_type_guard(ctx: &GuardContext) -> bool {
    match ctx.header::<ContentType>() {
        Some(ContentType(mime)) if mime == CT_ACTIVITY_JSON => true,
        Some(ContentType(mime)) if mime == CT_ACTIVITY_JSON_SHORT => true,
        _ => false,
    }
}

/// A guard to filter out requests that do not have either [`CT_ACTIVITY_JSON`] or
/// [`CT_ACTIVITY_JSON_SHORT`] in their Accept header.
pub fn accept_guard(ctx: &GuardContext) -> bool {
    match ctx.header::<Accept>() {
        Some(Accept(mimes)) => mimes
            .iter()
            .any(|mime| mime.item == CT_ACTIVITY_JSON || mime.item == CT_ACTIVITY_JSON_SHORT),
        _ => false,
    }
}

/// Short utility function for serializing the given Activity Streams object and
/// returning it inside a [`HttpResponse`] with the appropriate Content-Type
/// header.
pub fn response(obj: activitypub::Object) -> HttpResponse {
    match serde_json::to_string(&obj) {
        Ok(obj) => HttpResponse::Ok().append_header(CONTENT_TYPE.clone()).body(obj),
        Err(err) => {
            tracing::warn!("Could not serialize response: {err}");
            HttpResponse::InternalServerError().finish()
        }
    }
}