active_storage_validations 1.3.0 → 1.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +12 -8
  3. data/config/locales/da.yml +0 -1
  4. data/config/locales/de.yml +0 -1
  5. data/config/locales/en.yml +0 -1
  6. data/config/locales/es.yml +0 -1
  7. data/config/locales/fr.yml +0 -1
  8. data/config/locales/it.yml +0 -1
  9. data/config/locales/ja.yml +0 -1
  10. data/config/locales/nl.yml +0 -1
  11. data/config/locales/pl.yml +0 -1
  12. data/config/locales/pt-BR.yml +0 -1
  13. data/config/locales/ru.yml +0 -1
  14. data/config/locales/sv.yml +0 -1
  15. data/config/locales/tr.yml +0 -1
  16. data/config/locales/uk.yml +0 -1
  17. data/config/locales/vi.yml +0 -1
  18. data/config/locales/zh-CN.yml +0 -1
  19. data/lib/active_storage_validations/aspect_ratio_validator.rb +58 -43
  20. data/lib/active_storage_validations/attached_validator.rb +3 -3
  21. data/lib/active_storage_validations/base_size_validator.rb +5 -4
  22. data/lib/active_storage_validations/content_type_spoof_detector.rb +12 -48
  23. data/lib/active_storage_validations/content_type_validator.rb +97 -49
  24. data/lib/active_storage_validations/dimension_validator.rb +8 -7
  25. data/lib/active_storage_validations/limit_validator.rb +6 -5
  26. data/lib/active_storage_validations/matchers/aspect_ratio_validator_matcher.rb +7 -7
  27. data/lib/active_storage_validations/matchers/attached_validator_matcher.rb +6 -6
  28. data/lib/active_storage_validations/matchers/base_size_validator_matcher.rb +7 -7
  29. data/lib/active_storage_validations/matchers/content_type_validator_matcher.rb +33 -33
  30. data/lib/active_storage_validations/matchers/dimension_validator_matcher.rb +7 -7
  31. data/lib/active_storage_validations/matchers/limit_validator_matcher.rb +7 -7
  32. data/lib/active_storage_validations/matchers/processable_image_validator_matcher.rb +7 -7
  33. data/lib/active_storage_validations/metadata.rb +17 -18
  34. data/lib/active_storage_validations/processable_image_validator.rb +5 -5
  35. data/lib/active_storage_validations/shared/attachable.rb +134 -0
  36. data/lib/active_storage_validations/{concerns → shared}/errorable.rb +2 -1
  37. data/lib/active_storage_validations/shared/optionable.rb +27 -0
  38. data/lib/active_storage_validations/size_validator.rb +2 -2
  39. data/lib/active_storage_validations/total_size_validator.rb +2 -2
  40. data/lib/active_storage_validations/version.rb +1 -1
  41. data/lib/active_storage_validations.rb +0 -1
  42. metadata +65 -25
  43. data/lib/active_storage_validations/concerns/metadatable.rb +0 -31
  44. data/lib/active_storage_validations/option_proc_unfolding.rb +0 -16
  45. /data/lib/active_storage_validations/matchers/{concerns → shared}/active_storageable.rb +0 -0
  46. /data/lib/active_storage_validations/matchers/{concerns → shared}/allow_blankable.rb +0 -0
  47. /data/lib/active_storage_validations/matchers/{concerns → shared}/attachable.rb +0 -0
  48. /data/lib/active_storage_validations/matchers/{concerns → shared}/contextable.rb +0 -0
  49. /data/lib/active_storage_validations/matchers/{concerns → shared}/messageable.rb +0 -0
  50. /data/lib/active_storage_validations/matchers/{concerns → shared}/rspecable.rb +0 -0
  51. /data/lib/active_storage_validations/matchers/{concerns → shared}/validatable.rb +0 -0
  52. /data/lib/active_storage_validations/{concerns → shared}/active_storageable.rb +0 -0
  53. /data/lib/active_storage_validations/{concerns → shared}/loggable.rb +0 -0
  54. /data/lib/active_storage_validations/{concerns → shared}/symbolizable.rb +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '09b483aef23513aaf10b56c6df6046078bf14e93fcfdebe2e82f2db862b85514'
