paperclip-fix 4.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.hound.yml +1066 -0
- data/.rubocop.yml +1 -0
- data/.travis.yml +22 -0
- data/Appraisals +11 -0
- data/CONTRIBUTING.md +75 -0
- data/Gemfile +21 -0
- data/LICENSE +24 -0
- data/NEWS +420 -0
- data/README.md +979 -0
- data/RELEASING.md +17 -0
- data/Rakefile +44 -0
- data/UPGRADING +14 -0
- data/cucumber/paperclip_steps.rb +6 -0
- data/features/basic_integration.feature +80 -0
- data/features/migration.feature +94 -0
- data/features/rake_tasks.feature +62 -0
- data/features/step_definitions/attachment_steps.rb +110 -0
- data/features/step_definitions/html_steps.rb +15 -0
- data/features/step_definitions/rails_steps.rb +236 -0
- data/features/step_definitions/s3_steps.rb +14 -0
- data/features/step_definitions/web_steps.rb +107 -0
- data/features/support/env.rb +11 -0
- data/features/support/fakeweb.rb +13 -0
- data/features/support/file_helpers.rb +34 -0
- data/features/support/fixtures/boot_config.txt +15 -0
- data/features/support/fixtures/gemfile.txt +5 -0
- data/features/support/fixtures/preinitializer.txt +20 -0
- data/features/support/paths.rb +28 -0
- data/features/support/rails.rb +63 -0
- data/features/support/selectors.rb +19 -0
- data/gemfiles/3.2.gemfile +19 -0
- data/gemfiles/4.1.gemfile +19 -0
- data/gemfiles/4.2.gemfile +19 -0
- data/lib/generators/paperclip/USAGE +8 -0
- data/lib/generators/paperclip/paperclip_generator.rb +30 -0
- data/lib/generators/paperclip/templates/paperclip_migration.rb.erb +15 -0
- data/lib/paperclip/attachment.rb +608 -0
- data/lib/paperclip/attachment_registry.rb +59 -0
- data/lib/paperclip/callbacks.rb +40 -0
- data/lib/paperclip/content_type_detector.rb +79 -0
- data/lib/paperclip/deprecations.rb +42 -0
- data/lib/paperclip/errors.rb +32 -0
- data/lib/paperclip/file_command_content_type_detector.rb +30 -0
- data/lib/paperclip/filename_cleaner.rb +16 -0
- data/lib/paperclip/geometry.rb +158 -0
- data/lib/paperclip/geometry_detector_factory.rb +48 -0
- data/lib/paperclip/geometry_parser_factory.rb +31 -0
- data/lib/paperclip/glue.rb +17 -0
- data/lib/paperclip/has_attached_file.rb +109 -0
- data/lib/paperclip/helpers.rb +56 -0
- data/lib/paperclip/interpolations/plural_cache.rb +18 -0
- data/lib/paperclip/interpolations.rb +197 -0
- data/lib/paperclip/io_adapters/abstract_adapter.rb +47 -0
- data/lib/paperclip/io_adapters/attachment_adapter.rb +36 -0
- data/lib/paperclip/io_adapters/data_uri_adapter.rb +22 -0
- data/lib/paperclip/io_adapters/empty_string_adapter.rb +18 -0
- data/lib/paperclip/io_adapters/file_adapter.rb +22 -0
- data/lib/paperclip/io_adapters/http_url_proxy_adapter.rb +15 -0
- data/lib/paperclip/io_adapters/identity_adapter.rb +12 -0
- data/lib/paperclip/io_adapters/nil_adapter.rb +34 -0
- data/lib/paperclip/io_adapters/registry.rb +32 -0
- data/lib/paperclip/io_adapters/stringio_adapter.rb +33 -0
- data/lib/paperclip/io_adapters/uploaded_file_adapter.rb +42 -0
- data/lib/paperclip/io_adapters/uri_adapter.rb +63 -0
- data/lib/paperclip/locales/de.yml +18 -0
- data/lib/paperclip/locales/en.yml +18 -0
- data/lib/paperclip/locales/es.yml +18 -0
- data/lib/paperclip/locales/ja.yml +18 -0
- data/lib/paperclip/locales/pt-BR.yml +18 -0
- data/lib/paperclip/locales/zh-CN.yml +18 -0
- data/lib/paperclip/locales/zh-HK.yml +18 -0
- data/lib/paperclip/locales/zh-TW.yml +18 -0
- data/lib/paperclip/logger.rb +21 -0
- data/lib/paperclip/matchers/have_attached_file_matcher.rb +54 -0
- data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +100 -0
- data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +59 -0
- data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +96 -0
- data/lib/paperclip/matchers.rb +64 -0
- data/lib/paperclip/media_type_spoof_detector.rb +89 -0
- data/lib/paperclip/missing_attachment_styles.rb +79 -0
- data/lib/paperclip/processor.rb +48 -0
- data/lib/paperclip/processor_helpers.rb +50 -0
- data/lib/paperclip/rails_environment.rb +25 -0
- data/lib/paperclip/railtie.rb +31 -0
- data/lib/paperclip/schema.rb +83 -0
- data/lib/paperclip/storage/filesystem.rb +90 -0
- data/lib/paperclip/storage/fog.rb +241 -0
- data/lib/paperclip/storage/s3.rb +440 -0
- data/lib/paperclip/storage.rb +3 -0
- data/lib/paperclip/style.rb +109 -0
- data/lib/paperclip/tempfile.rb +43 -0
- data/lib/paperclip/tempfile_factory.rb +23 -0
- data/lib/paperclip/thumbnail.rb +121 -0
- data/lib/paperclip/url_generator.rb +72 -0
- data/lib/paperclip/validators/attachment_content_type_validator.rb +88 -0
- data/lib/paperclip/validators/attachment_file_name_validator.rb +80 -0
- data/lib/paperclip/validators/attachment_file_type_ignorance_validator.rb +29 -0
- data/lib/paperclip/validators/attachment_presence_validator.rb +30 -0
- data/lib/paperclip/validators/attachment_size_validator.rb +115 -0
- data/lib/paperclip/validators/media_type_spoof_detection_validator.rb +27 -0
- data/lib/paperclip/validators.rb +74 -0
- data/lib/paperclip/version.rb +3 -0
- data/lib/paperclip.rb +213 -0
- data/lib/tasks/paperclip.rake +127 -0
- data/paperclip.gemspec +51 -0
- data/shoulda_macros/paperclip.rb +134 -0
- data/spec/database.yml +4 -0
- data/spec/paperclip/attachment_definitions_spec.rb +13 -0
- data/spec/paperclip/attachment_processing_spec.rb +82 -0
- data/spec/paperclip/attachment_registry_spec.rb +130 -0
- data/spec/paperclip/attachment_spec.rb +1494 -0
- data/spec/paperclip/content_type_detector_spec.rb +48 -0
- data/spec/paperclip/deprecations_spec.rb +65 -0
- data/spec/paperclip/file_command_content_type_detector_spec.rb +26 -0
- data/spec/paperclip/filename_cleaner_spec.rb +14 -0
- data/spec/paperclip/geometry_detector_spec.rb +39 -0
- data/spec/paperclip/geometry_parser_spec.rb +73 -0
- data/spec/paperclip/geometry_spec.rb +255 -0
- data/spec/paperclip/glue_spec.rb +44 -0
- data/spec/paperclip/has_attached_file_spec.rb +142 -0
- data/spec/paperclip/integration_spec.rb +667 -0
- data/spec/paperclip/interpolations_spec.rb +262 -0
- data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +78 -0
- data/spec/paperclip/io_adapters/attachment_adapter_spec.rb +139 -0
- data/spec/paperclip/io_adapters/data_uri_adapter_spec.rb +83 -0
- data/spec/paperclip/io_adapters/empty_string_adapter_spec.rb +17 -0
- data/spec/paperclip/io_adapters/file_adapter_spec.rb +131 -0
- data/spec/paperclip/io_adapters/http_url_proxy_adapter_spec.rb +104 -0
- data/spec/paperclip/io_adapters/identity_adapter_spec.rb +8 -0
- data/spec/paperclip/io_adapters/nil_adapter_spec.rb +25 -0
- data/spec/paperclip/io_adapters/registry_spec.rb +35 -0
- data/spec/paperclip/io_adapters/stringio_adapter_spec.rb +64 -0
- data/spec/paperclip/io_adapters/uploaded_file_adapter_spec.rb +146 -0
- data/spec/paperclip/io_adapters/uri_adapter_spec.rb +127 -0
- data/spec/paperclip/matchers/have_attached_file_matcher_spec.rb +19 -0
- data/spec/paperclip/matchers/validate_attachment_content_type_matcher_spec.rb +99 -0
- data/spec/paperclip/matchers/validate_attachment_presence_matcher_spec.rb +69 -0
- data/spec/paperclip/matchers/validate_attachment_size_matcher_spec.rb +88 -0
- data/spec/paperclip/media_type_spoof_detector_spec.rb +79 -0
- data/spec/paperclip/meta_class_spec.rb +30 -0
- data/spec/paperclip/paperclip_missing_attachment_styles_spec.rb +84 -0
- data/spec/paperclip/paperclip_spec.rb +222 -0
- data/spec/paperclip/plural_cache_spec.rb +37 -0
- data/spec/paperclip/processor_helpers_spec.rb +57 -0
- data/spec/paperclip/processor_spec.rb +26 -0
- data/spec/paperclip/rails_environment_spec.rb +33 -0
- data/spec/paperclip/rake_spec.rb +103 -0
- data/spec/paperclip/schema_spec.rb +248 -0
- data/spec/paperclip/storage/filesystem_spec.rb +79 -0
- data/spec/paperclip/storage/fog_spec.rb +535 -0
- data/spec/paperclip/storage/s3_live_spec.rb +182 -0
- data/spec/paperclip/storage/s3_spec.rb +1526 -0
- data/spec/paperclip/style_spec.rb +255 -0
- data/spec/paperclip/tempfile_factory_spec.rb +33 -0
- data/spec/paperclip/thumbnail_spec.rb +500 -0
- data/spec/paperclip/url_generator_spec.rb +211 -0
- data/spec/paperclip/validators/attachment_content_type_validator_spec.rb +322 -0
- data/spec/paperclip/validators/attachment_file_name_validator_spec.rb +160 -0
- data/spec/paperclip/validators/attachment_presence_validator_spec.rb +85 -0
- data/spec/paperclip/validators/attachment_size_validator_spec.rb +229 -0
- data/spec/paperclip/validators/media_type_spoof_detection_validator_spec.rb +52 -0
- data/spec/paperclip/validators_spec.rb +164 -0
- data/spec/spec_helper.rb +43 -0
- data/spec/support/assertions.rb +71 -0
- data/spec/support/deprecations.rb +9 -0
- data/spec/support/fake_model.rb +25 -0
- data/spec/support/fake_rails.rb +12 -0
- data/spec/support/fixtures/12k.png +0 -0
- data/spec/support/fixtures/50x50.png +0 -0
- data/spec/support/fixtures/5k.png +0 -0
- data/spec/support/fixtures/animated +0 -0
- data/spec/support/fixtures/animated.gif +0 -0
- data/spec/support/fixtures/animated.unknown +0 -0
- data/spec/support/fixtures/bad.png +1 -0
- data/spec/support/fixtures/empty.html +1 -0
- data/spec/support/fixtures/empty.xlsx +0 -0
- data/spec/support/fixtures/fog.yml +8 -0
- data/spec/support/fixtures/rotated.jpg +0 -0
- data/spec/support/fixtures/s3.yml +8 -0
- data/spec/support/fixtures/spaced file.jpg +0 -0
- data/spec/support/fixtures/spaced file.png +0 -0
- data/spec/support/fixtures/text.txt +1 -0
- data/spec/support/fixtures/twopage.pdf +0 -0
- data/spec/support/fixtures/uppercase.PNG +0 -0
- data/spec/support/matchers/accept.rb +5 -0
- data/spec/support/matchers/exist.rb +5 -0
- data/spec/support/matchers/have_column.rb +23 -0
- data/spec/support/mock_attachment.rb +22 -0
- data/spec/support/mock_interpolator.rb +24 -0
- data/spec/support/mock_url_generator_builder.rb +27 -0
- data/spec/support/model_reconstruction.rb +60 -0
- data/spec/support/rails_helpers.rb +7 -0
- data/spec/support/test_data.rb +13 -0
- data/spec/support/version_helper.rb +9 -0
- metadata +606 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
zh-CN:
|
|
2
|
+
errors:
|
|
3
|
+
messages:
|
|
4
|
+
in_between: "文件大小必须介于 %{min} 到 %{max} 之间"
|
|
5
|
+
spoofed_media_type: "扩展名与内容类型不符"
|
|
6
|
+
|
|
7
|
+
number:
|
|
8
|
+
human:
|
|
9
|
+
storage_units:
|
|
10
|
+
format: "%n %u"
|
|
11
|
+
units:
|
|
12
|
+
byte:
|
|
13
|
+
one: "Byte"
|
|
14
|
+
other: "Bytes"
|
|
15
|
+
kb: "KB"
|
|
16
|
+
mb: "MB"
|
|
17
|
+
gb: "GB"
|
|
18
|
+
tb: "TB"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
zh-HK:
|
|
2
|
+
errors:
|
|
3
|
+
messages:
|
|
4
|
+
in_between: "必須介於%{min}到%{max}之間"
|
|
5
|
+
spoofed_media_type: "副檔名與內容類型不匹配"
|
|
6
|
+
|
|
7
|
+
number:
|
|
8
|
+
human:
|
|
9
|
+
storage_units:
|
|
10
|
+
format: "%n %u"
|
|
11
|
+
units:
|
|
12
|
+
byte:
|
|
13
|
+
one: "Byte"
|
|
14
|
+
other: "Bytes"
|
|
15
|
+
kb: "KB"
|
|
16
|
+
mb: "MB"
|
|
17
|
+
gb: "GB"
|
|
18
|
+
tb: "TB"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
zh-TW:
|
|
2
|
+
errors:
|
|
3
|
+
messages:
|
|
4
|
+
in_between: "檔案大小必須介於 %{min} 到 %{max} 之間"
|
|
5
|
+
spoofed_media_type: "副檔名與內容類型不符"
|
|
6
|
+
|
|
7
|
+
number:
|
|
8
|
+
human:
|
|
9
|
+
storage_units:
|
|
10
|
+
format: "%n %u"
|
|
11
|
+
units:
|
|
12
|
+
byte:
|
|
13
|
+
one: "Byte"
|
|
14
|
+
other: "Bytes"
|
|
15
|
+
kb: "KB"
|
|
16
|
+
mb: "MB"
|
|
17
|
+
gb: "GB"
|
|
18
|
+
tb: "TB"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Paperclip
|
|
2
|
+
module Logger
|
|
3
|
+
# Log a paperclip-specific line. This will log to STDOUT
|
|
4
|
+
# by default. Set Paperclip.options[:log] to false to turn off.
|
|
5
|
+
def log message
|
|
6
|
+
logger.info("[paperclip] #{message}") if logging?
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def logger #:nodoc:
|
|
10
|
+
@logger ||= options[:logger] || ::Logger.new(STDOUT)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def logger=(logger)
|
|
14
|
+
@logger = logger
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def logging? #:nodoc:
|
|
18
|
+
options[:log]
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Paperclip
|
|
2
|
+
module Shoulda
|
|
3
|
+
module Matchers
|
|
4
|
+
# Ensures that the given instance or class has an attachment with the
|
|
5
|
+
# given name.
|
|
6
|
+
#
|
|
7
|
+
# Example:
|
|
8
|
+
# describe User do
|
|
9
|
+
# it { should have_attached_file(:avatar) }
|
|
10
|
+
# end
|
|
11
|
+
def have_attached_file name
|
|
12
|
+
HaveAttachedFileMatcher.new(name)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class HaveAttachedFileMatcher
|
|
16
|
+
def initialize attachment_name
|
|
17
|
+
@attachment_name = attachment_name
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def matches? subject
|
|
21
|
+
@subject = subject
|
|
22
|
+
@subject = @subject.class unless Class === @subject
|
|
23
|
+
responds? && has_column?
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def failure_message
|
|
27
|
+
"Should have an attachment named #{@attachment_name}"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def failure_message_when_negated
|
|
31
|
+
"Should not have an attachment named #{@attachment_name}"
|
|
32
|
+
end
|
|
33
|
+
alias negative_failure_message failure_message_when_negated
|
|
34
|
+
|
|
35
|
+
def description
|
|
36
|
+
"have an attachment named #{@attachment_name}"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
protected
|
|
40
|
+
|
|
41
|
+
def responds?
|
|
42
|
+
methods = @subject.instance_methods.map(&:to_s)
|
|
43
|
+
methods.include?("#{@attachment_name}") &&
|
|
44
|
+
methods.include?("#{@attachment_name}=") &&
|
|
45
|
+
methods.include?("#{@attachment_name}?")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def has_column?
|
|
49
|
+
@subject.column_names.include?("#{@attachment_name}_file_name")
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
module Paperclip
|
|
2
|
+
module Shoulda
|
|
3
|
+
module Matchers
|
|
4
|
+
# Ensures that the given instance or class validates the content type of
|
|
5
|
+
# the given attachment as specified.
|
|
6
|
+
#
|
|
7
|
+
# Example:
|
|
8
|
+
# describe User do
|
|
9
|
+
# it { should validate_attachment_content_type(:icon).
|
|
10
|
+
# allowing('image/png', 'image/gif').
|
|
11
|
+
# rejecting('text/plain', 'text/xml') }
|
|
12
|
+
# end
|
|
13
|
+
def validate_attachment_content_type name
|
|
14
|
+
ValidateAttachmentContentTypeMatcher.new(name)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class ValidateAttachmentContentTypeMatcher
|
|
18
|
+
def initialize attachment_name
|
|
19
|
+
@attachment_name = attachment_name
|
|
20
|
+
@allowed_types = []
|
|
21
|
+
@rejected_types = []
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def allowing *types
|
|
25
|
+
@allowed_types = types.flatten
|
|
26
|
+
self
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def rejecting *types
|
|
30
|
+
@rejected_types = types.flatten
|
|
31
|
+
self
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def matches? subject
|
|
35
|
+
@subject = subject
|
|
36
|
+
@subject = @subject.new if @subject.class == Class
|
|
37
|
+
@allowed_types && @rejected_types &&
|
|
38
|
+
allowed_types_allowed? && rejected_types_rejected?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def failure_message
|
|
42
|
+
"#{expected_attachment}\n".tap do |message|
|
|
43
|
+
message << accepted_types_and_failures
|
|
44
|
+
message << "\n\n" if @allowed_types.present? && @rejected_types.present?
|
|
45
|
+
message << rejected_types_and_failures
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def description
|
|
50
|
+
"validate the content types allowed on attachment #{@attachment_name}"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
protected
|
|
54
|
+
|
|
55
|
+
def accepted_types_and_failures
|
|
56
|
+
if @allowed_types.present?
|
|
57
|
+
"Accept content types: #{@allowed_types.join(", ")}\n".tap do |message|
|
|
58
|
+
if @missing_allowed_types.any?
|
|
59
|
+
message << " #{@missing_allowed_types.join(", ")} were rejected."
|
|
60
|
+
else
|
|
61
|
+
message << " All were accepted successfully."
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
def rejected_types_and_failures
|
|
67
|
+
if @rejected_types.present?
|
|
68
|
+
"Reject content types: #{@rejected_types.join(", ")}\n".tap do |message|
|
|
69
|
+
if @missing_rejected_types.any?
|
|
70
|
+
message << " #{@missing_rejected_types.join(", ")} were accepted."
|
|
71
|
+
else
|
|
72
|
+
message << " All were rejected successfully."
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def expected_attachment
|
|
79
|
+
"Expected #{@attachment_name}:\n"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def type_allowed?(type)
|
|
83
|
+
@subject.send("#{@attachment_name}_content_type=", type)
|
|
84
|
+
@subject.valid?
|
|
85
|
+
@subject.errors[:"#{@attachment_name}_content_type"].blank?
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def allowed_types_allowed?
|
|
89
|
+
@missing_allowed_types ||= @allowed_types.reject { |type| type_allowed?(type) }
|
|
90
|
+
@missing_allowed_types.none?
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def rejected_types_rejected?
|
|
94
|
+
@missing_rejected_types ||= @rejected_types.select { |type| type_allowed?(type) }
|
|
95
|
+
@missing_rejected_types.none?
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module Paperclip
|
|
2
|
+
module Shoulda
|
|
3
|
+
module Matchers
|
|
4
|
+
# Ensures that the given instance or class validates the presence of the
|
|
5
|
+
# given attachment.
|
|
6
|
+
#
|
|
7
|
+
# describe User do
|
|
8
|
+
# it { should validate_attachment_presence(:avatar) }
|
|
9
|
+
# end
|
|
10
|
+
def validate_attachment_presence name
|
|
11
|
+
ValidateAttachmentPresenceMatcher.new(name)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class ValidateAttachmentPresenceMatcher
|
|
15
|
+
def initialize attachment_name
|
|
16
|
+
@attachment_name = attachment_name
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def matches? subject
|
|
20
|
+
@subject = subject
|
|
21
|
+
@subject = subject.new if subject.class == Class
|
|
22
|
+
error_when_not_valid? && no_error_when_valid?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def failure_message
|
|
26
|
+
"Attachment #{@attachment_name} should be required"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def failure_message_when_negated
|
|
30
|
+
"Attachment #{@attachment_name} should not be required"
|
|
31
|
+
end
|
|
32
|
+
alias negative_failure_message failure_message_when_negated
|
|
33
|
+
|
|
34
|
+
def description
|
|
35
|
+
"require presence of attachment #{@attachment_name}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
protected
|
|
39
|
+
|
|
40
|
+
def error_when_not_valid?
|
|
41
|
+
@subject.send(@attachment_name).assign(nil)
|
|
42
|
+
@subject.valid?
|
|
43
|
+
@subject.errors[:"#{@attachment_name}"].present?
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def no_error_when_valid?
|
|
47
|
+
@file = StringIO.new(".")
|
|
48
|
+
@subject.send(@attachment_name).assign(@file)
|
|
49
|
+
@subject.valid?
|
|
50
|
+
expected_message = [
|
|
51
|
+
@attachment_name.to_s.titleize,
|
|
52
|
+
I18n.t(:blank, scope: [:errors, :messages])
|
|
53
|
+
].join(' ')
|
|
54
|
+
@subject.errors.full_messages.exclude?(expected_message)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
module Paperclip
|
|
2
|
+
module Shoulda
|
|
3
|
+
module Matchers
|
|
4
|
+
# Ensures that the given instance or class validates the size of the
|
|
5
|
+
# given attachment as specified.
|
|
6
|
+
#
|
|
7
|
+
# Examples:
|
|
8
|
+
# it { should validate_attachment_size(:avatar).
|
|
9
|
+
# less_than(2.megabytes) }
|
|
10
|
+
# it { should validate_attachment_size(:icon).
|
|
11
|
+
# greater_than(1024) }
|
|
12
|
+
# it { should validate_attachment_size(:icon).
|
|
13
|
+
# in(0..100) }
|
|
14
|
+
def validate_attachment_size name
|
|
15
|
+
ValidateAttachmentSizeMatcher.new(name)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class ValidateAttachmentSizeMatcher
|
|
19
|
+
def initialize attachment_name
|
|
20
|
+
@attachment_name = attachment_name
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def less_than size
|
|
24
|
+
@high = size
|
|
25
|
+
self
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def greater_than size
|
|
29
|
+
@low = size
|
|
30
|
+
self
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def in range
|
|
34
|
+
@low, @high = range.first, range.last
|
|
35
|
+
self
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def matches? subject
|
|
39
|
+
@subject = subject
|
|
40
|
+
@subject = @subject.new if @subject.class == Class
|
|
41
|
+
lower_than_low? && higher_than_low? && lower_than_high? && higher_than_high?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def failure_message
|
|
45
|
+
"Attachment #{@attachment_name} must be between #{@low} and #{@high} bytes"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def failure_message_when_negated
|
|
49
|
+
"Attachment #{@attachment_name} cannot be between #{@low} and #{@high} bytes"
|
|
50
|
+
end
|
|
51
|
+
alias negative_failure_message failure_message_when_negated
|
|
52
|
+
|
|
53
|
+
def description
|
|
54
|
+
"validate the size of attachment #{@attachment_name}"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
protected
|
|
58
|
+
|
|
59
|
+
def override_method object, method, &replacement
|
|
60
|
+
(class << object; self; end).class_eval do
|
|
61
|
+
define_method(method, &replacement)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def passes_validation_with_size(new_size)
|
|
66
|
+
file = StringIO.new(".")
|
|
67
|
+
override_method(file, :size){ new_size }
|
|
68
|
+
override_method(file, :to_tempfile){ file }
|
|
69
|
+
|
|
70
|
+
@subject.send(@attachment_name).post_processing = false
|
|
71
|
+
@subject.send(@attachment_name).assign(file)
|
|
72
|
+
@subject.valid?
|
|
73
|
+
@subject.errors[:"#{@attachment_name}_file_size"].blank?
|
|
74
|
+
ensure
|
|
75
|
+
@subject.send(@attachment_name).post_processing = true
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def lower_than_low?
|
|
79
|
+
@low.nil? || !passes_validation_with_size(@low - 1)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def higher_than_low?
|
|
83
|
+
@low.nil? || passes_validation_with_size(@low + 1)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def lower_than_high?
|
|
87
|
+
@high.nil? || @high == Float::INFINITY || passes_validation_with_size(@high - 1)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def higher_than_high?
|
|
91
|
+
@high.nil? || @high == Float::INFINITY || !passes_validation_with_size(@high + 1)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
require 'paperclip/matchers/have_attached_file_matcher'
|
|
2
|
+
require 'paperclip/matchers/validate_attachment_presence_matcher'
|
|
3
|
+
require 'paperclip/matchers/validate_attachment_content_type_matcher'
|
|
4
|
+
require 'paperclip/matchers/validate_attachment_size_matcher'
|
|
5
|
+
|
|
6
|
+
module Paperclip
|
|
7
|
+
module Shoulda
|
|
8
|
+
# Provides RSpec-compatible & Test::Unit-compatible matchers for testing Paperclip attachments.
|
|
9
|
+
#
|
|
10
|
+
# *RSpec*
|
|
11
|
+
#
|
|
12
|
+
# In spec_helper.rb, you'll need to require the matchers:
|
|
13
|
+
#
|
|
14
|
+
# require "paperclip/matchers"
|
|
15
|
+
#
|
|
16
|
+
# And _include_ the module:
|
|
17
|
+
#
|
|
18
|
+
# RSpec.configure do |config|
|
|
19
|
+
# config.include Paperclip::Shoulda::Matchers
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# Example:
|
|
23
|
+
# describe User do
|
|
24
|
+
# it { should have_attached_file(:avatar) }
|
|
25
|
+
# it { should validate_attachment_presence(:avatar) }
|
|
26
|
+
# it { should validate_attachment_content_type(:avatar).
|
|
27
|
+
# allowing('image/png', 'image/gif').
|
|
28
|
+
# rejecting('text/plain', 'text/xml') }
|
|
29
|
+
# it { should validate_attachment_size(:avatar).
|
|
30
|
+
# less_than(2.megabytes) }
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
33
|
+
#
|
|
34
|
+
# *TestUnit*
|
|
35
|
+
#
|
|
36
|
+
# In test_helper.rb, you'll need to require the matchers as well:
|
|
37
|
+
#
|
|
38
|
+
# require "paperclip/matchers"
|
|
39
|
+
#
|
|
40
|
+
# And _extend_ the module:
|
|
41
|
+
#
|
|
42
|
+
# class ActiveSupport::TestCase
|
|
43
|
+
# extend Paperclip::Shoulda::Matchers
|
|
44
|
+
#
|
|
45
|
+
# #...other initializers...#
|
|
46
|
+
# end
|
|
47
|
+
#
|
|
48
|
+
# Example:
|
|
49
|
+
# require 'test_helper'
|
|
50
|
+
#
|
|
51
|
+
# class UserTest < ActiveSupport::TestCase
|
|
52
|
+
# should have_attached_file(:avatar)
|
|
53
|
+
# should validate_attachment_presence(:avatar)
|
|
54
|
+
# should validate_attachment_content_type(:avatar).
|
|
55
|
+
# allowing('image/png', 'image/gif').
|
|
56
|
+
# rejecting('text/plain', 'text/xml')
|
|
57
|
+
# should validate_attachment_size(:avatar).
|
|
58
|
+
# less_than(2.megabytes)
|
|
59
|
+
# end
|
|
60
|
+
#
|
|
61
|
+
module Matchers
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
module Paperclip
|
|
2
|
+
class MediaTypeSpoofDetector
|
|
3
|
+
def self.using(file, name, content_type)
|
|
4
|
+
new(file, name, content_type)
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def initialize(file, name, content_type)
|
|
8
|
+
@file = file
|
|
9
|
+
@name = name
|
|
10
|
+
@content_type = content_type || ""
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def spoofed?
|
|
14
|
+
if has_name? && has_extension? && media_type_mismatch? && mapping_override_mismatch?
|
|
15
|
+
Paperclip.log("Content Type Spoof: Filename #{File.basename(@name)} (#{supplied_content_type} from Headers, #{content_types_from_name.map(&:to_s)} from Extension), content type discovered from file command: #{calculated_content_type}. See documentation to allow this combination.")
|
|
16
|
+
true
|
|
17
|
+
else
|
|
18
|
+
false
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def has_name?
|
|
25
|
+
@name.present?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def has_extension?
|
|
29
|
+
File.extname(@name).present?
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def media_type_mismatch?
|
|
33
|
+
supplied_type_mismatch? || calculated_type_mismatch?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def supplied_type_mismatch?
|
|
37
|
+
supplied_media_type.present? && !media_types_from_name.include?(supplied_media_type)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def calculated_type_mismatch?
|
|
41
|
+
!media_types_from_name.include?(calculated_media_type)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def mapping_override_mismatch?
|
|
45
|
+
!Array(mapped_content_type).include?(calculated_content_type)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def supplied_content_type
|
|
50
|
+
@content_type
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def supplied_media_type
|
|
54
|
+
@content_type.split("/").first
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def content_types_from_name
|
|
58
|
+
@content_types_from_name ||= MIME::Types.type_for(@name)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def media_types_from_name
|
|
62
|
+
@media_types_from_name ||= content_types_from_name.collect(&:media_type)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def calculated_content_type
|
|
66
|
+
@calculated_content_type ||= type_from_file_command.chomp
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def calculated_media_type
|
|
70
|
+
@calculated_media_type ||= calculated_content_type.split("/").first
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def type_from_file_command
|
|
74
|
+
begin
|
|
75
|
+
Paperclip.run("file", "-b --mime :file", :file => @file.path).split(/[:;]\s+/).first
|
|
76
|
+
rescue Cocaine::CommandLineError
|
|
77
|
+
""
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def mapped_content_type
|
|
82
|
+
Paperclip.options[:content_type_mappings][filename_extension]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def filename_extension
|
|
86
|
+
File.extname(@name.to_s.downcase).sub(/^\./, '').to_sym
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
require 'paperclip/attachment_registry'
|
|
2
|
+
require 'set'
|
|
3
|
+
|
|
4
|
+
module Paperclip
|
|
5
|
+
class << self
|
|
6
|
+
attr_writer :registered_attachments_styles_path
|
|
7
|
+
def registered_attachments_styles_path
|
|
8
|
+
@registered_attachments_styles_path ||= Rails.root.join('public/system/paperclip_attachments.yml').to_s
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Get list of styles saved on previous deploy (running rake paperclip:refresh:missing_styles)
|
|
13
|
+
def self.get_registered_attachments_styles
|
|
14
|
+
YAML.load_file(Paperclip.registered_attachments_styles_path)
|
|
15
|
+
rescue Errno::ENOENT
|
|
16
|
+
nil
|
|
17
|
+
end
|
|
18
|
+
private_class_method :get_registered_attachments_styles
|
|
19
|
+
|
|
20
|
+
def self.save_current_attachments_styles!
|
|
21
|
+
File.open(Paperclip.registered_attachments_styles_path, 'w') do |f|
|
|
22
|
+
YAML.dump(current_attachments_styles, f)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Returns hash with styles for all classes using Paperclip.
|
|
27
|
+
# Unfortunately current version does not work with lambda styles:(
|
|
28
|
+
# {
|
|
29
|
+
# :User => {:avatar => [:small, :big]},
|
|
30
|
+
# :Book => {
|
|
31
|
+
# :cover => [:thumb, :croppable]},
|
|
32
|
+
# :sample => [:thumb, :big]},
|
|
33
|
+
# }
|
|
34
|
+
# }
|
|
35
|
+
def self.current_attachments_styles
|
|
36
|
+
Hash.new.tap do |current_styles|
|
|
37
|
+
Paperclip::AttachmentRegistry.each_definition do |klass, attachment_name, attachment_attributes|
|
|
38
|
+
# TODO: is it even possible to take into account Procs?
|
|
39
|
+
next if attachment_attributes[:styles].kind_of?(Proc)
|
|
40
|
+
attachment_attributes[:styles].try(:keys).try(:each) do |style_name|
|
|
41
|
+
klass_sym = klass.to_s.to_sym
|
|
42
|
+
current_styles[klass_sym] ||= Hash.new
|
|
43
|
+
current_styles[klass_sym][attachment_name.to_sym] ||= Array.new
|
|
44
|
+
current_styles[klass_sym][attachment_name.to_sym] << style_name.to_sym
|
|
45
|
+
current_styles[klass_sym][attachment_name.to_sym].map!(&:to_s).sort!.map!(&:to_sym).uniq!
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
private_class_method :current_attachments_styles
|
|
51
|
+
|
|
52
|
+
# Returns hash with styles missing from recent run of rake paperclip:refresh:missing_styles
|
|
53
|
+
# {
|
|
54
|
+
# :User => {:avatar => [:big]},
|
|
55
|
+
# :Book => {
|
|
56
|
+
# :cover => [:croppable]},
|
|
57
|
+
# }
|
|
58
|
+
# }
|
|
59
|
+
def self.missing_attachments_styles
|
|
60
|
+
current_styles = current_attachments_styles
|
|
61
|
+
registered_styles = get_registered_attachments_styles
|
|
62
|
+
|
|
63
|
+
Hash.new.tap do |missing_styles|
|
|
64
|
+
current_styles.each do |klass, attachment_definitions|
|
|
65
|
+
attachment_definitions.each do |attachment_name, styles|
|
|
66
|
+
registered = registered_styles[klass][attachment_name] || [] rescue []
|
|
67
|
+
missed = styles - registered
|
|
68
|
+
if missed.present?
|
|
69
|
+
klass_sym = klass.to_s.to_sym
|
|
70
|
+
missing_styles[klass_sym] ||= Hash.new
|
|
71
|
+
missing_styles[klass_sym][attachment_name.to_sym] ||= Array.new
|
|
72
|
+
missing_styles[klass_sym][attachment_name.to_sym].concat(missed.to_a)
|
|
73
|
+
missing_styles[klass_sym][attachment_name.to_sym].map!(&:to_s).sort!.map!(&:to_sym).uniq!
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module Paperclip
|
|
2
|
+
# Paperclip processors allow you to modify attached files when they are
|
|
3
|
+
# attached in any way you are able. Paperclip itself uses command-line
|
|
4
|
+
# programs for its included Thumbnail processor, but custom processors
|
|
5
|
+
# are not required to follow suit.
|
|
6
|
+
#
|
|
7
|
+
# Processors are required to be defined inside the Paperclip module and
|
|
8
|
+
# are also required to be a subclass of Paperclip::Processor. There is
|
|
9
|
+
# only one method you *must* implement to properly be a subclass:
|
|
10
|
+
# #make, but #initialize may also be of use. Both methods accept 3
|
|
11
|
+
# arguments: the file that will be operated on (which is an instance of
|
|
12
|
+
# File), a hash of options that were defined in has_attached_file's
|
|
13
|
+
# style hash, and the Paperclip::Attachment itself.
|
|
14
|
+
#
|
|
15
|
+
# All #make needs to return is an instance of File (Tempfile is
|
|
16
|
+
# acceptable) which contains the results of the processing.
|
|
17
|
+
#
|
|
18
|
+
# See Paperclip.run for more information about using command-line
|
|
19
|
+
# utilities from within Processors.
|
|
20
|
+
class Processor
|
|
21
|
+
attr_accessor :file, :options, :attachment
|
|
22
|
+
|
|
23
|
+
def initialize file, options = {}, attachment = nil
|
|
24
|
+
@file = file
|
|
25
|
+
@options = options
|
|
26
|
+
@attachment = attachment
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def make
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.make file, options = {}, attachment = nil
|
|
33
|
+
new(file, options, attachment).make
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# The convert method runs the convert binary with the provided arguments.
|
|
37
|
+
# See Paperclip.run for the available options.
|
|
38
|
+
def convert(arguments = "", local_options = {})
|
|
39
|
+
Paperclip.run('convert', arguments, local_options)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# The identify method runs the identify binary with the provided arguments.
|
|
43
|
+
# See Paperclip.run for the available options.
|
|
44
|
+
def identify(arguments = "", local_options = {})
|
|
45
|
+
Paperclip.run('identify', arguments, local_options)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|