From 7a7e0ba4cd23b2bb2def2b85530fe6c3a9308d7d Mon Sep 17 00:00:00 2001
From: Claire <claire.github-309c@sitedethib.com>
Date: Tue, 10 Jun 2025 15:26:29 +0200
Subject: [PATCH] Add basic support for remote attachments with multiple media
 types (#34996)

---
 app/helpers/json_ld_helper.rb                 | 11 ++++
 .../parser/media_attachment_parser.rb         |  4 +-
 .../parser/media_attachment_parser_spec.rb    | 51 +++++++++++++++++++
 3 files changed, 64 insertions(+), 2 deletions(-)
 create mode 100644 spec/lib/activitypub/parser/media_attachment_parser_spec.rb

diff --git a/app/helpers/json_ld_helper.rb b/app/helpers/json_ld_helper.rb
index 212894d0cd..078aba456a 100644
--- a/app/helpers/json_ld_helper.rb
+++ b/app/helpers/json_ld_helper.rb
@@ -26,6 +26,8 @@ module JsonLdHelper
   # The url attribute can be a string, an array of strings, or an array of objects.
   # The objects could include a mimeType. Not-included mimeType means it's text/html.
   def url_to_href(value, preferred_type = nil)
+    value = [value] if value.is_a?(Hash)
+
     single_value = if value.is_a?(Array) && !value.first.is_a?(String)
                      value.find { |link| preferred_type.nil? || ((link['mimeType'].presence || 'text/html') == preferred_type) }
                    elsif value.is_a?(Array)
@@ -41,6 +43,15 @@ module JsonLdHelper
     end
   end
 
+  def url_to_media_type(value, preferred_type = nil)
+    value = [value] if value.is_a?(Hash)
+    return unless value.is_a?(Array) && !value.first.is_a?(String)
+
+    single_value = value.find { |link| preferred_type.nil? || ((link['mimeType'].presence || 'text/html') == preferred_type) }
+
+    single_value['mediaType'] unless single_value.nil?
+  end
+
   def as_array(value)
     if value.nil?
       []
diff --git a/app/lib/activitypub/parser/media_attachment_parser.rb b/app/lib/activitypub/parser/media_attachment_parser.rb
index bcbf92214f..1f4f43cb15 100644
--- a/app/lib/activitypub/parser/media_attachment_parser.rb
+++ b/app/lib/activitypub/parser/media_attachment_parser.rb
@@ -15,7 +15,7 @@ class ActivityPub::Parser::MediaAttachmentParser
   end
 
   def remote_url
-    url = Addressable::URI.parse(@json['url'])&.normalize&.to_s
+    url = Addressable::URI.parse(url_to_href(@json['url']))&.normalize&.to_s
     url unless unsupported_uri_scheme?(url)
   rescue Addressable::URI::InvalidURIError
     nil
@@ -43,7 +43,7 @@ class ActivityPub::Parser::MediaAttachmentParser
   end
 
   def file_content_type
-    @json['mediaType']
+    @json['mediaType'] || url_to_media_type(@json['url'])
   end
 
   private
diff --git a/spec/lib/activitypub/parser/media_attachment_parser_spec.rb b/spec/lib/activitypub/parser/media_attachment_parser_spec.rb
new file mode 100644
index 0000000000..9456b5e648
--- /dev/null
+++ b/spec/lib/activitypub/parser/media_attachment_parser_spec.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe ActivityPub::Parser::MediaAttachmentParser do
+  subject { described_class.new(json) }
+
+  let(:json) do
+    {
+      '@context': 'https://www.w3.org/ns/activitystreams',
+      type: 'Document',
+      mediaType: 'image/png',
+      url: 'http://example.com/attachment.png',
+    }.deep_stringify_keys
+  end
+
+  it 'correctly parses media attachment' do
+    expect(subject).to have_attributes(
+      remote_url: 'http://example.com/attachment.png',
+      file_content_type: 'image/png'
+    )
+  end
+
+  context 'when the URL is a link with multiple options' do
+    let(:json) do
+      {
+        '@context': 'https://www.w3.org/ns/activitystreams',
+        type: 'Document',
+        url: [
+          {
+            type: 'Link',
+            mediaType: 'image/png',
+            href: 'http://example.com/attachment.png',
+          },
+          {
+            type: 'Link',
+            mediaType: 'image/avif',
+            href: 'http://example.com/attachment.avif',
+          },
+        ],
+      }.deep_stringify_keys
+    end
+
+    it 'returns the first option' do
+      expect(subject).to have_attributes(
+        remote_url: 'http://example.com/attachment.png',
+        file_content_type: 'image/png'
+      )
+    end
+  end
+end