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.
Files changed (191) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +17 -0
  3. data/.github/issue_template.md +3 -0
  4. data/.gitignore +19 -0
  5. data/.hound.yml +1050 -0
  6. data/.rubocop.yml +1 -0
  7. data/.travis.yml +47 -0
  8. data/Appraisals +24 -0
  9. data/CONTRIBUTING.md +86 -0
  10. data/Gemfile +18 -0
  11. data/LICENSE +24 -0
  12. data/NEWS +515 -0
  13. data/README.md +1053 -0
  14. data/RELEASING.md +17 -0
  15. data/Rakefile +52 -0
  16. data/UPGRADING +17 -0
  17. data/features/basic_integration.feature +85 -0
  18. data/features/migration.feature +29 -0
  19. data/features/rake_tasks.feature +62 -0
  20. data/features/step_definitions/attachment_steps.rb +110 -0
  21. data/features/step_definitions/html_steps.rb +15 -0
  22. data/features/step_definitions/rails_steps.rb +257 -0
  23. data/features/step_definitions/s3_steps.rb +14 -0
  24. data/features/step_definitions/web_steps.rb +106 -0
  25. data/features/support/env.rb +12 -0
  26. data/features/support/fakeweb.rb +11 -0
  27. data/features/support/file_helpers.rb +34 -0
  28. data/features/support/fixtures/boot_config.txt +15 -0
  29. data/features/support/fixtures/gemfile.txt +5 -0
  30. data/features/support/fixtures/preinitializer.txt +20 -0
  31. data/features/support/paths.rb +28 -0
  32. data/features/support/rails.rb +39 -0
  33. data/features/support/selectors.rb +19 -0
  34. data/gemfiles/4.2.gemfile +20 -0
  35. data/gemfiles/5.0.gemfile +20 -0
  36. data/gemfiles/5.1.gemfile +20 -0
  37. data/gemfiles/5.2.gemfile +20 -0
  38. data/gemfiles/6.0.gemfile +20 -0
  39. data/lib/generators/paperclip/USAGE +8 -0
  40. data/lib/generators/paperclip/paperclip_generator.rb +36 -0
  41. data/lib/generators/paperclip/templates/paperclip_migration.rb.erb +15 -0
  42. data/lib/paperclip.rb +215 -0
  43. data/lib/paperclip/attachment.rb +617 -0
  44. data/lib/paperclip/attachment_registry.rb +60 -0
  45. data/lib/paperclip/callbacks.rb +42 -0
  46. data/lib/paperclip/content_type_detector.rb +80 -0
  47. data/lib/paperclip/errors.rb +34 -0
  48. data/lib/paperclip/file_command_content_type_detector.rb +28 -0
  49. data/lib/paperclip/filename_cleaner.rb +15 -0
  50. data/lib/paperclip/geometry.rb +157 -0
  51. data/lib/paperclip/geometry_detector_factory.rb +45 -0
  52. data/lib/paperclip/geometry_parser_factory.rb +31 -0
  53. data/lib/paperclip/glue.rb +17 -0
  54. data/lib/paperclip/has_attached_file.rb +116 -0
  55. data/lib/paperclip/helpers.rb +60 -0
  56. data/lib/paperclip/interpolations.rb +201 -0
  57. data/lib/paperclip/interpolations/plural_cache.rb +18 -0
  58. data/lib/paperclip/io_adapters/abstract_adapter.rb +75 -0
  59. data/lib/paperclip/io_adapters/attachment_adapter.rb +47 -0
  60. data/lib/paperclip/io_adapters/data_uri_adapter.rb +22 -0
  61. data/lib/paperclip/io_adapters/empty_string_adapter.rb +19 -0
  62. data/lib/paperclip/io_adapters/file_adapter.rb +26 -0
  63. data/lib/paperclip/io_adapters/http_url_proxy_adapter.rb +16 -0
  64. data/lib/paperclip/io_adapters/identity_adapter.rb +17 -0
  65. data/lib/paperclip/io_adapters/nil_adapter.rb +37 -0
  66. data/lib/paperclip/io_adapters/registry.rb +36 -0
  67. data/lib/paperclip/io_adapters/stringio_adapter.rb +36 -0
  68. data/lib/paperclip/io_adapters/uploaded_file_adapter.rb +44 -0
  69. data/lib/paperclip/io_adapters/uri_adapter.rb +68 -0
  70. data/lib/paperclip/locales/en.yml +18 -0
  71. data/lib/paperclip/logger.rb +21 -0
  72. data/lib/paperclip/matchers.rb +64 -0
  73. data/lib/paperclip/matchers/have_attached_file_matcher.rb +54 -0
  74. data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +101 -0
  75. data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +59 -0
  76. data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +97 -0
  77. data/lib/paperclip/media_type_spoof_detector.rb +90 -0
  78. data/lib/paperclip/missing_attachment_styles.rb +84 -0
  79. data/lib/paperclip/processor.rb +56 -0
  80. data/lib/paperclip/processor_helpers.rb +52 -0
  81. data/lib/paperclip/rails_environment.rb +21 -0
  82. data/lib/paperclip/railtie.rb +31 -0
  83. data/lib/paperclip/schema.rb +81 -0
  84. data/lib/paperclip/storage.rb +3 -0
  85. data/lib/paperclip/storage/filesystem.rb +99 -0
  86. data/lib/paperclip/storage/fog.rb +252 -0
  87. data/lib/paperclip/storage/s3.rb +461 -0
  88. data/lib/paperclip/style.rb +106 -0
  89. data/lib/paperclip/tempfile.rb +42 -0
  90. data/lib/paperclip/tempfile_factory.rb +22 -0
  91. data/lib/paperclip/thumbnail.rb +131 -0
  92. data/lib/paperclip/url_generator.rb +76 -0
  93. data/lib/paperclip/validators.rb +73 -0
  94. data/lib/paperclip/validators/attachment_content_type_validator.rb +88 -0
  95. data/lib/paperclip/validators/attachment_file_name_validator.rb +75 -0
  96. data/lib/paperclip/validators/attachment_file_type_ignorance_validator.rb +28 -0
  97. data/lib/paperclip/validators/attachment_presence_validator.rb +28 -0
  98. data/lib/paperclip/validators/attachment_size_validator.rb +109 -0
  99. data/lib/paperclip/validators/media_type_spoof_detection_validator.rb +29 -0
  100. data/lib/paperclip/version.rb +3 -0
  101. data/lib/tasks/paperclip.rake +140 -0
  102. data/paperclip.gemspec +50 -0
  103. data/shoulda_macros/paperclip.rb +134 -0
  104. data/spec/database.yml +4 -0
  105. data/spec/paperclip/attachment_definitions_spec.rb +13 -0
  106. data/spec/paperclip/attachment_processing_spec.rb +79 -0
  107. data/spec/paperclip/attachment_registry_spec.rb +158 -0
  108. data/spec/paperclip/attachment_spec.rb +1590 -0
  109. data/spec/paperclip/content_type_detector_spec.rb +47 -0
  110. data/spec/paperclip/file_command_content_type_detector_spec.rb +40 -0
  111. data/spec/paperclip/filename_cleaner_spec.rb +13 -0
  112. data/spec/paperclip/geometry_detector_spec.rb +38 -0
  113. data/spec/paperclip/geometry_parser_spec.rb +73 -0
  114. data/spec/paperclip/geometry_spec.rb +255 -0
  115. data/spec/paperclip/glue_spec.rb +42 -0
  116. data/spec/paperclip/has_attached_file_spec.rb +78 -0
  117. data/spec/paperclip/integration_spec.rb +702 -0
  118. data/spec/paperclip/interpolations_spec.rb +270 -0
  119. data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +160 -0
  120. data/spec/paperclip/io_adapters/attachment_adapter_spec.rb +140 -0
  121. data/spec/paperclip/io_adapters/data_uri_adapter_spec.rb +88 -0
  122. data/spec/paperclip/io_adapters/empty_string_adapter_spec.rb +17 -0
  123. data/spec/paperclip/io_adapters/file_adapter_spec.rb +131 -0
  124. data/spec/paperclip/io_adapters/http_url_proxy_adapter_spec.rb +137 -0
  125. data/spec/paperclip/io_adapters/identity_adapter_spec.rb +8 -0
  126. data/spec/paperclip/io_adapters/nil_adapter_spec.rb +25 -0
  127. data/spec/paperclip/io_adapters/registry_spec.rb +35 -0
  128. data/spec/paperclip/io_adapters/stringio_adapter_spec.rb +64 -0
  129. data/spec/paperclip/io_adapters/uploaded_file_adapter_spec.rb +146 -0
  130. data/spec/paperclip/io_adapters/uri_adapter_spec.rb +221 -0
  131. data/spec/paperclip/matchers/have_attached_file_matcher_spec.rb +19 -0
  132. data/spec/paperclip/matchers/validate_attachment_content_type_matcher_spec.rb +108 -0
  133. data/spec/paperclip/matchers/validate_attachment_presence_matcher_spec.rb +69 -0
  134. data/spec/paperclip/matchers/validate_attachment_size_matcher_spec.rb +88 -0
  135. data/spec/paperclip/media_type_spoof_detector_spec.rb +120 -0
  136. data/spec/paperclip/meta_class_spec.rb +30 -0
  137. data/spec/paperclip/paperclip_missing_attachment_styles_spec.rb +88 -0
  138. data/spec/paperclip/paperclip_spec.rb +196 -0
  139. data/spec/paperclip/plural_cache_spec.rb +37 -0
  140. data/spec/paperclip/processor_helpers_spec.rb +57 -0
  141. data/spec/paperclip/processor_spec.rb +26 -0
  142. data/spec/paperclip/rails_environment_spec.rb +30 -0
  143. data/spec/paperclip/rake_spec.rb +103 -0
  144. data/spec/paperclip/schema_spec.rb +252 -0
  145. data/spec/paperclip/storage/filesystem_spec.rb +79 -0
  146. data/spec/paperclip/storage/fog_spec.rb +560 -0
  147. data/spec/paperclip/storage/s3_live_spec.rb +188 -0
  148. data/spec/paperclip/storage/s3_spec.rb +1695 -0
  149. data/spec/paperclip/style_spec.rb +251 -0
  150. data/spec/paperclip/tempfile_factory_spec.rb +33 -0
  151. data/spec/paperclip/tempfile_spec.rb +35 -0
  152. data/spec/paperclip/thumbnail_spec.rb +504 -0
  153. data/spec/paperclip/url_generator_spec.rb +221 -0
  154. data/spec/paperclip/validators/attachment_content_type_validator_spec.rb +322 -0
  155. data/spec/paperclip/validators/attachment_file_name_validator_spec.rb +159 -0
  156. data/spec/paperclip/validators/attachment_presence_validator_spec.rb +85 -0
  157. data/spec/paperclip/validators/attachment_size_validator_spec.rb +235 -0
  158. data/spec/paperclip/validators/media_type_spoof_detection_validator_spec.rb +48 -0
  159. data/spec/paperclip/validators_spec.rb +164 -0
  160. data/spec/spec_helper.rb +45 -0
  161. data/spec/support/assertions.rb +84 -0
  162. data/spec/support/fake_model.rb +24 -0
  163. data/spec/support/fake_rails.rb +12 -0
  164. data/spec/support/fixtures/12k.png +0 -0
  165. data/spec/support/fixtures/50x50.png +0 -0
  166. data/spec/support/fixtures/5k.png +0 -0
  167. data/spec/support/fixtures/animated +0 -0
  168. data/spec/support/fixtures/animated.gif +0 -0
  169. data/spec/support/fixtures/animated.unknown +0 -0
  170. data/spec/support/fixtures/bad.png +1 -0
  171. data/spec/support/fixtures/empty.html +1 -0
  172. data/spec/support/fixtures/empty.xlsx +0 -0
  173. data/spec/support/fixtures/fog.yml +8 -0
  174. data/spec/support/fixtures/rotated.jpg +0 -0
  175. data/spec/support/fixtures/s3.yml +8 -0
  176. data/spec/support/fixtures/spaced file.jpg +0 -0
  177. data/spec/support/fixtures/spaced file.png +0 -0
  178. data/spec/support/fixtures/text.txt +1 -0
  179. data/spec/support/fixtures/twopage.pdf +0 -0
  180. data/spec/support/fixtures/uppercase.PNG +0 -0
  181. data/spec/support/matchers/accept.rb +5 -0
  182. data/spec/support/matchers/exist.rb +5 -0
  183. data/spec/support/matchers/have_column.rb +23 -0
  184. data/spec/support/mock_attachment.rb +24 -0
  185. data/spec/support/mock_interpolator.rb +24 -0
  186. data/spec/support/mock_url_generator_builder.rb +26 -0
  187. data/spec/support/model_reconstruction.rb +72 -0
  188. data/spec/support/reporting.rb +11 -0
  189. data/spec/support/test_data.rb +13 -0
  190. data/spec/support/version_helper.rb +9 -0
  191. 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