active_storage_validations 1.4.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +619 -213
- data/config/locales/da.yml +50 -30
- data/config/locales/de.yml +50 -30
- data/config/locales/en.yml +50 -30
- data/config/locales/es.yml +50 -30
- data/config/locales/fr.yml +50 -30
- data/config/locales/it.yml +50 -30
- data/config/locales/ja.yml +50 -30
- data/config/locales/nl.yml +50 -30
- data/config/locales/pl.yml +50 -30
- data/config/locales/pt-BR.yml +50 -30
- data/config/locales/ru.yml +50 -30
- data/config/locales/sv.yml +50 -30
- data/config/locales/tr.yml +50 -30
- data/config/locales/uk.yml +50 -30
- data/config/locales/vi.yml +50 -30
- data/config/locales/zh-CN.yml +50 -30
- data/lib/active_storage_validations/analyzer/audio_analyzer.rb +58 -0
- data/lib/active_storage_validations/analyzer/content_type_analyzer.rb +60 -0
- data/lib/active_storage_validations/analyzer/image_analyzer/image_magick.rb +4 -4
- data/lib/active_storage_validations/analyzer/image_analyzer/vips.rb +11 -12
- data/lib/active_storage_validations/analyzer/image_analyzer.rb +9 -53
- data/lib/active_storage_validations/analyzer/null_analyzer.rb +2 -2
- data/lib/active_storage_validations/analyzer/shared/asv_ff_probable.rb +61 -0
- data/lib/active_storage_validations/analyzer/video_analyzer.rb +130 -0
- data/lib/active_storage_validations/analyzer.rb +54 -1
- data/lib/active_storage_validations/aspect_ratio_validator.rb +15 -11
- data/lib/active_storage_validations/{base_size_validator.rb → base_comparison_validator.rb} +18 -16
- data/lib/active_storage_validations/content_type_validator.rb +56 -23
- data/lib/active_storage_validations/dimension_validator.rb +20 -19
- data/lib/active_storage_validations/duration_validator.rb +55 -0
- data/lib/active_storage_validations/extensors/asv_blob_metadatable.rb +49 -0
- data/lib/active_storage_validations/{marcel_extensor.rb → extensors/asv_marcelable.rb} +3 -0
- data/lib/active_storage_validations/limit_validator.rb +14 -2
- data/lib/active_storage_validations/matchers/aspect_ratio_validator_matcher.rb +1 -1
- data/lib/active_storage_validations/matchers/{base_size_validator_matcher.rb → base_comparison_validator_matcher.rb} +31 -25
- data/lib/active_storage_validations/matchers/content_type_validator_matcher.rb +7 -3
- data/lib/active_storage_validations/matchers/dimension_validator_matcher.rb +1 -1
- data/lib/active_storage_validations/matchers/duration_validator_matcher.rb +39 -0
- data/lib/active_storage_validations/matchers/{processable_image_validator_matcher.rb → processable_file_validator_matcher.rb} +5 -5
- data/lib/active_storage_validations/matchers/size_validator_matcher.rb +18 -2
- data/lib/active_storage_validations/matchers/total_size_validator_matcher.rb +18 -2
- data/lib/active_storage_validations/matchers.rb +5 -3
- data/lib/active_storage_validations/{processable_image_validator.rb → processable_file_validator.rb} +4 -3
- data/lib/active_storage_validations/railtie.rb +5 -0
- data/lib/active_storage_validations/shared/asv_analyzable.rb +38 -3
- data/lib/active_storage_validations/shared/asv_attachable.rb +36 -15
- data/lib/active_storage_validations/size_validator.rb +11 -3
- data/lib/active_storage_validations/total_size_validator.rb +9 -3
- data/lib/active_storage_validations/version.rb +1 -1
- data/lib/active_storage_validations.rb +7 -3
- metadata +14 -8
- data/lib/active_storage_validations/content_type_spoof_detector.rb +0 -96
| @@ -12,13 +12,36 @@ module ActiveStorageValidations | |
| 12 12 |  | 
| 13 13 | 
             
                private
         | 
