diff --git a/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex b/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex index b0cdee552..f2df2250f 100644 --- a/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex @@ -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 diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 43d991a22..cc84cf8af 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -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, diff --git a/test/fixtures/fedidev.fun/attachment_embedded_note.json b/test/fixtures/fedidev.fun/attachment_embedded_note.json new file mode 100644 index 000000000..8e83762dc --- /dev/null +++ b/test/fixtures/fedidev.fun/attachment_embedded_note.json @@ -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" + } + } +} + diff --git a/test/fixtures/fedidev.fun/attachment_payto_link.json b/test/fixtures/fedidev.fun/attachment_payto_link.json new file mode 100644 index 000000000..1da324cb3 --- /dev/null +++ b/test/fixtures/fedidev.fun/attachment_payto_link.json @@ -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" + } + } +} + diff --git a/test/fixtures/fedidev.fun/attachment_url_list.json b/test/fixtures/fedidev.fun/attachment_url_list.json new file mode 100644 index 000000000..0efaeafdb --- /dev/null +++ b/test/fixtures/fedidev.fun/attachment_url_list.json @@ -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" + } + ] + } + } +} + diff --git a/test/pleroma/web/activity_pub/transmogrifier/fedidev_fun_attachments_test.exs b/test/pleroma/web/activity_pub/transmogrifier/fedidev_fun_attachments_test.exs new file mode 100644 index 000000000..a13912b08 --- /dev/null +++ b/test/pleroma/web/activity_pub/transmogrifier/fedidev_fun_attachments_test.exs @@ -0,0 +1,69 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# 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