AP ingest: harden attachment parsing

This commit is contained in:
Lain Soykaf 2026-01-17 00:08:19 +04:00
commit e781a7ef9b
6 changed files with 182 additions and 6 deletions

View file

@ -100,10 +100,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do
defp fix_replies(data), do: Map.delete(data, "replies")
def fix_attachments(%{"attachment" => attachment} = data) when is_map(attachment),
do: Map.put(data, "attachment", [attachment])
def fix_attachments(data), do: data
def fix_attachments(data), do: Transmogrifier.fix_attachments(data)
defp fix(data) do
data

View file

@ -293,6 +293,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> Map.drop(["conversation"])
end
defp valid_http_url?(href) when is_binary(href) do
match?({:ok, _}, ObjectValidators.Uri.cast(href))
end
defp valid_http_url?(_), do: false
def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachment) do
attachments =
Enum.map(attachment, fn data ->
@ -305,7 +311,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
media_type =
cond do
is_map(url) && url =~ Pleroma.Constants.mime_regex() ->
is_map(url) && is_bitstring(url["mediaType"]) &&
url["mediaType"] =~ Pleroma.Constants.mime_regex() ->
url["mediaType"]
is_bitstring(data["mediaType"]) && data["mediaType"] =~ Pleroma.Constants.mime_regex() ->
@ -326,7 +333,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
true -> nil
end
if href do
if href && valid_http_url?(href) do
attachment_url =
%{
"href" => href,

View file

@ -0,0 +1,32 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams"
],
"type": "Create",
"id": "https://fedidev.fun/activity/attachment-embedded-note",
"actor": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:43:01Z",
"object": {
"type": "Note",
"id": "https://fedidev.fun/objects/note-attachment-embedded-note",
"attributedTo": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:43:01Z",
"content": "Note with embedded Note attachment",
"attachment": {
"type": "Note",
"attributedTo": "https://fedidev.fun/users/alice",
"name": "attached note",
"content": "This is just a note",
"published": "2024-03-06T07:23:56Z"
}
}
}

View file

@ -0,0 +1,31 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams"
],
"type": "Create",
"id": "https://fedidev.fun/activity/attachment-payto-link",
"actor": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:43:00Z",
"object": {
"type": "Note",
"id": "https://fedidev.fun/objects/note-attachment-payto-link",
"attributedTo": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:43:00Z",
"content": "Payment Link",
"attachment": {
"type": "Link",
"name": "Donate",
"href": "payto://iban/DE75512108001245126199",
"rel": "payment"
}
}
}

View file

@ -0,0 +1,40 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams"
],
"type": "Create",
"id": "https://fedidev.fun/activity/attachment-url-list",
"actor": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:43:02Z",
"object": {
"type": "Note",
"id": "https://fedidev.fun/objects/note-attachment-url-list",
"attributedTo": "https://fedidev.fun/users/alice",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [],
"published": "2025-11-21T14:43:02Z",
"content": "url is array of two Link objects",
"attachment": {
"type": "Image",
"url": [
{
"type": "Link",
"href": "https://fedidev.fun/images/007.png",
"mediaType": "image/png"
},
{
"type": "Link",
"href": "https://fedidev.fun/images/008.jpg",
"mediaType": "image/jpeg"
}
]
}
}
}

View file

@ -0,0 +1,69 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.Transmogrifier.FedidevFunAttachmentsTest do
use Oban.Testing, repo: Pleroma.Repo
use Pleroma.DataCase
alias Pleroma.Object
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Transmogrifier
import Pleroma.Factory
setup_all do
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
:ok
end
defp ensure_remote_actor!(ap_id) when is_binary(ap_id) do
case User.get_cached_by_ap_id(ap_id) do
%User{} ->
:ok
_ ->
insert(:user,
local: false,
ap_id: ap_id,
follower_address: ap_id <> "/followers",
following_address: ap_id <> "/following",
featured_address: ap_id <> "/collections/featured",
inbox: ap_id <> "/inbox"
)
:ok
end
end
defp ingest_fixture!(rel_path) do
data = rel_path |> File.read!() |> Jason.decode!()
ensure_remote_actor!(data["actor"])
assert {:ok, activity} = Transmogrifier.handle_incoming(data)
object = Object.normalize(activity.data["object"], fetch: false)
{activity, object}
end
test "drops non-http(s) attachment href" do
{_activity, object} = ingest_fixture!("test/fixtures/fedidev.fun/attachment_payto_link.json")
assert object.data["attachment"] == []
end
test "drops embedded non-attachment objects" do
{_activity, object} =
ingest_fixture!("test/fixtures/fedidev.fun/attachment_embedded_note.json")
assert object.data["attachment"] == []
end
test "accepts attachment url as list and keeps the first link" do
{_activity, object} = ingest_fixture!("test/fixtures/fedidev.fun/attachment_url_list.json")
[attachment] = object.data["attachment"]
[%{"href" => href} | _] = attachment["url"]
assert href == "https://fedidev.fun/images/007.png"
end
end