| 14 14 |  | 
| 15 | 
            -
                 | 
| 16 | 
            -
             | 
| 15 | 
            +
                # Retrieve the ASV metadata from the blob.
         | 
| 16 | 
            +
                # If the blob has not been analyzed by our gem yet, the gem will analyze the
         | 
| 17 | 
            +
                # attachable with the corresponding analyzer and set the metadata in the
         | 
| 18 | 
            +
                # blob.
         | 
| 19 | 
            +
                def metadata_for(blob, attachable, metadata_keys)
         | 
| 20 | 
            +
                  return blob.active_storage_validations_metadata if blob_has_asv_metadata?(blob, metadata_keys)
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  new_metadata = generate_metadata_for(attachable, metadata_keys)
         | 
| 23 | 
            +
                  blob.merge_into_active_storage_validations_metadata(new_metadata)
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                def blob_has_asv_metadata?(blob, metadata_keys)
         | 
| 27 | 
            +
                  return false unless blob.active_storage_validations_metadata.present?
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  metadata_keys.all? { |key| blob.active_storage_validations_metadata.key?(key) }
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def generate_metadata_for(attachable, metadata_keys)
         | 
| 33 | 
            +
                  if metadata_keys == ActiveStorageValidations::ContentTypeValidator::METADATA_KEYS
         | 
| 34 | 
            +
                    content_type_analyzer_for(attachable).content_type
         | 
| 35 | 
            +
                  else
         | 
| 36 | 
            +
                    metadata_analyzer_for(attachable).metadata
         | 
| 37 | 
            +
                  end
         | 
| 17 38 | 
             
                end
         | 
| 18 39 |  | 
| 19 | 
            -
                def  | 
| 40 | 
            +
                def metadata_analyzer_for(attachable)
         | 
| 20 41 | 
             
                  case attachable_media_type(attachable)
         | 
| 21 42 | 
             
                  when "image" then image_analyzer_for(attachable)
         | 
| 43 | 
            +
                  when "video" then video_analyzer_for(attachable)
         | 
| 44 | 
            +
                  when "audio" then audio_analyzer_for(attachable)
         | 
| 22 45 | 
             
                  else fallback_analyzer_for(attachable)
         | 
| 23 46 | 
             
                  end
         | 
| 24 47 | 
             
                end
         | 
| @@ -38,8 +61,20 @@ module ActiveStorageValidations | |
| 38 61 | 
             
                  ActiveStorage.variant_processor || DEFAULT_IMAGE_PROCESSOR
         | 
| 39 62 | 
             
                end
         | 
| 40 63 |  | 
| 64 | 
            +
                def video_analyzer_for(attachable)
         | 
| 65 | 
            +
                  ActiveStorageValidations::Analyzer::VideoAnalyzer.new(attachable)
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                def audio_analyzer_for(attachable)
         | 
| 69 | 
            +
                  ActiveStorageValidations::Analyzer::AudioAnalyzer.new(attachable)
         | 
| 70 | 
            +
                end
         | 
| 71 | 
            +
             | 
| 41 72 | 
             
                def fallback_analyzer_for(attachable)
         | 
| 42 73 | 
             
                  ActiveStorageValidations::Analyzer::NullAnalyzer.new(attachable)
         | 
| 43 74 | 
             
                end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                def content_type_analyzer_for(attachable)
         | 
| 77 | 
            +
                  ActiveStorageValidations::Analyzer::ContentTypeAnalyzer.new(attachable)
         | 
| 78 | 
            +
                end
         | 
| 44 79 | 
             
              end
         | 
| 45 80 | 
             
            end
         | 
| @@ -16,22 +16,37 @@ module ActiveStorageValidations | |
| 16 16 | 
             
                # Loop through the newly submitted attachables to validate them. Using
         | 
| 17 17 | 
             
                # attachables is the only way to get the attached file io that is necessary
         | 
