From 6f3dafecc80fa3274cada0ef76caa5d5cf6d4447 Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 3 Jun 2025 15:04:23 +0200 Subject: [PATCH 01/18] Add tests for featured tag removal (#34888) --- spec/lib/activitypub/activity/remove_spec.rb | 65 +++++++++++++++----- 1 file changed, 48 insertions(+), 17 deletions(-) diff --git a/spec/lib/activitypub/activity/remove_spec.rb b/spec/lib/activitypub/activity/remove_spec.rb index fc12aec8c..809758e67 100644 --- a/spec/lib/activitypub/activity/remove_spec.rb +++ b/spec/lib/activitypub/activity/remove_spec.rb @@ -4,29 +4,60 @@ require 'rails_helper' RSpec.describe ActivityPub::Activity::Remove do let(:sender) { Fabricate(:account, featured_collection_url: 'https://example.com/featured') } - let(:status) { Fabricate(:status, account: sender) } - - let(:json) do - { - '@context': 'https://www.w3.org/ns/activitystreams', - id: 'foo', - type: 'Add', - actor: ActivityPub::TagManager.instance.uri_for(sender), - object: ActivityPub::TagManager.instance.uri_for(status), - target: sender.featured_collection_url, - }.with_indifferent_access - end describe '#perform' do subject { described_class.new(json, sender) } - before do - StatusPin.create!(account: sender, status: status) - subject.perform + context 'when removing a pinned status' do + let(:status) { Fabricate(:status, account: sender) } + + let(:json) do + { + '@context': 'https://www.w3.org/ns/activitystreams', + id: 'foo', + type: 'Remove', + actor: ActivityPub::TagManager.instance.uri_for(sender), + object: ActivityPub::TagManager.instance.uri_for(status), + target: sender.featured_collection_url, + }.deep_stringify_keys + end + + before do + StatusPin.create!(account: sender, status: status) + end + + it 'removes a pin' do + expect { subject.perform } + .to change { sender.pinned?(status) }.to(false) + end end - it 'removes a pin' do - expect(sender.pinned?(status)).to be false + context 'when removing a featured tag' do + let(:tag) { Fabricate(:tag) } + + let(:json) do + { + '@context': 'https://www.w3.org/ns/activitystreams', + id: 'foo', + type: 'Remove', + actor: ActivityPub::TagManager.instance.uri_for(sender), + object: { + type: 'Hashtag', + name: "##{tag.display_name}", + href: "https://example.com/tags/#{tag.name}", + }, + target: sender.featured_collection_url, + }.deep_stringify_keys + end + + before do + sender.featured_tags.find_or_create_by!(name: tag.name) + end + + it 'removes a pin' do + expect { subject.perform } + .to change { sender.featured_tags.exists?(tag: tag) }.to(false) + end end end end From d0e9197d6eefab34efe8e86782a3be3105ca9a3f Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 21 Nov 2024 11:58:04 +0100 Subject: [PATCH 02/18] Fix wrong video dimensions for some rotated videos (#33008) --- app/lib/video_metadata_extractor.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/lib/video_metadata_extractor.rb b/app/lib/video_metadata_extractor.rb index 215576625..fda640512 100644 --- a/app/lib/video_metadata_extractor.rb +++ b/app/lib/video_metadata_extractor.rb @@ -46,6 +46,9 @@ class VideoMetadataExtractor # For some video streams the frame_rate reported by `ffprobe` will be 0/0, but for these streams we # should use `r_frame_rate` instead. Video screencast generated by Gnome Screencast have this issue. @frame_rate ||= @r_frame_rate + # If the video has not been re-encoded by ffmpeg, it may contain rotation information, + # and we need to simulate applying it to the dimensions + @width, @height = @height, @width if video_stream[:side_data_list]&.any? { |x| x[:rotation].abs == 90 } end if (audio_stream = audio_streams.first) From 3e04f1a1b2e407241777e7b4dbee21f4e54d3c32 Mon Sep 17 00:00:00 2001 From: Jeong Arm Date: Tue, 10 Dec 2024 23:47:42 -0500 Subject: [PATCH 03/18] Handle rotation is not present in the video metadata (#33261) --- app/lib/video_metadata_extractor.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/video_metadata_extractor.rb b/app/lib/video_metadata_extractor.rb index fda640512..6e544486d 100644 --- a/app/lib/video_metadata_extractor.rb +++ b/app/lib/video_metadata_extractor.rb @@ -48,7 +48,7 @@ class VideoMetadataExtractor @frame_rate ||= @r_frame_rate # If the video has not been re-encoded by ffmpeg, it may contain rotation information, # and we need to simulate applying it to the dimensions - @width, @height = @height, @width if video_stream[:side_data_list]&.any? { |x| x[:rotation].abs == 90 } + @width, @height = @height, @width if video_stream[:side_data_list]&.any? { |x| x[:rotation]&.abs == 90 } end if (audio_stream = audio_streams.first) From d2f8a3888706471f01ea4e1339111435640f7483 Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 1 Jul 2025 16:18:25 +0200 Subject: [PATCH 04/18] Fix `/share` not using server-set characters limit (#33459) --- app/javascript/mastodon/containers/compose_container.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/javascript/mastodon/containers/compose_container.jsx b/app/javascript/mastodon/containers/compose_container.jsx index f76550678..7d99bdcd7 100644 --- a/app/javascript/mastodon/containers/compose_container.jsx +++ b/app/javascript/mastodon/containers/compose_container.jsx @@ -3,18 +3,19 @@ import { PureComponent } from 'react'; import { Provider } from 'react-redux'; import { fetchCustomEmojis } from '../actions/custom_emojis'; +import { fetchServer } from '../actions/server'; import { hydrateStore } from '../actions/store'; import Compose from '../features/standalone/compose'; import initialState from '../initial_state'; import { IntlProvider } from '../locales'; import { store } from '../store'; - if (initialState) { store.dispatch(hydrateStore(initialState)); } store.dispatch(fetchCustomEmojis()); +store.dispatch(fetchServer()); export default class ComposeContainer extends PureComponent { From f090fde8a8bbe5437796c4cfa8b3aca0c51ebae4 Mon Sep 17 00:00:00 2001 From: Marcel Hellkamp Date: Mon, 12 May 2025 17:51:53 +0200 Subject: [PATCH 05/18] fix: OIDC account creation fails for long display names (#34639) --- app/models/account.rb | 3 ++- app/models/concerns/omniauthable.rb | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/models/account.rb b/app/models/account.rb index d0ddca994..dac44d3ec 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -69,6 +69,7 @@ class Account < ApplicationRecord MENTION_RE = %r{(? { local? && will_save_change_to_username? && actor_type != 'Application' } validates_with UnreservedUsernameValidator, if: -> { local? && will_save_change_to_username? && actor_type != 'Application' } - validates :display_name, length: { maximum: 30 }, if: -> { local? && will_save_change_to_display_name? } + validates :display_name, length: { maximum: DISPLAY_NAME_LENGTH_LIMIT }, if: -> { local? && will_save_change_to_display_name? } validates :note, note_length: { maximum: 500 }, if: -> { local? && will_save_change_to_note? } validates :fields, length: { maximum: 4 }, if: -> { local? && will_save_change_to_fields? } validates :uri, absence: true, if: :local?, on: :create diff --git a/app/models/concerns/omniauthable.rb b/app/models/concerns/omniauthable.rb index 9c004a308..0b9693acc 100644 --- a/app/models/concerns/omniauthable.rb +++ b/app/models/concerns/omniauthable.rb @@ -99,7 +99,7 @@ module Omniauthable external: true, account_attributes: { username: ensure_unique_username(ensure_valid_username(auth.uid)), - display_name: auth.info.full_name || auth.info.name || [auth.info.first_name, auth.info.last_name].join(' '), + display_name: display_name_from_auth(auth), }, } end @@ -121,5 +121,10 @@ module Omniauthable temp_username = starting_username.gsub(/[^a-z0-9_]+/i, '') temp_username.truncate(30, omission: '') end + + def display_name_from_auth(auth) + display_name = auth.info.full_name || auth.info.name || [auth.info.first_name, auth.info.last_name].join(' ') + display_name.truncate(Account::DISPLAY_NAME_LENGTH_LIMIT, omission: '') + end end end From 423791e2fb73bc0ecb1a6a4ccfd377e78b65b793 Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 1 Jul 2025 15:31:56 +0200 Subject: [PATCH 06/18] Fix admin dashboard crash on specific Elasticsearch connection errors (#34683) --- app/lib/admin/system_check/elasticsearch_check.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/lib/admin/system_check/elasticsearch_check.rb b/app/lib/admin/system_check/elasticsearch_check.rb index 406bb5bcb..f1dc4e44e 100644 --- a/app/lib/admin/system_check/elasticsearch_check.rb +++ b/app/lib/admin/system_check/elasticsearch_check.rb @@ -17,7 +17,7 @@ class Admin::SystemCheck::ElasticsearchCheck < Admin::SystemCheck::BaseCheck return true unless Chewy.enabled? running_version.present? && compatible_version? && cluster_health['status'] == 'green' && indexes_match? && preset_matches? - rescue Faraday::ConnectionFailed, Elasticsearch::Transport::Transport::Error + rescue Faraday::ConnectionFailed, Elasticsearch::Transport::Transport::Error, HTTPClient::KeepAliveDisconnected false end @@ -49,7 +49,7 @@ class Admin::SystemCheck::ElasticsearchCheck < Admin::SystemCheck::BaseCheck else Admin::SystemCheck::Message.new(:elasticsearch_preset, nil, 'https://docs.joinmastodon.org/admin/elasticsearch/#scaling') end - rescue Faraday::ConnectionFailed, Elasticsearch::Transport::Transport::Error + rescue Faraday::ConnectionFailed, Elasticsearch::Transport::Transport::Error, HTTPClient::KeepAliveDisconnected Admin::SystemCheck::Message.new(:elasticsearch_running_check) end From 023c24f3de7dd417adcc1e5e334724a43c1466d8 Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 19 May 2025 10:29:31 +0200 Subject: [PATCH 07/18] Change passthrough video processing to emit `moov` atom at start of video (#34726) --- app/models/media_attachment.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb index b567003fb..7cb68e735 100644 --- a/app/models/media_attachment.rb +++ b/app/models/media_attachment.rb @@ -122,6 +122,7 @@ class MediaAttachment < ApplicationRecord output: { 'loglevel' => 'fatal', 'map_metadata' => '-1', + 'movflags' => 'faststart', # Move metadata to start of file so playback can begin before download finishes 'c:v' => 'copy', 'c:a' => 'copy', }.freeze, From f5ba9793175293a31231391a79ee544b350ae978 Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 23 May 2025 17:01:07 +0200 Subject: [PATCH 08/18] Fix handling of inlined `featured` collections in ActivityPub actor objects (#34789) --- .../fetch_featured_collection_service.rb | 6 +- .../activitypub/process_account_service.rb | 6 +- .../synchronize_featured_collection_worker.rb | 2 +- .../fetch_featured_collection_service_spec.rb | 55 ++++++++++++++++++- 4 files changed, 58 insertions(+), 11 deletions(-) diff --git a/app/services/activitypub/fetch_featured_collection_service.rb b/app/services/activitypub/fetch_featured_collection_service.rb index 89c3a1b6c..267c0b461 100644 --- a/app/services/activitypub/fetch_featured_collection_service.rb +++ b/app/services/activitypub/fetch_featured_collection_service.rb @@ -4,13 +4,11 @@ class ActivityPub::FetchFeaturedCollectionService < BaseService include JsonLdHelper def call(account, **options) - return if account.featured_collection_url.blank? || account.suspended? || account.local? + return if (account.featured_collection_url.blank? && options[:collection].blank?) || account.suspended? || account.local? @account = account @options = options - @json = fetch_resource(@account.featured_collection_url, true, local_follower) - - return unless supported_context?(@json) + @json = fetch_collection(options[:collection].presence || @account.featured_collection_url) process_items(collection_items(@json)) end diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb index 871042466..45f6bc361 100644 --- a/app/services/activitypub/process_account_service.rb +++ b/app/services/activitypub/process_account_service.rb @@ -57,7 +57,7 @@ class ActivityPub::ProcessAccountService < BaseService after_suspension_change! if suspension_changed? unless @options[:only_key] || @account.suspended? - check_featured_collection! if @account.featured_collection_url.present? + check_featured_collection! if @json['featured'].present? check_featured_tags_collection! if @json['featuredTags'].present? check_links! if @account.fields.any?(&:requires_verification?) end @@ -121,7 +121,7 @@ class ActivityPub::ProcessAccountService < BaseService end def set_immediate_attributes! - @account.featured_collection_url = @json['featured'] || '' + @account.featured_collection_url = valid_collection_uri(@json['featured']) @account.devices_url = @json['devices'] || '' @account.display_name = @json['name'] || '' @account.note = @json['summary'] || '' @@ -186,7 +186,7 @@ class ActivityPub::ProcessAccountService < BaseService end def check_featured_collection! - ActivityPub::SynchronizeFeaturedCollectionWorker.perform_async(@account.id, { 'hashtag' => @json['featuredTags'].blank?, 'request_id' => @options[:request_id] }) + ActivityPub::SynchronizeFeaturedCollectionWorker.perform_async(@account.id, { 'hashtag' => @json['featuredTags'].blank?, 'collection' => @json['featured'], 'request_id' => @options[:request_id] }) end def check_featured_tags_collection! diff --git a/app/workers/activitypub/synchronize_featured_collection_worker.rb b/app/workers/activitypub/synchronize_featured_collection_worker.rb index 7a187d7f5..f2643d696 100644 --- a/app/workers/activitypub/synchronize_featured_collection_worker.rb +++ b/app/workers/activitypub/synchronize_featured_collection_worker.rb @@ -6,7 +6,7 @@ class ActivityPub::SynchronizeFeaturedCollectionWorker sidekiq_options queue: 'pull', lock: :until_executed, lock_ttl: 1.day.to_i def perform(account_id, options = {}) - options = { note: true, hashtag: false }.deep_merge(options.deep_symbolize_keys) + options = { note: true, hashtag: false }.deep_merge(options.symbolize_keys) ActivityPub::FetchFeaturedCollectionService.new.call(Account.find(account_id), **options) rescue ActiveRecord::RecordNotFound diff --git a/spec/services/activitypub/fetch_featured_collection_service_spec.rb b/spec/services/activitypub/fetch_featured_collection_service_spec.rb index 237fc7123..42d2aef40 100644 --- a/spec/services/activitypub/fetch_featured_collection_service_spec.rb +++ b/spec/services/activitypub/fetch_featured_collection_service_spec.rb @@ -67,7 +67,7 @@ RSpec.describe ActivityPub::FetchFeaturedCollectionService, type: :service do type: 'Collection', id: actor.featured_collection_url, items: items, - }.with_indifferent_access + }.deep_stringify_keys end shared_examples 'sets pinned posts' do @@ -91,6 +91,55 @@ RSpec.describe ActivityPub::FetchFeaturedCollectionService, type: :service do end describe '#call' do + subject { described_class.new.call(actor, note: true, hashtag: false) } + + shared_examples 'sets pinned posts' do + before do + stub_request(:get, 'https://example.com/account/pinned/known').to_return(status: 200, body: Oj.dump(status_json_pinned_known), headers: { 'Content-Type': 'application/activity+json' }) + stub_request(:get, 'https://example.com/account/pinned/unknown-inlined').to_return(status: 200, body: Oj.dump(status_json_pinned_unknown_inlined), headers: { 'Content-Type': 'application/activity+json' }) + stub_request(:get, 'https://example.com/account/pinned/unknown-unreachable').to_return(status: 404) + stub_request(:get, 'https://example.com/account/pinned/unknown-reachable').to_return(status: 200, body: Oj.dump(status_json_pinned_unknown_reachable), headers: { 'Content-Type': 'application/activity+json' }) + stub_request(:get, 'https://example.com/account/collections/featured').to_return(status: 200, body: Oj.dump(featured_with_null), headers: { 'Content-Type': 'application/activity+json' }) + + subject + end + + it 'sets expected posts as pinned posts' do + expect(actor.pinned_statuses.pluck(:uri)).to contain_exactly( + 'https://example.com/account/pinned/known', + 'https://example.com/account/pinned/unknown-inlined', + 'https://example.com/account/pinned/unknown-reachable' + ) + expect(actor.pinned_statuses).to_not include(known_status) + end + end + + context 'when passing the collection via an argument' do + subject { described_class.new.call(actor, note: true, hashtag: false, collection: collection_or_uri) } + + context 'when the collection is an URL' do + let(:collection_or_uri) { actor.featured_collection_url } + + before do + stub_request(:get, actor.featured_collection_url).to_return(status: 200, body: Oj.dump(payload), headers: { 'Content-Type': 'application/activity+json' }) + end + + it_behaves_like 'sets pinned posts' + end + + context 'when the collection is inlined' do + let(:collection_or_uri) do + { + '@context': 'https://www.w3.org/ns/activitystreams', + type: 'Collection', + items: items, + }.deep_stringify_keys + end + + it_behaves_like 'sets pinned posts' + end + end + context 'when the endpoint is a Collection' do before do stub_request(:get, actor.featured_collection_url).to_return(status: 200, body: Oj.dump(payload), headers: { 'Content-Type': 'application/activity+json' }) @@ -120,7 +169,7 @@ RSpec.describe ActivityPub::FetchFeaturedCollectionService, type: :service do before do stub_request(:get, 'https://example.com/account/pinned/unknown-reachable').to_return(status: 200, body: Oj.dump(status_json_pinned_unknown_reachable), headers: { 'Content-Type': 'application/activity+json' }) - subject.call(actor, note: true, hashtag: false) + subject end it 'sets expected posts as pinned posts' do @@ -156,7 +205,7 @@ RSpec.describe ActivityPub::FetchFeaturedCollectionService, type: :service do before do stub_request(:get, 'https://example.com/account/pinned/unknown-reachable').to_return(status: 200, body: Oj.dump(status_json_pinned_unknown_reachable), headers: { 'Content-Type': 'application/activity+json' }) - subject.call(actor, note: true, hashtag: false) + subject end it 'sets expected posts as pinned posts' do From 8bc0fd526548eeabfb36b64f130dc2f3b8a451b3 Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 26 May 2025 10:24:46 +0200 Subject: [PATCH 09/18] Fix `NoMethodError` in `ActivityPub::FetchFeaturedCollectionService` (#34811) --- app/services/activitypub/fetch_featured_collection_service.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/services/activitypub/fetch_featured_collection_service.rb b/app/services/activitypub/fetch_featured_collection_service.rb index 267c0b461..13c55cd56 100644 --- a/app/services/activitypub/fetch_featured_collection_service.rb +++ b/app/services/activitypub/fetch_featured_collection_service.rb @@ -9,6 +9,7 @@ class ActivityPub::FetchFeaturedCollectionService < BaseService @account = account @options = options @json = fetch_collection(options[:collection].presence || @account.featured_collection_url) + return if @json.blank? process_items(collection_items(@json)) end From dd64836fbf28db042a99927c8a27efafd94724c1 Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 30 May 2025 15:20:51 +0200 Subject: [PATCH 10/18] Fix inconsistent filtering of silenced accounts for other silenced accounts (#34863) --- app/lib/status_filter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/status_filter.rb b/app/lib/status_filter.rb index c0e6f3331..eb522e544 100644 --- a/app/lib/status_filter.rb +++ b/app/lib/status_filter.rb @@ -38,7 +38,7 @@ class StatusFilter end def silenced_account? - !account&.silenced? && status_account_silenced? && !account_following_status_account? + status_account_silenced? && !account_following_status_account? end def status_account_silenced? From c35fffc33657bf00fd396e3d5bf48430494088b4 Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 10 Jun 2025 15:26:29 +0200 Subject: [PATCH 11/18] Add basic support for remote attachments with multiple media types (#34996) --- app/helpers/jsonld_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/jsonld_helper.rb b/app/helpers/jsonld_helper.rb index b0f2077db..dee320e8b 100644 --- a/app/helpers/jsonld_helper.rb +++ b/app/helpers/jsonld_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 bcbf92214..1f4f43cb1 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 000000000..9456b5e64 --- /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 From 414321d8ff0e18e1c242a0500ab129c57522c754 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 26 Jun 2025 12:35:49 +0200 Subject: [PATCH 12/18] Fix search operators sometimes getting lost (#35190) --- app/lib/search_query_transformer.rb | 2 +- spec/lib/search_query_transformer_spec.rb | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/app/lib/search_query_transformer.rb b/app/lib/search_query_transformer.rb index 8849acf12..06055eecd 100644 --- a/app/lib/search_query_transformer.rb +++ b/app/lib/search_query_transformer.rb @@ -35,7 +35,7 @@ class SearchQueryTransformer < Parslet::Transform private def clauses_by_operator - @clauses_by_operator ||= @clauses.compact.chunk(&:operator).to_h + @clauses_by_operator ||= @clauses.compact.group_by(&:operator) end def flags_from_clauses! diff --git a/spec/lib/search_query_transformer_spec.rb b/spec/lib/search_query_transformer_spec.rb index 5817e3d1d..bbe5dce74 100644 --- a/spec/lib/search_query_transformer_spec.rb +++ b/spec/lib/search_query_transformer_spec.rb @@ -77,4 +77,24 @@ describe SearchQueryTransformer do expect(subject.send(:filter_clauses).map(&:term)).to contain_exactly(lt: '2022-01-01 23:00', time_zone: 'UTC') end end + + context 'with multiple prefix clauses before a search term' do + let(:query) { 'from:me has:media foo' } + + it 'transforms clauses' do + expect(subject.send(:must_clauses).map(&:term)).to contain_exactly('foo') + expect(subject.send(:must_not_clauses)).to be_empty + expect(subject.send(:filter_clauses).map(&:prefix)).to contain_exactly('from', 'has') + end + end + + context 'with a search term between two prefix clauses' do + let(:query) { 'from:me foo has:media' } + + it 'transforms clauses' do + expect(subject.send(:must_clauses).map(&:term)).to contain_exactly('foo') + expect(subject.send(:must_not_clauses)).to be_empty + expect(subject.send(:filter_clauses).map(&:prefix)).to contain_exactly('from', 'has') + end + end end From 3d3f89bf84c8749b34584df5c9cb3cd5625c607d Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 19 Nov 2024 21:21:12 +0100 Subject: [PATCH 13/18] Fix error when viewing statuses to deleted replies in moderation view (#32986) --- app/views/admin/statuses/show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/admin/statuses/show.html.haml b/app/views/admin/statuses/show.html.haml index e070e5872..287c195e6 100644 --- a/app/views/admin/statuses/show.html.haml +++ b/app/views/admin/statuses/show.html.haml @@ -15,7 +15,7 @@ - if @status.reply? %tr %th= t('admin.statuses.in_reply_to') - %td= admin_account_link_to @status.in_reply_to_account, path: admin_account_status_path(@status.thread.account_id, @status.in_reply_to_id) + %td= admin_account_link_to @status.in_reply_to_account, path: @status.thread.present? ? admin_account_status_path(@status.thread.account_id, @status.in_reply_to_id) : nil %tr %th= t('admin.statuses.application') %td= @status.application&.name From 1969a67a13c9863052927f8c57420a35927a03e0 Mon Sep 17 00:00:00 2001 From: Darius Kazemi Date: Wed, 28 May 2025 05:39:55 -0400 Subject: [PATCH 14/18] Fix `NoMethodError` in edge case of emoji cache handling (#34749) Co-authored-by: Claire --- app/lib/entity_cache.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/lib/entity_cache.rb b/app/lib/entity_cache.rb index 80b0046ee..020abeb56 100644 --- a/app/lib/entity_cache.rb +++ b/app/lib/entity_cache.rb @@ -26,7 +26,9 @@ class EntityCache uncached_ids << shortcode unless cached.key?(to_key(:emoji, shortcode, domain)) end - unless uncached_ids.empty? + if uncached_ids.empty? + uncached = {} + else uncached = CustomEmoji.where(shortcode: shortcodes, domain: domain, disabled: false).index_by(&:shortcode) uncached.each_value { |item| Rails.cache.write(to_key(:emoji, item.shortcode, domain), item, expires_in: MAX_EXPIRATION) } end From 362974f7cb93a72c9d86a2672241ab5574958ab0 Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 1 Jul 2025 16:39:21 +0200 Subject: [PATCH 15/18] Bump version to v4.2.22 --- CHANGELOG.md | 19 +++++++++++++++++++ docker-compose.yml | 6 +++--- lib/mastodon/version.rb | 2 +- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c583667fb..73fed1ca8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,25 @@ All notable changes to this project will be documented in this file. +## [4.2.22] - 2025-07-02 + +### Changed + +- Change passthrough video processing to emit `moov` atom at start of video (#34726 by @ClearlyClaire) + +### Fixed + +- Fix `NoMethodError` in edge case of emoji cache handling (#34749 by @dariusk) +- Fix error when viewing statuses to deleted replies in moderation view (#32986 by @ClearlyClaire) +- Fix search operators sometimes getting lost (#35190 by @ClearlyClaire) +- Fix handling of remote attachments with multiple media types (#34996 by @ClearlyClaire) +- Fix inconsistent filtering of silenced accounts for other silenced accounts (#34863 by @ClearlyClaire) +- Fix handling of inlined `featured` collections in ActivityPub actor objects (#34789 and #34811 by @ClearlyClaire) +- Fix admin dashboard crash on specific Elasticsearch connection errors (#34683 by @ClearlyClaire) +- Fix OIDC account creation failing for long display names (#34639 by @defnull) +- Fix `/share` not using server-set characters limit (#33459 by @kescherCode) +- Fix wrong video dimensions for some rotated videos (#33008 and #33261 by @Gargron and @tribela) + ## [4.2.21] - 2025-05-06 ### Security diff --git a/docker-compose.yml b/docker-compose.yml index 424f6822a..c6b5b172c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -56,7 +56,7 @@ services: web: build: . - image: ghcr.io/mastodon/mastodon:v4.2.21 + image: ghcr.io/mastodon/mastodon:v4.2.22 restart: always env_file: .env.production command: bash -c "rm -f /mastodon/tmp/pids/server.pid; bundle exec rails s -p 3000" @@ -77,7 +77,7 @@ services: streaming: build: . - image: ghcr.io/mastodon/mastodon:v4.2.21 + image: ghcr.io/mastodon/mastodon:v4.2.22 restart: always env_file: .env.production command: node ./streaming @@ -95,7 +95,7 @@ services: sidekiq: build: . - image: ghcr.io/mastodon/mastodon:v4.2.21 + image: ghcr.io/mastodon/mastodon:v4.2.22 restart: always env_file: .env.production command: bundle exec sidekiq diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index 6223a7595..d7ca67bc5 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -13,7 +13,7 @@ module Mastodon end def patch - 21 + 22 end def default_prerelease From 5a70b2a8313cb2026f4eb2fd36e3c4b04a413788 Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 8 Jul 2025 16:24:06 +0200 Subject: [PATCH 16/18] Update security policy (#35293) --- SECURITY.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 26c06e67f..19f431fac 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -13,8 +13,9 @@ A "vulnerability in Mastodon" is a vulnerability in the code distributed through ## Supported Versions -| Version | Supported | -| ------- | --------- | -| 4.3.x | Yes | -| 4.2.x | Yes | -| < 4.2 | No | +| Version | Supported | +| ------- | ---------------- | +| 4.4.x | Yes | +| 4.3.x | Yes | +| 4.2.x | Until 2026-01-08 | +| < 4.2 | No | From 57967afb1545e795e8fd59fb7e4cdb5ba94ae657 Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Mon, 21 Jul 2025 17:05:49 +0200 Subject: [PATCH 17/18] Bump version to v4.2.23 --- CHANGELOG.md | 6 ++++++ Gemfile.lock | 6 +++--- docker-compose.yml | 6 +++--- lib/mastodon/version.rb | 2 +- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73fed1ca8..605f611bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to this project will be documented in this file. +## [4.2.23] - 2025-07-23 + +### Security + +- Updated dependencies + ## [4.2.22] - 2025-07-02 ### Changed diff --git a/Gemfile.lock b/Gemfile.lock index deafe1b60..45b95473a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -446,7 +446,7 @@ GEM mime-types-data (~> 3.2015) mime-types-data (3.2023.0808) mini_mime (1.1.5) - mini_portile2 (2.8.8) + mini_portile2 (2.8.9) minitest (5.19.0) msgpack (1.7.1) multi_json (1.15.0) @@ -469,7 +469,7 @@ GEM net-protocol net-ssh (7.1.0) nio4r (2.7.4) - nokogiri (1.18.8) + nokogiri (1.18.9) mini_portile2 (~> 2.8.2) racc (~> 1.4) nsa (0.3.0) @@ -533,7 +533,7 @@ GEM activesupport (>= 3.0.0) raabro (1.4.0) racc (1.8.1) - rack (2.2.13) + rack (2.2.17) rack-attack (6.7.0) rack (>= 1.0, < 4) rack-cors (2.0.2) diff --git a/docker-compose.yml b/docker-compose.yml index c6b5b172c..34f65f2b0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -56,7 +56,7 @@ services: web: build: . - image: ghcr.io/mastodon/mastodon:v4.2.22 + image: ghcr.io/mastodon/mastodon:v4.2.23 restart: always env_file: .env.production command: bash -c "rm -f /mastodon/tmp/pids/server.pid; bundle exec rails s -p 3000" @@ -77,7 +77,7 @@ services: streaming: build: . - image: ghcr.io/mastodon/mastodon:v4.2.22 + image: ghcr.io/mastodon/mastodon:v4.2.23 restart: always env_file: .env.production command: node ./streaming @@ -95,7 +95,7 @@ services: sidekiq: build: . - image: ghcr.io/mastodon/mastodon:v4.2.22 + image: ghcr.io/mastodon/mastodon:v4.2.23 restart: always env_file: .env.production command: bundle exec sidekiq diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index d7ca67bc5..0ed8c1674 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -13,7 +13,7 @@ module Mastodon end def patch - 22 + 23 end def default_prerelease From 0b10d3c5269703b60a6f003b8e62a52ce707aebf Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Wed, 23 Jul 2025 12:01:04 +0200 Subject: [PATCH 18/18] Update dependency thor --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 45b95473a..bdca97dae 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -741,7 +741,7 @@ GEM terrapin (0.6.0) climate_control (>= 0.0.3, < 1.0) test-prof (1.2.3) - thor (1.3.2) + thor (1.4.0) tilt (2.2.0) timeout (0.4.3) tpm-key_attestation (0.12.0)