Fix AVIF attachments (#26264)
This commit is contained in:
		
							parent
							
								
									71fd70335a
								
							
						
					
					
						commit
						f2257069b2
					
				
					 9 changed files with 87 additions and 36 deletions
				
			
		| 
						 | 
				
			
			@ -57,7 +57,7 @@ class MediaAttachment < ApplicationRecord
 | 
			
		|||
  ).freeze
 | 
			
		||||
 | 
			
		||||
  IMAGE_MIME_TYPES             = %w(image/jpeg image/png image/gif image/heic image/heif image/webp image/avif).freeze
 | 
			
		||||
  IMAGE_CONVERTIBLE_MIME_TYPES = %w(image/heic image/heif).freeze
 | 
			
		||||
  IMAGE_CONVERTIBLE_MIME_TYPES = %w(image/heic image/heif image/avif).freeze
 | 
			
		||||
  VIDEO_MIME_TYPES             = %w(video/webm video/mp4 video/quicktime video/ogg).freeze
 | 
			
		||||
  VIDEO_CONVERTIBLE_MIME_TYPES = %w(video/webm video/quicktime).freeze
 | 
			
		||||
  AUDIO_MIME_TYPES             = %w(audio/wave audio/wav audio/x-wav audio/x-pn-wave audio/vnd.wave audio/ogg audio/vorbis audio/mpeg audio/mp3 audio/webm audio/flac audio/aac audio/m4a audio/x-m4a audio/mp4 audio/3gpp video/x-ms-asf).freeze
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,6 +22,6 @@
 | 
			
		|||
 | 
			
		||||
  <!-- Disallow any coder by default, and only enable ones required by Mastodon -->
 | 
			
		||||
  <policy domain="coder" rights="none" pattern="*" />
 | 
			
		||||
  <policy domain="coder" rights="read | write" pattern="{PNG,JPEG,GIF,HEIC,WEBP}" />
 | 
			
		||||
  <policy domain="coder" rights="read | write" pattern="{JPEG,PNG,GIF,WEBP,HEIC,AVIF}" />
 | 
			
		||||
  <policy domain="coder" rights="write" pattern="{HISTOGRAM,RGB,INFO}" />
 | 
			
		||||
</policymap>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,13 +2,15 @@
 | 
			
		|||
 | 
			
		||||
module Paperclip
 | 
			
		||||
  module MediaTypeSpoofDetectorExtensions
 | 
			
		||||
    MARCEL_MIME_TYPES = %w(audio/mpeg image/avif).freeze
 | 
			
		||||
 | 
			
		||||
    def calculated_content_type
 | 
			
		||||
      return @calculated_content_type if defined?(@calculated_content_type)
 | 
			
		||||
 | 
			
		||||
      @calculated_content_type = type_from_file_command.chomp
 | 
			
		||||
 | 
			
		||||
      # The `file` command fails to recognize some MP3 files as such
 | 
			
		||||
      @calculated_content_type = type_from_marcel if @calculated_content_type == 'application/octet-stream' && type_from_marcel == 'audio/mpeg'
 | 
			
		||||
      @calculated_content_type = type_from_marcel if @calculated_content_type == 'application/octet-stream' && type_from_marcel.in?(MARCEL_MIME_TYPES)
 | 
			
		||||
      @calculated_content_type
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										
											BIN
										
									
								
								spec/fixtures/files/600x400.avif
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								spec/fixtures/files/600x400.avif
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 7.6 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								spec/fixtures/files/600x400.heic
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								spec/fixtures/files/600x400.heic
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								spec/fixtures/files/600x400.jpeg
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								spec/fixtures/files/600x400.jpeg
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 21 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								spec/fixtures/files/600x400.png
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								spec/fixtures/files/600x400.png
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 14 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								spec/fixtures/files/600x400.webp
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								spec/fixtures/files/600x400.webp
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 8.8 KiB  | 
| 
						 | 
				
			
			@ -84,7 +84,87 @@ RSpec.describe MediaAttachment, paperclip_processing: true do
 | 
			
		|||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe 'animated gif conversion' do
 | 
			
		||||
  shared_examples 'static 600x400 image' do |content_type, extension|
 | 
			
		||||
    after do
 | 
			
		||||
      media.destroy
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'saves media attachment' do
 | 
			
		||||
      expect(media.persisted?).to be true
 | 
			
		||||
      expect(media.file).to_not be_nil
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'completes processing' do
 | 
			
		||||
      expect(media.processing_complete?).to be true
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'sets type' do
 | 
			
		||||
      expect(media.type).to eq 'image'
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'sets content type' do
 | 
			
		||||
      expect(media.file_content_type).to eq content_type
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'sets file extension' do
 | 
			
		||||
      expect(media.file_file_name).to end_with extension
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'strips original file name' do
 | 
			
		||||
      expect(media.file_file_name).to_not start_with '600x400'
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'sets meta for original' do
 | 
			
		||||
      expect(media.file.meta['original']['width']).to eq 600
 | 
			
		||||
      expect(media.file.meta['original']['height']).to eq 400
 | 
			
		||||
      expect(media.file.meta['original']['aspect']).to eq 1.5
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'sets meta for thumbnail' do
 | 
			
		||||
      expect(media.file.meta['small']['width']).to eq 588
 | 
			
		||||
      expect(media.file.meta['small']['height']).to eq 392
 | 
			
		||||
      expect(media.file.meta['small']['aspect']).to eq 1.5
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe 'jpeg' do
 | 
			
		||||
    let(:media) { described_class.create(account: Fabricate(:account), file: attachment_fixture('600x400.jpeg')) }
 | 
			
		||||
 | 
			
		||||
    it_behaves_like 'static 600x400 image', 'image/jpeg', '.jpeg'
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe 'png' do
 | 
			
		||||
    let(:media) { described_class.create(account: Fabricate(:account), file: attachment_fixture('600x400.png')) }
 | 
			
		||||
 | 
			
		||||
    it_behaves_like 'static 600x400 image', 'image/png', '.png'
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe 'webp' do
 | 
			
		||||
    let(:media) { described_class.create(account: Fabricate(:account), file: attachment_fixture('600x400.webp')) }
 | 
			
		||||
 | 
			
		||||
    it_behaves_like 'static 600x400 image', 'image/webp', '.webp'
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe 'avif' do
 | 
			
		||||
    let(:media) { described_class.create(account: Fabricate(:account), file: attachment_fixture('600x400.avif')) }
 | 
			
		||||
 | 
			
		||||
    it_behaves_like 'static 600x400 image', 'image/jpeg', '.jpeg'
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe 'heic' do
 | 
			
		||||
    let(:media) { described_class.create(account: Fabricate(:account), file: attachment_fixture('600x400.heic')) }
 | 
			
		||||
 | 
			
		||||
    it_behaves_like 'static 600x400 image', 'image/jpeg', '.jpeg'
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe 'base64-encoded image' do
 | 
			
		||||
    let(:base64_attachment) { "data:image/jpeg;base64,#{Base64.encode64(attachment_fixture('600x400.jpeg').read)}" }
 | 
			
		||||
    let(:media) { described_class.create(account: Fabricate(:account), file: base64_attachment) }
 | 
			
		||||
 | 
			
		||||
    it_behaves_like 'static 600x400 image', 'image/jpeg', '.jpeg'
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe 'animated gif' do
 | 
			
		||||
    let(:media) { described_class.create(account: Fabricate(:account), file: attachment_fixture('avatar.gif')) }
 | 
			
		||||
 | 
			
		||||
    it 'sets type to gifv' do
 | 
			
		||||
