ap: use indexed vocabulary for processing

This commit is contained in:
anna 2023-07-27 01:18:25 +02:00
parent d493bcce23
commit 36becee078
Signed by: fef
GPG key ID: 2585C2DC6D79B485
5 changed files with 530 additions and 26 deletions

View file

@ -96,13 +96,15 @@ impl<I: Clone + Eq + Hash + Send + Sync, M: Send, T: Clone + Send> CachedLoader<
) -> Result<RemoteDocument<I, M, T>> { ) -> Result<RemoteDocument<I, M, T>> {
const MAX_REDIRECTS: u32 = 8; const MAX_REDIRECTS: u32 = 8;
const ACCEPT_ACTIVITY_PUB: &str = const ACCEPT_ACTIVITY_PUB: &str =
"application/activity+json, application/ld+json, application/json"; "application/activity+json, application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\", application/json";
let mut redirect_count = 0; let mut redirect_count = 0;
'next_url: loop { 'next_url: loop {
if redirect_count > MAX_REDIRECTS { if redirect_count > MAX_REDIRECTS {
return Err(Error::NotFound); return Err(Error::MalformedApub(format!(
"Refusing to follow more than {MAX_REDIRECTS} redirects"
)));
} }
let iri = vocabulary let iri = vocabulary

View file

@ -1,2 +1,3 @@
pub mod loader; pub mod loader;
pub mod processor; pub mod processor;
pub mod vocab;

View file

@ -1,16 +1,19 @@
use json_ld::{ use json_ld::{
syntax::{Parse, Value}, syntax::{Parse, Value},
Expand, RemoteDocument, Expand, IndexedObject, RemoteDocument,
}; };
use rdf_types::{IndexVocabulary, IriVocabulary}; use locspan::Span;
use rdf_types::vocabulary::BlankIdIndex;
use rdf_types::{vocabulary::IriIndex, IndexVocabulary, Vocabulary};
use crate::ap::loader::CachedLoader; use crate::ap::{loader::CachedLoader, vocab::Ids};
use crate::core::*; use crate::core::*;
use crate::state::AppState; use crate::state::AppState;
/// Main API for handling ActivityPub ingress, called by [`crate::job::inbox::InboxWorker`]. /// Main API for handling ActivityPub ingress, called by [`crate::job::inbox::InboxWorker`].
pub async fn process_document(state: &AppState, raw: &str) -> Result<()> { pub async fn process_document(state: &AppState, raw: &str) -> Result<()> {
let mut vocab: IndexVocabulary = IndexVocabulary::new(); let mut vocab: IndexVocabulary = IndexVocabulary::new();
let indices = Ids::populate(&mut vocab);
let document = Value::parse_str(raw, |span| span) let document = Value::parse_str(raw, |span| span)
.map_err(|e| Error::MalformedApub(format!("Could not parse document: {e}")))?; .map_err(|e| Error::MalformedApub(format!("Could not parse document: {e}")))?;
@ -22,27 +25,54 @@ pub async fn process_document(state: &AppState, raw: &str) -> Result<()> {
); );
let mut loader = CachedLoader::new(state.clone()); let mut loader = CachedLoader::new(state.clone());
let rd = rd.expand_with(&mut vocab, &mut loader).await.unwrap(); let rd = rd.expand_with(&mut vocab, &mut loader).await.unwrap();
let vocab = vocab;
// this loop will usually only run once (one object per request) // this loop will usually only run once (one object per request)
for object in rd.into_value() { for object in rd.into_value() {
let id = object if let Err(e) = process_object(object, &vocab, &indices).await {
.id() error!("Error in remote document: {e}");
.and_then(|i| i.as_iri())
.and_then(|index| vocab.iri(index))
.ok_or(Error::MalformedApub(String::from(
"Document does not have an id",
)))?;
let mut typ = None;
for t in object.types() {
typ = Some(t);
} }
let typ = typ.ok_or(Error::MalformedApub(String::from(
"Document does not have a type",
)))?;
// just for testing some stuff
let typ = typ.as_iri().and_then(|index| vocab.iri(index)).unwrap();
debug!("Object id=\"{id}\" type=\"{typ}\"");
} }
Ok(()) Ok(())
} }
async fn process_object(
obj: IndexedObject<IriIndex, BlankIdIndex, Span>,
vocab: &impl Vocabulary<Iri = IriIndex>,
ids: &Ids,
) -> Result<()> {
let id = obj
.id()
.and_then(|id| id.as_iri())
.and_then(|index| vocab.iri(index))
.ok_or_else(apub_err("Object does not have an id"))?;
let typ = obj
.types()
.next()
.ok_or_else(apub_err("Object does not have a type"))?
.as_iri()
.and_then(|index| vocab.iri(index))
.ok_or_else(apub_err("Object has invalid type"))?;
// just for testing: extract and print `object.content`
// to see if the expansion actually works as expected
let properties = obj
.as_node()
.ok_or_else(apub_err("Object is not a node"))?
.properties();
let object = properties
.get_any(&ids.apub.property.object)
.and_then(|prop| prop.as_node())
.and_then(|node| node.properties().get_any(&ids.apub.property.content))
.and_then(|prop| prop.as_value())
.and_then(|val| val.as_str())
.unwrap();
debug!("content = \"{object}\"");
Ok(())
}
const fn apub_err(msg: impl Into<String>) -> impl FnOnce() -> Error {
move || Error::MalformedApub(msg.into())
}

468
src/ap/vocab.rs Normal file
View file

@ -0,0 +1,468 @@
//! An annoyingly huge collection of all known IRIs, from all supported namespaces.
//!
//! This might be replaced with an entirely custom implementation of [`Vocabulary`]
//! in the future because reinitializing the entire AP vocabulary from scratch for
//! every single post to the inbox is probably a bit inefficient. I hate my life.
use rdf_types::{
vocabulary::{BlankIdIndex, IriIndex},
Subject, VocabularyMut,
};
use static_iref::iri;
pub struct Ids {
/// IRI identifiers for the base ActivityStreams and ActivityPub
/// namespace (`https://www.w3.org/ns/activitystreams#`).
pub apub: ApubIds,
/// IRI identifiers for Mastodon's extension namespace
/// (`http://joinmastodon.org/ns#`).
pub toot: TootIds,
}
pub type Id = Subject<IriIndex, BlankIdIndex>;
/// Transform
/// `key => iri!("...")`
/// into
/// `key: Id::Iri(vocab.insert(iri!("..."))`
/// so the lines don't exceed five trillion characters.
macro_rules! populate_ids {
($vocab:ident, $($name:ident => $iri:expr),* $(,)?) => {
Self {
$($name: Id::Iri($vocab.insert($iri)),)*
}
}
}
impl Ids {
pub fn populate(vocab: &mut impl VocabularyMut<Iri = IriIndex>) -> Self {
Self {
apub: ApubIds::populate(vocab),
toot: TootIds::populate(vocab),
}
}
}
pub struct ApubIds {
pub object: ApubObjectIds,
pub activity: ApubActivityIds,
pub link: ApubLinkIds,
pub property: ApubPropertyIds,
}
impl ApubIds {
fn populate(vocab: &mut impl VocabularyMut<Iri = IriIndex>) -> Self {
Self {
object: ApubObjectIds::populate(vocab),
activity: ApubActivityIds::populate(vocab),
link: ApubLinkIds::populate(vocab),
property: ApubPropertyIds::populate(vocab),
}
}
}
pub struct ApubObjectIds {
pub activity: Id,
pub application: Id,
pub article: Id,
pub audio: Id,
pub collection: Id,
pub collection_page: Id,
pub relationship: Id,
pub document: Id,
pub event: Id,
pub group: Id,
pub image: Id,
pub intransitive_activity: Id,
pub note: Id,
pub object: Id,
pub ordered_collection: Id,
pub ordered_collection_page: Id,
pub organization: Id,
pub page: Id,
pub person: Id,
pub place: Id,
pub profile: Id,
pub question: Id,
pub service: Id,
pub tombstone: Id,
pub video: Id,
}
impl ApubObjectIds {
fn populate(vocab: &mut impl VocabularyMut<Iri = IriIndex>) -> Self {
populate_ids! {
vocab,
activity => iri!("https://www.w3.org/ns/activitystreams#Activity"),
application => iri!("https://www.w3.org/ns/activitystreams#Application"),
article => iri!("https://www.w3.org/ns/activitystreams#Article"),
audio => iri!("https://www.w3.org/ns/activitystreams#Audio"),
collection => iri!("https://www.w3.org/ns/activitystreams#Collection"),
collection_page => iri!("https://www.w3.org/ns/activitystreams#CollectionPage"),
relationship => iri!("https://www.w3.org/ns/activitystreams#Relationship"),
document => iri!("https://www.w3.org/ns/activitystreams#Document"),
event => iri!("https://www.w3.org/ns/activitystreams#Event"),
group => iri!("https://www.w3.org/ns/activitystreams#Group"),
image => iri!("https://www.w3.org/ns/activitystreams#Image"),
intransitive_activity => iri!("https://www.w3.org/ns/activitystreams#IntransitiveActivity"),
note => iri!("https://www.w3.org/ns/activitystreams#Note"),
object => iri!("https://www.w3.org/ns/activitystreams#Object"),
ordered_collection => iri!("https://www.w3.org/ns/activitystreams#OrderedCollection"),
ordered_collection_page => iri!("https://www.w3.org/ns/activitystreams#OrderedCollectionPage"),
organization => iri!("https://www.w3.org/ns/activitystreams#Organization"),
page => iri!("https://www.w3.org/ns/activitystreams#Page"),
person => iri!("https://www.w3.org/ns/activitystreams#Person"),
place => iri!("https://www.w3.org/ns/activitystreams#Place"),
profile => iri!("https://www.w3.org/ns/activitystreams#Profile"),
question => iri!("https://www.w3.org/ns/activitystreams#Question"),
service => iri!("https://www.w3.org/ns/activitystreams#Service"),
tombstone => iri!("https://www.w3.org/ns/activitystreams#Tombstone"),
video => iri!("https://www.w3.org/ns/activitystreams#Video"),
}
}
}
pub struct ApubActivityIds {
pub accept: Id,
pub add: Id,
pub announce: Id,
pub arrive: Id,
pub block: Id,
pub create: Id,
pub delete: Id,
pub dislike: Id,
pub follow: Id,
pub flag: Id,
pub ignore: Id,
pub invite: Id,
pub join: Id,
pub leave: Id,
pub like: Id,
pub listen: Id,
pub mov: Id, // move
pub offer: Id,
pub read: Id,
pub reject: Id,
pub tentative_accept: Id,
pub tentative_reject: Id,
pub travel: Id,
pub undo: Id,
pub update: Id,
pub view: Id,
}
impl ApubActivityIds {
fn populate(vocab: &mut impl VocabularyMut<Iri = IriIndex>) -> Self {
populate_ids! {
vocab,
accept => iri!("https://www.w3.org/ns/activitystreams#Accept"),
add => iri!("https://www.w3.org/ns/activitystreams#Add"),
announce => iri!("https://www.w3.org/ns/activitystreams#Announce"),
arrive => iri!("https://www.w3.org/ns/activitystreams#Arrive"),
block => iri!("https://www.w3.org/ns/activitystreams#Block"),
create => iri!("https://www.w3.org/ns/activitystreams#Create"),
delete => iri!("https://www.w3.org/ns/activitystreams#Delete"),
dislike => iri!("https://www.w3.org/ns/activitystreams#Dislike"),
follow => iri!("https://www.w3.org/ns/activitystreams#Follow"),
flag => iri!("https://www.w3.org/ns/activitystreams#Flag"),
ignore => iri!("https://www.w3.org/ns/activitystreams#Ignore"),
invite => iri!("https://www.w3.org/ns/activitystreams#Invite"),
join => iri!("https://www.w3.org/ns/activitystreams#Join"),
leave => iri!("https://www.w3.org/ns/activitystreams#Leave"),
like => iri!("https://www.w3.org/ns/activitystreams#Like"),
listen => iri!("https://www.w3.org/ns/activitystreams#Listen"),
mov => iri!("https://www.w3.org/ns/activitystreams#Move"),
offer => iri!("https://www.w3.org/ns/activitystreams#Offer"),
read => iri!("https://www.w3.org/ns/activitystreams#Read"),
reject => iri!("https://www.w3.org/ns/activitystreams#Reject"),
tentative_accept => iri!("https://www.w3.org/ns/activitystreams#TentativeAccept"),
tentative_reject => iri!("https://www.w3.org/ns/activitystreams#TentativeReject"),
travel => iri!("https://www.w3.org/ns/activitystreams#Travel"),
undo => iri!("https://www.w3.org/ns/activitystreams#Undo"),
update => iri!("https://www.w3.org/ns/activitystreams#Update"),
view => iri!("https://www.w3.org/ns/activitystreams#View"),
}
}
}
pub struct ApubLinkIds {
pub link: Id,
pub mention: Id,
pub is_following: Id,
pub is_followed_by: Id,
pub is_contact: Id,
pub is_member: Id,
}
impl ApubLinkIds {
fn populate(vocab: &mut impl VocabularyMut<Iri = IriIndex>) -> Self {
populate_ids! {
vocab,
link => iri!("https://www.w3.org/ns/activitystreams#Link"),
mention => iri!("https://www.w3.org/ns/activitystreams#Mention"),
is_following => iri!("https://www.w3.org/ns/activitystreams#IsFollowing"),
is_followed_by => iri!("https://www.w3.org/ns/activitystreams#IsFollowedBy"),
is_contact => iri!("https://www.w3.org/ns/activitystreams#IsContact"),
is_member => iri!("https://www.w3.org/ns/activitystreams#IsMember"),
}
}
}
pub struct ApubPropertyIds {
pub subject: Id,
pub relationship: Id,
pub actor: Id,
pub attributed_to: Id,
pub attachment: Id,
pub attachments: Id,
pub author: Id,
pub bcc: Id,
pub bto: Id,
pub cc: Id,
pub context: Id,
pub current: Id,
pub first: Id,
pub generator: Id,
pub icon: Id,
pub image: Id,
pub in_reply_to: Id,
pub items: Id,
pub instrument: Id,
pub ordered_items: Id,
pub last: Id,
pub location: Id,
pub next: Id,
pub object: Id,
pub one_of: Id,
pub any_of: Id,
pub closed: Id,
pub origin: Id,
pub accuracy: Id,
pub prev: Id,
pub preview: Id,
pub provider: Id,
pub replies: Id,
pub result: Id,
pub audience: Id,
pub part_of: Id,
pub tag: Id,
pub tags: Id,
pub target: Id,
pub to: Id,
pub url: Id,
pub altitude: Id,
pub content: Id,
pub content_map: Id,
pub name: Id,
pub name_map: Id,
pub downstream_duplicates: Id,
pub duration: Id,
pub end_time: Id,
pub height: Id,
pub href: Id,
pub hreflang: Id,
pub latitude: Id,
pub longitude: Id,
pub media_type: Id,
pub published: Id,
pub radius: Id,
pub rating: Id,
pub rel: Id,
pub start_index: Id,
pub start_time: Id,
pub summary: Id,
pub summary_map: Id,
pub total_items: Id,
pub units: Id,
pub updated: Id,
pub upstream_duplicates: Id,
pub verb: Id,
pub width: Id,
pub describes: Id,
pub former_type: Id,
pub deleted: Id,
// ActivityPub extensions
pub endpoints: Id,
pub following: Id,
pub followers: Id,
pub inbox: Id,
pub liked: Id,
pub shares: Id,
pub likes: Id,
pub oauth_authorization_endpoint: Id,
pub oauth_token_endpoint: Id,
pub outbox: Id,
pub preferred_username: Id,
pub provide_client_key: Id,
pub proxy_url: Id,
pub shared_inbox: Id,
pub sign_client_key: Id,
pub source: Id,
pub streams: Id,
pub upload_media: Id,
// DID Core extensions
pub also_known_as: Id,
}
impl ApubPropertyIds {
fn populate(vocab: &mut impl VocabularyMut<Iri = IriIndex>) -> Self {
populate_ids! {
vocab,
subject => iri!("https://www.w3.org/ns/activitystreams#subject"),
relationship => iri!("https://www.w3.org/ns/activitystreams#relationship"),
actor => iri!("https://www.w3.org/ns/activitystreams#actor"),
attributed_to => iri!("https://www.w3.org/ns/activitystreams#attributedTo"),
attachment => iri!("https://www.w3.org/ns/activitystreams#attachment"),
attachments => iri!("https://www.w3.org/ns/activitystreams#attachments"),
author => iri!("https://www.w3.org/ns/activitystreams#author"),
bcc => iri!("https://www.w3.org/ns/activitystreams#bcc"),
bto => iri!("https://www.w3.org/ns/activitystreams#bto"),
cc => iri!("https://www.w3.org/ns/activitystreams#cc"),
context => iri!("https://www.w3.org/ns/activitystreams#context"),
current => iri!("https://www.w3.org/ns/activitystreams#current"),
first => iri!("https://www.w3.org/ns/activitystreams#first"),
generator => iri!("https://www.w3.org/ns/activitystreams#generator"),
icon => iri!("https://www.w3.org/ns/activitystreams#icon"),
image => iri!("https://www.w3.org/ns/activitystreams#image"),
in_reply_to => iri!("https://www.w3.org/ns/activitystreams#inReplyTo"),
items => iri!("https://www.w3.org/ns/activitystreams#items"),
instrument => iri!("https://www.w3.org/ns/activitystreams#instrument"),
ordered_items => iri!("https://www.w3.org/ns/activitystreams#orderedItems"),
last => iri!("https://www.w3.org/ns/activitystreams#last"),
location => iri!("https://www.w3.org/ns/activitystreams#location"),
next => iri!("https://www.w3.org/ns/activitystreams#next"),
object => iri!("https://www.w3.org/ns/activitystreams#object"),
one_of => iri!("https://www.w3.org/ns/activitystreams#oneOf"),
any_of => iri!("https://www.w3.org/ns/activitystreams#anyOf"),
closed => iri!("https://www.w3.org/ns/activitystreams#closed"),
origin => iri!("https://www.w3.org/ns/activitystreams#origin"),
accuracy => iri!("https://www.w3.org/ns/activitystreams#accuracy"),
prev => iri!("https://www.w3.org/ns/activitystreams#prev"),
preview => iri!("https://www.w3.org/ns/activitystreams#preview"),
provider => iri!("https://www.w3.org/ns/activitystreams#provider"),
replies => iri!("https://www.w3.org/ns/activitystreams#replies"),
result => iri!("https://www.w3.org/ns/activitystreams#result"),
audience => iri!("https://www.w3.org/ns/activitystreams#audience"),
part_of => iri!("https://www.w3.org/ns/activitystreams#partOf"),
tag => iri!("https://www.w3.org/ns/activitystreams#tag"),
tags => iri!("https://www.w3.org/ns/activitystreams#tags"),
target => iri!("https://www.w3.org/ns/activitystreams#taget"),
to => iri!("https://www.w3.org/ns/activitystreams#to"),
url => iri!("https://www.w3.org/ns/activitystreams#url"),
altitude => iri!("https://www.w3.org/ns/activitystreams#altitude"),
content => iri!("https://www.w3.org/ns/activitystreams#content"),
content_map => iri!("https://www.w3.org/ns/activitystreams#contentMap"),
name => iri!("https://www.w3.org/ns/activitystreams#name"),
name_map => iri!("https://www.w3.org/ns/activitystreams#nameMap"),
downstream_duplicates => iri!("https://www.w3.org/ns/activitystreams#downstreamDuplicates"),
duration => iri!("https://www.w3.org/ns/activitystreams#duration"),
end_time => iri!("https://www.w3.org/ns/activitystreams#endTime"),
height => iri!("https://www.w3.org/ns/activitystreams#height"),
href => iri!("https://www.w3.org/ns/activitystreams#href"),
hreflang => iri!("https://www.w3.org/ns/activitystreams#hreflang"),
latitude => iri!("https://www.w3.org/ns/activitystreams#latitude"),
longitude => iri!("https://www.w3.org/ns/activitystreams#longitude"),
media_type => iri!("https://www.w3.org/ns/activitystreams#mediaType"),
published => iri!("https://www.w3.org/ns/activitystreams#published"),
radius => iri!("https://www.w3.org/ns/activitystreams#radius"),
rating => iri!("https://www.w3.org/ns/activitystreams#ratine"),
rel => iri!("https://www.w3.org/ns/activitystreams#rel"),
start_index => iri!("https://www.w3.org/ns/activitystreams#startIndex"),
start_time => iri!("https://www.w3.org/ns/activitystreams#startTime"),
summary => iri!("https://www.w3.org/ns/activitystreams#summary"),
summary_map => iri!("https://www.w3.org/ns/activitystreams#summaryMap"),
total_items => iri!("https://www.w3.org/ns/activitystreams#totalItems"),
units => iri!("https://www.w3.org/ns/activitystreams#units"),
updated => iri!("https://www.w3.org/ns/activitystreams#updated"),
upstream_duplicates => iri!("https://www.w3.org/ns/activitystreams#upstreamDuplicates"),
verb => iri!("https://www.w3.org/ns/activitystreams#verb"),
width => iri!("https://www.w3.org/ns/activitystreams#width"),
describes => iri!("https://www.w3.org/ns/activitystreams#describes"),
former_type => iri!("https://www.w3.org/ns/activitystreams#formerType"),
deleted => iri!("https://www.w3.org/ns/activitystreams#deleted"),
// ActivityPub extensions
endpoints => iri!("https://www.w3.org/ns/activitystreams#endpoints"),
following => iri!("https://www.w3.org/ns/activitystreams#following"),
followers => iri!("https://www.w3.org/ns/activitystreams#followers"),
inbox => iri!("https://www.w3.org/ns/activitystreams#inbox"),
liked => iri!("https://www.w3.org/ns/activitystreams#liked"),
shares => iri!("https://www.w3.org/ns/activitystreams#shares"),
likes => iri!("https://www.w3.org/ns/activitystreams#likes"),
oauth_authorization_endpoint => iri!("https://www.w3.org/ns/activitystreams#oauthAuthorizationEndpoint"),
oauth_token_endpoint => iri!("https://www.w3.org/ns/activitystreams#oauthTokenEndpoint"),
outbox => iri!("https://www.w3.org/ns/activitystreams#outbox"),
preferred_username => iri!("https://www.w3.org/ns/activitystreams#preferredUsername"),
provide_client_key => iri!("https://www.w3.org/ns/activitystreams#provideClientKey"),
proxy_url => iri!("https://www.w3.org/ns/activitystreams#proxyUrl"),
shared_inbox => iri!("https://www.w3.org/ns/activitystreams#sharedInbox"),
sign_client_key => iri!("https://www.w3.org/ns/activitystreams#signClientKey"),
source => iri!("https://www.w3.org/ns/activitystreams#source"),
streams => iri!("https://www.w3.org/ns/activitystreams#streams"),
upload_media => iri!("https://www.w3.org/ns/activitystreams#uploadMedia"),
also_known_as => iri!("https://www.w3.org/ns/activitystreams#alsoKnownAs"),
}
}
}
pub struct TootIds {
pub class: TootClassIds,
pub props: TootPropIds,
}
impl TootIds {
fn populate(vocab: &mut impl VocabularyMut<Iri = IriIndex>) -> Self {
Self {
class: TootClassIds::populate(vocab),
props: TootPropIds::populate(vocab),
}
}
}
pub struct TootClassIds {
pub emoji: Id,
pub identity_proof: Id,
}
impl TootClassIds {
fn populate(vocab: &mut impl VocabularyMut<Iri = IriIndex>) -> Self {
populate_ids! {
vocab,
emoji => iri!("http://joinmastodon.org/ns#Emoji"),
identity_proof => iri!("http://joinmastodon.org/ns#IdentityProof"),
}
}
}
pub struct TootPropIds {
pub blurhash: Id,
pub focal_point: Id,
pub featured: Id,
pub featured_tags: Id,
pub discoverable: Id,
pub suspended: Id,
pub voters_count: Id,
}
impl TootPropIds {
fn populate(vocab: &mut impl VocabularyMut<Iri = IriIndex>) -> Self {
populate_ids! {
vocab,
blurhash => iri!("http://joinmastodon.org/ns#blurhash"),
focal_point => iri!("http://joinmastodon.org/ns#focalPoint"),
featured => iri!("http://joinmastodon.org/ns#featured"),
featured_tags => iri!("http://joinmastodon.org/ns#featuredTags"),
discoverable => iri!("http://joinmastodon.org/ns#discoverable"),
suspended => iri!("http://joinmastodon.org/ns#suspended"),
voters_count => iri!("http://joinmastodon.org/ns#votersCount"),
}
}
}

View file

@ -1,3 +1,4 @@
use actix_web::http::header::{ACCEPT, CONTENT_TYPE};
use actix_web::{post, web, HttpRequest, HttpResponse}; use actix_web::{post, web, HttpRequest, HttpResponse};
use crate::core::*; use crate::core::*;
@ -6,22 +7,24 @@ use crate::state::AppState;
#[post("")] #[post("")]
async fn post_inbox(body: String, request: HttpRequest, state: AppState) -> Result<HttpResponse> { async fn post_inbox(body: String, request: HttpRequest, state: AppState) -> Result<HttpResponse> {
const CONTENT_TYPES: &[&'static str] = &[ const CONTENT_TYPES: &[&str] = &[
"application/activity+json", "application/activity+json",
"application/ld+json", "application/ld+json",
"application/json", "application/json",
]; ];
let content_type = request let content_type = request
.headers() .headers()
.get("Content-Type") .get(CONTENT_TYPE)
.ok_or(Error::BadRequest)? .ok_or(Error::BadRequest)?
.to_str()?; .to_str()?;
if CONTENT_TYPES.iter().all(|typ| *typ != content_type) { if CONTENT_TYPES.iter().all(|&typ| typ != content_type) {
return Ok(HttpResponse::BadRequest().finish()); return Ok(HttpResponse::UnsupportedMediaType()
.append_header((ACCEPT, "application/activity+json, application/ld+json"))
.finish());
} }
state.sched.schedule(InboxWorker::new(body)); state.sched.schedule(InboxWorker::new(body));
Ok(HttpResponse::Ok().finish()) Ok(HttpResponse::Accepted().finish())
} }
pub fn configure(cfg: &mut web::ServiceConfig) { pub fn configure(cfg: &mut web::ServiceConfig) {