active_storage_validations 1.3.4 → 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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -67
  3. data/config/locales/da.yml +1 -0
  4. data/config/locales/de.yml +1 -0
  5. data/config/locales/en.yml +1 -0
  6. data/config/locales/es.yml +1 -0
  7. data/config/locales/fr.yml +1 -0
  8. data/config/locales/it.yml +1 -0
  9. data/config/locales/ja.yml +1 -0
  10. data/config/locales/nl.yml +1 -0
  11. data/config/locales/pl.yml +1 -0
  12. data/config/locales/pt-BR.yml +1 -0
  13. data/config/locales/ru.yml +1 -0
  14. data/config/locales/sv.yml +1 -0
  15. data/config/locales/tr.yml +1 -0
  16. data/config/locales/uk.yml +1 -0
  17. data/config/locales/vi.yml +1 -0
  18. data/config/locales/zh-CN.yml +1 -0
  19. data/lib/active_storage_validations/analyzer/image_analyzer/image_magick.rb +47 -0
  20. data/lib/active_storage_validations/analyzer/image_analyzer/vips.rb +58 -0
  21. data/lib/active_storage_validations/analyzer/image_analyzer.rb +93 -0
  22. data/lib/active_storage_validations/analyzer/null_analyzer.rb +18 -0
  23. data/lib/active_storage_validations/analyzer.rb +34 -0
  24. data/lib/active_storage_validations/aspect_ratio_validator.rb +150 -118
  25. data/lib/active_storage_validations/content_type_spoof_detector.rb +3 -1
  26. data/lib/active_storage_validations/content_type_validator.rb +13 -5
  27. data/lib/active_storage_validations/dimension_validator.rb +2 -0
  28. data/lib/active_storage_validations/marcel_extensor.rb +2 -0
  29. data/lib/active_storage_validations/matchers/processable_image_validator_matcher.rb +4 -4
  30. data/lib/active_storage_validations/matchers.rb +2 -1
  31. data/lib/active_storage_validations/processable_image_validator.rb +2 -0
  32. data/lib/active_storage_validations/shared/asv_active_storageable.rb +2 -2
  33. data/lib/active_storage_validations/shared/asv_analyzable.rb +45 -0
  34. data/lib/active_storage_validations/shared/asv_attachable.rb +63 -16
  35. data/lib/active_storage_validations/version.rb +1 -1
  36. data/lib/active_storage_validations.rb +6 -0
  37. metadata +8 -3
  38. data/lib/active_storage_validations/metadata.rb +0 -179
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '0539919d5597eda332d4c989c432986009f1944e0ab62251882cc250fa6f021a'
4
- data.tar.gz: 8d7566007ede3adf24a60297c20d7a39b28e211bc00f5158148f3c2cdf0a82e5
3
+ metadata.gz: 12a58907c84f972cb00aab13b6a60d26afda0383cbe88e13ec12b1c9d178ba36
4
+ data.tar.gz: 4df97d0b9e88054403ff7a39c89b00c4b3f9a0c3de7a38997f4f3a6193d803af
5
5
  SHA512:
6
- metadata.gz: 364050c102e1fa3feac404972479edf30eb25d7df969e7593afae838ffe556bb0cb69693c94ad8ced10b056b0d8b5c232800bc3885bdc37984c9cf51a836bff1
7
- data.tar.gz: 7350e9ffb7d0c93bc233561ab3457aec477f988c447708498d791f559440a08207cfddf245b2b835716f978d2d8171f4c71252697183df5284ee8f33ce136540
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:
@@ -354,8 +355,8 @@ describe User do
354
355
 
355
356
  # limit
356
357
  # #min, #max
357
- it { is_expected.to validate_limit_of(:avatar).min(1) }
358
- it { is_expected.to validate_limit_of(:avatar).max(5) }
358
+ it { is_expected.to validate_limits_of(:avatar).min(1) }
359
+ it { is_expected.to validate_limits_of(:avatar).max(5) }
359
360
 
360
361
  # content_type:
361
362
  # #allowing, #rejecting
@@ -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 7.0
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
 
@@ -30,3 +30,4 @@ da:
30
30
  aspect_ratio_not_landscape: "skal være et landskabsbillede"