| 18 18 | 
             
                # to perform file analyses.
         | 
| 19 | 
            -
                def validate_changed_files_from_metadata(record, attribute)
         | 
| 20 | 
            -
                   | 
| 21 | 
            -
                    is_valid?(record, attribute, attachable, metadata_for(attachable))
         | 
| 19 | 
            +
                def validate_changed_files_from_metadata(record, attribute, metadata_keys)
         | 
| 20 | 
            +
                  attachables_and_blobs(record, attribute).each do |attachable, blob|
         | 
| 21 | 
            +
                    is_valid?(record, attribute, attachable, metadata_for(blob, attachable, metadata_keys))
         | 
| 22 22 | 
             
                  end
         | 
| 23 23 | 
             
                end
         | 
| 24 24 |  | 
| 25 | 
            -
                # Retrieve an array of  | 
| 26 | 
            -
                #  | 
| 27 | 
            -
                #  | 
| 28 | 
            -
                 | 
| 29 | 
            -
             | 
| 30 | 
            -
             | 
| 25 | 
            +
                # Retrieve an array-like of attachables and blobs. Unlike its name suggests,
         | 
| 26 | 
            +
                # getting attachables from attachment_changes is not getting the changed
         | 
| 27 | 
            +
                # attachables but all attachables from the `has_many_attached` relation.
         | 
| 28 | 
            +
                # For the `has_one_attached` relation, it only yields the new attachable,
         | 
| 29 | 
            +
                # but if we are validating previously attached file, we need to use the blob
         | 
| 30 | 
            +
                # See #attach at: https://github.com/rails/rails/blob/main/activestorage/lib/active_storage/attached/many.rb
         | 
| 31 | 
            +
                #
         | 
| 32 | 
            +
                # Some file could be passed several times, we just need to perform the
         | 
| 33 | 
            +
                # analysis once on the file, therefore the use of #uniq.
         | 
| 34 | 
            +
                def attachables_and_blobs(record, attribute)
         | 
| 35 | 
            +
                  changes = if record.public_send(attribute).is_a?(ActiveStorage::Attached::One)
         | 
| 36 | 
            +
                    record.attachment_changes[attribute.to_s].presence || record.public_send(attribute)
         | 
| 37 | 
            +
                  else
         | 
| 38 | 
            +
                    record.attachment_changes[attribute.to_s]
         | 
| 39 | 
            +
                  end
         | 
| 31 40 |  | 
| 32 | 
            -
                   | 
| 33 | 
            -
             | 
| 34 | 
            -
                  ) | 
| 41 | 
            +
                  return to_enum(:attachables_and_blobs, record, attribute) if changes.blank? || !block_given?
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  if changes.is_a?(ActiveStorage::Attached::Changes::CreateMany)
         | 
| 44 | 
            +
                    changes.attachables.uniq.zip(changes.blobs.uniq).each do |attachable, blob|
         | 
| 45 | 
            +
                      yield attachable, blob
         | 
| 46 | 
            +
                    end
         | 
| 47 | 
            +
                  else
         | 
| 48 | 
            +
                    yield changes.is_a?(ActiveStorage::Attached::Changes::CreateOne) ? changes.attachable : changes.blob, changes.blob
         | 
| 49 | 
            +
                  end
         | 
| 35 50 | 
             
                end
         | 
| 36 51 |  | 
| 37 52 | 
             
                # Retrieve the full declared content_type from attachable.
         | 
| @@ -60,9 +75,15 @@ module ActiveStorageValidations | |
| 60 75 | 
             
                # Retrieve the declared content_type from attachable without potential mime
         | 
| 61 76 | 
             
                # type parameters (e.g. 'application/x-rar-compressed;version=5')
         | 
| 62 77 | 
             
                def attachable_content_type(attachable)
         | 
| 63 | 
            -
                  full_attachable_content_type(attachable) && full_attachable_content_type(attachable) | 
