active_storage_validations 1.3.4 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
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