31
31
  aspect_ratio_is_not: "skal have et størrelsesforhold på %{aspect_ratio}"
32
32
  image_not_processable: "er ikke et gyldigt billede"
33
+ aspect_ratio_invalid: "har et ugyldigt billedformat"
@@ -30,3 +30,4 @@ de:
30
30
  aspect_ratio_not_landscape: "muss Querformat sein"
31
31
  aspect_ratio_is_not: "muss ein Bildseitenverhältnis von %{aspect_ratio} haben"
32
32
  image_not_processable: "ist kein gültiges Bild"
33
+ aspect_ratio_invalid: "hat ein ungültiges Seitenverhältnis"
@@ -30,3 +30,4 @@ en:
30
30
  aspect_ratio_not_landscape: "must be a landscape image"
31
31
  aspect_ratio_is_not: "must have an aspect ratio of %{aspect_ratio}"
32
32
  image_not_processable: "is not a valid image"
33
+ aspect_ratio_invalid: "has an invalid aspect ratio"
@@ -30,3 +30,4 @@ es:
30
30
  aspect_ratio_not_landscape: "debe ser una imagen apaisada"
31
31
  aspect_ratio_is_not: "debe tener una relación de aspecto de %{aspect_ratio}"
32
32
  image_not_processable: "no es una imagen válida"
33
+ aspect_ratio_invalid: "tiene una relación de aspecto no válida"
@@ -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"
@@ -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"
@@ -30,3 +30,4 @@ ja:
30
30
  aspect_ratio_not_landscape: "は横長にしてください"
31
31
  aspect_ratio_is_not: "のアスペクト比は %{aspect_ratio} にしてください"
32
32
  image_not_processable: "は不正な画像です"
33
+ aspect_ratio_invalid: "アスペクト比が無効です"
@@ -30,3 +30,4 @@ nl:
30
30
  aspect_ratio_not_landscape: "moet een liggende afbeelding zijn"
31
31
  aspect_ratio_is_not: "moet een beeldverhouding hebben van %{aspect_ratio}"
32
32
  image_not_processable: "is geen geldige afbeelding"
33
+ aspect_ratio_invalid: "heeft een ongeldige beeldverhouding"
@@ -30,3 +30,4 @@ pl:
30
30
  aspect_ratio_not_landscape: "musi mieć proporcje pejzażu"
31
31
  aspect_ratio_is_not: "musi mieć proporcje %{aspect_ratio}"
32
32
  image_not_processable: "nie jest prawidłowym obrazem"
33
+ aspect_ratio_invalid: "ma nieprawidłowy współczynnik proporcji"
@@ -30,3 +30,4 @@ pt-BR:
30
30
  aspect_ratio_not_landscape: "não está no formato paisagem"
31
31
  aspect_ratio_is_not: "não contém uma proporção de %{aspect_ratio}"
32
32
  image_not_processable: "não é uma imagem válida"
33
+ aspect_ratio_invalid: "tem uma proporção de aspecto inválida"
@@ -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: "имеет недопустимое соотношение сторон"
@@ -30,3 +30,4 @@ sv:
30
30
  aspect_ratio_not_landscape: "måste vara en landskapsorienterad bild"
31
31
  aspect_ratio_is_not: "måste ha en följande aspect ratio %{aspect_ratio}"
32
32
  image_not_processable: "är inte en giltig bild"
33
+ aspect_ratio_invalid: "har ett ogiltigt bildförhållande"
@@ -30,3 +30,4 @@ tr:
30
30
  aspect_ratio_not_landscape: "yatay bir imaj olmalı"
31
31
  aspect_ratio_is_not: "%{aspect_ratio} en boy oranına sahip olmalı"
32
32
  image_not_processable: "geçerli bir imaj değil"
33
+ aspect_ratio_invalid: "geçersiz bir en boy oranına sahip"
@@ -30,3 +30,4 @@ uk:
30
30
  aspect_ratio_not_landscape: "мусить бути пейзажне зображення"
31
31
  aspect_ratio_is_not: "мусить мати співвідношення сторін %{aspect_ratio}"
32
32
  image_not_processable: "не є допустимим зображенням"
