active_storage_validations 1.3.5 → 1.4.0
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 +3 -65
- data/config/locales/da.yml +1 -0
- data/config/locales/de.yml +1 -0
- data/config/locales/en.yml +1 -0
- data/config/locales/es.yml +1 -0
- data/config/locales/fr.yml +1 -0
- data/config/locales/it.yml +1 -0
- data/config/locales/ja.yml +1 -0
- data/config/locales/nl.yml +1 -0
- data/config/locales/pl.yml +1 -0
- data/config/locales/pt-BR.yml +1 -0
- data/config/locales/ru.yml +1 -0
- data/config/locales/sv.yml +1 -0
- data/config/locales/tr.yml +1 -0
- data/config/locales/uk.yml +1 -0
- data/config/locales/vi.yml +1 -0
- data/config/locales/zh-CN.yml +1 -0
- data/lib/active_storage_validations/analyzer/image_analyzer/image_magick.rb +8 -7
- data/lib/active_storage_validations/analyzer/image_analyzer/vips.rb +8 -7
- data/lib/active_storage_validations/analyzer/image_analyzer.rb +16 -0
- data/lib/active_storage_validations/aspect_ratio_validator.rb +150 -120
- data/lib/active_storage_validations/content_type_spoof_detector.rb +1 -1
- data/lib/active_storage_validations/content_type_validator.rb +11 -5
- data/lib/active_storage_validations/marcel_extensor.rb +2 -0
- data/lib/active_storage_validations/shared/asv_active_storageable.rb +2 -2
- data/lib/active_storage_validations/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 12a58907c84f972cb00aab13b6a60d26afda0383cbe88e13ec12b1c9d178ba36
|
|
4
|
+
data.tar.gz: 4df97d0b9e88054403ff7a39c89b00c4b3f9a0c3de7a38997f4f3a6193d803af
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 80210f9dd1636fac2f2dd9ab15c0090990312fdcd1c34f2c0aa6157c9c4e27b1aed9a03c064e91b148014388eaec3ed4171dd57d4c059e4f2e9ee919610d098a
|
|
7
|
+
data.tar.gz: f23b85dbb613af03faa0637fd5e9dd626e8e975b87590a0b87aedeba296bcd24c0f79c16dc20059ddb8f41b892887098d0f7dc58d1b951eaae0094b5dd15ea2a
|
data/README.md
CHANGED
|
@@ -204,6 +204,7 @@ en:
|
|
|
204
204
|
aspect_ratio_not_landscape: "must be a landscape image"
|
|
205
205
|
aspect_ratio_is_not: "must have an aspect ratio of %{aspect_ratio}"
|
|
206
206
|
image_not_processable: "is not a valid image"
|
|
207
|
+
aspect_ratio_invalid: "has invalid aspect ratio"
|
|
207
208
|
```
|
|
208
209
|
|
|
209
210
|
In several cases, Active Storage Validations provides variables to help you customize messages:
|
|
@@ -437,7 +438,7 @@ Then you can use the matchers with the syntax specified in the RSpec section, ju
|
|
|
437
438
|
|
|
438
439
|
To run tests in root folder of gem:
|
|
439
440
|
|
|
440
|
-
* `BUNDLE_GEMFILE=gemfiles/rails_6_1_4.gemfile bundle exec rake test` to run for Rails
|
|
441
|
+
* `BUNDLE_GEMFILE=gemfiles/rails_6_1_4.gemfile bundle exec rake test` to run for Rails 6.1
|
|
441
442
|
* `BUNDLE_GEMFILE=gemfiles/rails_7_0.gemfile bundle exec rake test` to run for Rails 7.0
|
|
442
443
|
* `BUNDLE_GEMFILE=gemfiles/rails_7_1.gemfile bundle exec rake test` to run for Rails 7.1
|
|
443
444
|
* `BUNDLE_GEMFILE=gemfiles/rails_7_2.gemfile bundle exec rake test` to run for Rails 7.2
|
|
@@ -471,71 +472,8 @@ You are welcome to contribute.
|
|
|
471
472
|
/>](https://opensource-heroes.com/r/igorkasyanchuk/active_storage_validations)
|
|
472
473
|
|
|
473
474
|
## Contributors (BIG THANK YOU)
|
|
474
|
-
- https://github.com/schweigert
|
|
475
|
-
- https://github.com/tleneveu
|
|
476
|
-
- https://github.com/reckerswartz
|
|
477
|
-
- https://github.com/Uysim
|
|
478
|
-
- https://github.com/D-system
|
|
479
|
-
- https://github.com/ivanelrey
|
|
480
|
-
- https://github.com/phlegx
|
|
481
|
-
- https://github.com/rr-dev
|
|
482
|
-
- https://github.com/dsmalko
|
|
483
|
-
- https://github.com/danderozier
|
|
484
|
-
- https://github.com/cseelus
|
|
485
|
-
- https://github.com/vkinelev
|
|
486
|
-
- https://github.com/reed
|
|
487
|
-
- https://github.com/connorshea
|
|
488
|
-
- https://github.com/Atul9
|
|
489
|
-
- https://github.com/victorbueno
|
|
490
|
-
- https://github.com/UICJohn
|
|
491
|
-
- https://github.com/giovannibonetti
|
|
492
|
-
- https://github.com/dlepage
|
|
493
|
-
- https://github.com/StefSchenkelaars
|
|
494
|
-
- https://github.com/willnet
|
|
495
|
-
- https://github.com/mohanklein
|
|
496
|
-
- https://github.com/High5Apps
|
|
497
|
-
- https://github.com/mschnitzer
|
|
498
|
-
- https://github.com/sinankeskin
|
|
499
|
-
- https://github.com/alejandrodevs
|
|
500
|
-
- https://github.com/molfar
|
|
501
|
-
- https://github.com/connorshea
|
|
502
|
-
- https://github.com/yshmarov
|
|
503
|
-
- https://github.com/fongfan999
|
|
504
|
-
- https://github.com/cooperka
|
|
505
|
-
- https://github.com/dolarsrg
|
|
506
|
-
- https://github.com/jayshepherd
|
|
507
|
-
- https://github.com/ohbarye
|
|
508
|
-
- https://github.com/randsina
|
|
509
|
-
- https://github.com/vietqhoang
|
|
510
|
-
- https://github.com/kemenaran
|
|
511
|
-
- https://github.com/jrmhaig
|
|
512
|
-
- https://github.com/evedovelli
|
|
513
|
-
- https://github.com/JuanVqz
|
|
514
|
-
- https://github.com/luiseugenio
|
|
515
|
-
- https://github.com/equivalent
|
|
516
|
-
- https://github.com/NARKOZ
|
|
517
|
-
- https://github.com/stephensolis
|
|
518
|
-
- https://github.com/kwent
|
|
519
|
-
- https://github.com/Animesh-Ghosh
|
|
520
|
-
- https://github.com/gr8bit
|
|
521
|
-
- https://github.com/codegeek319
|
|
522
|
-
- https://github.com/clwy-cn
|
|
523
|
-
- https://github.com/kukicola
|
|
524
|
-
- https://github.com/sobrinho
|
|
525
|
-
- https://github.com/iainbeeston
|
|
526
|
-
- https://github.com/marckohlbrugge
|
|
527
|
-
- https://github.com/Mth0158
|
|
528
|
-
- https://github.com/technicalpickles
|
|
529
|
-
- https://github.com/ricsdeol
|
|
530
|
-
- https://github.com/Fonsan
|
|
531
|
-
- https://github.com/tagliala
|
|
532
|
-
- https://github.com/ocarreterom
|
|
533
|
-
- https://github.com/aditya-cherukuri
|
|
534
|
-
- https://github.com/searls
|
|
535
|
-
- https://github.com/yenshirak
|
|
536
|
-
- https://github.com/wataori
|
|
537
|
-
- https://github.com/Scorpahr
|
|
538
475
|
|
|
476
|
+
https://github.com/igorkasyanchuk/active_storage_validations/graphs/contributors
|
|
539
477
|
|
|
540
478
|
## License
|
|
541
479
|
|
data/config/locales/da.yml
CHANGED
data/config/locales/de.yml
CHANGED
data/config/locales/en.yml
CHANGED
data/config/locales/es.yml
CHANGED
data/config/locales/fr.yml
CHANGED
|
@@ -30,3 +30,4 @@ fr:
|
|
|
30
30
|
aspect_ratio_not_landscape: "doit être une image en format paysage"
|
|
31
31
|
aspect_ratio_is_not: "doit avoir un rapport hauteur / largeur de %{aspect_ratio}"
|
|
32
32
|
image_not_processable: "n'est pas une image valide"
|
|
33
|
+
aspect_ratio_invalid: "a un rapport hauteur / largeur invalide"
|
data/config/locales/it.yml
CHANGED
|
@@ -30,3 +30,4 @@ it:
|
|
|
30
30
|
aspect_ratio_not_landscape: "l’orientamento dell’immagine deve essere orizzontale"
|
|
31
31
|
aspect_ratio_is_not: "deve avere un rapporto altezza / larghezza di %{aspect_ratio}"
|
|
32
32
|
image_not_processable: "non è un'immagine valida"
|
|
33
|
+
aspect_ratio_invalid: "ha proporzioni non valide"
|
data/config/locales/ja.yml
CHANGED
data/config/locales/nl.yml
CHANGED
data/config/locales/pl.yml
CHANGED
data/config/locales/pt-BR.yml
CHANGED
data/config/locales/ru.yml
CHANGED
|
@@ -30,3 +30,4 @@ ru:
|
|
|
30
30
|
aspect_ratio_not_landscape: "должно быть пейзажное изображение"
|
|
31
31
|
aspect_ratio_is_not: "должен иметь соотношение сторон %{aspect_ratio}"
|
|
32
32
|
image_not_processable: "не является допустимым изображением"
|
|
33
|
+
aspect_ratio_invalid: "имеет недопустимое соотношение сторон"
|
data/config/locales/sv.yml
CHANGED
data/config/locales/tr.yml
CHANGED
data/config/locales/uk.yml
CHANGED
data/config/locales/vi.yml
CHANGED
data/config/locales/zh-CN.yml
CHANGED
|
@@ -9,13 +9,6 @@ module ActiveStorageValidations
|
|
|
9
9
|
private
|
|
10
10
|
|
|
11
11
|
def read_image
|
|
12
|
-
begin
|
|
13
|
-
require "mini_magick" unless defined?(MiniMagick)
|
|
14
|
-
rescue LoadError
|
|
15
|
-
logger.info "Skipping image analysis because the mini_magick gem isn't installed"
|
|
16
|
-
return {}
|
|
17
|
-
end
|
|
18
|
-
|
|
19
12
|
Tempfile.create(binmode: true) do |tempfile|
|
|
20
13
|
begin
|
|
21
14
|
if image(tempfile).valid?
|
|
@@ -42,5 +35,13 @@ module ActiveStorageValidations
|
|
|
42
35
|
def rotated_image?(image)
|
|
43
36
|
%w[ RightTop LeftBottom TopRight BottomLeft ].include?(image["%[orientation]"])
|
|
44
37
|
end
|
|
38
|
+
|
|
39
|
+
def supported?
|
|
40
|
+
require "mini_magick"
|
|
41
|
+
true
|
|
42
|
+
rescue LoadError
|
|
43
|
+
logger.info "Skipping image analysis because the mini_magick gem isn't installed"
|
|
44
|
+
false
|
|
45
|
+
end
|
|
45
46
|
end
|
|
46
47
|
end
|
|
@@ -8,13 +8,6 @@ module ActiveStorageValidations
|
|
|
8
8
|
private
|
|
9
9
|
|
|
10
10
|
def read_image
|
|
11
|
-
begin
|
|
12
|
-
require "vips" unless defined?(::Vips)
|
|
13
|
-
rescue LoadError
|
|
14
|
-
logger.info "Skipping image analysis because the ruby-vips gem isn't installed"
|
|
15
|
-
return {}
|
|
16
|
-
end
|
|
17
|
-
|
|
18
11
|
Tempfile.create(binmode: true) do |tempfile|
|
|
19
12
|
begin
|
|
20
13
|
if image(tempfile)
|
|
@@ -47,6 +40,14 @@ module ActiveStorageValidations
|
|
|
47
40
|
end
|
|
48
41
|
end
|
|
49
42
|
|
|
43
|
+
def supported?
|
|
44
|
+
require "vips"
|
|
45
|
+
true
|
|
46
|
+
rescue LoadError
|
|
47
|
+
logger.info "Skipping image analysis because the ruby-vips gem isn't installed"
|
|
48
|
+
false
|
|
49
|
+
end
|
|
50
|
+
|
|
50
51
|
ROTATIONS = /Right-top|Left-bottom|Top-right|Bottom-left/
|
|
51
52
|
def rotated_image?(image)
|
|
52
53
|
ROTATIONS === image.get("exif-ifd0-Orientation")
|
|
@@ -12,7 +12,11 @@ module ActiveStorageValidations
|
|
|
12
12
|
# ActiveStorage::Analyzer::ImageAnalyzer::ImageMagick.new(attachable).metadata
|
|
13
13
|
# # => { width: 4104, height: 2736 }
|
|
14
14
|
class Analyzer::ImageAnalyzer < Analyzer
|
|
15
|
+
@@supported_analyzers = {}
|
|
16
|
+
|
|
15
17
|
def metadata
|
|
18
|
+
return {} unless analyzer_supported?
|
|
19
|
+
|
|
16
20
|
read_image do |image|
|
|
17
21
|
if rotated_image?(image)
|
|
18
22
|
{ width: image.height, height: image.width }
|
|
@@ -62,6 +66,14 @@ module ActiveStorageValidations
|
|
|
62
66
|
image_from_path(tempfile.path)
|
|
63
67
|
end
|
|
64
68
|
|
|
69
|
+
def analyzer_supported?
|
|
70
|
+
if @@supported_analyzers.key?(self)
|
|
71
|
+
@@supported_analyzers.fetch(self)
|
|
72
|
+
else
|
|
73
|
+
@@supported_analyzers[self] = supported?
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
65
77
|
def read_image
|
|
66
78
|
raise NotImplementedError
|
|
67
79
|
end
|
|
@@ -73,5 +85,9 @@ module ActiveStorageValidations
|
|
|
73
85
|
def rotated_image?(image)
|
|
74
86
|
raise NotImplementedError
|
|
75
87
|
end
|
|
88
|
+
|
|
89
|
+
def supported?
|
|
90
|
+
raise NotImplementedError
|
|
91
|
+
end
|
|
76
92
|
end
|
|
77
93
|
end
|
|
@@ -1,120 +1,150 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'shared/asv_active_storageable'
|
|
4
|
-
require_relative 'shared/asv_analyzable'
|
|
5
|
-
require_relative 'shared/asv_attachable'
|
|
6
|
-
require_relative 'shared/asv_errorable'
|
|
7
|
-
require_relative 'shared/asv_optionable'
|
|
8
|
-
require_relative 'shared/asv_symbolizable'
|
|
9
|
-
|
|
10
|
-
module ActiveStorageValidations
|
|
11
|
-
class AspectRatioValidator < ActiveModel::EachValidator # :nodoc
|
|
12
|
-
include ASVActiveStorageable
|
|
13
|
-
include ASVAnalyzable
|
|
14
|
-
include ASVAttachable
|
|
15
|
-
include ASVErrorable
|
|
16
|
-
include ASVOptionable
|
|
17
|
-
include ASVSymbolizable
|
|
18
|
-
|
|
19
|
-
AVAILABLE_CHECKS = %i[with].freeze
|
|
20
|
-
NAMED_ASPECT_RATIOS = %i[square portrait landscape].freeze
|
|
21
|
-
ASPECT_RATIO_REGEX = /is_([1-9]\d*)_([1-9]\d*)/.freeze
|
|
22
|
-
ERROR_TYPES = %i[
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
def
|
|
109
|
-
return
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'shared/asv_active_storageable'
|
|
4
|
+
require_relative 'shared/asv_analyzable'
|
|
5
|
+
require_relative 'shared/asv_attachable'
|
|
6
|
+
require_relative 'shared/asv_errorable'
|
|
7
|
+
require_relative 'shared/asv_optionable'
|
|
8
|
+
require_relative 'shared/asv_symbolizable'
|
|
9
|
+
|
|
10
|
+
module ActiveStorageValidations
|
|
11
|
+
class AspectRatioValidator < ActiveModel::EachValidator # :nodoc
|
|
12
|
+
include ASVActiveStorageable
|
|
13
|
+
include ASVAnalyzable
|
|
14
|
+
include ASVAttachable
|
|
15
|
+
include ASVErrorable
|
|
16
|
+
include ASVOptionable
|
|
17
|
+
include ASVSymbolizable
|
|
18
|
+
|
|
19
|
+
AVAILABLE_CHECKS = %i[with in].freeze
|
|
20
|
+
NAMED_ASPECT_RATIOS = %i[square portrait landscape].freeze
|
|
21
|
+
ASPECT_RATIO_REGEX = /is_([1-9]\d*)_([1-9]\d*)/.freeze
|
|
22
|
+
ERROR_TYPES = %i[
|
|
23
|
+
aspect_ratio_not_square
|
|
24
|
+
aspect_ratio_not_portrait
|
|
25
|
+
aspect_ratio_not_landscape
|
|
26
|
+
aspect_ratio_is_not
|
|
27
|
+
aspect_ratio_invalid
|
|
28
|
+
image_metadata_missing
|
|
29
|
+
].freeze
|
|
30
|
+
PRECISION = 3.freeze
|
|
31
|
+
|
|
32
|
+
def check_validity!
|
|
33
|
+
ensure_at_least_one_validator_option
|
|
34
|
+
ensure_aspect_ratio_validity
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def validate_each(record, attribute, _value)
|
|
38
|
+
return if no_attachments?(record, attribute)
|
|
39
|
+
|
|
40
|
+
flat_options = set_flat_options(record)
|
|
41
|
+
@authorized_aspect_ratios = authorized_aspect_ratios_from_options(flat_options).compact
|
|
42
|
+
return if @authorized_aspect_ratios.empty?
|
|
43
|
+
|
|
44
|
+
validate_changed_files_from_metadata(record, attribute)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def is_valid?(record, attribute, attachable, metadata)
|
|
50
|
+
!image_metadata_missing?(record, attribute, attachable, metadata) &&
|
|
51
|
+
authorized_aspect_ratio?(record, attribute, attachable, metadata)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def authorized_aspect_ratio?(record, attribute, attachable, metadata)
|
|
55
|
+
attachable_aspect_ratio_is_authorized = @authorized_aspect_ratios.any? do |authorized_aspect_ratio|
|
|
56
|
+
case authorized_aspect_ratio
|
|
57
|
+
when :square then valid_square_aspect_ratio?(metadata)
|
|
58
|
+
when :portrait then valid_portrait_aspect_ratio?(metadata)
|
|
59
|
+
when :landscape then valid_landscape_aspect_ratio?(metadata)
|
|
60
|
+
when ASPECT_RATIO_REGEX then valid_regex_aspect_ratio?(authorized_aspect_ratio, metadata)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
return true if attachable_aspect_ratio_is_authorized
|
|
65
|
+
|
|
66
|
+
errors_options = initialize_error_options(options, attachable)
|
|
67
|
+
errors_options[:aspect_ratio] = string_aspect_ratios
|
|
68
|
+
add_error(record, attribute, aspect_ratio_error_mapping, **errors_options)
|
|
69
|
+
false
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def aspect_ratio_error_mapping
|
|
73
|
+
return :aspect_ratio_invalid unless @authorized_aspect_ratios.one?
|
|
74
|
+
|
|
75
|
+
aspect_ratio = @authorized_aspect_ratios.first
|
|
76
|
+
NAMED_ASPECT_RATIOS.include?(aspect_ratio) ? :"aspect_ratio_not_#{aspect_ratio}" : :aspect_ratio_is_not
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def image_metadata_missing?(record, attribute, attachable, metadata)
|
|
80
|
+
return false if metadata[:width].to_i > 0 && metadata[:height].to_i > 0
|
|
81
|
+
|
|
82
|
+
errors_options = initialize_error_options(options, attachable)
|
|
83
|
+
errors_options[:aspect_ratio] = string_aspect_ratios
|
|
84
|
+
add_error(record, attribute, :image_metadata_missing, **errors_options)
|
|
85
|
+
true
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def valid_square_aspect_ratio?(metadata)
|
|
89
|
+
metadata[:width] == metadata[:height]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def valid_portrait_aspect_ratio?(metadata)
|
|
93
|
+
metadata[:width] < metadata[:height]
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def valid_landscape_aspect_ratio?(metadata)
|
|
97
|
+
metadata[:width] > metadata[:height]
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def valid_regex_aspect_ratio?(aspect_ratio, metadata)
|
|
101
|
+
aspect_ratio =~ ASPECT_RATIO_REGEX
|
|
102
|
+
x = ::Regexp.last_match(1).to_i
|
|
103
|
+
y = ::Regexp.last_match(2).to_i
|
|
104
|
+
|
|
105
|
+
x > 0 && y > 0 && (x.to_f / y).round(PRECISION) == (metadata[:width].to_f / metadata[:height]).round(PRECISION)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def ensure_at_least_one_validator_option
|
|
109
|
+
return if AVAILABLE_CHECKS.any? { |argument| options.key?(argument) }
|
|
110
|
+
|
|
111
|
+
raise ArgumentError, 'You must pass either :with or :in to the validator'
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def ensure_aspect_ratio_validity
|
|
115
|
+
return true if options[:with]&.is_a?(Proc) || options[:in]&.is_a?(Proc)
|
|
116
|
+
|
|
117
|
+
authorized_aspect_ratios_from_options(options).each do |aspect_ratio|
|
|
118
|
+
unless NAMED_ASPECT_RATIOS.include?(aspect_ratio) || aspect_ratio =~ ASPECT_RATIO_REGEX
|
|
119
|
+
raise ArgumentError, invalid_aspect_ratio_message
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def invalid_aspect_ratio_message
|
|
125
|
+
<<~ERROR_MESSAGE
|
|
126
|
+
You must pass a valid aspect ratio to the validator
|
|
127
|
+
It should either be a named aspect ratio (#{NAMED_ASPECT_RATIOS.join(', ')})
|
|
128
|
+
Or an aspect ratio like 'is_16_9' (matching /#{ASPECT_RATIO_REGEX.source}/)
|
|
129
|
+
ERROR_MESSAGE
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def authorized_aspect_ratios_from_options(flat_options)
|
|
133
|
+
(Array.wrap(flat_options[:with]) + Array.wrap(flat_options[:in]))
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def string_aspect_ratios
|
|
137
|
+
@authorized_aspect_ratios.map do |aspect_ratio|
|
|
138
|
+
if NAMED_ASPECT_RATIOS.include?(aspect_ratio)
|
|
139
|
+
aspect_ratio
|
|
140
|
+
else
|
|
141
|
+
aspect_ratio =~ ASPECT_RATIO_REGEX
|
|
142
|
+
x = ::Regexp.last_match(1).to_i
|
|
143
|
+
y = ::Regexp.last_match(2).to_i
|
|
144
|
+
|
|
145
|
+
"#{x}:#{y}"
|
|
146
|
+
end
|
|
147
|
+
end.join(', ')
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
@@ -34,9 +34,11 @@ module ActiveStorageValidations
|
|
|
34
34
|
@authorized_content_types = authorized_content_types_from_options(record)
|
|
35
35
|
return if @authorized_content_types.empty?
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
checked_files = disable_spoofing_protection? ? attached_files(record, attribute) : attachables_from_changes(record, attribute)
|
|
38
|
+
|
|
39
|
+
checked_files.each do |file|
|
|
40
|
+
set_attachable_cached_values(file)
|
|
41
|
+
is_valid?(record, attribute, file)
|
|
40
42
|
end
|
|
41
43
|
end
|
|
42
44
|
|
|
@@ -54,8 +56,8 @@ module ActiveStorageValidations
|
|
|
54
56
|
end
|
|
55
57
|
|
|
56
58
|
def set_attachable_cached_values(attachable)
|
|
57
|
-
@attachable_content_type = attachable_content_type_rails_like(attachable)
|
|
58
|
-
@attachable_filename = attachable_filename(attachable).to_s
|
|
59
|
+
@attachable_content_type = disable_spoofing_protection? ? attachable.blob.content_type : attachable_content_type_rails_like(attachable)
|
|
60
|
+
@attachable_filename = disable_spoofing_protection? ? attachable.blob.filename.to_s : attachable_filename(attachable).to_s
|
|
59
61
|
end
|
|
60
62
|
|
|
61
63
|
# Check if the provided content_type is authorized and not spoofed against
|
|
@@ -116,6 +118,10 @@ module ActiveStorageValidations
|
|
|
116
118
|
Marcel::MimeType.for(declared_type: @attachable_content_type, name: @attachable_filename)
|
|
117
119
|
end
|
|
118
120
|
|
|
121
|
+
def disable_spoofing_protection?
|
|
122
|
+
!enable_spoofing_protection?
|
|
123
|
+
end
|
|
124
|
+
|
|
119
125
|
def enable_spoofing_protection?
|
|
120
126
|
options[:spoofing_protection] == true
|
|
121
127
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "marcel"
|
|
4
|
+
|
|
3
5
|
Marcel::MimeType.extend "application/x-rar-compressed", parents: %(application/x-rar)
|
|
4
6
|
Marcel::MimeType.extend "audio/x-hx-aac-adts", parents: %(audio/x-aac)
|
|
5
7
|
Marcel::MimeType.extend "audio/x-m4a", parents: %(audio/mp4)
|
|
@@ -9,8 +9,8 @@ module ActiveStorageValidations
|
|
|
9
9
|
|
|
10
10
|
private
|
|
11
11
|
|
|
12
|
-
# Retrieve either an ActiveStorage::Attached::One or an
|
|
13
|
-
# ActiveStorage::Attached::Many instance depending on attribute definition
|
|
12
|
+
# Retrieve either an `ActiveStorage::Attached::One` or an
|
|
13
|
+
# `ActiveStorage::Attached::Many` instance depending on attribute definition
|
|
14
14
|
def attached_files(record, attribute)
|
|
15
15
|
Array.wrap(record.send(attribute))
|
|
16
16
|
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: 1.
|
|
4
|
+
version: 1.4.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: 2024-12-
|
|
11
|
+
date: 2024-12-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activejob
|