| 78 | 
            +
                  full_attachable_content_type(attachable) && content_type_without_parameters(full_attachable_content_type(attachable))
         | 
| 79 | 
            +
                end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                # Remove the potential mime type parameters from the content_type (e.g.
         | 
| 82 | 
            +
                # 'application/x-rar-compressed;version=5')
         | 
| 83 | 
            +
                def content_type_without_parameters(content_type)
         | 
| 84 | 
            +
                  content_type && content_type.downcase.split(/[;,\s]/, 2).first
         | 
| 64 85 | 
             
                end
         | 
| 65 | 
            -
             | 
| 86 | 
            +
             | 
| 66 87 | 
             
                # Retrieve the content_type from attachable using the same logic as Rails
         | 
| 67 88 | 
             
                # ActiveStorage::Blob::Identifiable#identify_content_type
         | 
| 68 89 | 
             
                def attachable_content_type_rails_like(attachable)
         | 
| @@ -79,7 +100,7 @@ module ActiveStorageValidations | |
| 79 100 | 
             
                def attachable_media_type(attachable)
         | 
| 80 101 | 
             
                  (full_attachable_content_type(attachable) || marcel_content_type_from_filename(attachable)).split("/").first
         | 
| 81 102 | 
             
                end
         | 
| 82 | 
            -
             | 
| 103 | 
            +
             | 
| 83 104 | 
             
                # Retrieve the io from attachable.
         | 
| 84 105 | 
             
                def attachable_io(attachable, max_byte_size: nil)
         | 
| 85 106 | 
             
                  io = case attachable
         | 
| @@ -1,9 +1,9 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            require_relative ' | 
| 3 | 
            +
            require_relative 'base_comparison_validator'
         | 
| 4 4 |  | 
| 5 5 | 
             
            module ActiveStorageValidations
         | 
| 6 | 
            -
              class SizeValidator <  | 
| 6 | 
            +
              class SizeValidator < BaseComparisonValidator
         | 
| 7 7 | 
             
                ERROR_TYPES = %i[
         | 
| 8 8 | 
             
                  file_size_not_less_than
         | 
| 9 9 | 
             
                  file_size_not_less_than_or_equal_to
         | 
| @@ -12,6 +12,8 @@ module ActiveStorageValidations | |
| 12 12 | 
             
                  file_size_not_between
         | 
| 13 13 | 
             
                ].freeze
         | 
| 14 14 |  | 
| 15 | 
            +
                delegate :number_to_human_size, to: ActiveSupport::NumberHelper
         | 
| 16 | 
            +
             | 
| 15 17 | 
             
                def validate_each(record, attribute, _value)
         | 
| 16 18 | 
             
                  return if no_attachments?(record, attribute)
         | 
| 17 19 |  | 
| @@ -22,7 +24,7 @@ module ActiveStorageValidations | |
| 22 24 |  | 
| 23 25 | 
             
                    errors_options = initialize_error_options(options, file)
         | 
| 24 26 | 
             
                    populate_error_options(errors_options, flat_options)
         | 
| 25 | 
            -
                    errors_options[:file_size] =  | 
| 27 | 
            +
                    errors_options[:file_size] = format_bound_value(file.blob.byte_size)
         | 
| 26 28 |  | 
| 27 29 | 
             
                    keys = AVAILABLE_CHECKS & flat_options.keys
         | 
| 28 30 | 
             
                    error_type = "file_size_not_#{keys.first}".to_sym
         | 
| @@ -30,5 +32,11 @@ module ActiveStorageValidations | |
| 30 32 | 
             
                    add_error(record, attribute, error_type, **errors_options)
         | 
| 31 33 | 
             
                  end
         | 
| 32 34 | 
             
                end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                private
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                def format_bound_value(value)
         | 
| 39 | 
            +
                  number_to_human_size(value)
         | 
| 40 | 
            +
                end
         | 
| 33 41 | 
             
              end
         | 
| 34 42 | 
             
            end
         | 