| 
						 | 
				
			
			@ -101,7 +181,7 @@ RSpec.describe MediaAttachment, paperclip_processing: true do
 | 
			
		|||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe 'non-animated gif non-conversion' do
 | 
			
		||||
  describe 'static gif' do
 | 
			
		||||
    fixtures = [
 | 
			
		||||
      { filename: 'attachment.gif', width: 600, height: 400, aspect: 1.5 },
 | 
			
		||||
      { filename: 'mini-static.gif', width: 32, height: 32, aspect: 1.0 },
 | 
			
		||||
| 
						 | 
				
			
			@ -172,37 +252,6 @@ RSpec.describe MediaAttachment, paperclip_processing: true do
 | 
			
		|||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe 'jpeg' do
 | 
			
		||||
    let(:media) { described_class.create(account: Fabricate(:account), file: attachment_fixture('attachment.jpg')) }
 | 
			
		||||
 | 
			
		||||
    it 'sets meta for different style' do
 | 
			
		||||
      expect(media.file.meta['original']['width']).to eq 600
 | 
			
		||||
      expect(media.file.meta['original']['height']).to eq 400
 | 
			
		||||
      expect(media.file.meta['original']['aspect']).to eq 1.5
 | 
			
		||||
      expect(media.file.meta['small']['width']).to eq 588
 | 
			
		||||
      expect(media.file.meta['small']['height']).to eq 392
 | 
			
		||||
      expect(media.file.meta['small']['aspect']).to eq 1.5
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'gives the file a random name' do
 | 
			
		||||
      expect(media.file_file_name).to_not eq 'attachment.jpg'
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe 'base64-encoded jpeg' do
 | 
			
		||||
    let(:base64_attachment) { "data:image/jpeg;base64,#{Base64.encode64(attachment_fixture('attachment.jpg').read)}" }
 | 
			
		||||
    let(:media) { described_class.create(account: Fabricate(:account), file: base64_attachment) }
 | 
			
		||||
 | 
			
		||||
    it 'saves media attachment' do
 | 
			
		||||
      expect(media.persisted?).to be true
 | 
			
		||||
      expect(media.file).to_not be_nil
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'gives the file a file name' do
 | 
			
		||||
      expect(media.file_file_name).to_not be_blank
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  it 'is invalid without file' do
 | 
			
		||||
    media = described_class.new(account: Fabricate(:account))
 | 
			
		||||
    expect(media.valid?).to be false
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Reference in a new issue