active_storage_validations 1.4.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +616 -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 +49 -21
- 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 +24 -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 +4 -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
@@ -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.0
|
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-24 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
|