| @@ -1,9 +1,9 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            require_relative ' | 
| 3 | 
            +
            require_relative 'base_comparison_validator'
         | 
| 4 4 |  | 
| 5 5 | 
             
            module ActiveStorageValidations
         | 
| 6 | 
            -
              class TotalSizeValidator <  | 
| 6 | 
            +
              class TotalSizeValidator < BaseComparisonValidator
         | 
| 7 7 | 
             
                ERROR_TYPES = %i[
         | 
| 8 8 | 
             
                  total_file_size_not_less_than
         | 
| 9 9 | 
             
                  total_file_size_not_less_than_or_equal_to
         | 
| @@ -12,6 +12,8 @@ module ActiveStorageValidations | |
| 12 12 | 
             
                  total_file_size_not_between
         | 
| 13 13 | 
             
                ].freeze
         | 
| 14 14 |  | 
| 15 | 
            +
                delegate :number_to_human_size, to: ActiveSupport::NumberHelper
         | 
| 16 | 
            +
             | 
| 15 17 | 
             
                def validate_each(record, attribute, _value)
         | 
| 16 18 | 
             
                  custom_check_validity!(record, attribute)
         | 
| 17 19 |  | 
| @@ -24,7 +26,7 @@ module ActiveStorageValidations | |
| 24 26 |  | 
| 25 27 | 
             
                  errors_options = initialize_error_options(options, nil)
         | 
| 26 28 | 
             
                  populate_error_options(errors_options, flat_options)
         | 
| 27 | 
            -
                  errors_options[:total_file_size] =  | 
| 29 | 
            +
                  errors_options[:total_file_size] = format_bound_value(total_file_size)
         | 
| 28 30 |  | 
| 29 31 | 
             
                  keys = AVAILABLE_CHECKS & flat_options.keys
         | 
| 30 32 | 
             
                  error_type = "total_file_size_not_#{keys.first}".to_sym
         | 
| @@ -41,5 +43,9 @@ module ActiveStorageValidations | |
| 41 43 | 
             
                    raise ArgumentError, 'This validator is only available for has_many_attached relations'
         | 
| 42 44 | 
             
                  end
         | 
| 43 45 | 
             
                end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                def format_bound_value(value)
         | 
| 48 | 
            +
                  number_to_human_size(value)
         | 
| 49 | 
            +
                end
         | 
| 44 50 | 
             
              end
         | 
| 45 51 | 
             
            end
         | 
| @@ -8,6 +8,11 @@ require 'active_storage_validations/analyzer/image_analyzer' | |
| 8 8 | 
             
            require 'active_storage_validations/analyzer/image_analyzer/image_magick'
         | 
| 9 9 | 
             
            require 'active_storage_validations/analyzer/image_analyzer/vips'
         | 
| 10 10 | 
             
            require 'active_storage_validations/analyzer/null_analyzer'
         | 
| 11 | 
            +
            require 'active_storage_validations/analyzer/video_analyzer'
         | 
| 12 | 
            +
            require 'active_storage_validations/analyzer/audio_analyzer'
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            require 'active_storage_validations/extensors/asv_blob_metadatable'
         | 
| 15 | 
            +
            require 'active_storage_validations/extensors/asv_marcelable'
         | 
| 11 16 |  | 
| 12 17 | 
             
            require 'active_storage_validations/railtie'
         | 
| 13 18 | 
             
            require 'active_storage_validations/engine'
         | 
| @@ -15,13 +20,12 @@ require 'active_storage_validations/attached_validator' | |
| 15 20 | 
             
            require 'active_storage_validations/content_type_validator'
         | 
| 16 21 | 
             
            require 'active_storage_validations/limit_validator'
         | 
| 17 22 | 
             
            require 'active_storage_validations/dimension_validator'
         | 
| 23 | 
            +
            require 'active_storage_validations/duration_validator'
         | 
| 18 24 | 
             
            require 'active_storage_validations/aspect_ratio_validator'
         | 
