diff options
29 files changed, 222 insertions, 22 deletions
| diff --git a/changelog.d/301-small-image-redirect.change b/changelog.d/301-small-image-redirect.change new file mode 100644 index 000000000..c5be80539 --- /dev/null +++ b/changelog.d/301-small-image-redirect.change @@ -0,0 +1 @@ +Performance: Use 301 (permanent) redirect instead of 302 (temporary) when redirecting small images in media proxy. This allows browsers to cache the redirect response. 
\ No newline at end of file diff --git a/changelog.d/actor-published-date.add b/changelog.d/actor-published-date.add new file mode 100644 index 000000000..feac85894 --- /dev/null +++ b/changelog.d/actor-published-date.add @@ -0,0 +1 @@ +Include "published" in actor view diff --git a/changelog.d/backup-links.add b/changelog.d/backup-links.add new file mode 100644 index 000000000..ff19e736b --- /dev/null +++ b/changelog.d/backup-links.add @@ -0,0 +1 @@ +Link to exported outbox/followers/following collections in backup actor.json diff --git a/changelog.d/description-update-suggestions.skip b/changelog.d/description-update-suggestions.skip new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/changelog.d/description-update-suggestions.skip diff --git a/changelog.d/fix-mastodon-edits.fix b/changelog.d/fix-mastodon-edits.fix new file mode 100644 index 000000000..2e79977e0 --- /dev/null +++ b/changelog.d/fix-mastodon-edits.fix @@ -0,0 +1 @@ +Fix Mastodon incoming edits with inlined "likes" diff --git a/changelog.d/fix-wrong-config-section.skip b/changelog.d/fix-wrong-config-section.skip new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/changelog.d/fix-wrong-config-section.skip diff --git a/changelog.d/incoming-scrobbles.fix b/changelog.d/incoming-scrobbles.fix new file mode 100644 index 000000000..fb1e2581c --- /dev/null +++ b/changelog.d/incoming-scrobbles.fix @@ -0,0 +1 @@ +Allow incoming "Listen" activities diff --git a/changelog.d/rich-media-ignore-host.fix b/changelog.d/rich-media-ignore-host.fix new file mode 100644 index 000000000..b70866ac7 --- /dev/null +++ b/changelog.d/rich-media-ignore-host.fix @@ -0,0 +1 @@ +Fix missing check for domain presence in rich media ignore_host configuration diff --git a/changelog.d/vips-blurhash.fix b/changelog.d/vips-blurhash.fix new file mode 100644 index 000000000..9e8951b15 --- /dev/null +++ b/changelog.d/vips-blurhash.fix @@ -0,0 +1 @@ +Fix blurhash generation crashes diff --git a/config/description.exs b/config/description.exs index 47f4771eb..e8d154124 100644 --- a/config/description.exs +++ b/config/description.exs @@ -3302,8 +3302,7 @@ config :pleroma, :config_description, [          suggestions: [            Pleroma.Web.Preload.Providers.Instance,            Pleroma.Web.Preload.Providers.User, -          Pleroma.Web.Preload.Providers.Timelines, -          Pleroma.Web.Preload.Providers.StatusNet +          Pleroma.Web.Preload.Providers.Timelines          ]        }      ] diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md index 36e9cbba2..6e2fddcb6 100644 --- a/docs/configuration/cheatsheet.md +++ b/docs/configuration/cheatsheet.md @@ -98,7 +98,7 @@ To add configuration to your config file, you can copy it from the base config.  * `moderator_privileges`: A list of privileges a moderator has (e.g. delete messages, manage reports...)      * Possible values are the same as for `admin_privileges` -## :database +## :features  * `improved_hashtag_timeline`: Setting to force toggle / force disable improved hashtags timeline. `:enabled` forces hashtags to be fetched from `hashtags` table for hashtags timeline. `:disabled` forces object-embedded hashtags to be used (slower). Keep it `:auto` for automatic behaviour (it is auto-set to `:enabled` [unless overridden] when HashtagsTableMigrator completes).  ## Background migrations diff --git a/lib/pleroma/constants.ex b/lib/pleroma/constants.ex index 2828c79a9..c11c66f4d 100644 --- a/lib/pleroma/constants.ex +++ b/lib/pleroma/constants.ex @@ -100,7 +100,8 @@ defmodule Pleroma.Constants do        "Announce",        "Undo",        "Flag", -      "EmojiReact" +      "EmojiReact", +      "Listen"      ]    ) diff --git a/lib/pleroma/upload/filter/analyze_metadata.ex b/lib/pleroma/upload/filter/analyze_metadata.ex index 7ee643277..a8480bf36 100644 --- a/lib/pleroma/upload/filter/analyze_metadata.ex +++ b/lib/pleroma/upload/filter/analyze_metadata.ex @@ -90,9 +90,13 @@ defmodule Pleroma.Upload.Filter.AnalyzeMetadata do        {:ok, rgb} =          if Image.has_alpha?(resized_image) do            # remove alpha channel -          resized_image -          |> Operation.extract_band!(0, n: 3) -          |> Image.write_to_binary() +          case Operation.extract_band(resized_image, 0, n: 3) do +            {:ok, data} -> +              Image.write_to_binary(data) + +            _ -> +              Image.write_to_binary(resized_image) +          end          else            Image.write_to_binary(resized_image)          end diff --git a/lib/pleroma/user/backup.ex b/lib/pleroma/user/backup.ex index d77d49890..cdff297a9 100644 --- a/lib/pleroma/user/backup.ex +++ b/lib/pleroma/user/backup.ex @@ -246,7 +246,13 @@ defmodule Pleroma.User.Backup do    defp actor(dir, user) do      with {:ok, json} <-             UserView.render("user.json", %{user: user}) -           |> Map.merge(%{"likes" => "likes.json", "bookmarks" => "bookmarks.json"}) +           |> Map.merge(%{ +             "bookmarks" => "bookmarks.json", +             "likes" => "likes.json", +             "outbox" => "outbox.json", +             "followers" => "followers.json", +             "following" => "following.json" +           })             |> Jason.encode() do        File.write(Path.join(dir, "actor.json"), json)      end 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 1b5b2e8fb..ada1a4ea9 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 @@ -85,6 +85,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do      |> fix_replies()      |> fix_attachments()      |> CommonFixes.fix_quote_url() +    |> CommonFixes.fix_likes()      |> Transmogrifier.fix_emoji()      |> Transmogrifier.fix_content_map()    end diff --git a/lib/pleroma/web/activity_pub/object_validators/audio_image_video_validator.ex b/lib/pleroma/web/activity_pub/object_validators/audio_image_video_validator.ex index 65ac6bb93..034c6f33f 100644 --- a/lib/pleroma/web/activity_pub/object_validators/audio_image_video_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/audio_image_video_validator.ex @@ -100,6 +100,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioImageVideoValidator do      |> CommonFixes.fix_actor()      |> CommonFixes.fix_object_defaults()      |> CommonFixes.fix_quote_url() +    |> CommonFixes.fix_likes()      |> Transmogrifier.fix_emoji()      |> fix_url()      |> fix_content() diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex index 4699029d4..a39110e10 100644 --- a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex +++ b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex @@ -114,6 +114,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do    def fix_quote_url(data), do: data +  # On Mastodon, `"likes"` attribute includes an inlined `Collection` with `totalItems`, +  # not a list of users. +  # https://github.com/mastodon/mastodon/pull/32007 +  def fix_likes(%{"likes" => %{}} = data), do: Map.drop(data, ["likes"]) + +  def fix_likes(data), do: data +    # https://codeberg.org/fediverse/fep/src/branch/main/fep/e232/fep-e232.md    def object_link_tag?(%{          "type" => "Link", diff --git a/lib/pleroma/web/activity_pub/object_validators/event_validator.ex b/lib/pleroma/web/activity_pub/object_validators/event_validator.ex index ab204f69a..c87515e80 100644 --- a/lib/pleroma/web/activity_pub/object_validators/event_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/event_validator.ex @@ -47,6 +47,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do      data      |> CommonFixes.fix_actor()      |> CommonFixes.fix_object_defaults() +    |> CommonFixes.fix_likes()      |> Transmogrifier.fix_emoji()    end diff --git a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex index 7f9d4d648..21940f4f1 100644 --- a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex @@ -64,6 +64,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do      |> CommonFixes.fix_actor()      |> CommonFixes.fix_object_defaults()      |> CommonFixes.fix_quote_url() +    |> CommonFixes.fix_likes()      |> Transmogrifier.fix_emoji()      |> fix_closed()    end diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index cd485ed64..61975387b 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -127,7 +127,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do        "capabilities" => capabilities,        "alsoKnownAs" => user.also_known_as,        "vcard:bday" => birthday, -      "webfinger" => "acct:#{User.full_nickname(user)}" +      "webfinger" => "acct:#{User.full_nickname(user)}", +      "published" => Pleroma.Web.CommonAPI.Utils.to_masto_date(user.inserted_at)      }      |> Map.merge(        maybe_make_image( diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index 0b446e0a6..a0aafc32e 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -71,11 +71,15 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do            drop_static_param_and_redirect(conn)          content_type == "image/gif" -> -          redirect(conn, external: media_proxy_url) +          conn +          |> put_status(301) +          |> redirect(external: media_proxy_url)          min_content_length_for_preview() > 0 and content_length > 0 and              content_length < min_content_length_for_preview() -> -          redirect(conn, external: media_proxy_url) +          conn +          |> put_status(301) +          |> redirect(external: media_proxy_url)          true ->            handle_preview(content_type, conn, media_proxy_url) diff --git a/lib/pleroma/web/rich_media/card.ex b/lib/pleroma/web/rich_media/card.ex index abad4957e..6b4bb9555 100644 --- a/lib/pleroma/web/rich_media/card.ex +++ b/lib/pleroma/web/rich_media/card.ex @@ -54,7 +54,10 @@ defmodule Pleroma.Web.RichMedia.Card do    @spec get_by_url(String.t() | nil) :: t() | nil | :error    def get_by_url(url) when is_binary(url) do -    if @config_impl.get([:rich_media, :enabled]) do +    host = URI.parse(url).host + +    with true <- @config_impl.get([:rich_media, :enabled]), +         true <- host not in @config_impl.get([:rich_media, :ignore_hosts], []) do        url_hash = url_to_hash(url)        @cachex.fetch!(:rich_media_cache, url_hash, fn _ -> @@ -69,7 +72,7 @@ defmodule Pleroma.Web.RichMedia.Card do          end        end)      else -      :error +      false -> :error      end    end @@ -77,7 +80,10 @@ defmodule Pleroma.Web.RichMedia.Card do    @spec get_or_backfill_by_url(String.t(), keyword()) :: t() | nil    def get_or_backfill_by_url(url, opts \\ []) do -    if @config_impl.get([:rich_media, :enabled]) do +    host = URI.parse(url).host + +    with true <- @config_impl.get([:rich_media, :enabled]), +         true <- host not in @config_impl.get([:rich_media, :ignore_hosts], []) do        case get_by_url(url) do          %__MODULE__{} = card ->            card @@ -94,7 +100,7 @@ defmodule Pleroma.Web.RichMedia.Card do            nil        end      else -      nil +      false -> nil      end    end diff --git a/test/fixtures/break_analyze.png b/test/fixtures/break_analyze.pngBinary files differ new file mode 100644 index 000000000..b5e91b08a --- /dev/null +++ b/test/fixtures/break_analyze.png diff --git a/test/fixtures/mastodon-update-with-likes.json b/test/fixtures/mastodon-update-with-likes.json new file mode 100644 index 000000000..3bdb3ba3d --- /dev/null +++ b/test/fixtures/mastodon-update-with-likes.json @@ -0,0 +1,90 @@ +{ +  "@context": [ +    "https://www.w3.org/ns/activitystreams", +    { +      "atomUri": "ostatus:atomUri", +      "conversation": "ostatus:conversation", +      "inReplyToAtomUri": "ostatus:inReplyToAtomUri", +      "ostatus": "http://ostatus.org#", +      "sensitive": "as:sensitive", +      "toot": "http://joinmastodon.org/ns#", +      "votersCount": "toot:votersCount" +    }, +    "https://w3id.org/security/v1" +  ], +  "actor": "https://pol.social/users/mkljczk", +  "cc": ["https://www.w3.org/ns/activitystreams#Public", +   "https://pol.social/users/aemstuz", "https://gts.mkljczk.pl/users/mkljczk", +   "https://pl.fediverse.pl/users/mkljczk", +   "https://fedi.kutno.pl/users/mkljczk"], +  "id": "https://pol.social/users/mkljczk/statuses/113907871635572263#updates/1738096776", +  "object": { +    "atomUri": "https://pol.social/users/mkljczk/statuses/113907871635572263", +    "attachment": [], +    "attributedTo": "https://pol.social/users/mkljczk", +    "cc": ["https://www.w3.org/ns/activitystreams#Public", +     "https://pol.social/users/aemstuz", "https://gts.mkljczk.pl/users/mkljczk", +     "https://pl.fediverse.pl/users/mkljczk", +     "https://fedi.kutno.pl/users/mkljczk"], +    "content": "<p>test</p>", +    "contentMap": { +      "pl": "<p>test</p>" +    }, +    "conversation": "https://fedi.kutno.pl/contexts/43c14c70-d3fb-42b4-a36d-4eacfab9695a", +    "id": "https://pol.social/users/mkljczk/statuses/113907871635572263", +    "inReplyTo": "https://pol.social/users/aemstuz/statuses/113907854282654767", +    "inReplyToAtomUri": "https://pol.social/users/aemstuz/statuses/113907854282654767", +    "likes": { +      "id": "https://pol.social/users/mkljczk/statuses/113907871635572263/likes", +      "totalItems": 1, +      "type": "Collection" +    }, +    "published": "2025-01-28T20:29:45Z", +    "replies": { +      "first": { +        "items": [], +        "next": "https://pol.social/users/mkljczk/statuses/113907871635572263/replies?only_other_accounts=true&page=true", +        "partOf": "https://pol.social/users/mkljczk/statuses/113907871635572263/replies", +        "type": "CollectionPage" +      }, +      "id": "https://pol.social/users/mkljczk/statuses/113907871635572263/replies", +      "type": "Collection" +    }, +    "sensitive": false, +    "shares": { +      "id": "https://pol.social/users/mkljczk/statuses/113907871635572263/shares", +      "totalItems": 0, +      "type": "Collection" +    }, +    "summary": null, +    "tag": [ +      { +        "href": "https://pol.social/users/aemstuz", +        "name": "@aemstuz", +        "type": "Mention" +      }, +      { +        "href": "https://gts.mkljczk.pl/users/mkljczk", +        "name": "@mkljczk@gts.mkljczk.pl", +        "type": "Mention" +      }, +      { +        "href": "https://pl.fediverse.pl/users/mkljczk", +        "name": "@mkljczk@fediverse.pl", +        "type": "Mention" +      }, +      { +        "href": "https://fedi.kutno.pl/users/mkljczk", +        "name": "@mkljczk@fedi.kutno.pl", +        "type": "Mention" +      } +    ], +    "to": ["https://pol.social/users/mkljczk/followers"], +    "type": "Note", +    "updated": "2025-01-28T20:39:36Z", +    "url": "https://pol.social/@mkljczk/113907871635572263" +  }, +  "published": "2025-01-28T20:39:36Z", +  "to": ["https://pol.social/users/mkljczk/followers"], +  "type": "Update" +} diff --git a/test/pleroma/upload/filter/analyze_metadata_test.exs b/test/pleroma/upload/filter/analyze_metadata_test.exs index e4ac673b2..6e1f2afaf 100644 --- a/test/pleroma/upload/filter/analyze_metadata_test.exs +++ b/test/pleroma/upload/filter/analyze_metadata_test.exs @@ -34,6 +34,20 @@ defmodule Pleroma.Upload.Filter.AnalyzeMetadataTest do      assert meta.blurhash == "eXJi-E:SwCEm5rCmn$+YWYn+15K#5A$xxCi{SiV]s*W:Efa#s.jE-T"    end +  test "it gets dimensions for grayscale images" do +    upload = %Pleroma.Upload{ +      name: "break_analyze.png", +      content_type: "image/png", +      path: Path.absname("test/fixtures/break_analyze.png"), +      tempfile: Path.absname("test/fixtures/break_analyze.png") +    } + +    {:ok, :filtered, meta} = AnalyzeMetadata.filter(upload) + +    assert %{width: 1410, height: 2048} = meta +    assert is_nil(meta.blurhash) +  end +    test "adds the dimensions for videos" do      upload = %Pleroma.Upload{        name: "coolvideo.mp4", diff --git a/test/pleroma/user/backup_test.exs b/test/pleroma/user/backup_test.exs index 24fe09f7e..f4b92adf8 100644 --- a/test/pleroma/user/backup_test.exs +++ b/test/pleroma/user/backup_test.exs @@ -185,13 +185,13 @@ defmodule Pleroma.User.BackupTest do                 %{"@language" => "und"}               ],               "bookmarks" => "bookmarks.json", -             "followers" => "http://cofe.io/users/cofe/followers", -             "following" => "http://cofe.io/users/cofe/following", +             "followers" => "followers.json", +             "following" => "following.json",               "id" => "http://cofe.io/users/cofe",               "inbox" => "http://cofe.io/users/cofe/inbox",               "likes" => "likes.json",               "name" => "Cofe", -             "outbox" => "http://cofe.io/users/cofe/outbox", +             "outbox" => "outbox.json",               "preferredUsername" => "cofe",               "publicKey" => %{                 "id" => "http://cofe.io/users/cofe#main-key", diff --git a/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs b/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs index e1dbb20c3..b1cbdbe81 100644 --- a/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs +++ b/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs @@ -128,6 +128,17 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidatorTest      %{valid?: true} = ArticleNotePageValidator.cast_and_validate(note)    end +  test "a Note with validated likes collection validates" do +    insert(:user, ap_id: "https://pol.social/users/mkljczk") + +    %{"object" => note} = +      "test/fixtures/mastodon-update-with-likes.json" +      |> File.read!() +      |> Jason.decode!() + +    %{valid?: true} = ArticleNotePageValidator.cast_and_validate(note) +  end +    test "Fedibird quote post" do      insert(:user, ap_id: "https://fedibird.com/users/noellabo") diff --git a/test/pleroma/web/media_proxy/media_proxy_controller_test.exs b/test/pleroma/web/media_proxy/media_proxy_controller_test.exs index f0c1dd640..f7e52483c 100644 --- a/test/pleroma/web/media_proxy/media_proxy_controller_test.exs +++ b/test/pleroma/web/media_proxy/media_proxy_controller_test.exs @@ -248,8 +248,8 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do        response = get(conn, url) -      assert response.status == 302 -      assert redirected_to(response) == media_proxy_url +      assert response.status == 301 +      assert redirected_to(response, 301) == media_proxy_url      end      test "with `static` param and non-GIF image preview requested, " <> @@ -290,8 +290,8 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do        response = get(conn, url) -      assert response.status == 302 -      assert redirected_to(response) == media_proxy_url +      assert response.status == 301 +      assert redirected_to(response, 301) == media_proxy_url      end      test "thumbnails PNG images into PNG", %{ @@ -356,5 +356,32 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do        assert response.status == 302        assert redirected_to(response) == media_proxy_url      end + +    test "redirects to media proxy URI with 301 when image is too small for preview", %{ +      conn: conn, +      url: url, +      media_proxy_url: media_proxy_url +    } do +      clear_config([:media_preview_proxy], +        enabled: true, +        min_content_length: 1000, +        image_quality: 85, +        thumbnail_max_width: 100, +        thumbnail_max_height: 100 +      ) + +      Tesla.Mock.mock(fn +        %{method: :head, url: ^media_proxy_url} -> +          %Tesla.Env{ +            status: 200, +            body: "", +            headers: [{"content-type", "image/png"}, {"content-length", "500"}] +          } +      end) + +      response = get(conn, url) +      assert response.status == 301 +      assert redirected_to(response, 301) == media_proxy_url +    end    end  end diff --git a/test/pleroma/web/rich_media/card_test.exs b/test/pleroma/web/rich_media/card_test.exs index 387defc8c..c69f85323 100644 --- a/test/pleroma/web/rich_media/card_test.exs +++ b/test/pleroma/web/rich_media/card_test.exs @@ -83,4 +83,23 @@ defmodule Pleroma.Web.RichMedia.CardTest do               Card.get_by_activity(activity)             )    end + +  test "refuses to crawl URL in activity from ignored host/domain" do +    clear_config([:rich_media, :ignore_hosts], ["example.com"]) + +    user = insert(:user) + +    url = "https://example.com/ogp" + +    {:ok, activity} = +      CommonAPI.post(user, %{ +        status: "[test](#{url})", +        content_type: "text/markdown" +      }) + +    refute_enqueued( +      worker: RichMediaWorker, +      args: %{"url" => url, "activity_id" => activity.id} +    ) +  end  end | 
