kt-paperclip 6.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.codeclimate.yml +17 -0
- data/.github/issue_template.md +3 -0
- data/.gitignore +19 -0
- data/.hound.yml +1050 -0
- data/.rubocop.yml +1 -0
- data/.travis.yml +47 -0
- data/Appraisals +24 -0
- data/CONTRIBUTING.md +86 -0
- data/Gemfile +18 -0
- data/LICENSE +24 -0
- data/NEWS +515 -0
- data/README.md +1053 -0
- data/RELEASING.md +17 -0
- data/Rakefile +52 -0
- data/UPGRADING +17 -0
- data/features/basic_integration.feature +85 -0
- data/features/migration.feature +29 -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 +257 -0
- data/features/step_definitions/s3_steps.rb +14 -0
- data/features/step_definitions/web_steps.rb +106 -0
- data/features/support/env.rb +12 -0
- data/features/support/fakeweb.rb +11 -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 +39 -0
- data/features/support/selectors.rb +19 -0
- data/gemfiles/4.2.gemfile +20 -0
- data/gemfiles/5.0.gemfile +20 -0
- data/gemfiles/5.1.gemfile +20 -0
- data/gemfiles/5.2.gemfile +20 -0
- data/gemfiles/6.0.gemfile +20 -0
- data/lib/generators/paperclip/USAGE +8 -0
- data/lib/generators/paperclip/paperclip_generator.rb +36 -0
- data/lib/generators/paperclip/templates/paperclip_migration.rb.erb +15 -0
- data/lib/paperclip.rb +215 -0
- data/lib/paperclip/attachment.rb +617 -0
- data/lib/paperclip/attachment_registry.rb +60 -0
- data/lib/paperclip/callbacks.rb +42 -0
- data/lib/paperclip/content_type_detector.rb +80 -0
- data/lib/paperclip/errors.rb +34 -0
- data/lib/paperclip/file_command_content_type_detector.rb +28 -0
- data/lib/paperclip/filename_cleaner.rb +15 -0
- data/lib/paperclip/geometry.rb +157 -0
- data/lib/paperclip/geometry_detector_factory.rb +45 -0
- data/lib/paperclip/geometry_parser_factory.rb +31 -0
- data/lib/paperclip/glue.rb +17 -0
- data/lib/paperclip/has_attached_file.rb +116 -0
- data/lib/paperclip/helpers.rb +60 -0
- data/lib/paperclip/interpolations.rb +201 -0
- data/lib/paperclip/interpolations/plural_cache.rb +18 -0
- data/lib/paperclip/io_adapters/abstract_adapter.rb +75 -0
- data/lib/paperclip/io_adapters/attachment_adapter.rb +47 -0
- data/lib/paperclip/io_adapters/data_uri_adapter.rb +22 -0
- data/lib/paperclip/io_adapters/empty_string_adapter.rb +19 -0
- data/lib/paperclip/io_adapters/file_adapter.rb +26 -0
- data/lib/paperclip/io_adapters/http_url_proxy_adapter.rb +16 -0
- data/lib/paperclip/io_adapters/identity_adapter.rb +17 -0
- data/lib/paperclip/io_adapters/nil_adapter.rb +37 -0
- data/lib/paperclip/io_adapters/registry.rb +36 -0
- data/lib/paperclip/io_adapters/stringio_adapter.rb +36 -0
- data/lib/paperclip/io_adapters/uploaded_file_adapter.rb +44 -0
- data/lib/paperclip/io_adapters/uri_adapter.rb +68 -0
- data/lib/paperclip/locales/en.yml +18 -0
- data/lib/paperclip/logger.rb +21 -0
- data/lib/paperclip/matchers.rb +64 -0
- data/lib/paperclip/matchers/have_attached_file_matcher.rb +54 -0
- data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +101 -0
- data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +59 -0
- data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +97 -0
- data/lib/paperclip/media_type_spoof_detector.rb +90 -0
- data/lib/paperclip/missing_attachment_styles.rb +84 -0
- data/lib/paperclip/processor.rb +56 -0
- data/lib/paperclip/processor_helpers.rb +52 -0
- data/lib/paperclip/rails_environment.rb +21 -0
- data/lib/paperclip/railtie.rb +31 -0
- data/lib/paperclip/schema.rb +81 -0
- data/lib/paperclip/storage.rb +3 -0
- data/lib/paperclip/storage/filesystem.rb +99 -0
- data/lib/paperclip/storage/fog.rb +252 -0
- data/lib/paperclip/storage/s3.rb +461 -0
- data/lib/paperclip/style.rb +106 -0
- data/lib/paperclip/tempfile.rb +42 -0
- data/lib/paperclip/tempfile_factory.rb +22 -0
- data/lib/paperclip/thumbnail.rb +131 -0
- data/lib/paperclip/url_generator.rb +76 -0
- data/lib/paperclip/validators.rb +73 -0
- data/lib/paperclip/validators/attachment_content_type_validator.rb +88 -0
- data/lib/paperclip/validators/attachment_file_name_validator.rb +75 -0
- data/lib/paperclip/validators/attachment_file_type_ignorance_validator.rb +28 -0
- data/lib/paperclip/validators/attachment_presence_validator.rb +28 -0
- data/lib/paperclip/validators/attachment_size_validator.rb +109 -0
- data/lib/paperclip/validators/media_type_spoof_detection_validator.rb +29 -0
- data/lib/paperclip/version.rb +3 -0
- data/lib/tasks/paperclip.rake +140 -0
- data/paperclip.gemspec +50 -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 +79 -0
- data/spec/paperclip/attachment_registry_spec.rb +158 -0
- data/spec/paperclip/attachment_spec.rb +1590 -0
- data/spec/paperclip/content_type_detector_spec.rb +47 -0
- data/spec/paperclip/file_command_content_type_detector_spec.rb +40 -0
- data/spec/paperclip/filename_cleaner_spec.rb +13 -0
- data/spec/paperclip/geometry_detector_spec.rb +38 -0
- data/spec/paperclip/geometry_parser_spec.rb +73 -0
- data/spec/paperclip/geometry_spec.rb +255 -0
- data/spec/paperclip/glue_spec.rb +42 -0
- data/spec/paperclip/has_attached_file_spec.rb +78 -0
- data/spec/paperclip/integration_spec.rb +702 -0
- data/spec/paperclip/interpolations_spec.rb +270 -0
- data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +160 -0
- data/spec/paperclip/io_adapters/attachment_adapter_spec.rb +140 -0
- data/spec/paperclip/io_adapters/data_uri_adapter_spec.rb +88 -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 +137 -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 +221 -0
- data/spec/paperclip/matchers/have_attached_file_matcher_spec.rb +19 -0
- data/spec/paperclip/matchers/validate_attachment_content_type_matcher_spec.rb +108 -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 +120 -0
- data/spec/paperclip/meta_class_spec.rb +30 -0
- data/spec/paperclip/paperclip_missing_attachment_styles_spec.rb +88 -0
- data/spec/paperclip/paperclip_spec.rb +196 -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 +30 -0
- data/spec/paperclip/rake_spec.rb +103 -0
- data/spec/paperclip/schema_spec.rb +252 -0
- data/spec/paperclip/storage/filesystem_spec.rb +79 -0
- data/spec/paperclip/storage/fog_spec.rb +560 -0
- data/spec/paperclip/storage/s3_live_spec.rb +188 -0
- data/spec/paperclip/storage/s3_spec.rb +1695 -0
- data/spec/paperclip/style_spec.rb +251 -0
- data/spec/paperclip/tempfile_factory_spec.rb +33 -0
- data/spec/paperclip/tempfile_spec.rb +35 -0
- data/spec/paperclip/thumbnail_spec.rb +504 -0
- data/spec/paperclip/url_generator_spec.rb +221 -0
- data/spec/paperclip/validators/attachment_content_type_validator_spec.rb +322 -0
- data/spec/paperclip/validators/attachment_file_name_validator_spec.rb +159 -0
- data/spec/paperclip/validators/attachment_presence_validator_spec.rb +85 -0
- data/spec/paperclip/validators/attachment_size_validator_spec.rb +235 -0
- data/spec/paperclip/validators/media_type_spoof_detection_validator_spec.rb +48 -0
- data/spec/paperclip/validators_spec.rb +164 -0
- data/spec/spec_helper.rb +45 -0
- data/spec/support/assertions.rb +84 -0
- data/spec/support/fake_model.rb +24 -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 +24 -0
- data/spec/support/mock_interpolator.rb +24 -0
- data/spec/support/mock_url_generator_builder.rb +26 -0
- data/spec/support/model_reconstruction.rb +72 -0
- data/spec/support/reporting.rb +11 -0
- data/spec/support/test_data.rb +13 -0
- data/spec/support/version_helper.rb +9 -0
- metadata +586 -0
@@ -0,0 +1,18 @@
|
|
1
|
+
en:
|
2
|
+
errors:
|
3
|
+
messages:
|
4
|
+
in_between: "must be in between %{min} and %{max}"
|
5
|
+
spoofed_media_type: "has contents that are not what they are reported to be"
|
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,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,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.to_s) &&
|
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,101 @@
|
|
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.to_s
|
44
|
+
message << "\n\n" if @allowed_types.present? && @rejected_types.present?
|
45
|
+
message << rejected_types_and_failures.to_s
|
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
|
+
message << if @missing_allowed_types.present?
|
59
|
+
" #{@missing_allowed_types.join(', ')} were rejected."
|
60
|
+
else
|
61
|
+
" All were accepted successfully."
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def rejected_types_and_failures
|
68
|
+
if @rejected_types.present?
|
69
|
+
"Reject content types: #{@rejected_types.join(', ')}\n".tap do |message|
|
70
|
+
message << if @missing_rejected_types.present?
|
71
|
+
" #{@missing_rejected_types.join(', ')} were accepted."
|
72
|
+
else
|
73
|
+
" All were rejected successfully."
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def expected_attachment
|
80
|
+
"Expected #{@attachment_name}:\n"
|
81
|
+
end
|
82
|
+
|
83
|
+
def type_allowed?(type)
|
84
|
+
@subject.send("#{@attachment_name}_content_type=", type)
|
85
|
+
@subject.valid?
|
86
|
+
@subject.errors[:"#{@attachment_name}_content_type"].blank?
|
87
|
+
end
|
88
|
+
|
89
|
+
def allowed_types_allowed?
|
90
|
+
@missing_allowed_types ||= @allowed_types.reject { |type| type_allowed?(type) }
|
91
|
+
@missing_allowed_types.none?
|
92
|
+
end
|
93
|
+
|
94
|
+
def rejected_types_rejected?
|
95
|
+
@missing_rejected_types ||= @rejected_types.select { |type| type_allowed?(type) }
|
96
|
+
@missing_rejected_types.none?
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
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,97 @@
|
|
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 = range.first
|
35
|
+
@high = range.last
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def matches?(subject)
|
40
|
+
@subject = subject
|
41
|
+
@subject = @subject.new if @subject.class == Class
|
42
|
+
lower_than_low? && higher_than_low? && lower_than_high? && higher_than_high?
|
43
|
+
end
|
44
|
+
|
45
|
+
def failure_message
|
46
|
+
"Attachment #{@attachment_name} must be between #{@low} and #{@high} bytes"
|
47
|
+
end
|
48
|
+
|
49
|
+
def failure_message_when_negated
|
50
|
+
"Attachment #{@attachment_name} cannot be between #{@low} and #{@high} bytes"
|
51
|
+
end
|
52
|
+
alias negative_failure_message failure_message_when_negated
|
53
|
+
|
54
|
+
def description
|
55
|
+
"validate the size of attachment #{@attachment_name}"
|
56
|
+
end
|
57
|
+
|
58
|
+
protected
|
59
|
+
|
60
|
+
def override_method(object, method, &replacement)
|
61
|
+
(class << object; self; end).class_eval do
|
62
|
+
define_method(method, &replacement)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def passes_validation_with_size(new_size)
|
67
|
+
file = StringIO.new(".")
|
68
|
+
override_method(file, :size) { new_size }
|
69
|
+
override_method(file, :to_tempfile) { file }
|
70
|
+
|
71
|
+
@subject.send(@attachment_name).post_processing = false
|
72
|
+
@subject.send(@attachment_name).assign(file)
|
73
|
+
@subject.valid?
|
74
|
+
@subject.errors[:"#{@attachment_name}_file_size"].blank?
|
75
|
+
ensure
|
76
|
+
@subject.send(@attachment_name).post_processing = true
|
77
|
+
end
|
78
|
+
|
79
|
+
def lower_than_low?
|
80
|
+
@low.nil? || !passes_validation_with_size(@low - 1)
|
81
|
+
end
|
82
|
+
|
83
|
+
def higher_than_low?
|
84
|
+
@low.nil? || passes_validation_with_size(@low + 1)
|
85
|
+
end
|
86
|
+
|
87
|
+
def lower_than_high?
|
88
|
+
@high.nil? || @high == Float::INFINITY || passes_validation_with_size(@high - 1)
|
89
|
+
end
|
90
|
+
|
91
|
+
def higher_than_high?
|
92
|
+
@high.nil? || @high == Float::INFINITY || !passes_validation_with_size(@high + 1)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,90 @@
|
|
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? && 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
|
+
extension_type_mismatch? || calculated_type_mismatch?
|
34
|
+
end
|
35
|
+
|
36
|
+
def extension_type_mismatch?
|
37
|
+
supplied_media_type.present? &&
|
38
|
+
has_extension? &&
|
39
|
+
!media_types_from_name.include?(supplied_media_type)
|
40
|
+
end
|
41
|
+
|
42
|
+
def calculated_type_mismatch?
|
43
|
+
supplied_media_type.present? &&
|
44
|
+
!calculated_content_type.include?(supplied_media_type)
|
45
|
+
end
|
46
|
+
|
47
|
+
def mapping_override_mismatch?
|
48
|
+
!Array(mapped_content_type).include?(calculated_content_type)
|
49
|
+
end
|
50
|
+
|
51
|
+
def supplied_content_type
|
52
|
+
@content_type
|
53
|
+
end
|
54
|
+
|
55
|
+
def supplied_media_type
|
56
|
+
@content_type.split("/").first
|
57
|
+
end
|
58
|
+
|
59
|
+
def content_types_from_name
|
60
|
+
@content_types_from_name ||= MIME::Types.type_for(@name)
|
61
|
+
end
|
62
|
+
|
63
|
+
def media_types_from_name
|
64
|
+
@media_types_from_name ||= content_types_from_name.collect(&:media_type)
|
65
|
+
end
|
66
|
+
|
67
|
+
def calculated_content_type
|
68
|
+
@calculated_content_type ||= type_from_file_command.chomp
|
69
|
+
end
|
70
|
+
|
71
|
+
def calculated_media_type
|
72
|
+
@calculated_media_type ||= calculated_content_type.split("/").first
|
73
|
+
end
|
74
|
+
|
75
|
+
def type_from_file_command
|
76
|
+
Paperclip.run("file", "-b --mime :file", file: @file.path).
|
77
|
+
split(/[:;\s]+/).first
|
78
|
+
rescue Terrapin::CommandLineError
|
79
|
+
""
|
80
|
+
end
|
81
|
+
|
82
|
+
def mapped_content_type
|
83
|
+
Paperclip.options[:content_type_mappings][filename_extension]
|
84
|
+
end
|
85
|
+
|
86
|
+
def filename_extension
|
87
|
+
File.extname(@name.to_s.downcase).sub(/^\./, "").to_sym
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|