active_storage_validations 1.2.0 → 1.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +35 -22
- data/config/locales/da.yml +1 -2
- data/config/locales/de.yml +1 -1
- data/config/locales/en.yml +1 -1
- data/config/locales/es.yml +1 -1
- data/config/locales/fr.yml +1 -1
- data/config/locales/it.yml +1 -1
- data/config/locales/ja.yml +1 -1
- data/config/locales/nl.yml +1 -1
- data/config/locales/pl.yml +1 -1
- data/config/locales/pt-BR.yml +1 -1
- data/config/locales/ru.yml +1 -1
- data/config/locales/sv.yml +1 -1
- data/config/locales/tr.yml +1 -1
- data/config/locales/uk.yml +1 -1
- data/config/locales/vi.yml +1 -1
- data/config/locales/zh-CN.yml +1 -1
- data/lib/active_storage_validations/aspect_ratio_validator.rb +57 -66
- data/lib/active_storage_validations/attached_validator.rb +5 -3
- data/lib/active_storage_validations/base_size_validator.rb +4 -1
- data/lib/active_storage_validations/concerns/active_storageable.rb +28 -0
- data/lib/active_storage_validations/concerns/attachable.rb +134 -0
- data/lib/active_storage_validations/concerns/errorable.rb +4 -4
- data/lib/active_storage_validations/concerns/loggable.rb +9 -0
- data/lib/active_storage_validations/concerns/optionable.rb +27 -0
- data/lib/active_storage_validations/content_type_spoof_detector.rb +94 -0
- data/lib/active_storage_validations/content_type_validator.rb +113 -39
- data/lib/active_storage_validations/dimension_validator.rb +32 -52
- data/lib/active_storage_validations/limit_validator.rb +8 -5
- data/lib/active_storage_validations/marcel_extensor.rb +5 -0
- data/lib/active_storage_validations/matchers/concerns/attachable.rb +27 -9
- data/lib/active_storage_validations/matchers/concerns/messageable.rb +1 -1
- data/lib/active_storage_validations/matchers/content_type_validator_matcher.rb +7 -0
- data/lib/active_storage_validations/matchers/limit_validator_matcher.rb +127 -0
- data/lib/active_storage_validations/matchers/total_size_validator_matcher.rb +1 -10
- data/lib/active_storage_validations/matchers.rb +4 -15
- data/lib/active_storage_validations/metadata.rb +22 -26
- data/lib/active_storage_validations/processable_image_validator.rb +17 -32
- data/lib/active_storage_validations/size_validator.rb +3 -7
- data/lib/active_storage_validations/total_size_validator.rb +4 -8
- data/lib/active_storage_validations/version.rb +1 -1
- data/lib/active_storage_validations.rb +2 -1
- metadata +67 -21
- data/lib/active_storage_validations/option_proc_unfolding.rb +0 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 435de3a42881b172293cf50bc3edb1e5462f4a0f8346543f14936502abe7a041
|
4
|
+
data.tar.gz: 6dc253e3b1dcf5ac55ae13bcd4fe9d9cabe39cdf49e0de25cc0f091bfb5c3d1b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bfd15f884cda85c47ad25c1d7e2ca8423f6d6a9e3e4aadeb415492c3a9830a00b10c611ed0eb0a5866bd12e1c2b61aaa69cfccf3952447c3cd716c07e51780c5
|
7
|
+
data.tar.gz: 91ab2c226d9657f52f6cc5f289fb8697608954d59d34400042e0521b7e60657e5ac5f256b268eef16fd1ffcd83169f61345ba3e7c9e925d1384d427f10a00bad
|
data/README.md
CHANGED
@@ -90,6 +90,28 @@ Example code for adding a new content type to Marcel:
|
|
90
90
|
Marcel::MimeType.extend "application/ino", extensions: %w(ino), parents: "text/plain" # Registering arduino INO files
|
91
91
|
```
|
92
92
|
|
93
|
+
**Content type spoofing protection**
|
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).
|
96
|
+
|
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.
|
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`.
|
100
|
+
|
101
|
+
The difficulty to accurately predict a mime type may generate false positives, if so there are two solutions available:
|
102
|
+
- If the ActiveStorage blob content type is closely related to the detected content type using the `file` analyzer, you can enhance `Marcel::TYPE_PARENTS` mapping using `Marcel::MimeType.extend "application/x-rar-compressed", parents: %(application/x-rar)` in the `config/initializers/mime_types.rb` file. (Please drop an issue so we can add it to the gem for everyone!)
|
103
|
+
- If the ActiveStorage blob content type is not closely related, you still can disable the content type spoofing protection in the validator, if so, please drop us an issue so we can fix it for everyone!
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
class User < ApplicationRecord
|
107
|
+
has_one_attached :avatar
|
108
|
+
|
109
|
+
validates :avatar, attached: true, content_type: :png # spoofing_protection not enabled, at your own risks!
|
110
|
+
validates :avatar, attached: true, content_type: { with: :png, spoofing_protection: true } # spoofing_protection enabled
|
111
|
+
end
|
112
|
+
```
|
113
|
+
|
114
|
+
|
93
115
|
- Dimension validation with `width`, `height` and `in`.
|
94
116
|
|
95
117
|
```ruby
|
@@ -181,7 +203,6 @@ en:
|
|
181
203
|
aspect_ratio_not_portrait: "must be a portrait image"
|
182
204
|
aspect_ratio_not_landscape: "must be a landscape image"
|
183
205
|
aspect_ratio_is_not: "must have an aspect ratio of %{aspect_ratio}"
|
184
|
-
aspect_ratio_unknown: "has an unknown aspect ratio"
|
185
206
|
image_not_processable: "is not a valid image"
|
186
207
|
```
|
187
208
|
|
@@ -297,8 +318,7 @@ Very simple example of validation with file attached, content type check and cus
|
|
297
318
|
[![Sample](https://raw.githubusercontent.com/igorkasyanchuk/active_storage_validations/master/docs/preview.png)](https://raw.githubusercontent.com/igorkasyanchuk/active_storage_validations/master/docs/preview.png)
|
298
319
|
|
299
320
|
## Test matchers
|
300
|
-
|
301
|
-
Provides RSpec-compatible and Minitest-compatible matchers for testing the validators. Only `aspect_ratio`, `attached`, `content_type`, `processable_image`, `dimension`, `size` and `total_size` validators currently have their matcher developed.
|
321
|
+
Provides RSpec-compatible and Minitest-compatible matchers for testing the validators.
|
302
322
|
|
303
323
|
### RSpec
|
304
324
|
|
@@ -331,6 +351,11 @@ describe User do
|
|
331
351
|
# processable_image
|
332
352
|
it { is_expected.to validate_processable_image_of(:avatar) }
|
333
353
|
|
354
|
+
# limit
|
355
|
+
# #min, #max
|
356
|
+
it { is_expected.to validate_limit_of(:avatar).min(1) }
|
357
|
+
it { is_expected.to validate_limit_of(:avatar).max(5) }
|
358
|
+
|
334
359
|
# content_type:
|
335
360
|
# #allowing, #rejecting
|
336
361
|
it { is_expected.to validate_content_type_of(:avatar).allowing('image/png', 'image/gif') }
|
@@ -411,22 +436,22 @@ Then you can use the matchers with the syntax specified in the RSpec section, ju
|
|
411
436
|
|
412
437
|
To run tests in root folder of gem:
|
413
438
|
|
414
|
-
* `BUNDLE_GEMFILE=gemfiles/rails_6_1_3_1.gemfile bundle exec rake test` to run for Rails 6.1
|
415
439
|
* `BUNDLE_GEMFILE=gemfiles/rails_7_0.gemfile bundle exec rake test` to run for Rails 7.0
|
416
|
-
* `BUNDLE_GEMFILE=gemfiles/rails_7_1.gemfile bundle exec rake test` to run for Rails 7.
|
417
|
-
* `BUNDLE_GEMFILE=gemfiles/
|
440
|
+
* `BUNDLE_GEMFILE=gemfiles/rails_7_1.gemfile bundle exec rake test` to run for Rails 7.1
|
441
|
+
* `BUNDLE_GEMFILE=gemfiles/rails_7_2.gemfile bundle exec rake test` to run for Rails 7.2
|
442
|
+
* `BUNDLE_GEMFILE=gemfiles/rails_8_0.gemfile bundle exec rake test` to run for Rails 8.0
|
418
443
|
|
419
444
|
Snippet to run in console:
|
420
445
|
|
421
446
|
```bash
|
422
|
-
BUNDLE_GEMFILE=gemfiles/rails_6_1_3_1.gemfile bundle
|
423
447
|
BUNDLE_GEMFILE=gemfiles/rails_7_0.gemfile bundle
|
424
448
|
BUNDLE_GEMFILE=gemfiles/rails_7_1.gemfile bundle
|
425
|
-
BUNDLE_GEMFILE=gemfiles/
|
426
|
-
BUNDLE_GEMFILE=gemfiles/
|
449
|
+
BUNDLE_GEMFILE=gemfiles/rails_7_2.gemfile bundle
|
450
|
+
BUNDLE_GEMFILE=gemfiles/rails_8_0.gemfile bundle
|
427
451
|
BUNDLE_GEMFILE=gemfiles/rails_7_0.gemfile bundle exec rake test
|
428
452
|
BUNDLE_GEMFILE=gemfiles/rails_7_1.gemfile bundle exec rake test
|
429
|
-
BUNDLE_GEMFILE=gemfiles/
|
453
|
+
BUNDLE_GEMFILE=gemfiles/rails_7_2.gemfile bundle exec rake test
|
454
|
+
BUNDLE_GEMFILE=gemfiles/rails_8_0.gemfile bundle exec rake test
|
430
455
|
```
|
431
456
|
|
432
457
|
Tips:
|
@@ -434,18 +459,6 @@ Tips:
|
|
434
459
|
- To focus a specific file, use the TEST option provided by minitest, e.g. to only run size_validator_test.rb file you will execute the following command: `bundle exec rake test TEST=test/validators/size_validator_test.rb`
|
435
460
|
|
436
461
|
|
437
|
-
## Known issues
|
438
|
-
|
439
|
-
- There is an issue in Rails which it possible to get if you have added a validation and generating for example an image preview of attachments. It can be fixed with this:
|
440
|
-
|
441
|
-
```erb
|
442
|
-
<% if @user.avatar.attached? && @user.avatar.attachment.blob.present? && @user.avatar.attachment.blob.persisted? %>
|
443
|
-
<%= image_tag @user.avatar %>
|
444
|
-
<% end %>
|
445
|
-
```
|
446
|
-
|
447
|
-
This is a Rails issue, and is fixed in Rails 6.
|
448
|
-
|
449
462
|
## Contributing
|
450
463
|
|
451
464
|
You are welcome to contribute.
|
data/config/locales/da.yml
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
# Danish
|
2
1
|
da:
|
3
2
|
errors:
|
4
3
|
messages:
|
5
4
|
content_type_invalid: "har en ugyldig indholdstype"
|
5
|
+
spoofed_content_type: "har en indholdstype, der ikke er, hvad den erklæres gennem sit indhold, filnavn og udvidelse"
|
6
6
|
file_size_not_less_than: "filstørrelsen skal være mindre end %{max_size} (den nuværende størrelse er %{file_size})"
|
7
7
|
file_size_not_less_than_or_equal_to: "filstørrelsen skal være mindre end eller lig med %{max_size} (den nuværende størrelse er %{file_size})"
|
8
8
|
file_size_not_greater_than: "filstørrelsen skal være større end %{min_size} (den nuværende størrelse er %{file_size})"
|
@@ -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"
|
data/config/locales/de.yml
CHANGED
@@ -2,6 +2,7 @@ de:
|
|
2
2
|
errors:
|
3
3
|
messages:
|
4
4
|
content_type_invalid: "hat einen ungültigen Dateityp"
|
5
|
+
spoofed_content_type: "hat einen Inhaltstyp, der nicht dem entspricht, was durch Inhalt, Dateinamen und Erweiterung deklariert wird"
|
5
6
|
file_size_not_less_than: "Dateigröße muss kleiner als %{max_size} sein (aktuelle Dateigröße ist %{file_size})"
|
6
7
|
file_size_not_less_than_or_equal_to: "Dateigröße muss kleiner oder gleich %{max_size} sein (aktuelle Dateigröße ist %{file_size})"
|
7
8
|
file_size_not_greater_than: "Dateigröße muss größer als %{min_size} sein (aktuelle Dateigröße ist %{file_size})"
|
@@ -28,5 +29,4 @@ de:
|
|
28
29
|
aspect_ratio_not_portrait: "muss Hochformat sein"
|
29
30
|
aspect_ratio_not_landscape: "muss Querformat sein"
|
30
31
|
aspect_ratio_is_not: "muss ein Bildseitenverhältnis von %{aspect_ratio} haben"
|
31
|
-
aspect_ratio_unknown: "hat ein unbekanntes Bildseitenverhältnis"
|
32
32
|
image_not_processable: "ist kein gültiges Bild"
|
data/config/locales/en.yml
CHANGED
@@ -2,6 +2,7 @@ en:
|
|
2
2
|
errors:
|
3
3
|
messages:
|
4
4
|
content_type_invalid: "has an invalid content type"
|
5
|
+
spoofed_content_type: "has a content type that is not what it is declared through its content, filename and extension"
|
5
6
|
file_size_not_less_than: "file size must be less than %{max_size} (current size is %{file_size})"
|
6
7
|
file_size_not_less_than_or_equal_to: "file size must be less than or equal to %{max_size} (current size is %{file_size})"
|
7
8
|
file_size_not_greater_than: "file size must be greater than %{min_size} (current size is %{file_size})"
|
@@ -28,5 +29,4 @@ en:
|
|
28
29
|
aspect_ratio_not_portrait: "must be a portrait image"
|
29
30
|
aspect_ratio_not_landscape: "must be a landscape image"
|
30
31
|
aspect_ratio_is_not: "must have an aspect ratio of %{aspect_ratio}"
|
31
|
-
aspect_ratio_unknown: "has an unknown aspect ratio"
|
32
32
|
image_not_processable: "is not a valid image"
|
data/config/locales/es.yml
CHANGED
@@ -2,6 +2,7 @@ es:
|
|
2
2
|
errors:
|
3
3
|
messages:
|
4
4
|
content_type_invalid: "tiene un tipo de contenido inválido"
|
5
|
+
spoofed_content_type: "tiene un tipo de contenido que no es el que se declara a través de su contenido, nombre de archivo y extensión"
|
5
6
|
file_size_not_less_than: "el tamaño del archivo debe ser inferior a %{max_size} (el tamaño actual es %{file_size})"
|
6
7
|
file_size_not_less_than_or_equal_to: "el tamaño del archivo debe ser menor o igual a %{max_size} (el tamaño actual es %{file_size})"
|
7
8
|
file_size_not_greater_than: "el tamaño del archivo debe ser mayor que %{min_size} (el tamaño actual es %{file_size})"
|
@@ -28,5 +29,4 @@ es:
|
|
28
29
|
aspect_ratio_not_portrait: "debe ser una imagen vertical"
|
29
30
|
aspect_ratio_not_landscape: "debe ser una imagen apaisada"
|
30
31
|
aspect_ratio_is_not: "debe tener una relación de aspecto de %{aspect_ratio}"
|
31
|
-
aspect_ratio_unknown: "tiene una relación de aspecto desconocida"
|
32
32
|
image_not_processable: "no es una imagen válida"
|
data/config/locales/fr.yml
CHANGED
@@ -2,6 +2,7 @@ fr:
|
|
2
2
|
errors:
|
3
3
|
messages:
|
4
4
|
content_type_invalid: "a un type de contenu non valide"
|
5
|
+
spoofed_content_type: "a un type de contenu qui n'est pas celui déclaré via son contenu, son nom de fichier et son extension"
|
5
6
|
file_size_not_less_than: "la taille du fichier doit être inférieure à %{max_size} (la taille actuelle est %{file_size})"
|
6
7
|
file_size_not_less_than_or_equal_to: "la taille du fichier doit être inférieure ou égale à %{max_size} (la taille actuelle est %{file_size})"
|
7
8
|
file_size_not_greater_than: "la taille du fichier doit être supérieure à %{min_size} (la taille actuelle est %{file_size})"
|
@@ -28,5 +29,4 @@ fr:
|
|
28
29
|
aspect_ratio_not_portrait: "doit être une image en format portrait"
|
29
30
|
aspect_ratio_not_landscape: "doit être une image en format paysage"
|
30
31
|
aspect_ratio_is_not: "doit avoir un rapport hauteur / largeur de %{aspect_ratio}"
|
31
|
-
aspect_ratio_unknown: "a un rapport d'aspect inconnu"
|
32
32
|
image_not_processable: "n'est pas une image valide"
|
data/config/locales/it.yml
CHANGED
@@ -2,6 +2,7 @@ it:
|
|
2
2
|
errors:
|
3
3
|
messages:
|
4
4
|
content_type_invalid: "ha un tipo di contenuto non valido"
|
5
|
+
spoofed_content_type: "ha un tipo di contenuto che non è quello dichiarato tramite contenuto, nome file ed estensione"
|
5
6
|
file_size_not_less_than: "la dimensione del file deve essere inferiore a %{max_size} (la dimensione attuale è %{file_size})"
|
6
7
|
file_size_not_less_than_or_equal_to: "la dimensione del file deve essere minore o uguale a %{max_size} (la dimensione attuale è %{file_size})"
|
7
8
|
file_size_not_greater_than: "la dimensione del file deve essere maggiore di %{min_size} (la dimensione attuale è %{file_size})"
|
@@ -28,5 +29,4 @@ it:
|
|
28
29
|
aspect_ratio_not_portrait: "l’orientamento dell’immagine deve essere verticale"
|
29
30
|
aspect_ratio_not_landscape: "l’orientamento dell’immagine deve essere orizzontale"
|
30
31
|
aspect_ratio_is_not: "deve avere un rapporto altezza / larghezza di %{aspect_ratio}"
|
31
|
-
aspect_ratio_unknown: "ha un rapporto altezza / larghezza sconosciuto"
|
32
32
|
image_not_processable: "non è un'immagine valida"
|
data/config/locales/ja.yml
CHANGED
@@ -2,6 +2,7 @@ ja:
|
|
2
2
|
errors:
|
3
3
|
messages:
|
4
4
|
content_type_invalid: "のContent Typeが不正です"
|
5
|
+
spoofed_content_type: "コンテンツ、ファイル名、拡張子で宣言されているコンテンツ タイプと異なるコンテンツ タイプが含まれています"
|
5
6
|
file_size_not_less_than: "ファイル サイズは %{max_size} 未満にする必要があります (現在のサイズは %{file_size})"
|
6
7
|
file_size_not_less_than_or_equal_to: "ファイル サイズは %{max_size} 以下である必要があります (現在のサイズは %{file_size})"
|
7
8
|
file_size_not_greater_than: "ファイル サイズは %{min_size} より大きい必要があります (現在のサイズは %{file_size} です)"
|
@@ -28,5 +29,4 @@ ja:
|
|
28
29
|
aspect_ratio_not_portrait: "は縦長にしてください"
|
29
30
|
aspect_ratio_not_landscape: "は横長にしてください"
|
30
31
|
aspect_ratio_is_not: "のアスペクト比は %{aspect_ratio} にしてください"
|
31
|
-
aspect_ratio_unknown: "のアスペクト比を取得できませんでした"
|
32
32
|
image_not_processable: "は不正な画像です"
|
data/config/locales/nl.yml
CHANGED
@@ -2,6 +2,7 @@ nl:
|
|
2
2
|
errors:
|
3
3
|
messages:
|
4
4
|
content_type_invalid: "heeft een ongeldig inhoudstype"
|
5
|
+
spoofed_content_type: "heeft een inhoudstype dat niet is wat het wordt aangegeven via de inhoud, bestandsnaam en extensie"
|
5
6
|
file_size_not_less_than: "bestandsgrootte moet kleiner zijn dan %{max_size} (huidige grootte is %{file_size})"
|
6
7
|
file_size_not_less_than_or_equal_to: "bestandsgrootte moet kleiner zijn dan of gelijk zijn aan %{max_size} (huidige grootte is %{file_size})"
|
7
8
|
file_size_not_greater_than: "bestandsgrootte moet groter zijn dan %{min_size} (huidige grootte is %{file_size})"
|
@@ -28,5 +29,4 @@ nl:
|
|
28
29
|
aspect_ratio_not_portrait: "moet een staande afbeelding zijn"
|
29
30
|
aspect_ratio_not_landscape: "moet een liggende afbeelding zijn"
|
30
31
|
aspect_ratio_is_not: "moet een beeldverhouding hebben van %{aspect_ratio}"
|
31
|
-
aspect_ratio_unknown: "heeft een onbekende beeldverhouding"
|
32
32
|
image_not_processable: "is geen geldige afbeelding"
|
data/config/locales/pl.yml
CHANGED
@@ -2,6 +2,7 @@ pl:
|
|
2
2
|
errors:
|
3
3
|
messages:
|
4
4
|
content_type_invalid: "jest nieprawidłowego typu"
|
5
|
+
spoofed_content_type: "ma typ zawartości inny niż zadeklarowany w treści, nazwie pliku i rozszerzeniu"
|
5
6
|
file_size_not_less_than: "rozmiar pliku musi być mniejszy niż %{max_size} (obecny rozmiar to %{file_size})"
|
6
7
|
file_size_not_less_than_or_equal_to: "rozmiar pliku musi być mniejszy lub równy %{max_size} (obecny rozmiar to %{file_size})"
|
7
8
|
file_size_not_greater_than: "rozmiar pliku musi być większy niż %{min_size} (obecny rozmiar to %{file_size})"
|
@@ -28,5 +29,4 @@ pl:
|
|
28
29
|
aspect_ratio_not_portrait: "musi mieć proporcje portretu"
|
29
30
|
aspect_ratio_not_landscape: "musi mieć proporcje pejzażu"
|
30
31
|
aspect_ratio_is_not: "musi mieć proporcje %{aspect_ratio}"
|
31
|
-
aspect_ratio_unknown: "ma nieokreślone proporcje"
|
32
32
|
image_not_processable: "nie jest prawidłowym obrazem"
|
data/config/locales/pt-BR.yml
CHANGED
@@ -2,6 +2,7 @@ pt-BR:
|
|
2
2
|
errors:
|
3
3
|
messages:
|
4
4
|
content_type_invalid: "tem um tipo de arquivo inválido"
|
5
|
+
spoofed_content_type: "tem um tipo de conteúdo que não é o declarado através de seu conteúdo, nome de arquivo e extensão"
|
5
6
|
file_size_not_less_than: "o tamanho do arquivo deve ser menor que %{max_size} (o tamanho atual é %{file_size})"
|
6
7
|
file_size_not_less_than_or_equal_to: "o tamanho do arquivo deve ser menor ou igual a %{max_size} (o tamanho atual é %{file_size})"
|
7
8
|
file_size_not_greater_than: "o tamanho do arquivo deve ser maior que %{min_size} (o tamanho atual é %{file_size})"
|
@@ -28,5 +29,4 @@ pt-BR:
|
|
28
29
|
aspect_ratio_not_portrait: "não está no formato retrato"
|
29
30
|
aspect_ratio_not_landscape: "não está no formato paisagem"
|
30
31
|
aspect_ratio_is_not: "não contém uma proporção de %{aspect_ratio}"
|
31
|
-
aspect_ratio_unknown: "não tem uma proporção definida"
|
32
32
|
image_not_processable: "não é uma imagem válida"
|
data/config/locales/ru.yml
CHANGED
@@ -2,6 +2,7 @@ ru:
|
|
2
2
|
errors:
|
3
3
|
messages:
|
4
4
|
content_type_invalid: "имеет недопустимый тип содержимого"
|
5
|
+
spoofed_content_type: "имеет тип контента, который не соответствует тому, который объявлен в его содержимом, имени файла и расширении"
|
5
6
|
file_size_not_less_than: "размер файла должен быть меньше %{max_size} (текущий размер %{file_size})"
|
6
7
|
file_size_not_less_than_or_equal_to: "размер файла должен быть меньше или равен %{max_size} (текущий размер %{file_size})"
|
7
8
|
file_size_not_greater_than: "размер файла должен быть больше %{min_size} (текущий размер %{file_size})"
|
@@ -28,5 +29,4 @@ ru:
|
|
28
29
|
aspect_ratio_not_portrait: "должно быть портретное изображение"
|
29
30
|
aspect_ratio_not_landscape: "должно быть пейзажное изображение"
|
30
31
|
aspect_ratio_is_not: "должен иметь соотношение сторон %{aspect_ratio}"
|
31
|
-
aspect_ratio_unknown: "имеет неизвестное соотношение сторон"
|
32
32
|
image_not_processable: "не является допустимым изображением"
|
data/config/locales/sv.yml
CHANGED
@@ -2,6 +2,7 @@ sv:
|
|
2
2
|
errors:
|
3
3
|
messages:
|
4
4
|
content_type_invalid: "Har en ogiltig filtyp"
|
5
|
+
spoofed_content_type: "har en innehållstyp som inte är vad den deklareras genom sitt innehåll, filnamn och tillägg"
|
5
6
|
file_size_not_less_than: "filstorleken måste vara mindre än %{max_size} (nuvarande storlek är %{file_size})"
|
6
7
|
file_size_not_less_than_or_equal_to: "filstorleken måste vara mindre än eller lika med %{max_size} (nuvarande storlek är %{file_size})"
|
7
8
|
file_size_not_greater_than: "filstorleken måste vara större än %{min_size} (nuvarande storlek är %{file_size})"
|
@@ -28,5 +29,4 @@ sv:
|
|
28
29
|
aspect_ratio_not_portrait: "måste vara en porträttorienterad bild"
|
29
30
|
aspect_ratio_not_landscape: "måste vara en landskapsorienterad bild"
|
30
31
|
aspect_ratio_is_not: "måste ha en följande aspect ratio %{aspect_ratio}"
|
31
|
-
aspect_ratio_unknown: "har en okänd aspect ratio"
|
32
32
|
image_not_processable: "är inte en giltig bild"
|
data/config/locales/tr.yml
CHANGED
@@ -2,6 +2,7 @@ tr:
|
|
2
2
|
errors:
|
3
3
|
messages:
|
4
4
|
content_type_invalid: "geçersiz dosya tipine sahip"
|
5
|
+
spoofed_content_type: "içeriği, dosya adı ve uzantısı aracılığıyla bildirildiği gibi olmayan bir içerik türüne sahip"
|
5
6
|
file_size_not_less_than: "dosya boyutu %{max_size} boyutundan küçük olmalıdır (geçerli boyut %{file_size}'dir)"
|
6
7
|
file_size_not_less_than_or_equal_to: "dosya boyutu %{max_size} değerinden küçük veya ona eşit olmalıdır (geçerli boyut %{file_size}'dir)"
|
7
8
|
file_size_not_greater_than: "dosya boyutu %{min_size} boyutundan büyük olmalıdır (geçerli boyut %{file_size}'dir)"
|
@@ -28,5 +29,4 @@ tr:
|
|
28
29
|
aspect_ratio_not_portrait: "dikey bir imaj olmalı"
|
29
30
|
aspect_ratio_not_landscape: "yatay bir imaj olmalı"
|
30
31
|
aspect_ratio_is_not: "%{aspect_ratio} en boy oranına sahip olmalı"
|
31
|
-
aspect_ratio_unknown: "bilinmeyen en boy oranı"
|
32
32
|
image_not_processable: "geçerli bir imaj değil"
|
data/config/locales/uk.yml
CHANGED
@@ -2,6 +2,7 @@ uk:
|
|
2
2
|
errors:
|
3
3
|
messages:
|
4
4
|
content_type_invalid: "має неприпустимий тип вмісту"
|
5
|
+
spoofed_content_type: "має тип вмісту, який не відповідає тому, що він оголошений через його вміст, назву файлу та розширення"
|
5
6
|
file_size_not_less_than: "розмір файлу має бути менше %{max_size} (поточний розмір %{file_size})"
|
6
7
|
file_size_not_less_than_or_equal_to: "розмір файлу має бути меншим або дорівнювати %{max_size} (поточний розмір %{file_size})"
|
7
8
|
file_size_not_greater_than: "розмір файлу має бути більшим ніж %{min_size} (поточний розмір %{file_size})"
|
@@ -28,5 +29,4 @@ uk:
|
|
28
29
|
aspect_ratio_not_portrait: "мусить бути портретне зображення"
|
29
30
|
aspect_ratio_not_landscape: "мусить бути пейзажне зображення"
|
30
31
|
aspect_ratio_is_not: "мусить мати співвідношення сторін %{aspect_ratio}"
|
31
|
-
aspect_ratio_unknown: "має невідоме співвідношення сторін"
|
32
32
|
image_not_processable: "не є допустимим зображенням"
|
data/config/locales/vi.yml
CHANGED
@@ -2,6 +2,7 @@ vi:
|
|
2
2
|
errors:
|
3
3
|
messages:
|
4
4
|
content_type_invalid: "tệp không hợp lệ"
|
5
|
+
spoofed_content_type: "có loại nội dung không phải là loại nội dung được khai báo thông qua nội dung, tên tệp và phần mở rộng của nó"
|
5
6
|
file_size_not_less_than: "kích thước tệp phải nhỏ hơn %{max_size} (kích thước hiện tại là %{file_size})"
|
6
7
|
file_size_not_less_than_or_equal_to: "kích thước tệp phải nhỏ hơn hoặc bằng %{max_size} (kích thước hiện tại là %{file_size})"
|
7
8
|
file_size_not_greater_than: "kích thước tệp phải lớn hơn %{min_size} (kích thước hiện tại là %{file_size})"
|
@@ -28,5 +29,4 @@ vi:
|
|
28
29
|
aspect_ratio_not_portrait: "phải là ảnh đứng"
|
29
30
|
aspect_ratio_not_landscape: "phải là ảnh ngang"
|
30
31
|
aspect_ratio_is_not: "phải có tỉ lệ ảnh %{aspect_ratio}"
|
31
|
-
aspect_ratio_unknown: "tỉ lệ ảnh không xác định"
|
32
32
|
image_not_processable: "không phải là ảnh"
|
data/config/locales/zh-CN.yml
CHANGED
@@ -2,6 +2,7 @@ zh-CN:
|
|
2
2
|
errors:
|
3
3
|
messages:
|
4
4
|
content_type_invalid: "文件类型错误"
|
5
|
+
spoofed_content_type: "内容类型与通过内容、文件名和扩展名声明的内容类型不同"
|
5
6
|
file_size_not_less_than: "文件大小必须小于 %{max_size}(当前大小为 %{file_size})"
|
6
7
|
file_size_not_less_than_or_equal_to: "文件大小必须小于或等于 %{max_size}(当前大小为 %{file_size})"
|
7
8
|
file_size_not_greater_than: "文件大小必须大于 %{min_size}(当前大小为 %{file_size})"
|
@@ -28,5 +29,4 @@ zh-CN:
|
|
28
29
|
aspect_ratio_not_portrait: "必须是竖屏图片"
|
29
30
|
aspect_ratio_not_landscape: "必须是横屏图片"
|
30
31
|
aspect_ratio_is_not: "纵横比必须是 %{aspect_ratio}"
|
31
|
-
aspect_ratio_unknown: "未知的纵横比"
|
32
32
|
image_not_processable: "不是有效的图像"
|
@@ -1,13 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'concerns/active_storageable.rb'
|
4
|
+
require_relative 'concerns/attachable.rb'
|
3
5
|
require_relative 'concerns/errorable.rb'
|
6
|
+
require_relative 'concerns/optionable.rb'
|
4
7
|
require_relative 'concerns/symbolizable.rb'
|
5
|
-
require_relative 'metadata.rb'
|
6
8
|
|
7
9
|
module ActiveStorageValidations
|
8
10
|
class AspectRatioValidator < ActiveModel::EachValidator # :nodoc
|
9
|
-
include
|
11
|
+
include ActiveStorageable
|
12
|
+
include Attachable
|
10
13
|
include Errorable
|
14
|
+
include Optionable
|
11
15
|
include Symbolizable
|
12
16
|
|
13
17
|
AVAILABLE_CHECKS = %i[with].freeze
|
@@ -19,7 +23,6 @@ module ActiveStorageValidations
|
|
19
23
|
aspect_ratio_not_portrait
|
20
24
|
aspect_ratio_not_landscape
|
21
25
|
aspect_ratio_is_not
|
22
|
-
aspect_ratio_unknown
|
23
26
|
].freeze
|
24
27
|
PRECISION = 3.freeze
|
25
28
|
|
@@ -28,82 +31,70 @@ module ActiveStorageValidations
|
|
28
31
|
ensure_aspect_ratio_validity
|
29
32
|
end
|
30
33
|
|
31
|
-
|
32
|
-
|
33
|
-
return true unless record.send(attribute).attached?
|
34
|
+
def validate_each(record, attribute, _value)
|
35
|
+
return if no_attachments?(record, attribute)
|
34
36
|
|
35
|
-
|
36
|
-
|
37
|
+
validate_changed_files_from_metadata(record, attribute)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
37
41
|
|
38
|
-
|
42
|
+
def is_valid?(record, attribute, attachable, metadata)
|
43
|
+
flat_options = set_flat_options(record)
|
39
44
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
+
return if image_metadata_missing?(record, attribute, attachable, flat_options, metadata)
|
46
|
+
|
47
|
+
case flat_options[:with]
|
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)
|
45
52
|
end
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
53
|
+
end
|
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
|
50
57
|
|
51
|
-
|
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
|
52
63
|
|
53
|
-
|
54
|
-
|
55
|
-
file.analyze; file.reload unless file.analyzed?
|
56
|
-
metadata = file.metadata
|
64
|
+
def validate_square_aspect_ratio(record, attribute, attachable, flat_options, metadata)
|
65
|
+
return if metadata[:width] == metadata[:height]
|
57
66
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
end
|
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)
|
62
70
|
end
|
63
71
|
|
64
|
-
|
72
|
+
def validate_portrait_aspect_ratio(record, attribute, attachable, flat_options, metadata)
|
73
|
+
return if metadata[:width] < metadata[:height]
|
65
74
|
|
66
|
-
|
67
|
-
|
68
|
-
|
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
|
69
79
|
|
70
|
-
|
71
|
-
|
80
|
+
def validate_landscape_aspect_ratio(record, attribute, attachable, flat_options, metadata)
|
81
|
+
return if metadata[:width] > metadata[:height]
|
72
82
|
|
73
|
-
|
74
|
-
|
75
|
-
|
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
|
76
87
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
when :landscape
|
89
|
-
return true if metadata[:width] > metadata[:height]
|
90
|
-
errors_options[:aspect_ratio] = flat_options[:with]
|
91
|
-
add_error(record, attribute, :aspect_ratio_not_landscape, **errors_options)
|
92
|
-
|
93
|
-
when ASPECT_RATIO_REGEX
|
94
|
-
flat_options[:with] =~ ASPECT_RATIO_REGEX
|
95
|
-
x = $1.to_i
|
96
|
-
y = $2.to_i
|
97
|
-
|
98
|
-
return true if x > 0 && y > 0 && (x.to_f / y).round(PRECISION) == (metadata[:width].to_f / metadata[:height]).round(PRECISION)
|
99
|
-
|
100
|
-
errors_options[:aspect_ratio] = "#{x}:#{y}"
|
101
|
-
add_error(record, attribute, :aspect_ratio_is_not, **errors_options)
|
102
|
-
else
|
103
|
-
errors_options[:aspect_ratio] = flat_options[:with]
|
104
|
-
add_error(record, attribute, :aspect_ratio_unknown, **errors_options)
|
105
|
-
return false
|
106
|
-
end
|
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)
|
107
98
|
end
|
108
99
|
|
109
100
|
def ensure_at_least_one_validator_option
|
@@ -1,10 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'concerns/active_storageable.rb'
|
3
4
|
require_relative 'concerns/errorable.rb'
|
4
5
|
require_relative 'concerns/symbolizable.rb'
|
5
6
|
|
6
7
|
module ActiveStorageValidations
|
7
8
|
class AttachedValidator < ActiveModel::EachValidator # :nodoc:
|
9
|
+
include ActiveStorageable
|
8
10
|
include Errorable
|
9
11
|
include Symbolizable
|
10
12
|
|
@@ -13,14 +15,14 @@ module ActiveStorageValidations
|
|
13
15
|
def check_validity!
|
14
16
|
%i[allow_nil allow_blank].each do |not_authorized_option|
|
15
17
|
if options.include?(not_authorized_option)
|
16
|
-
raise ArgumentError, "You cannot pass the :#{not_authorized_option} option to the #{self.class.
|
18
|
+
raise ArgumentError, "You cannot pass the :#{not_authorized_option} option to the #{self.class.to_sym} validator"
|
17
19
|
end
|
18
20
|
end
|
19
21
|
end
|
20
22
|
|
21
23
|
def validate_each(record, attribute, _value)
|
22
|
-
return if record
|
23
|
-
|
24
|
+
return if attachments_present?(record, attribute) &&
|
25
|
+
will_have_attachments_after_save?(record, attribute)
|
24
26
|
|
25
27
|
errors_options = initialize_error_options(options)
|
26
28
|
add_error(record, attribute, ERROR_TYPES.first, **errors_options)
|
@@ -1,12 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'concerns/active_storageable.rb'
|
3
4
|
require_relative 'concerns/errorable.rb'
|
5
|
+
require_relative 'concerns/optionable.rb'
|
4
6
|
require_relative 'concerns/symbolizable.rb'
|
5
7
|
|
6
8
|
module ActiveStorageValidations
|
7
9
|
class BaseSizeValidator < ActiveModel::EachValidator # :nodoc:
|
8
|
-
include
|
10
|
+
include ActiveStorageable
|
9
11
|
include Errorable
|
12
|
+
include Optionable
|
10
13
|
include Symbolizable
|
11
14
|
|
12
15
|
delegate :number_to_human_size, to: ActiveSupport::NumberHelper
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module ActiveStorageValidations
|
2
|
+
# ActiveStorageValidations::ActiveStorageable
|
3
|
+
#
|
4
|
+
# Validator helper methods to make our code more explicit.
|
5
|
+
module ActiveStorageable
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
# Retrieve either an ActiveStorage::Attached::One or an
|
11
|
+
# ActiveStorage::Attached::Many instance depending on attribute definition
|
12
|
+
def attached_files(record, attribute)
|
13
|
+
Array.wrap(record.send(attribute))
|
14
|
+
end
|
15
|
+
|
16
|
+
def attachments_present?(record, attribute)
|
17
|
+
record.send(attribute).attached?
|
18
|
+
end
|
19
|
+
|
20
|
+
def no_attachments?(record, attribute)
|
21
|
+
!attachments_present?(record, attribute)
|
22
|
+
end
|
23
|
+
|
24
|
+
def will_have_attachments_after_save?(record, attribute)
|
25
|
+
!Array.wrap(record.send(attribute)).all?(&:marked_for_destruction?)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|