4
- data.tar.gz: d040c36c24fc1fb2b4d1a2e6bb92bde86521cedfda4b4e96436a10b364499f63
3
+ metadata.gz: 6e1145fabe1ee64e7f7033a38c5b1de1114b364e5e2d9eb010628afde0a3e0b3
4
+ data.tar.gz: 497b550e5d98bf96e4398969de34022b8e8f828003a620261909a6d39e349aff
5
5
  SHA512:
6
- metadata.gz: eb6df31c0374b1bfa804281f0effbaa0443ff6a812abd4a46e1ac4293a881e691fd5c61cd4c2622b027085acb3d10c14255f04cac3bab01f13f133d2f26c60af
7
- data.tar.gz: b5553a4a7a2f08542c8e6f9dd928b0cc78a09eb846f73346dcdec1b3826bb4a5f04ac200c21e6e2f0f58f6dcc173f226a04e7a2c85d7c15a24b5b827bfb04dcb
6
+ metadata.gz: b671b5b33834ff904de1dea6dca51bfd741eaf2e10d931896a6fc4ff9ff83fc213f1581d59c562a806640cdc7a10537fa5ddb240051152ff38b5933ea7c7dc33
7
+ data.tar.gz: 381da6fbfb0fa544357bc492cfc9535992097ed7118846bae62f1b1953e5058f7b1bbc389ebd0dd8543fb035edaba55cee34a8c73a1b4fa84fc2ffc3e8bf9654
data/README.md CHANGED
@@ -91,9 +91,10 @@ Marcel::MimeType.extend "application/ino", extensions: %w(ino), parents: "text/p
91
91
  ```
92
92
 
93
93
  **Content type spoofing protection**
94
+
94
95
  File content type spoofing happens when an ill-intentioned user uploads a file which hides its true content type by faking its extension and its declared content type value. For example, a user may try to upload a `.exe` file (application/x-msdownload content type) dissimulated as a `.jpg` file (image/jpeg content type).
95
96
 
96
- By default, the gem does not prevent content type spoofing (prevent it by default is a breaking change that will be implemented in v2). The spoofing protection relies on both the linux `file` command and `Marcel` gem.
97
+ By default, the gem does not prevent content type spoofing (prevent it by default is a breaking change that will be implemented in v2). The spoofing protection relies on both the linux `file` command and `Marcel` gem. Be careful, since it needs to load the whole file io to perform the analysis, it will use a lot of RAM for very large files. Therefore it could be a wise decision not to enable it in this case.
97
98
 
98
99
  Take note that the `file` analyzer will not find the exactly same content type as the ActiveStorage blob (its content type detection relies on a different logic using content+filename+extension). To handle this issue, we consider a close parent content type to be a match. For example, for an ActiveStorage blob which content type is `video/x-ms-wmv`, the `file` analyzer will probably detect a `video/x-ms-asf` content type, this will be considered as a valid match because these 2 content types are closely related. The correlation mapping is based on `Marcel::TYPE_PARENTS`.
99
100
 
@@ -202,7 +203,6 @@ en:
202
203
  aspect_ratio_not_portrait: "must be a portrait image"
203
204
  aspect_ratio_not_landscape: "must be a landscape image"
204
205
  aspect_ratio_is_not: "must have an aspect ratio of %{aspect_ratio}"
205
- aspect_ratio_unknown: "has an unknown aspect ratio"
206
206
  image_not_processable: "is not a valid image"
207
207
  ```
208
208
 
@@ -221,7 +221,8 @@ aspect_ratio_is_not: "must be a %{aspect_ratio} image"
221
221
 
222
222
  ### Content type
223
223
  The `content_type_invalid` key has three variables that you can use:
224
- - `content_type` containing the content type of the sent file
224
+ - `content_type` containing the exact content type of the sent file
225
+ - `human_content_type` containing a more user-friendly version of the sent file content type (e.g. 'TXT' for 'text/plain')
225
226
  - `authorized_types` containing the list of authorized content types
226
227
  - `filename` containing the current file name
227
228
 
@@ -436,10 +437,11 @@ Then you can use the matchers with the syntax specified in the RSpec section, ju
436
437
 
437
438
  To run tests in root folder of gem:
438
439
 
439
- * `BUNDLE_GEMFILE=gemfiles/rails_6_1_4.gemfile bundle exec rake test` to run for Rails 6.1.4
440
+ * `BUNDLE_GEMFILE=gemfiles/rails_6_1_4.gemfile bundle exec rake test` to run for Rails 7.0
440
441
  * `BUNDLE_GEMFILE=gemfiles/rails_7_0.gemfile bundle exec rake test` to run for Rails 7.0
441
- * `BUNDLE_GEMFILE=gemfiles/rails_7_1.gemfile bundle exec rake test` to run for Rails 7.0
442
- * `BUNDLE_GEMFILE=gemfiles/rails_next.gemfile bundle exec rake test` to run for Rails main branch
442
+ * `BUNDLE_GEMFILE=gemfiles/rails_7_1.gemfile bundle exec rake test` to run for Rails 7.1
443
+ * `BUNDLE_GEMFILE=gemfiles/rails_7_2.gemfile bundle exec rake test` to run for Rails 7.2
444
+ * `BUNDLE_GEMFILE=gemfiles/rails_8_0.gemfile bundle exec rake test` to run for Rails 8.0
443
445
 
444
446
  Snippet to run in console:
445
447
 
@@ -447,11 +449,13 @@ Snippet to run in console:
447
449
  BUNDLE_GEMFILE=gemfiles/rails_6_1_4.gemfile bundle
448
450
  BUNDLE_GEMFILE=gemfiles/rails_7_0.gemfile bundle
449
451
  BUNDLE_GEMFILE=gemfiles/rails_7_1.gemfile bundle
450
- BUNDLE_GEMFILE=gemfiles/rails_next.gemfile bundle
452
+ BUNDLE_GEMFILE=gemfiles/rails_7_2.gemfile bundle
453
+ BUNDLE_GEMFILE=gemfiles/rails_8_0.gemfile bundle
451
454
  BUNDLE_GEMFILE=gemfiles/rails_6_1_4.gemfile bundle exec rake test
452
455
  BUNDLE_GEMFILE=gemfiles/rails_7_0.gemfile bundle exec rake test
453
456
  BUNDLE_GEMFILE=gemfiles/rails_7_1.gemfile bundle exec rake test