33
+ aspect_ratio_invalid: "має недійсне співвідношення сторін"
@@ -30,3 +30,4 @@ vi:
30
30
  aspect_ratio_not_landscape: "phải là ảnh ngang"
31
31
  aspect_ratio_is_not: "phải có tỉ lệ ảnh %{aspect_ratio}"
32
32
  image_not_processable: "không phải là ảnh"
33
+ aspect_ratio_invalid: "có tỷ lệ khung hình không hợp lệ"
@@ -30,3 +30,4 @@ zh-CN:
30
30
  aspect_ratio_not_landscape: "必须是横屏图片"
31
31
  aspect_ratio_is_not: "纵横比必须是 %{aspect_ratio}"
32
32
  image_not_processable: "不是有效的图像"
33
+ aspect_ratio_invalid: "宽高比无效"
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorageValidations
4
+ # This analyzer relies on the third-party {MiniMagick}[https://github.com/minimagick/minimagick] gem.
5
+ # MiniMagick requires the {ImageMagick}[http://www.imagemagick.org] system library.
6
+ # This is the default Rails image analyzer.
7
+ class Analyzer::ImageAnalyzer::ImageMagick < Analyzer::ImageAnalyzer
8
+
9
+ private
10
+
11
+ def read_image
12
+ Tempfile.create(binmode: true) do |tempfile|
13
+ begin
14
+ if image(tempfile).valid?
15
+ yield image(tempfile)
16
+ else
17
+ logger.info "Skipping image analysis because ImageMagick doesn't support the file"
18
+ {}
19
+ end
20
+ ensure
21
+ tempfile.close
22
+ end
23
+ end
24
+ rescue MiniMagick::Error => error
25
+ logger.error "Skipping image analysis due to an ImageMagick error: #{error.message}"
26
+ {}
27
+ end
28
+
29
+ def image_from_path(path)
30
+ instrument("mini_magick") do
31
+ MiniMagick::Image.new(path)
32
+ end
33
+ end
34
+
35
+ def rotated_image?(image)
36
+ %w[ RightTop LeftBottom TopRight BottomLeft ].include?(image["%[orientation]"])
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
46
+ end
47
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorageValidations
4
+ # This analyzer relies on the third-party {ruby-vips}[https://github.com/libvips/ruby-vips] gem.
5
+ # Ruby-vips requires the {libvips}[https://libvips.github.io/libvips/] system library.
6
+ class Analyzer::ImageAnalyzer::Vips < Analyzer::ImageAnalyzer
7
+
8
+ private
9
+
10
+ def read_image
11
+ Tempfile.create(binmode: true) do |tempfile|
12
+ begin
13
+ if image(tempfile)
14
+ yield image(tempfile)
15
+ else
16
+ logger.info "Skipping image analysis because Vips doesn't support the file"
17
+ {}
18
+ end
19
+ ensure
20
+ tempfile.close
21
+ end
22
+ end
23
+ rescue ::Vips::Error => error
24
+ logger.error "Skipping image analysis due to a Vips error: #{error.message}"
25
+ {}
26
+ end
27
+
28
+ def image_from_path(path)
29
+ instrument("vips") do
30
+ begin
31
+ ::Vips::Image.new_from_file(path, access: :sequential)
32
+ rescue ::Vips::Error
33
+ # Vips throw errors rather than returning false when reading a not
34
+ # supported attachable.
35
+ # We stumbled upon this issue while reading 0 byte size attachable
36
+ # https://github.com/janko/image_processing/issues/97
37
+ logger.info "Skipping image analysis because Vips doesn't support the file"
38
+ nil
39
+ end
40
+ end
41
+ end
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
+
51
+ ROTATIONS = /Right-top|Left-bottom|Top-right|Bottom-left/
52
+ def rotated_image?(image)
53
+ ROTATIONS === image.get("exif-ifd0-Orientation")
54
+ rescue ::Vips::Error
55
+ false
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorageValidations
4
+ # = Active Storage Image \Analyzer
5
+ #
6
+ # This is an abstract base class for image analyzers, which extract width and height from an image attachable.
7
+ #
8
+ # If the image contains EXIF data indicating its angle is 90 or 270 degrees, its width and height are swapped for convenience.
9
+ #
10
+ # Example:
11
+ #
12
+ # ActiveStorage::Analyzer::ImageAnalyzer::ImageMagick.new(attachable).metadata
13
+ # # => { width: 4104, height: 2736 }
14
+ class Analyzer::ImageAnalyzer < Analyzer
15
+ @@supported_analyzers = {}
16
+
17
+ def metadata
18
+ return {} unless analyzer_supported?
19
+
20
+ read_image do |image|
21
+ if rotated_image?(image)
22
+ { width: image.height, height: image.width }
23
+ else
24
+ { width: image.width, height: image.height }
25
+ end
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def image(tempfile)
32
+ case @attachable
33
+ when ActiveStorage::Blob, String
34
+ blob = @attachable.is_a?(String) ? ActiveStorage::Blob.find_signed!(@attachable) : @attachable
35
+ image_from_tempfile_path(tempfile, blob)
36
+ when Hash
37
+ io = @attachable[:io]
38
+ if io.is_a?(StringIO)
39
+ image_from_tempfile_path(tempfile, io)
40
+ else
41
+ File.open(io) do |file|
42
+ image_from_path(file.path)
43
+ end
44
+ end
45
+ when ActionDispatch::Http::UploadedFile, Rack::Test::UploadedFile
46
+ image_from_path(@attachable.path)
47
+ when File
48
+ supports_file_attachment? ? image_from_path(@attachable.path) : raise_rails_like_error(@attachable)
49
+ when Pathname
50
+ supports_pathname_attachment? ? image_from_path(@attachable.to_s) : raise_rails_like_error(@attachable)
51
+ else
52
+ raise_rails_like_error(@attachable)
53
+ end
54
+ end
55
+
56
+ def image_from_tempfile_path(tempfile, file_representation)
57
+ if file_representation.is_a?(ActiveStorage::Blob)
58
+ file_representation.download { |chunk| tempfile.write(chunk) }
59
+ else
60
+ IO.copy_stream(file_representation, tempfile)
61
+ file_representation.rewind
62
+ end
63
+
64
+ tempfile.flush
65
+ tempfile.rewind
66
+ image_from_path(tempfile.path)
67
+ end
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
+
77
+ def read_image
78
+ raise NotImplementedError
79
+ end
80
+
81
+ def image_from_path(path)
82
+ raise NotImplementedError
83
+ end
84
+
85
+ def rotated_image?(image)
86
+ raise NotImplementedError
87
+ end
88
+
89
+ def supported?
90
+ raise NotImplementedError
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorageValidations
4
+ # = Active Storage Null Analyzer
5
+ #
6
+ # This is a fallback analyzer when the attachable media type is not supported
7
+ # by our gem.
8
+ #
9
+ # Example:
10
+ #
11
+ # ActiveStorage::Analyzer::NullAnalyzer.new(attachable).metadata
12
+ # # => {}
13
+ class Analyzer::NullAnalyzer < Analyzer
14
+ def metadata
15
+ {}
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'shared/asv_attachable'
4
+ require_relative 'shared/asv_loggable'
5
+
6
+ module ActiveStorageValidations
7
+ # = Active Storage Validations \Analyzer
8
+ #
9
+ # This is an abstract base class for analyzers, which extract metadata from attachables.
10
+ # See ActiveStorageValidations::Analyzer::ImageAnalyzer for an example of a concrete subclass.
11
+ #
12
+ # Heavily (not to say 100%) inspired by Rails own ActiveStorage::Analyzer
13
+ class Analyzer
14
+ include ASVAttachable
15
+ include ASVLoggable
16
+
17
+ attr_reader :attachable
18
+
19
+ def initialize(attachable)
20
+ @attachable = attachable
21
+ end
22
+
23
+ # Override this method in a concrete subclass. Have it return a Hash of metadata.
24
+ def metadata
25
+ raise NotImplementedError
26
+ end
27
+
28
+ private
29
+
30
+ def instrument(analyzer, &block)
31
+ ActiveSupport::Notifications.instrument("analyze.active_storage_validations", analyzer: analyzer, &block)
32
+ end
33
+ end
34
+ end