| 19 | 
            -
            require 'active_storage_validations/ | 
| 25 | 
            +
            require 'active_storage_validations/processable_file_validator'
         | 
| 20 26 | 
             
            require 'active_storage_validations/size_validator'
         | 
| 21 27 | 
             
            require 'active_storage_validations/total_size_validator'
         | 
| 22 28 |  | 
| 23 | 
            -
            require 'active_storage_validations/marcel_extensor'
         | 
| 24 | 
            -
             | 
| 25 29 | 
             
            ActiveSupport.on_load(:active_record) do
         | 
| 26 30 | 
             
              send :include, ActiveStorageValidations
         | 
| 27 31 | 
             
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: active_storage_validations
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version:  | 
| 4 | 
            +
              version: 2.0.1
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Igor Kasyanchuk
         | 
| 8 8 | 
             
            autorequire:
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date:  | 
| 11 | 
            +
            date: 2025-01-28 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: activejob
         | 
| @@ -260,27 +260,33 @@ files: | |
| 260 260 | 
             
            - config/locales/zh-CN.yml
         | 
| 261 261 | 
             
            - lib/active_storage_validations.rb
         | 
| 262 262 | 
             
            - lib/active_storage_validations/analyzer.rb
         | 
| 263 | 
            +
            - lib/active_storage_validations/analyzer/audio_analyzer.rb
         | 
| 264 | 
            +
            - lib/active_storage_validations/analyzer/content_type_analyzer.rb
         | 
| 263 265 | 
             
            - lib/active_storage_validations/analyzer/image_analyzer.rb
         | 
| 264 266 | 
             
            - lib/active_storage_validations/analyzer/image_analyzer/image_magick.rb
         | 
| 265 267 | 
             
            - lib/active_storage_validations/analyzer/image_analyzer/vips.rb
         | 
| 266 268 | 
             
            - lib/active_storage_validations/analyzer/null_analyzer.rb
         | 
| 269 | 
            +
            - lib/active_storage_validations/analyzer/shared/asv_ff_probable.rb
         | 
| 270 | 
            +
            - lib/active_storage_validations/analyzer/video_analyzer.rb
         | 
| 267 271 | 
             
            - lib/active_storage_validations/aspect_ratio_validator.rb
         | 
| 268 272 | 
             
            - lib/active_storage_validations/attached_validator.rb
         | 
| 269 | 
            -
            - lib/active_storage_validations/ | 
| 270 | 
            -
            - lib/active_storage_validations/content_type_spoof_detector.rb
         | 
| 273 | 
            +
            - lib/active_storage_validations/base_comparison_validator.rb
         | 
| 271 274 | 
             
            - lib/active_storage_validations/content_type_validator.rb
         | 
| 272 275 | 
             
            - lib/active_storage_validations/dimension_validator.rb
         | 
| 276 | 
            +
            - lib/active_storage_validations/duration_validator.rb
         | 
| 273 277 | 
             
            - lib/active_storage_validations/engine.rb
         | 
| 278 | 
            +
            - lib/active_storage_validations/extensors/asv_blob_metadatable.rb
         | 
| 279 | 
            +
            - lib/active_storage_validations/extensors/asv_marcelable.rb
         | 
| 274 280 | 
             
            - lib/active_storage_validations/limit_validator.rb
         | 
| 275 | 
            -
            - lib/active_storage_validations/marcel_extensor.rb
         | 
| 276 281 | 
             
            - lib/active_storage_validations/matchers.rb
         | 
| 277 282 | 
             
            - lib/active_storage_validations/matchers/aspect_ratio_validator_matcher.rb
         | 
| 278 283 | 
             
            - lib/active_storage_validations/matchers/attached_validator_matcher.rb
         | 
| 279 | 
            -
            - lib/active_storage_validations/matchers/ | 
| 284 | 
            +
            - lib/active_storage_validations/matchers/base_comparison_validator_matcher.rb
         | 
