diff --git a/docs/src/contributing_apub_api_outline.md b/docs/src/contributing_apub_api_outline.md index f5c7a5951..68abdf87b 100644 --- a/docs/src/contributing_apub_api_outline.md +++ b/docs/src/contributing_apub_api_outline.md @@ -64,6 +64,7 @@ Receives activities from user: `Follow`, `Undo/Follow`, `Create`, `Update`, `Lik "https://enterprise.lemmy.ml/u/riker" ], "content": "Welcome to the default community!", + "mediaType": "text/markdown", "icon": { "type": "Image", "url": "https://enterprise.lemmy.ml/pictrs/image/Z8pFFb21cl.png" @@ -123,7 +124,8 @@ Sends and receives activities from/to other users: `Create/Note`, `Update/Note`, "type": "Person", "preferredUsername": "picard", "name": "Jean-Luc Picard", - "summary": "The user bio", + "content": "The user bio", + "mediaType": "text/markdown", "icon": { "type": "Image", "url": "https://enterprise.lemmy.ml/pictrs/image/DS3q0colRA.jpg" @@ -150,7 +152,7 @@ Sends and receives activities from/to other users: `Create/Note`, `Update/Note`, |---|---|---| | `preferredUsername` | yes | Name of the actor | | `name` | no | The user's displayname | -| `summary` | no | User bio | +| `content` | no | User bio | | `icon` | no | The user's avatar, shown next to the username | | `image` | no | The user's banner, shown on top of the profile | | `inbox` | no | ActivityPub inbox URL | @@ -174,6 +176,7 @@ A page with title, and optional URL and text content. The URL often leads to an "to": "https://voyager.lemmy.ml/c/main", "summary": "Test thumbnail 2", "content": "blub blub", + "mediaType": "text/markdown", "url": "https://voyager.lemmy.ml:/pictrs/image/fzGwCsq7BJ.jpg", "image": { "type": "Image", @@ -213,6 +216,7 @@ A reply to a post, or reply to another comment. Contains only text (including re "attributedTo": "https://enterprise.lemmy.ml/u/picard", "to": "https://enterprise.lemmy.ml/c/main", "content": "mmmk", + "mediaType": "text/markdown", "inReplyTo": [ "https://enterprise.lemmy.ml/post/38", "https://voyager.lemmy.ml/comment/73" @@ -243,6 +247,7 @@ A direct message from one user to another. Can not include additional users. Thr "attributedTo": "https://enterprise.lemmy.ml/u/picard", "to": "https://voyager.lemmy.ml/u/janeway", "content": "test", + "mediaType": "text/markdown", "published": "2020-10-08T19:10:46.542820+00:00", "updated": "2020-10-08T20:13:52.547156+00:00" } diff --git a/lemmy_apub/src/objects/comment.rs b/lemmy_apub/src/objects/comment.rs index ca0b0e850..448170bb0 100644 --- a/lemmy_apub/src/objects/comment.rs +++ b/lemmy_apub/src/objects/comment.rs @@ -4,7 +4,7 @@ use crate::{ get_or_fetch_and_insert_post, get_or_fetch_and_upsert_user, }, - objects::{check_object_domain, create_tombstone}, + objects::{check_is_markdown, check_object_domain, create_tombstone, mime_markdown}, FromApub, ToApub, }; @@ -64,6 +64,7 @@ impl ToApub for Comment { .set_to(community.actor_id) .set_many_in_reply_tos(in_reply_to_vec) .set_content(self.content.to_owned()) + .set_media_type(mime_markdown()?) .set_attributed_to(creator.actor_id); if let Some(u) = self.updated { @@ -124,6 +125,9 @@ impl FromApub for CommentForm { } None => None, }; + + check_is_markdown(note.media_type())?; + let content = note .content() .context(location_info!())? diff --git a/lemmy_apub/src/objects/community.rs b/lemmy_apub/src/objects/community.rs index d697c70b0..d28583114 100644 --- a/lemmy_apub/src/objects/community.rs +++ b/lemmy_apub/src/objects/community.rs @@ -1,7 +1,7 @@ use crate::{ extensions::group_extensions::GroupExtension, fetcher::get_or_fetch_and_upsert_user, - objects::{check_object_domain, create_tombstone}, + objects::{check_is_markdown, check_object_domain, create_tombstone, mime_markdown}, ActorType, FromApub, GroupExt, @@ -58,9 +58,7 @@ impl ToApub for Community { group.set_updated(convert_datetime(u)); } if let Some(d) = self.description.to_owned() { - // TODO: this should be html, also add source field with raw markdown - // -> same for post.content and others - group.set_content(d); + group.set_content(d).set_media_type(mime_markdown()?); } if let Some(icon_url) = &self.icon { @@ -146,6 +144,10 @@ impl FromApub for CommunityForm { .map(|s| s.as_single_xsd_string()) .flatten() .map(|s| s.to_string()); + if description.is_some() { + check_is_markdown(group.media_type())?; + } + check_slurs(&name)?; check_slurs(&title)?; check_slurs_opt(&description)?; diff --git a/lemmy_apub/src/objects/mod.rs b/lemmy_apub/src/objects/mod.rs index 8fd0e5679..1d0ea0e44 100644 --- a/lemmy_apub/src/objects/mod.rs +++ b/lemmy_apub/src/objects/mod.rs @@ -2,6 +2,7 @@ use crate::check_is_apub_id_valid; use activitystreams::{ base::{AsBase, BaseExt}, markers::Base, + mime::{FromStrError, Mime}, object::{Tombstone, TombstoneExt}, }; use anyhow::{anyhow, Context}; @@ -58,3 +59,18 @@ where }; Ok(actor_id.to_string()) } + +pub(in crate::objects) fn mime_markdown() -> Result { + "text/markdown".parse() +} + +pub(in crate::objects) fn check_is_markdown(mime: Option<&Mime>) -> Result<(), LemmyError> { + let mime = mime.context(location_info!())?; + if !mime.eq(&mime_markdown()?) { + Err(LemmyError::from(anyhow!( + "Lemmy only supports markdown content" + ))) + } else { + Ok(()) + } +} diff --git a/lemmy_apub/src/objects/post.rs b/lemmy_apub/src/objects/post.rs index 6b42e6908..9ecd3cc17 100644 --- a/lemmy_apub/src/objects/post.rs +++ b/lemmy_apub/src/objects/post.rs @@ -1,7 +1,7 @@ use crate::{ extensions::page_extension::PageExtension, fetcher::{get_or_fetch_and_upsert_community, get_or_fetch_and_upsert_user}, - objects::{check_object_domain, create_tombstone}, + objects::{check_is_markdown, check_object_domain, create_tombstone, mime_markdown}, FromApub, PageExt, ToApub, @@ -57,7 +57,9 @@ impl ToApub for Post { .set_attributed_to(creator.actor_id); if let Some(body) = &self.body { - page.set_content(body.to_owned()); + page + .set_content(body.to_owned()) + .set_media_type(mime_markdown()?); } // TODO: hacky code because we get self.url == Some("") @@ -162,6 +164,7 @@ impl FromApub for PostForm { .as_single_xsd_string() .context(location_info!())? .to_string(); + let body = page .inner .content() @@ -169,6 +172,10 @@ impl FromApub for PostForm { .map(|c| c.as_single_xsd_string()) .flatten() .map(|s| s.to_string()); + if body.is_some() { + check_is_markdown(page.media_type())?; + } + check_slurs(&name)?; let body_slurs_removed = body.map(|b| remove_slurs(&b)); Ok(PostForm { diff --git a/lemmy_apub/src/objects/private_message.rs b/lemmy_apub/src/objects/private_message.rs index 64047963c..edab85a02 100644 --- a/lemmy_apub/src/objects/private_message.rs +++ b/lemmy_apub/src/objects/private_message.rs @@ -1,7 +1,7 @@ use crate::{ check_is_apub_id_valid, fetcher::get_or_fetch_and_upsert_user, - objects::{check_object_domain, create_tombstone}, + objects::{check_is_markdown, check_object_domain, create_tombstone, mime_markdown}, FromApub, ToApub, }; @@ -39,6 +39,7 @@ impl ToApub for PrivateMessage { .set_id(Url::parse(&self.ap_id.to_owned())?) .set_published(convert_datetime(self.published)) .set_content(self.content.to_owned()) + .set_media_type(mime_markdown()?) .set_to(recipient.actor_id) .set_attributed_to(creator.actor_id); @@ -83,6 +84,8 @@ impl FromApub for PrivateMessageForm { let ap_id = note.id_unchecked().context(location_info!())?.to_string(); check_is_apub_id_valid(&Url::parse(&ap_id)?)?; + check_is_markdown(note.media_type())?; + Ok(PrivateMessageForm { creator_id: creator.id, recipient_id: recipient.id, diff --git a/lemmy_apub/src/objects/user.rs b/lemmy_apub/src/objects/user.rs index 49b7c9e5c..67554b90c 100644 --- a/lemmy_apub/src/objects/user.rs +++ b/lemmy_apub/src/objects/user.rs @@ -1,4 +1,10 @@ -use crate::{objects::check_object_domain, ActorType, FromApub, PersonExt, ToApub}; +use crate::{ + objects::{check_is_markdown, check_object_domain, mime_markdown}, + ActorType, + FromApub, + PersonExt, + ToApub, +}; use activitystreams::{ actor::{ApActor, Endpoints, Person}, object::{Image, Tombstone}, @@ -47,7 +53,12 @@ impl ToApub for User_ { } if let Some(bio) = &self.bio { - person.set_summary(bio.to_owned()); + // Use `content` here because it supports markdown, while summary does not. + person + .set_content(bio.to_owned()) + .set_media_type(mime_markdown()?) + // Also set summary for compatibility with older Lemmy versions. Remove this after a while. + .set_summary(bio.to_owned()); } if let Some(i) = self.preferred_username.to_owned() { @@ -121,10 +132,14 @@ impl FromApub for UserForm { // here when we federate to other platforms. Same for preferred_username let bio = person .inner - .summary() + .content() .map(|s| s.as_single_xsd_string()) .flatten() .map(|s| s.to_string()); + if bio.is_some() { + check_is_markdown(person.media_type())?; + } + check_slurs(&name)?; check_slurs_opt(&preferred_username)?; check_slurs_opt(&bio)?;