454
- BUNDLE_GEMFILE=gemfiles/rails_next.gemfile bundle exec rake test
457
+ BUNDLE_GEMFILE=gemfiles/rails_7_2.gemfile bundle exec rake test
458
+ BUNDLE_GEMFILE=gemfiles/rails_8_0.gemfile bundle exec rake test
455
459
  ```
456
460
 
457
461
  Tips:
@@ -29,5 +29,4 @@ da:
29
29
  aspect_ratio_not_portrait: "skal være et portrætbillede"
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
- aspect_ratio_unknown: "har et ukendt størrelsesforhold"
33
32
  image_not_processable: "er ikke et gyldigt billede"
@@ -29,5 +29,4 @@ de:
29
29
  aspect_ratio_not_portrait: "muss Hochformat sein"
30
30
  aspect_ratio_not_landscape: "muss Querformat sein"
31
31
  aspect_ratio_is_not: "muss ein Bildseitenverhältnis von %{aspect_ratio} haben"
32
- aspect_ratio_unknown: "hat ein unbekanntes Bildseitenverhältnis"
33
32
  image_not_processable: "ist kein gültiges Bild"
@@ -29,5 +29,4 @@ en:
29
29
  aspect_ratio_not_portrait: "must be a portrait image"
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
- aspect_ratio_unknown: "has an unknown aspect ratio"
33
32
  image_not_processable: "is not a valid image"
@@ -29,5 +29,4 @@ es:
29
29
  aspect_ratio_not_portrait: "debe ser una imagen vertical"
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
- aspect_ratio_unknown: "tiene una relación de aspecto desconocida"
33
32
  image_not_processable: "no es una imagen válida"
@@ -29,5 +29,4 @@ fr:
29
29
  aspect_ratio_not_portrait: "doit être une image en format portrait"
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
- aspect_ratio_unknown: "a un rapport d'aspect inconnu"
33
32
  image_not_processable: "n'est pas une image valide"
@@ -29,5 +29,4 @@ it:
29
29
  aspect_ratio_not_portrait: "l’orientamento dell’immagine deve essere verticale"
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
- aspect_ratio_unknown: "ha un rapporto altezza / larghezza sconosciuto"
33
32
  image_not_processable: "non è un'immagine valida"
@@ -29,5 +29,4 @@ ja:
29
29
  aspect_ratio_not_portrait: "は縦長にしてください"
30
30
  aspect_ratio_not_landscape: "は横長にしてください"
31
31
  aspect_ratio_is_not: "のアスペクト比は %{aspect_ratio} にしてください"
32
- aspect_ratio_unknown: "のアスペクト比を取得できませんでした"
33
32
  image_not_processable: "は不正な画像です"
@@ -29,5 +29,4 @@ nl:
29
29
  aspect_ratio_not_portrait: "moet een staande afbeelding zijn"
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
- aspect_ratio_unknown: "heeft een onbekende beeldverhouding"
33
32
  image_not_processable: "is geen geldige afbeelding"
@@ -29,5 +29,4 @@ pl:
29
29
  aspect_ratio_not_portrait: "musi mieć proporcje portretu"
30
30
  aspect_ratio_not_landscape: "musi mieć proporcje pejzażu"
31
31
  aspect_ratio_is_not: "musi mieć proporcje %{aspect_ratio}"
32
- aspect_ratio_unknown: "ma nieokreślone proporcje"
33
32
  image_not_processable: "nie jest prawidłowym obrazem"
@@ -29,5 +29,4 @@ pt-BR:
29
29
  aspect_ratio_not_portrait: "não está no formato retrato"
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
- aspect_ratio_unknown: "não tem uma proporção definida"
33
32
  image_not_processable: "não é uma imagem válida"
@@ -29,5 +29,4 @@ ru:
29
29
  aspect_ratio_not_portrait: "должно быть портретное изображение"
30
30
  aspect_ratio_not_landscape: "должно быть пейзажное изображение"
31
31
  aspect_ratio_is_not: "должен иметь соотношение сторон %{aspect_ratio}"
32
- aspect_ratio_unknown: "имеет неизвестное соотношение сторон"
33
32
  image_not_processable: "не является допустимым изображением"
@@ -29,5 +29,4 @@ sv:
29
29
  aspect_ratio_not_portrait: "måste vara en porträttorienterad bild"
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
- aspect_ratio_unknown: "har en okänd aspect ratio"
33
32
  image_not_processable: "är inte en giltig bild"
@@ -29,5 +29,4 @@ tr:
29
29
  aspect_ratio_not_portrait: "dikey bir imaj olmalı"
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
- aspect_ratio_unknown: "bilinmeyen en boy oranı"
33
32
  image_not_processable: "geçerli bir imaj değil"
@@ -29,5 +29,4 @@ uk:
29
29
  aspect_ratio_not_portrait: "мусить бути портретне зображення"
30
30
  aspect_ratio_not_landscape: "мусить бути пейзажне зображення"
31
31
  aspect_ratio_is_not: "мусить мати співвідношення сторін %{aspect_ratio}"
32
- aspect_ratio_unknown: "має невідоме співвідношення сторін"
33
32
  image_not_processable: "не є допустимим зображенням"
@@ -29,5 +29,4 @@ vi:
29
29
  aspect_ratio_not_portrait: "phải là ảnh đứng"
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
- aspect_ratio_unknown: "tỉ lệ ảnh không xác định"
33
32
  image_not_processable: "không phải là ảnh"
@@ -29,5 +29,4 @@ zh-CN:
29
29
  aspect_ratio_not_portrait: "必须是竖屏图片"
30
30
  aspect_ratio_not_landscape: "必须是横屏图片"
31
31
  aspect_ratio_is_not: "纵横比必须是 %{aspect_ratio}"
32
- aspect_ratio_unknown: "未知的纵横比"
33
32
  image_not_processable: "不是有效的图像"
@@ -1,16 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'concerns/active_storageable.rb'
4
- require_relative 'concerns/errorable.rb'
5
- require_relative 'concerns/metadatable.rb'
6
- require_relative 'concerns/symbolizable.rb'
3
+ require_relative 'shared/active_storageable'
4
+ require_relative 'shared/attachable'
5
+ require_relative 'shared/errorable'
6
+ require_relative 'shared/optionable'
7
+ require_relative 'shared/symbolizable'
7
8
 
8
9
  module ActiveStorageValidations
9
10
  class AspectRatioValidator < ActiveModel::EachValidator # :nodoc
10
11
  include ActiveStorageable
12
+ include Attachable
11
13
  include Errorable
12
- include Metadatable
13
- include OptionProcUnfolding
14
+ include Optionable
14
15
  include Symbolizable
15
16
 
16
17
  AVAILABLE_CHECKS = %i[with].freeze
@@ -22,7 +23,6 @@ module ActiveStorageValidations
22
23
  aspect_ratio_not_portrait
23
24
  aspect_ratio_not_landscape
24
25
  aspect_ratio_is_not
25
- aspect_ratio_unknown
26
26
  ].freeze
27
27
  PRECISION = 3.freeze
28
28
 
@@ -40,48 +40,63 @@ module ActiveStorageValidations
40
40
  private
41
41
 
42
42
  def is_valid?(record, attribute, attachable, metadata)
43
- flat_options = unfold_procs(record, self.options, AVAILABLE_CHECKS)
44
- errors_options = initialize_error_options(options, attachable)
45
-
46
- if metadata[:width].to_i <= 0 || metadata[:height].to_i <= 0
47
- errors_options[:aspect_ratio] = flat_options[:with]
43
+ flat_options = set_flat_options(record)
48
44
 
49
- add_error(record, attribute, :image_metadata_missing, **errors_options)
50
- return false
51
- end
45
+ return if image_metadata_missing?(record, attribute, attachable, flat_options, metadata)
52
46
 
53
47
  case flat_options[:with]
54
- when :square
55
- return true if metadata[:width] == metadata[:height]
56
- errors_options[:aspect_ratio] = flat_options[:with]
57
- add_error(record, attribute, :aspect_ratio_not_square, **errors_options)
58
-
59
- when :portrait
60
- return true if metadata[:height] > metadata[:width]
61
- errors_options[:aspect_ratio] = flat_options[:with]
62
- add_error(record, attribute, :aspect_ratio_not_portrait, **errors_options)
63
-
64
- when :landscape
65
- return true if metadata[:width] > metadata[:height]
66
- errors_options[:aspect_ratio] = flat_options[:with]
67
- add_error(record, attribute, :aspect_ratio_not_landscape, **errors_options)
68
-
69
- when ASPECT_RATIO_REGEX
70
- flat_options[:with] =~ ASPECT_RATIO_REGEX
71
- x = $1.to_i
72
- y = $2.to_i
73
-
74
- return true if x > 0 && y > 0 && (x.to_f / y).round(PRECISION) == (metadata[:width].to_f / metadata[:height]).round(PRECISION)
75
-
76
- errors_options[:aspect_ratio] = "#{x}:#{y}"
77
- add_error(record, attribute, :aspect_ratio_is_not, **errors_options)
78
- else
79
- errors_options[:aspect_ratio] = flat_options[:with]
80
- add_error(record, attribute, :aspect_ratio_unknown, **errors_options)
81
- return false
48
+ when :square then validate_square_aspect_ratio(record, attribute, attachable, flat_options, metadata)
49
+ when :portrait then validate_portrait_aspect_ratio(record, attribute, attachable, flat_options, metadata)
50
+ when :landscape then validate_landscape_aspect_ratio(record, attribute, attachable, flat_options, metadata)
51
+ when ASPECT_RATIO_REGEX then validate_regex_aspect_ratio(record, attribute, attachable, flat_options, metadata)
82
52
  end
83
53
  end
84
54
 
55
+ def image_metadata_missing?(record, attribute, attachable, flat_options, metadata)
56
+ return false if metadata[:width].to_i > 0 && metadata[:height].to_i > 0
57
+
58
+ errors_options = initialize_error_options(options, attachable)
59
+ errors_options[:aspect_ratio] = flat_options[:with]
60
+ add_error(record, attribute, :image_metadata_missing, **errors_options)
61
+ true
62
+ end
63
+
64
+ def validate_square_aspect_ratio(record, attribute, attachable, flat_options, metadata)
65
+ return if metadata[:width] == metadata[:height]
66
+
67
+ errors_options = initialize_error_options(options, attachable)
68
+ errors_options[:aspect_ratio] = flat_options[:with]
69
+ add_error(record, attribute, :aspect_ratio_not_square, **errors_options)
70
+ end
71
+
72
+ def validate_portrait_aspect_ratio(record, attribute, attachable, flat_options, metadata)
73
+ return if metadata[:width] < metadata[:height]
74
+
75
+ errors_options = initialize_error_options(options, attachable)
76
+ errors_options[:aspect_ratio] = flat_options[:with]
77
+ add_error(record, attribute, :aspect_ratio_not_portrait, **errors_options)
78
+ end
79
+
80
+ def validate_landscape_aspect_ratio(record, attribute, attachable, flat_options, metadata)
81
+ return if metadata[:width] > metadata[:height]
82
+
83
+ errors_options = initialize_error_options(options, attachable)
84
+ errors_options[:aspect_ratio] = flat_options[:with]
85
+ add_error(record, attribute, :aspect_ratio_not_landscape, **errors_options)
86
+ end
87
+
88
+ def validate_regex_aspect_ratio(record, attribute, attachable, flat_options, metadata)
89
+ flat_options[:with] =~ ASPECT_RATIO_REGEX
90
+ x = $1.to_i
91
+ y = $2.to_i
92
+
93
+ return if x > 0 && y > 0 && (x.to_f / y).round(PRECISION) == (metadata[:width].to_f / metadata[:height]).round(PRECISION)
94
+
95
+ errors_options = initialize_error_options(options, attachable)
96
+ errors_options[:aspect_ratio] = "#{x}:#{y}"
97
+ add_error(record, attribute, :aspect_ratio_is_not, **errors_options)
98
+ end
99
+
85
100
  def ensure_at_least_one_validator_option
86
101
  unless AVAILABLE_CHECKS.any? { |argument| options.key?(argument) }
87
102
  raise ArgumentError, 'You must pass :with to the validator'
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'concerns/active_storageable.rb'
4
- require_relative 'concerns/errorable.rb'
5
- require_relative 'concerns/symbolizable.rb'
3
+ require_relative 'shared/active_storageable'
4
+ require_relative 'shared/errorable'
5
+ require_relative 'shared/symbolizable'
6
6
 
7
7
  module ActiveStorageValidations
8
8
  class AttachedValidator < ActiveModel::EachValidator # :nodoc:
@@ -1,14 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'concerns/active_storageable.rb'
4
- require_relative 'concerns/errorable.rb'
5
- require_relative 'concerns/symbolizable.rb'
3
+ require_relative 'shared/active_storageable'
4
+ require_relative 'shared/errorable'
5
+ require_relative 'shared/optionable'
6
+ require_relative 'shared/symbolizable'
6
7
 
7
8
  module ActiveStorageValidations
8
9
  class BaseSizeValidator < ActiveModel::EachValidator # :nodoc:
9
10
  include ActiveStorageable
10
11
  include Errorable
11
- include OptionProcUnfolding
12
+ include Optionable
12
13
  include Symbolizable
13
14
 
14
15
  delegate :number_to_human_size, to: ActiveSupport::NumberHelper
@@ -1,18 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'concerns/loggable'
3
+ require_relative 'shared/attachable'
4
+ require_relative 'shared/loggable'
4
5
  require 'open3'
5
6
 
6
7
  module ActiveStorageValidations
7
8
  class ContentTypeSpoofDetector
8
9
  class FileCommandLineToolNotInstalledError < StandardError; end
9
10
 
11
+ include Attachable
10
12
  include Loggable
11
13
 
12
- def initialize(record, attribute, file)
14
+ def initialize(record, attribute, attachable)
13
15
  @record = record
14
16
  @attribute = attribute
15
- @file = file
17
+ @attachable = attachable
16
18
  end
17
19
 
18
20
  def spoofed?
@@ -27,60 +29,22 @@ module ActiveStorageValidations
27
29
  private
28
30
 
29
31
  def filename
30
- @filename ||= @file.blob.present? && @file.blob.filename.to_s
32
+ @filename ||= attachable_filename(@attachable).to_s
31
33
  end
32
34
 
33
35
  def supplied_content_type
34
- # We remove potential mime type parameters
35
- @supplied_content_type ||= @file.blob.present? && @file.blob.content_type.downcase.split(/[;,\s]/, 2).first
36
+ @supplied_content_type ||= attachable_content_type(@attachable)
36
37
  end
37
38
 
38
39
  def io
39
- @io ||= case @record.public_send(@attribute)
40
- when ActiveStorage::Attached::One then get_io_from_one
41
- when ActiveStorage::Attached::Many then get_io_from_many
42
- end
43
- end
44
-
45
- def get_io_from_one
46
- attachable = @record.attachment_changes[@attribute.to_s].attachable
47
-
48
- case attachable
49
- when ActionDispatch::Http::UploadedFile
50
- attachable.read
51
- when String
52
- blob = ActiveStorage::Blob.find_signed!(attachable)
53
- blob.download
54
- when ActiveStorage::Blob
55
- attachable.download
56
- when Hash
57
- attachable[:io].read
58
- end
59
- end
60
-
61
- def get_io_from_many
62
- attachables = @record.attachment_changes[@attribute.to_s].attachables
63
-
64
- if attachables.all? { |attachable| attachable.is_a?(ActionDispatch::Http::UploadedFile) }
65
- attachables.find do |uploaded_file|
66
- checksum = ActiveStorage::Blob.new.send(:compute_checksum_in_chunks, uploaded_file)
67
- checksum == @file.checksum
68
- end.read
69
- elsif attachables.all? { |attachable| attachable.is_a?(String) }
70
- # It's only possible to pass one String as attachable (not an array of String)
71
- blob = ActiveStorage::Blob.find_signed!(attachables.first)
72
- blob.download
73
- elsif attachables.all? { |attachable| attachable.is_a?(ActiveStorage::Blob) }
74
- attachables.find { |blob| blob == @file.blob }.download
75
- elsif attachables.all? { |attachable| attachable.is_a?(Hash) }
76
- # It's only possible to pass one Hash as attachable (not an array of Hash)
77
- attachables.first[:io].read
78
- end
40
+ @io ||= attachable_io(@attachable)
79
41
  end
80
42
 
43
+ # Return the content_type found by Open3 analysis.
44
+ #
45
+ # Using Open3 is a better alternative than Marcel (Marcel::MimeType.for(io))
46
+ # for analyzing content type solely based on the file io.
81
47
  def content_type_from_analyzer
82
- # Using Open3 is a better alternative than Marcel (Marcel::MimeType.for(io))
83
- # for analyzing content type solely based on the file io
84
48
  @content_type_from_analyzer ||= open3_mime_type_for_io
85
49
  end
86
50