| 280 285 | 
             
            - lib/active_storage_validations/matchers/content_type_validator_matcher.rb
         | 
| 281 286 | 
             
            - lib/active_storage_validations/matchers/dimension_validator_matcher.rb
         | 
| 287 | 
            +
            - lib/active_storage_validations/matchers/duration_validator_matcher.rb
         | 
| 282 288 | 
             
            - lib/active_storage_validations/matchers/limit_validator_matcher.rb
         | 
| 283 | 
            -
            - lib/active_storage_validations/matchers/ | 
| 289 | 
            +
            - lib/active_storage_validations/matchers/processable_file_validator_matcher.rb
         | 
| 284 290 | 
             
            - lib/active_storage_validations/matchers/shared/asv_active_storageable.rb
         | 
| 285 291 | 
             
            - lib/active_storage_validations/matchers/shared/asv_allow_blankable.rb
         | 
| 286 292 | 
             
            - lib/active_storage_validations/matchers/shared/asv_attachable.rb
         | 
| @@ -290,7 +296,7 @@ files: | |
| 290 296 | 
             
            - lib/active_storage_validations/matchers/shared/asv_validatable.rb
         | 
| 291 297 | 
             
            - lib/active_storage_validations/matchers/size_validator_matcher.rb
         | 
| 292 298 | 
             
            - lib/active_storage_validations/matchers/total_size_validator_matcher.rb
         | 
| 293 | 
            -
            - lib/active_storage_validations/ | 
| 299 | 
            +
            - lib/active_storage_validations/processable_file_validator.rb
         | 
| 294 300 | 
             
            - lib/active_storage_validations/railtie.rb
         | 
| 295 301 | 
             
            - lib/active_storage_validations/shared/asv_active_storageable.rb
         | 
| 296 302 | 
             
            - lib/active_storage_validations/shared/asv_analyzable.rb
         | 
| @@ -1,96 +0,0 @@ | |
| 1 | 
            -
            # frozen_string_literal: true
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            require_relative 'shared/asv_analyzable'
         | 
| 4 | 
            -
            require_relative 'shared/asv_attachable'
         | 
| 5 | 
            -
            require_relative 'shared/asv_loggable'
         | 
| 6 | 
            -
            require 'open3'
         | 
| 7 | 
            -
             | 
| 8 | 
            -
            module ActiveStorageValidations
         | 
| 9 | 
            -
              class ContentTypeSpoofDetector
         | 
| 10 | 
            -
                class FileCommandLineToolNotInstalledError < StandardError; end
         | 
| 11 | 
            -
             | 
| 12 | 
            -
                include ASVAnalyzable
         | 
| 13 | 
            -
                include ASVAttachable
         | 
| 14 | 
            -
                include ASVLoggable
         | 
| 15 | 
            -
             | 
| 16 | 
            -
                def initialize(record, attribute, attachable)
         | 
| 17 | 
            -
                  @record = record
         | 
| 18 | 
            -
                  @attribute = attribute
         | 
| 19 | 
            -
                  @attachable = attachable
         | 
| 20 | 
            -
                end
         | 
| 21 | 
            -
             | 
| 22 | 
            -
                def spoofed?
         | 
| 23 | 
            -
                  if supplied_content_type_vs_open3_analizer_mismatch?
         | 
| 24 | 
            -
                    logger.info "Content Type Spoofing detected for file '#{filename}'. The supplied content type is '#{supplied_content_type}' but the content type discovered using open3 is '#{content_type_from_analyzer}'."
         | 
| 25 | 
            -
                    true
         | 
| 26 | 
            -
                  else
         | 
| 27 | 
            -
                    false
         | 
| 28 | 
            -
                  end
         | 
| 29 | 
            -
                end
         | 
| 30 | 
            -
             | 
| 31 | 
            -
                private
         | 
| 32 | 
            -
             | 
| 33 | 
            -
                def filename
         | 
| 34 | 
            -
                  @filename ||= attachable_filename(@attachable).to_s
         | 
| 35 | 
            -
                end
         | 
| 36 | 
            -
             | 
| 37 | 
            -
                def supplied_content_type
         | 
| 38 | 
            -
                  @supplied_content_type ||= attachable_content_type(@attachable)
         | 
| 39 | 
            -
                end
         | 
| 40 | 
            -
             | 
| 41 | 
            -
                def io
         | 
| 42 | 
            -
                  @io ||= attachable_io(@attachable)
         | 
| 43 | 
            -
                end
         | 
| 44 | 
            -
             | 
| 45 | 
            -
                # Return the content_type found by Open3 analysis.
         | 
| 46 | 
            -
                #
         | 
| 47 | 
            -
                # Using Open3 is a better alternative than Marcel (Marcel::MimeType.for(io))
         | 
| 48 | 
            -
                # for analyzing content type solely based on the file io.
         | 
| 49 | 
            -
                def content_type_from_analyzer
         | 
| 50 | 
            -
                  @content_type_from_analyzer ||= open3_mime_type_for_io
         | 
| 51 | 
            -
                end
         | 
| 52 | 
            -
             | 
| 53 | 
            -
                def open3_mime_type_for_io
         | 
| 54 | 
            -
                  return nil if io.bytesize == 0
         | 
| 55 | 
            -
             | 
| 56 | 
            -
                  Tempfile.create do |tempfile|
         | 
| 57 | 
            -
                    tempfile.binmode
         | 
| 58 | 
            -
                    tempfile.write(io)
         | 
| 59 | 
            -
                    tempfile.rewind
         | 
| 60 | 
            -
             | 
| 61 | 
            -
                    command = "file -b --mime-type #{tempfile.path}"
         | 
| 62 | 
            -
                    output, status = Open3.capture2(command)
         | 
| 63 | 
            -
             | 
| 64 | 
            -
                    if status.success?
         | 
| 65 | 
            -
                      mime_type = output.strip
         | 
| 66 | 
            -
                      return mime_type
         | 
| 67 | 
            -
                    else
         | 
| 68 | 
            -
                      raise "Error determining MIME type: #{output}"
         | 
| 69 | 
            -
                    end
         | 
| 70 | 
            -
             | 
| 71 | 
            -
                  rescue Errno::ENOENT
         | 
| 72 | 
            -
                    raise FileCommandLineToolNotInstalledError, 'file command-line tool is not installed'
         | 
| 73 | 
            -
                  end
         | 
| 74 | 
            -
                end
         | 
| 75 | 
            -
             | 
| 76 | 
            -
                def supplied_content_type_vs_open3_analizer_mismatch?
         | 
| 77 | 
            -
                  supplied_content_type.present? &&
         | 
| 78 | 
            -
                    !supplied_content_type_intersects_content_type_from_analyzer?
         | 
| 79 | 
            -
                end
         | 
| 80 | 
            -
             | 
| 81 | 
            -
                def supplied_content_type_intersects_content_type_from_analyzer?
         | 
| 82 | 
            -
                  # Ruby intersects? method is only available from 3.1
         | 
| 83 | 
            -
                  enlarged_content_type(supplied_content_type).any? do |item|
         | 
| 84 | 
            -
                    enlarged_content_type(content_type_from_analyzer).include?(item)
         | 
| 85 | 
            -
                  end
         | 
| 86 | 
            -
                end
         | 
| 87 | 
            -
             | 
| 88 | 
            -
                def enlarged_content_type(content_type)
         | 
| 89 | 
            -
                  [content_type, *parent_content_types(content_type)].compact.uniq
         | 
| 90 | 
            -
                end
         | 
| 91 | 
            -
             | 
| 92 | 
            -
                def parent_content_types(content_type)
         | 
| 93 | 
            -
                  Marcel::TYPE_PARENTS[content_type] || []
         | 
| 94 | 
            -
                end
         | 
| 95 | 
            -
              end
         | 
| 96 | 
            -
            end
         |